twitterの「いいね」画像をダウンロード 修正版

追記 2017/07/15
C#に書き直した修正版 → https://ptsv.jp/2016/04/20/tweet-image-download-agent/


2015年11月27日・29日 コード修正
コード修正 画像ファイルの正規表現の間違い修正・ちょこっと追加。
コード修正 URL を favorites => likes に変更

2015年11月23日 コード修正
2回目以降のtimelineのURLの決定方法が間違っておりましたので修正しました。ごめんなさい(m_m)

2015年11月17日 コード修正
ログインを行うコードを追加。これにより、ブラウザからクッキーファイルをエクスポートする手間を省くようにした。ログインコードはこれでいいのかどうかわからん。twitterってrails使ってんだっっけ? よくわからんが、適当。
本来はTwitter APIを使わないといけないと思うけど・・・API経由は正直メンドクサイ。勉強する気なし。(m_m)

C#で書き直したプロトタイプをさっき書いたので、これもデバッグが終わり次第また書こっと。
『perlなんかインストールしてねーよ、ボケ!』って方は、C#で書き直した方に ビルドしたやつを置いてます。


今年の2月ぐらいに書いたtwitter画像ダウンロードスクリプトが動かなくなったので修正・修正(^_^;;

2回目以降のタイムライン取得のURLが変更になったみたい?。昼休みにブラウザでアクセスしてデベロッパーツールでちょこっと解析したんですが・・・どのパラメーターで読めばいいのか、いまいち分かんないです。適当です。あくまで、自分用の備忘録なんで、すみません。取れればいいんです(m_m)

twitterにログインするところまで書きなおそうと一瞬思いましたが、また今度にします(・・;
ブラウザでログインしてクッキーエクスポート、twitter.comドメインだけ抜き出して、カレントディレクトリへcookie.txtというファイル名で保存。テキトーですまない。

使い方は下記参考。ログインしたcookie.txtが必要なのは、いいね(旧称:お気に入り)の取得のみ。他人のIDの「いいね」も同様にログインが必要です。この辺よくわからん。自分もしくは他人のIDのタイムラインにログインクッキーは必要ない。です。

#!/usr/bin/perl
=pod

=head1 ツイッターでのタイムラインから、画像のみダウンロードするスクリプト

使い方 targetに画像を取得したいアカウント名を指定する。

(いいね 画像の場合は type に favo をセット(デフォルト) 
(※ いいね 取得は自分以外のアカウントでも必ずログインが必要みたいです。)

> twitter.pl --username=xxxxx --password=yyyyy --type=favo --target=zzzz


(単に任意のアカウントのタイムラインの画像が欲しい場合は type に profileをセット)
(TLに流れているリツイートも含めた画像を取得したい場合は、timelineをセット)
(※通常公開されているアカウントのタイムラインではusename,passwordは必要ありません。
(※非公開アカウントではフォローを許可されたアカウントのusername/passwordが必要です。)

> twitter.pl --type=profile --target=公開アカウント名


ディレクトリ<tmp>に画像が吐き出されます。

=cut

use strict;
use warnings;
use LWP::UserAgent;
use Getopt::Long qw/:config no_ignore_case/;

my %CONFIG = (username => '', password => '', dir => './tmp', type => 'favo', target => '');
my %TIMELINE = (favo    => 'https://twitter.com/%s/likes/timeline',
                profile => 'https://twitter.com/i/profiles/show/%s/media_timeline',
                timeline => 'https://twitter.com/i/profiles/show/%s/timeline');

#エージェント
my $UserAgent = LWP::UserAgent->new(cookie_jar => {});

&{sub
{
  my @argv = @_;
  my $result = Getopt::Long::GetOptionsFromArray(\@argv,
                                                 'username=s' => \$CONFIG{username},
                                                 'password=s' => \$CONFIG{password},
                                                 'type=s'     => \$CONFIG{type},
                                                 'dir=s'      => \$CONFIG{dir},
                                                 'target=s'   => \$CONFIG{target});

  exists $TIMELINE{$CONFIG{type}} or die "invalid type....\n";
  $CONFIG{dir} || die "specified output directory...\n";
  $CONFIG{target} || die "specified target account name...\n";

  mkdir $CONFIG{dir} unless(-e $CONFIG{dir});

  &use_lwp_agent;

}}(@ARGV);

sub use_lwp_agent
{
  my ($param,$result) = ('?include_available_features=1&include_entities=1','');

  if($CONFIG{username} && $CONFIG{password})
    {
      my $response = $UserAgent->get('https://twitter.com/login');
      $result = $response->decoded_content;

      $result =~ /<input type="hidden" value="([\d\w]+?)" name="authenticity_token"(?:\s*\/)?>/ or die "can not detect authenticity_token...\n";

      my $auth = $1;
      $response = $UserAgent->post('https://twitter.com/sessions',
                                   {'session[username_or_email]' => $CONFIG{username},
                                    'session[password]'          => $CONFIG{password},
                                    'authenticity_token'         => $auth,
                                    'remember_me'                => '1',
                                    'redirect_after_login'       => '/' }) or die "can not access login page... \n";

      $result = $response->decoded_content;
      die "failed to login...\n" if($result =~ /error/);
    }

  my $url = sprintf($TIMELINE{$CONFIG{type}},$CONFIG{target});
  do
    {
      my $timeline = $url.$param;

      print "---- getting and parsing\n$timeline ...\n----\n";
      $result = &lwp_agent($timeline,'-') || die "can not get timeline. may be wrong url.\n";

      $param = &get_images($result,\&lwp_agent);

    } while( $param );

  print "done!\n";
}

#タイムラインのJSONデータから画像を取得して、次のタイムラインのパラメータを返す。
sub get_images
{
  my ($json,$agent) = @_;

  $json =~ s/\\\//\//g;
  foreach my $url_($json =~ m!https://pbs\.twimg\.com/media/[\w\-]+\.\w{3,4}(?::large)?!g)
    {
      if($url_ =~ m/([\w\-]+\.\w{3,4})(:large)?$/)
        {
          my $basename = $1;
          my $filename = "$CONFIG{dir}/$basename";
          if($2)
            {
              $url_ =~ s/:large/:orig/;
            }
          else
            {
              $url_ .= ':orig';
            }

          unless(-e $filename)
            {
              print "fetching: $url_\n";
              &$agent($url_,$filename);
              print "saved $basename\n";
            }
        }
    }

  my @ids = $json =~ m/data-tweet-id=\\\"([0-9]+)/g;
  my $max_id = @ids > 0 ? pop @ids : '';

  return $max_id ? "?max_position=${max_id}&include_available_features=1&include_entities=1" : undef;
}

#URLを取得して返す。
sub lwp_agent
{
  my ($url,$ofile) = @_;
  my %options = ();

  if($ofile ne '-')
    {
      if($ofile eq ':src')
        {
          if($url =~ /([\w\-\.%]+?\.\w)$/)
            {
              $ofile = "$CONFIG{dir}/$1";
            }
          else
            {
              goto cleanup;
            }
        }

      return $UserAgent->get($url,':content_file' => $ofile);
    }

cleanup:
  if(my $response = $UserAgent->get($url))
    {
      return $response->decoded_content;
    }

  0;
}

__END__

リンク追跡タグがいい加減うざい件

グーグル検索とかヤフーとかインプレスのサイトはよく見たり利用したりするんですが、マウスカーソルをリンクの上に持ってきたときにステータスバーに表示されるアドレスとは違うURLに飛ばされることに違和感を覚え、ちょっと調べたら、どうやら、個人のトラフィック調査(追跡)機能が仕組まれていることに今さらながら気付いた。遅いな(^^;;;

例えばこんなやつ↓

<a href="http://hogehoge.com/" onmousedown="this.href='追跡機能のためのURL';">ほげほげ</a>

つまり、マウスのボタンが押されたら、強制的にhref属性を追跡機能のためのURLに飛ばし、その先で、本来のリンク先(書き換え前のURL)にリダイレクトする、ってやつ。このmousedownイベントは、左ボタンだけでなく、右ボタン、ホイールボタン(あ、Windows前提ね(m_m))でも発動してしまうから、スキップできない!!!

SEO的には問題ない。href属性はそのままだし、ユーザーがクリックしない限りURLは書き換えられないから、ロボットにも対応できる。質悪いな、と思った。いくら追跡を拒否しようが、そんなのお構いなし(笑)

でも、元からhref属性(リンク先)に、追跡機能のためのURLが記述されているなら、まだ良心的だ。追跡してるぞ! というのが分かるから。だけど、このonmousedownを使った手法は、追跡しているのをユーザーに気付かせない(隠している?)ようにも受け取られてしまう。リンク先のURLをある意味偽装してるんじゃないか? ってね。ユーザーは追跡を拒否できないわけだ。

こうして、「無料」と言う名の対価を払い便利なサイトを利用しているわけだけど、「無料」とは名ばかりで、自分の「趣味嗜好」を金儲けの道具にされているのはなんだかすっげー気持ち悪い。特にずっとログインしっぱなしのサイトでそれやられると、ログイン情報とその「趣味嗜好」が緋付けされて監視されているようで薄気味悪い。

さらにスマホやタブレットなんかPCほど簡単にはチェックできないから、やりたい放題なわけだ。

街中では監視カメラがいっぱいありますが・・・ネットの中でも監視されて、もう、嫌になりますなー。

ブックマークレットで、その都度 onmousedown属性をゴッソリ消してしまえるけど、そのたびにいちいちブックマークレットを実行するのもなー・・・。ページ開いたらonmousedown属性だけバッサリ削除してくれるブラウザの拡張機能・・・ってあるのかなー。それくらい自分で作れってか(^^;

追記・ブックマークレット (Remove onmousedown attr)

javascript:(function(){var as = document.getElementsByTagName('a'); if(as){for(var i=0;i<as.length;i++)as[i].removeAttribute('onmousedown');}})();

MDBファイルへのアクセス

備忘録です。

ちょっと前に Accessで作成しているシステムをWebシステムに置き換える案件の仕事をしている時に、MDBファイルを覗く(データ抽出)する必要があった。そのときに使っていたPCには、ランタイム版ではない、本物の? 32bit版のMicrosoft Accessがインストールされていたので問題は無かったのですが、今使っているPCはMicrosoft Officeは入っていない。

いや、たしか、OLEDB経由でMDBファイルは読めるはず・・・と思ってコントロールパネルからODBCドライバが入っているかどうか見てみたけど・・・残念・・・入ってなかった・・・。

ググったら、「Microsoft Access データベース エンジン 2010 再頒布可能コンポーネント(https://www.microsoft.com/ja-jp/download/details.aspx?id=13255)」をインストールすれば読める、ということで、64bit版のドライバを入れて、Visual Studio 2015 community のサーバーエクスプローラで確認。

で、一応32bit版も入れとくか・・・と思って、32bit版もダウンロードしてインストーラーを立ち上げたら、先に64bit版を削除しろ! と怒られた(^^;

ええい、/passive オプションをつけてバージョン・チェックなしでもう一度インストーラーを起動して、問答無用で入れてやりましたよ!32bit版はバージョン違いのものが既にインストールされてた。この辺よく分からん。

odbc64  odbc32

左が %WINDIR%\system32\odbcad32.exe右が%WINDIR%\syswow64\odbcad32.exe
32bit/64bitアプリケーションでちゃんと動くのかな?今度確認してみよ。

でも、これ、下記ような制限があるんだけど、意味が分からない。MDBファイルを読むために使っちゃダメなの? いいの? 分からん・・・

  1. Jet の全般的な代替としての使用。Jet の全般的な代替が必要な場合は、SQL Server Express Edition (英語版) が必要です。
  2. サーバー側アプリケーション内での Jet OLEDB プロバイダーとしての使用。
  3. 一般的なワード プロセッサ、スプレッドシート、またはデータベース管理システムとしての使用。 つまり、ファイル作成の手段としての使用。Microsoft Office または Office オートメーションを使うと、Microsoft Office でサポートされるファイルを作成することができます。
  4. システム サービスまたはサーバー側プログラム (コードがシステム アカウントの下で実行されるもの、複数のユーザー ID を同時に処理するもの、高度に再入可能で動作が不安定になるもの) による使用。これには、ユーザーがログインしていないときにタスク スケジューラーから実行されるプログラムや、ASP.NET などのサーバー側 Web アプリケーションから呼びだされるプログラム、COM+ サービスの元で実行される分散コンポーネントなどがあります。

明日のメシの心配

営業さんしかいない部署にいると、時折聞こえてくる「遠い未来のご馳走より、明日のメシの心配しろ」的なことが総意になるわけです。中小企業は目先の利益が最重要・最優先項目になるのは仕方ないのかな、と思います。

おりこう ナンチャラ とかいう、素人相手のすんげぇ高価なCMSがあって、そこでできないことをレンタルサーバーを別途契約して、別途そこでゴニョゴニョしよう、という案件。

そもそも会社で売ってる「おりこう○○○」(伏せ字で失礼(_ _) って何ね? と思って調べてみるが、中身がよく分からん。営業の人に聞いてもそもそもHPで書いてある以上のことは分からない、という。なんじゃそりゃ?

ドメイン名もそこが管理するらしいけど、他でレンタルサーバー借りるんなら、あとで連携が必要になったときのクロスドメイン問題を回避するためにも、サブドメイン運用したいのだが・・・それが、その「おりこう ナンチャラ」で可能なのか・・・というのを調べようにも資料が全くない。営業さんに言っても、Webシステムの中途半端な知識しかないから、サブドメイン運用の意味が全く分かってくれないので、「もうそういう契約になってるので!」の一点張り。

しかし、まぁ、社員さんが言うところの「売った数字」でしか評価されない、というのは、契約社員という名のアルバイトの身には、全くもってどうでもいいことなんで、とにかく、後々のためにも、遠い未来(笑)の先のことを考えないと、苦しむのは自分。

どうすれば素人からお金を吸い取れるか? でのみで作られたCMSが「使いやすいシステム」でないことは確かだ。

まぁ素人(ITリテラシーが低い人、いわゆる情弱)が使いやすいシステムと、ウェブ開発者達が言うところの使いやすいシステムっていうのは、根本的に違う、ということは分かっているのだが・・・

弁当食いに来てるだけの給料ドロボーだった4~5年前の部署に戻りたい(笑)

iframe要素のsandbox属性

先日、iframeを使用している自作のjQueryプラグインを使って構築しているWebアプリの管理画面が突然正常に動かなくなった!

ブラウザの開発者ツールのコンソールから原因を調べたら、iframe要素内で読み込んでいる同じドメイン内のリソースからフォームを投げたり親ウィンドウへのアクセスがことごとくエラーになっていたorz コンソールには、allow-modalsがなんとかかんとか、という見たこともないエラー。

でも、エラーになるブラウザはchromeだけ。FireFoxやIEは問題なかったので、たぶんchromeの最近のバージョンアップでiframe要素のセキュリティが高くなってしまったのかなー、とよくよく調べたら、下記sandbox属性をiframeに追加すればちゃんと動くようになった。どうやらHTML5.1にchromeが対応しただけ? (というか、HTML5.1なんて、そんなもんあったの?無知は嫌だねぇ(_ _) 常にアンテナはっとかないとダメですよねぇ・・・)

  • allow-modals
  • allow-pointer-lock
  • allow-popups
  • allow-popups-to-escape-sandbox
  • allow-top-navigation

名前からだいたいの想像はつくんですが・・・allow-modalsとかallow-popupsとか違いが分からない。また今度時間あったら調べよう。

もともと、<iframe sandbox=”allow-same-origin allow-forms allow-scripts”> という風にしてたんですが・・・もっと調べたら、上記5つのsandbox属性は、HTML5.1から追加された?もののようですね。
(この辺、自分ではよく分かってません(_ _)

ってことは、このまま放っておくと、InternetExplorerはともかく、いずれはFireFoxもMicrosoft Edgeも動かなく・・・。このプラグイン使っているところは、修正したプラグインのJSファイルだけ上書き全部しないとなー。

根本的にはiframe要素をすべて排除して、全てajaxな作り方すれば良かったんですけどね・・・中途半端は良くないですね・・・でも、iframeって便利なんですよね(^.^;;;