twitterのお気に入りの画像を一括ダウンロード

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


追記 2015/11/10
修正版 → スクリプトを修正したものを投稿していますm(_ _)m


ツイッターで、検索したりタイムラインに流れる画像で気になったのは片っ端から「お気に入り」に入れてました。で、とある日曜の夜中にふと、「ファボッったツイートの画像を全部ダウンロードしたい!」欲求に駆られ、ググってみたら下記ブログ記事を見つけた。

dyama.org/2014/10/berryjack-simple-twitter-media-downloader-by-shellscript/

ここで書かれていたtimelineに直接リクエストを投げる方法を、ブラウザで確認しながら調べてみると、できそうだったので、シェルスクリプトを流用させてもらいました。(m_m)
・・・Windowsなので、perlに書き直して完了。Win32版のwget.exeが必要ですが・・・。

ただ、「お気に入り」の画像を一括ダウンロードするにはログインが必要なんで、その部分を調べて書くのが面倒だったんで、Internet Explorerでログインした後、クッキーをエクスポートし、twitter.comドメインの部分だけ抜き出し cookies.txt と保存し、それをwgetに食わせることで強引に解決(^^;;; とりあえずなんで目をつむる。

Twitter APIとか使わないといけないのかなー、と思ってたんですが、上記ブログ記事のシェルスクリプトのおかげで随分簡単にゲトできました。ありがとうございました(^.^)

探せば便利なソフトがあると思うんですが・・・簡単にできたんで別にいいか(^^;
また、時間があれば、C#に書き直してGUIつくろー。

#!/usr/bin/perl
# 使い方
#
# > perl twitter.pl 画像タイムラインのURL(下記参照)
#
# 【任意のTwitterIDの画像の場合】
# https://twitter.com/i/profiles/show/(TwitterID)/media_timeline

# 【自分のお気に入りの場合】
# https://twitter.com/(自分のTwitterID)/favorites/timeline
# 別途ログイン後のクッキーが必要。IEなどでログインしてエクスポート。
# twitter.comドメインだけ抜き出して 同じディレクトリにcookies.txt として保存する必要あり。
#
# tmpというディレクトリがカレントディレクトリに作成され、画像がダウンロードされます。
# ダウンロードエージェントに GNU Wgetを利用しています。
# おいらのようにwindowsな人は別途 Win32版 GUN WGETが必要。

# 【追記】 wgetを使用せず、LWP::UserAgentを使用するように加筆したものを一番下に追加

use bigint;
mkdir 'tmp' unless(-e './tmp');
$param = '';
$timeline = $ARGV[0] || die "input timeline url...\n";

@options = ('--no-check-certificate',
            '-O -',
            "-a ./tmp/wget_$$.log");
push @options,'--load-cookies=cookies.txt' if(-e 'cookies.txt');

$options = join(' ',@options);
do
{
  $wget = sprintf('wget %s "%s%s"',$options,$timeline,$param);
  print $wget,"\n";
  $result = `$wget`;
  $result =~ s/\\\//\//g;

  @result = $result =~ m!https://pbs\.twimg\.com/media/\w+\.\w{3,4}\:large!g;
  foreach(@result)
    {
      $out = '';
      if(m/(\w+\.\w{3,4})\:large$/)
        {
          $filename = "tmp/$1";
          $out = "-O $filename";

          unless(-e $filename)
            {
              system("wget --no-check-certificate -a ./tmp/wget_$$.log -P tmp -nd $out $_");
              print "saved $1\n";
            }
        }
    }

  @result = $result =~ m/data-tweet-id=\\\"([0-9]+)/g;
  @result = sort @result;

  $cxt_id = shift @result;
  $max_id = $cxt_id - 1;

  $param = "?contextual_tweet_id=$cxt_id&max_id=$max_id";
} while( $cxt_id );

【追記】

wget を使う代わりに、LWP::UserAgent を使用するように改変。使い方は上と一緒。
・・・なんか無駄にコードが増えた(ーー;;;

#!/usr/bin/perl

# 使い方は上記wgetを使用するものと一緒

use strict;
use warnings;
use bigint;
use LWP::UserAgent;
use HTTP::Cookies;
use Time::Piece;

#クッキーファイル名
my $COOKIE = 'cookies.txt';

#作業用クッキーファイルパス
my $TMP_COOKIE = './tmp/libwww.'.$COOKIE;

#エージェント
my $UserAgent = LWP::UserAgent->new;

&{sub
{
  #arguments
  my @argv = @_;
  mkdir 'tmp' unless(-e './tmp');

  my $tmp = "./tmp/$$.media";
  my $param = '';
  my $timeline = $argv[0] || die "input timeline url...\n";
  my ($cxt_id,$max_id);

  #cookies.txtがカレントディレクトリにあればフォーマット変換して作業用ファイルに保存
  if(-e $COOKIE)
    {
      &convert_cookie;
      $UserAgent->cookie_jar(HTTP::Cookies->new(file => $TMP_COOKIE,autosave => 1));
    }

  do
    {
      $timeline = $argv[0].$param;

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

      foreach my $url_($result =~ m!https://pbs\.twimg\.com/media/\w+\.\w{3,4}\:large!g)
        {
          my $out = '';
          if($url_ =~ m/(\w+\.\w{3,4})\:large$/)
            {
              my $filename = "tmp/$1";
              unless(-e $filename)
                {
                  &lwp_agent($url_,$filename);
                  print "saved $1\n";
                }
            }
        }

      my @result = $result =~ m/data-tweet-id=\\\"([0-9]+)/g;
      @result = sort @result;

      $cxt_id = shift @result;
      $max_id = $cxt_id - 1;

      $param = "?contextual_tweet_id=$cxt_id&max_id=$max_id" if(defined $cxt_id);

    } while( $cxt_id );

  print "done!\n";

  #作業用のクッキー削除
  unlink $TMP_COOKIE if(-e $TMP_COOKIE);

}}(@ARGV);

sub lwp_agent
{
  my ($url,$ofile) = @_;
  my %options = ();

  if($ofile ne '-')
    {
      return $UserAgent->get($url,':content_file' => $ofile);
    }
  else
    {
      if(my $response = $UserAgent->get($url))
        {
          return $response->decoded_content;
        }
    }
  0;
}

sub convert_cookie
{
  my $fin = IO::File->new($COOKIE);
  my $fout = IO::File->new('>'.$TMP_COOKIE);

  $fout->print("#LWP-Cookies-1.0\n");
  foreach my $line_($fin->getlines)
    {
      chomp $line_;
      next if($line_ =~ /^$/ || $line_ =~ /^\#/);
      my ($domain,$flag,$path,$secure,$expires,$name,$value) = split(/\s+/,$line_);
      my $t = gmtime($expires);
      $expires = sprintf('%s, %d-%s-%04d %02d:%02d:%02d GMT',
                         $t->day,
                         $t->mday,
                         $t->month,
                         $t->year,
                         $t->hour,
                         $t->min,
                         $t->sec);
      $secure = $secure eq 'TRUE' ? 'secure' : '';

      $fout->print(qq(Set-Cookie3: $name=$value; path="$path"; domain=$domain; path_spec; expires="$expires"; $secure\n));
    }

  $fin->close;
  $fout->close;
}
__END__

1年間無償でアップグレード提供!

今年はWindows8の後継バージョン、Windows10がリリース予定。
Appleに対抗したかったのか、Windows7以降のOSであれば、リリース日から1年間は無償でバージョンアップできる、とのこと。なぜ1年間なのかはさておき、どーせ、1年縛りは撤廃されるんじゃないかなあ。

Windows7もすでにセキュリティフィックスのみの更新になってしまったし、かつてのWindowsXPと同じ扱い。
僕的にWindows8/8.1はパソコンのOSとしては完全に失敗だと思う。SurfaceのOSとしては良いと思うんだけど。マウスとキーボードを使う、いわゆるパソコン(PC)のOSでは無かった、という意見の方が多いと思う(根拠無し・妄想)。

スタートメニューの善し悪しはともかく、スタートメニューの無いWindowsなんて、牛肉が入っていない、すき焼きと同じですよ! Windowsを名乗っちゃダメよ~ダメダメ!ですよ!(笑)

 

中継サーバーを経由して社内LAN上のテストサーバーへ接続する

いや、まぁ、タイトルどおりです(^^ゞ 実験です。興味本位です。(^^;;;

社内のテストサーバーに自宅から、好きなときに、どうにかしてSSH接続できないものか・・・?とてもじゃないけど、「VPN設置して家からアクセスさせてください」とか、「ルータのポート一個開けて、俺っちのテストサーバーへルーティングして」とか、そんな都合のいいことが通る(許可される)はずもありません(^^;;;

幸いなことに、テストサーバーはCentOS6で、社内の資産管理システムからは除外されている。というか、社内のIT管理部門にLinuxを扱えるシステム精通者はいません(笑) プロバイダー業も一時期やってたから数年前にはそれなりに詳しい人がいたかもしれませんが、経営方針とやらが変わってからそういう人たちは殆どいなくなった。会社から技術が無くなるって、ほんとあっけないですね。

ってなわけで・・・・?

サーバー・ネットワークの素人なりに考えた結果、やはりポート転送を使うしか方法がないかなと。ただ、家にあるPCはブロードバンドルーター(もはや死語か?)の内側のLANに繋がっているので、単純にSSHポート転送すればいい話ではないのは、自明です。

素人なりに考えたあげく、下記のように、現在契約しているVPSサーバーを中継してSSHポート転送を行えば、やりたいことは可能なはず・・・

ポートフォワーディングでファイヤーウォールを越える2

まず、社内のテストサーバーから中継サーバーに逆ポート転送をかけます。下記のような感じでしょうか。

>> ssh -N -R 10022:localhost:22  user@xxx.xxx.xxx.xxx

これで、中継サーバーの10022番へアクセスしたら社内の22番ポート(SSHD)にアクセスできるようになった。ただ、これだと、社内のテストサーバーにログインするためには、いちいち中継サーバーにログインして、そこから、またsshでログインして・・・という風になるので、もう一つ、家のPCから中継サーバへポート転送してしまおう、というのが今回の実験の目的。

中継サーバーへポート転送するには、こんな感じ?

>> ssh -N -L 22:localhost:10022 user@xxx.xxx.xxx.xxx

理屈はあってると思うんだけど、これで家のPCのローカルポート22番へsshで接続すると、中継サーバーの10022番ポートへ接続され、さらに中継サーバーの10022番ポートは、社内のテストサーバーの22番ポートへ転送される。

別にこんなことしなくても、多段SSHすりゃいいじゃん、ってな声も聞こえますけど、SSHじゃなくて、たとえば社内のテストサーバーにFTP接続したい、ってな時だと、やっぱり中継サーバーへのポート転送を行わないとセキュリティ的にねぇ・・・。
(※実際には中継サーバーへの接続には鍵認証を使い、家のPC(Windows7)からはPortForwarderを使用して接続しています。)

どこかに穴を開ける(ポートを開ける)リスクを最小限に抑えつつ、その場しのぎの方法としてSSHポート転送はついつい銀の弾丸に思えてきます(^^;;;

ちなみに、一度試みようと、家に帰ってきて繋げてみたところ、接続できませんでした。テストサーバーでKeepAliveの設定をし忘れてしまって・・・接続が切れてしまってたとさ。トホホ。

追記:
後日 ~/.ssh/config ファイルにServerAliveInterval を設定して、完了。快適です。SSHポートだけじゃなく、FTP/HTTPポートも転送することでやりかけの仕事を家でできるようになった(^.^)

なんかの加減で社内テストサーバーから中継サーバーへの接続が切れてしまうと一切接続できなくなるのは困りもの。autosshを使えばいい、というブログ記事が多いけど・・・鍵認証にパスフレーズを入力するようにしてあるので、ちょっと無理かな。現状、切れたら終了~。

サブ・ディスプレイ

ウィンドウをパカパカ何個も開いてコーディングしてると、やっぱりディスプレイがもう一つ欲しくなるわけです。エディタで編集/複数のブラウザ、タブ/メールソフト/画像編集ソフト/無数のフォルダウィンドウ/サーバー接続へのターミナル・・・と、一つのディスプレイ上で複数のウィンドウをスイッチして使ってると、とてつもなく効率が悪い。

ってなわけで、先週末に仕事の打ち合わせで大阪へ出てふと帰りに寄ったヨドバシで、ほぼ衝動買い(^^;;;

ASUS の MB168B+ というUSBモニタ

これを選んだ理由は簡単! USBケーブル一本で接続できる、ということ。電源ケーブルとか必要なし。USBバスパワーで動作可能な点。それに極薄!(笑) 15インチのFullHDで解像度的にも良いし。ノートパソコンからディスプレイパネルを引きちぎってきた感じでしょうか。

インストールはDisplayLinkチップのドライバを入れるだけで終わり。

ただ、これ、Windows7を使っているんですが、ディスプレイ毎の色調整ができない? ディスプレイの色温度がおそらく9000Kとかになってるのか、すっげー青いっす。なんか解決方法ないのかな・・・? ディスプレイ自体は輝度・コントラストしか調整できないので、そこだけが残念かな。

色が調整できない!!!

色が調整できない!!!

このサイズのUSBモニタの需要が少ないのか、あまり数が無さそうです。まぁ、普通はHDMI入力のディスプレイを選びますよね(^^ゞ
会社のPCで使う前提なんで、USB接続しか選択肢がないんですよねぇ。。。

PHP配列の悪夢、再び

おれって、相当アホ!!! ってな備忘録。

こんなコードを書いた。(実際に書いたコードはもっと複雑ですが・・・)

<?php
/******************************

 配列の中身を出力する。だけ。

******************************/
function array_wrong($files)
{
  $num = count($files);
  for($i=0;$i<$num;$i++)
    {
      printf("%d = %sn",$i,@$files[$i]);
    }
}

もうこの時点で、「お前の書くコードなんて一切使わない」宣言されるだろう。
はい、そうですね。僕もそう思います。(T-T)

こんな基本的な間違いを平気で犯す自分に自己嫌悪して数時間立ち直れませんでした・・・。

たぶん、次のような引数を渡すと期待するように動作するでしょう。

<?php
$files = array('abc.txt',
               'xyz.pdf',
               'def.jpg');

//たぶん上手くいく。オール、オッケー
array_wrong($files);

しかし・・・次のようにしたら・・・

<?php
$files = array('abc.txt',
               'xyz.pdf',
               'def.jpg');

unset($files[1]);

$files[10] = 'zzzz.gif';
$files[7] = 'bbbbb.ai';

array_wrong($files);

僕が期待した出力には当然なりません。とほほ。

こういうバカ・コードが原因だと気づくのに、半日要してしまったよ・・・・。
PHPの配列とcount関数の挙動が全然理解してない証拠っすね。。。恥ずかしい。。。

原因が分かれば・・・foreachもしくは、count関数を使わず・・・ループすればいい。

<?php
function array_right($files)
{
  ksort($files);
  foreach($files as $i => $file)
    {
      printf("%d = %sn",$i,@$files[$i]);
    }
}

//もしくは・・・count()関数でループ回数を決定するのではなく配列キーの最大値を利用する。
function array_right($files)
{
  ksort($files);
  $num = max(array_keys($files));
  for($i=0;$i<$num;$i++)
    {
      if(isset($files[$i]))
        printf("%d = %sn",$i,@$files[$i]);
    }
}

はぁ・・・。いつになったら使える人間になれるんだろう・・・。