2017年7月15日
こっちに移動 → https://ptsv.jp/2016/04/20/tweet-image-download-agent/
2016年4月29日 Windows フォームを利用したGUI版追加
検索ワード対応とスレッドプールにダウンロード処理を投げるように、ソース本体も改編した版
2015年11月27日・29日 コード修正・他
コード修正 画像ファイルの正規表現の間違い修正及びcommandを追加。
コード修正 URL を favorites => likes に変更。文章をちょろっと変更。
2015年11月23日 コード修正
2回目以降のtimeline URLの取得方法が間違ってました。ごめんなさい。(m_m)
perl で組んだものを C# に書き直したコードです。perlで組んだもので僕がやりたかったことは全部できた訳ですが・・・ま、今仕事暇なんで(^^;
エラーチェックとか全然してません。このコードは参考テスト程度のものなので、あしからず。
間違ったtargetとか入力してサーバーが404エラー返すと例外吐いて死にます(^_^;; エラー処理をコードに盛り込むとコードの要点が見えずらくなるので・・・いないとは思いますが、コードを使う時は、エラー処理(例外捕捉とか)を入れてね。
また、Twitter API使った方法ではないので、twitter側がブラウザ表示の方法を変更してしまうと、たぶんエラーで落ちると思う。まぁ、いつまでこの方法で使えんのかな、という感じです。
コンソールプログラムで良ければ、コンパイルしたものを置いときます。[ getTweetImage.exe ]
※使用方法は下記コードのコメントを参照してください。
(EXEファイルをダウンロードすると、たぶんセキュリティーチェックで引っかかると思いますが、信用できない方は、下記コードをご自身でコンパイルしてください。 )
たぶん Windows以外のOSでも、monoが入っていたら 上記 exe ファイルがそのまま動くんじゃないかなー。
[追記]
CentOS6 + mono で実行してみましたが、https通信でセキュリティ?かなんかの例外が発生してしまいました。
デフォルトでインストールしたまんまのmonoの場合はルート証明書をインポートする等しないといけないみたいですね。mozroots.exeをインストールして解決しました。下記ページを参考にさせていただきましたm(__)m。
~Monoで、Secure Socket Layer (SSL) / Transport Layer Security (TLS)通信を行うには~
http://qiita.com/takanemu/items/129acc13d8b7ce088b92
[追記ここまで]
これにて終了。
/******************************************************************************* saving image file on twitter.(getTweetImage.cs) build: Visual Studio 2015 Community.( also Express Edition) or Windows SDK IDE使うほどでもないので、コマンドラインツールでcsc.exeでバイトコンパイルしてください。 >> csc.exe getTweetImage.cs howto: Console command >> getTweetImage.exe command target [username password] command : ターゲットが"いいね"した画像 => 'favo' ターゲットがアップした画像 => 'profile' ターゲットのTLの画像 => 'timeline' target : ターゲットとなる取得したい画像のアカウント名 username : 自分のログインアカウント password : 自分のパスワード usernameとpasswordは、「いいね」画像の取得及び、 ターゲットとなるアカウントが非公開アカの場合に必要かと思います。 *******************************************************************************/ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Net; using System.Linq; namespace ptsv.jp { public class ConsoleApplication1 { private static Dictionary<string,string> TIMELINEURL = new Dictionary<string,string> { { "profile","https://twitter.com/i/profiles/show/{0}/media_timeline"}, { "favo","https://twitter.com/{0}/likes/timeline"}, { "timeline","https://twitter.com/i/profiles/show/{0}/timeline"} }; /// <summary> /// スタートアップ /// </summary> /// <param name="args">コマンドライン引数</param> public static void Main(string [] args) { if(args.Length < 2) { Console.WriteLine("USAGE: getTweetImage.exe command target [username password]"); Console.WriteLine("USAGE: command is 'favo' or 'profile'"); Console.WriteLine("require username and password\n when command is 'favo',or target is private account."); return; } string command = args[0]; string target = args[1]; string username_or_email = null; string password = null; if(args.Length == 4) { username_or_email = args[2]; password = args[3]; } if(command == "favo" && (string.IsNullOrEmpty(username_or_email) || string.IsNullOrEmpty(password))) { Console.WriteLine("when command is 'favo', username and password is required."); Console.WriteLine("USAGE: getTweetImage.exe command target [username password]"); return; } string timeline_url = TIMELINEURL.ContainsKey(command) ? string.Format(TIMELINEURL[command],target) : null; var twitter = new TwitterDownload((type,v) => { switch(type) { case "get-timeline": Console.WriteLine("---- getting and parsing\n{0} ...\n----",v); break; case "getting-image": Console.WriteLine("fetching: {0}",v); break; case "saved-image": Console.WriteLine("saved {0}",v); break; case "file-exists": Console.WriteLine("{0} is already exists.",v); break; case "fail-login": Console.WriteLine("Auth code not found.",""); break; case "fail-auth": Console.WriteLine("username or password were refused.",""); break; case "try-login": Console.WriteLine("Trying to login by '{0}' for twitter.com", v); break; } }); if(!string.IsNullOrEmpty(timeline_url)) { twitter.Agent(timeline_url,username_or_email,password); } else { Console.WriteLine("[INVALID COMMAND] command should be 'favo' or 'profile'"); } } } /// <summary> /// 画像ダウンロード本体クラス /// </summary> public class TwitterDownload { /*------------------------------------------------------------------------------ Const ------------------------------------------------------------------------------*/ public const string URL_LOGIN = "https://twitter.com/login"; public const string URL_SESSIONS = "https://twitter.com/sessions"; public const string USER_AGENT = "Mozilla/5.0 (compatible; MSIE 11.0; Windows NT 6.0; Trident/5.0)"; //適当 public const string REFERER = "https://twitter.com/"; private const string RE_MEDIA_URL = @"https://pbs\.twimg\.com/media/([\w\-]+\.\w{3,4})(:large)?"; private const string RE_TWEET_ID = @"data-tweet-id=\\""(\d+)\\"""; private const string RE_AUTH_CODE = @"<input type=""hidden"" value=""([\d\w]+?)"" name=""authenticity_token""(?:\s*/)?>"; /*------------------------------------------------------------------------------ Instance ------------------------------------------------------------------------------*/ private string OutputDirectory = Path.Combine(".", "tmp"); private TwitterWebClient webClient; private Action<string,string> Callback; public TwitterDownload(Action<string,string> callback, string cookieFile) { this.Callback = callback; this.webClient = new TwitterWebClient(cookieFile); } public TwitterDownload(Action<string,string> callback) { this.Callback = callback; this.webClient = new TwitterWebClient(); } public string directory { set { this.OutputDirectory = value; } get { return this.OutputDirectory; } } public void Agent(string timelineURL,string username,string password) { if(String.IsNullOrEmpty(timelineURL)) return; if(!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) { if (!GetLogin(username, password)) { Callback("fail-auth", ""); return; } } if (!Directory.Exists(OutputDirectory)) Directory.CreateDirectory(OutputDirectory); string param = "?include_available_features=1&include_entities=1"; string url; do { url = timelineURL + param; Callback("get-timeline",url); string result = webClient.DownloadString(url); param = GetImage(result); } while(!string.IsNullOrEmpty(param)); } private bool GetLogin(string username,string password) { Callback("try-login", username); webClient.Headers[HttpRequestHeader.UserAgent] = USER_AGENT; webClient.Headers[HttpRequestHeader.Referer] = REFERER; string loginResult = webClient.DownloadString(URL_LOGIN); Match authMatch = Regex.Match(loginResult,RE_AUTH_CODE); if(!authMatch.Success) return false; var param = new NameValueCollection(); param.Add("session[username_or_email]",username); param.Add("session[password]",password); param.Add("authenticity_token",authMatch.Groups[1].Value); param.Add("remember_me","1"); param.Add("redirect_after_login","/"); return Encoding.UTF8.GetString(webClient.UploadValues(URL_SESSIONS,param)).IndexOf("error") < 0; } private string GetImage(string json) { json = json.Replace(@"\/", "/"); foreach(Match match in Regex.Matches(json,RE_MEDIA_URL)) { string url = match.Value; string basename = match.Groups[1].Value; string filename = Path.Combine(OutputDirectory,basename); url = string.IsNullOrEmpty(match.Groups[2].Value) ? url + ":orig" : url.Replace(":large",":orig"); if(File.Exists(filename)) { Callback("file-exists",filename); } else { Callback("getting-image",url); webClient.DownloadFile(url,filename); Callback("saved-image",filename); } } var ids = new List<string>(); foreach(Match match in Regex.Matches(json,RE_TWEET_ID)) { ids.Add(match.Groups[1].Value); } return ids.Count > 0 ? string.Format("?max_position={0}&include_available_features=1&include_entities=1",ids.Last()) : null; } } /// <summary> /// クッキーの読書に対応するためだけのWebClientクラス。 /// </summary> internal class TwitterWebClient : WebClient { private CookieContainer cookieContainer = new CookieContainer(); private bool isAutoRedirectEnabled = false; public TwitterWebClient() : this("") {} /// <summary> /// もしクッキーファイルが存在したらクッキーファイルを読み込んでCookieContainerに追加する。 /// </summary> /// <param name="cookieFile">ブラウザからエクスポートしたクッキーファイル</param> public TwitterWebClient(string cookieFile) { if(File.Exists(cookieFile)) { using(var sr = new StreamReader(cookieFile)) { string line; var reCRLF = new Regex(@"[\r\n]$"); var reEmpty = new Regex("^$"); var reComment = new Regex("^#"); var reSpace = new Regex(@"\s+"); while ((line = sr.ReadLine()) != null) { line = reCRLF.Replace(line,""); if(reEmpty.Match(line).Success || reComment.Match(line).Success) continue; string[] ar = reSpace.Split(line); cookieContainer.Add(new Cookie(ar[5],ar[6],ar[2],ar[0])); } } } } /// <summary> /// WebClient.GetWebRequestメソッドをオーバーライド /// </summary> protected override WebRequest GetWebRequest(Uri address) { WebRequest request = base.GetWebRequest(address); if (request is HttpWebRequest) { var httpWebRequest = request as HttpWebRequest; if(httpWebRequest != null) { httpWebRequest.CookieContainer = this.cookieContainer; httpWebRequest.AllowAutoRedirect = this.isAutoRedirectEnabled; } } return request; } /// <summary> /// 自動リダイレクトするか? /// </summary> public bool AutoRedirect { set { isAutoRedirectEnabled = value; } get { return isAutoRedirectEnabled; } } } }