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; }
}
}
}