OSDN Git Service

WayBackKey, WayBackTimeをNicoCacneImplのフィールドから削除しコメント用情報クラスに移す.
[coroid/inqubus.git] / frontend / src / saccubus / net / NicoClientImpl.java
1 package saccubus.net;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileNotFoundException;
6 import java.io.FileOutputStream;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.io.OutputStream;
11 import java.net.URL;
12 import java.net.HttpURLConnection;
13 import java.net.InetSocketAddress;
14 import java.net.Proxy;
15 import java.net.URLEncoder;
16 import java.net.URLDecoder;
17 import java.text.DateFormat;
18 import java.text.ParseException;
19 import java.text.SimpleDateFormat;
20 import java.util.Date;
21 import java.util.HashMap;
22 import java.util.Map;
23 import javax.net.ssl.HttpsURLConnection;
24 import org.apache.commons.lang.StringUtils;
25 import saccubus.ConvertStopFlag;
26 import saccubus.util.FileUtil;
27 import yukihane.Util;
28 import static saccubus.net.VideoInfo.OfficialOption;
29
30 /**
31  * <p>
32  * タイトル: さきゅばす
33  * </p>
34  *
35  * <p>
36  * 説明: ニコニコ動画の動画をコメントつきで保存
37  * </p>
38  *
39  * <p>
40  * 著作権: Copyright (c) 2007 PSI
41  * </p>
42  *
43  * <p>
44  * 会社名:
45  * </p>
46  *
47  * @author 未入力
48  * @version 1.0
49  */
50 public class NicoClientImpl implements NicoClient {
51
52     private String Cookie = null;
53     private final String User;
54     private final String Pass;
55     private boolean Logged_in = false;
56     private final ConvertStopFlag StopFlag;
57     private final Proxy ConProxy;
58     private final static String WAYBACKKEY_STR = "waybackkey=";
59
60     public NicoClientImpl(final String user, final String pass,
61             final ConvertStopFlag flag, final String proxy, final int proxy_port) {
62         User = user;
63         Pass = pass;
64         if (proxy != null && proxy.length() > 0 && proxy_port >= 0
65                 && proxy_port <= 65535) {
66             ConProxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxy,
67                     proxy_port));
68         } else {
69             ConProxy = Proxy.NO_PROXY;
70         }
71         // ログイン
72         Logged_in = login();
73         StopFlag = flag;
74     }
75
76     @Override
77     public File getComment(VideoInfo vi, CommentInfo ci, final File file, final TextProgressListener status, String back_comment) {
78         return downloadComment(back_comment, file, vi, ci, status, false);
79     }
80
81     /**
82      * 投稿者コメントをダウンロードする.
83      * @param vi ビデオ情報.
84      * @param file ダウンロード先ファイル.
85      * @param status 進捗通知リスナ.
86      * @return ダウンロードされたファイル. ダウンロードできなければnull.
87      */
88     @Override
89     public File getTcomment(VideoInfo vi, final File file, final TextProgressListener status) {
90         return downloadComment("500", file, vi, status, true);
91     }
92
93     private File downloadComment(String back_comment, final File file, VideoInfo vi, final TextProgressListener status,
94             boolean isTcomm) throws NumberFormatException {
95         return downloadComment(back_comment, file, vi, CommentInfo.DEFAULT, status, isTcomm);
96     }
97
98     private File downloadComment(String back_comment, final File file, VideoInfo vi, CommentInfo ci, final TextProgressListener status,
99             boolean isTcomm) throws NumberFormatException {
100         System.out.print("Downloading comment size:" + back_comment + "...");
101         try {
102             if (file.canRead()) { // ファイルがすでに存在するなら削除する。
103                 file.delete();
104             }
105             OutputStream fos = new FileOutputStream(file);
106             HttpURLConnection con = (HttpURLConnection) (new URL(vi.getMsgUrl())).openConnection(ConProxy);
107             con.setDoOutput(true);
108             con.setDoInput(true);
109             con.setRequestMethod("POST");
110             con.addRequestProperty("Cookie", Cookie);
111             con.addRequestProperty("Connection", "close");
112             con.connect();
113             OutputStream os = con.getOutputStream();
114             String tcommStr = (isTcomm) ? "fork=\"1\" " : "";
115             String official = "";
116             OfficialOption oo = vi.getOfficialOption();
117             if (oo != null) {
118                 official = "force_184=\"" + oo.getForce184() + "\" threadkey=\"" + oo.getThreadKey() + "\" ";
119             }
120             String req = "<thread user_id=\"" + vi.getUserId() + "\" when=\"" + ci.getWayBackTime() + "\" waybackkey=\""
121                     + ci.getWayBackKey() + "\" res_from=\"-" + back_comment + "\" version=\"20061206\" thread=\"" + vi.
122                     getThreadId() + "\" " + tcommStr + official + "/>";
123             os.write(req.getBytes());
124             os.flush();
125             os.close();
126             if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
127                 System.out.println("ng.\nCan't download comment:" + vi.getMsgUrl());
128                 return null;
129             }
130             InputStream is = con.getInputStream();
131             int read = 0;
132             int max_size = 0;
133             String content_length_str = con.getHeaderField("Content-length");
134             if (content_length_str != null && !content_length_str.equals("")) {
135                 max_size = Integer.parseInt(content_length_str);
136             }
137             int size = 0;
138             final byte[] buf = new byte[1024 * 1024];
139             while ((read = is.read(buf, 0, buf.length)) > 0) {
140                 fos.write(buf, 0, read);
141                 size += read;
142                 if (max_size != 0) {
143                     String per = Double.toString((((double) size) * 100) / max_size);
144                     per = per.substring(0, Math.min(per.indexOf(".") + 3, per.length()));
145                     status.setText("コメントダウンロード:" + per + "パーセント完了");
146                 } else {
147                     status.setText("コメントダウンロード中:" + Integer.toString(size >> 10) + "kbytesダウンロード");
148                 }
149                 if (StopFlag.needStop()) {
150                     System.out.println("Stopped.");
151                     is.close();
152                     os.flush();
153                     os.close();
154                     con.disconnect();
155                     file.delete();
156                     return null;
157                 }
158             }
159             System.out.println("ok.");
160             is.close();
161             fos.flush();
162             fos.close();
163             con.disconnect();
164             return file;
165         } catch (IOException ex) {
166             ex.printStackTrace();
167         }
168         return null;
169     }
170
171     @Override
172     public File getVideo(VideoInfo vi, final File file, final TextProgressListener status) {
173         if (vi.getVideoUrl() == null) {
174             System.out.println("Video url is not detected.");
175             return null;
176         }
177         try {
178 //                      if (file.canRead()) { // ファイルがすでに存在するなら削除する。
179 //                              file.delete();
180 //                      }
181             HttpURLConnection con = (HttpURLConnection) (new URL(vi.getVideoUrl())).openConnection(ConProxy);
182             /* 出力のみ */
183             con.setDoInput(true);
184             con.setRequestMethod("GET");
185             con.addRequestProperty("Cookie", Cookie);
186             con.connect();
187             if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
188                 System.out.println("Can't get video:" + vi.getVideoUrl());
189                 return null;
190             }
191             final String extension = Util.getExtention(con.getContentType());
192             File outFile = appendExtension(file, extension);
193             InputStream is = con.getInputStream();
194             OutputStream os = new FileOutputStream(outFile);
195             String content_length_str = con.getHeaderField("Content-length");
196             int max_size = 0;
197             if (content_length_str != null && !content_length_str.equals("")) {
198                 max_size = Integer.parseInt(content_length_str);
199             }
200             int size = 0;
201             System.out.print("Downloading video...");
202             int read = 0;
203             final byte[] buf = new byte[1024 * 1024];
204             while ((read = is.read(buf, 0, buf.length)) > 0) {
205                 size += read;
206                 os.write(buf, 0, read);
207                 if (max_size != 0) {
208                     String per = Double.toString((((double) size) * 100)
209                             / max_size);
210                     per = per.substring(0, Math.min(per.indexOf(".") + 3, per.length()));
211                     status.setText("動画ダウンロード:" + per + "パーセント完了");
212                 } else {
213                     status.setText("動画ダウンロード中:" + Integer.toString(size >> 10)
214                             + "kbytesダウンロード");
215                 }
216                 if (StopFlag.needStop()) {
217                     System.out.println("Stopped.");
218                     is.close();
219                     os.flush();
220                     os.close();
221                     con.disconnect();
222                     outFile.delete();
223                     return null;
224                 }
225             }
226             System.out.println("ok.");
227             is.close();
228             os.flush();
229             os.close();
230             con.disconnect();
231             return outFile;
232         } catch (FileNotFoundException ex) {
233             ex.printStackTrace();
234         } catch (IOException ex) {
235             ex.printStackTrace();
236         }
237         return null;
238     }
239
240     /** @return ビデオ名 */
241     public String getVideoHistoryAndTitle(String tag) throws IOException {
242         String url = "http://www.nicovideo.jp/watch/" + tag;
243         System.out.print("Getting video history...");
244         String new_cookie = null;
245         String videoTitle = null;
246
247         HttpURLConnection con = (HttpURLConnection) (new URL(url)).openConnection(ConProxy);
248         /* リクエストの設定 */
249         con.setRequestMethod("GET");
250         con.addRequestProperty("Cookie", Cookie);
251         con.addRequestProperty("Connection", "close");
252         con.connect();
253         if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
254             throw new IOException("Can't getVideoHistory:" + url);
255         }
256         int i = 1;
257         String key;
258         String value;
259         while ((key = con.getHeaderFieldKey(i)) != null) {
260             if (key.equalsIgnoreCase("Set-Cookie")) {
261                 value = con.getHeaderField(i);
262                 if (value != null) {
263                     new_cookie = value.substring(0, value.indexOf(";"));
264                 }
265             }
266             i++;
267         }
268         BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream(), "UTF-8"));
269         String ret;
270         int index = -1;
271         while ((ret = br.readLine()) != null && index < 0) {
272             final String TITLE_PARSE_STR_START = "<title>";
273             if ((index = ret.indexOf(TITLE_PARSE_STR_START)) >= 0) {
274                 videoTitle = ret.substring(index + TITLE_PARSE_STR_START.length(), ret.indexOf("‐", index));
275                 videoTitle = FileUtil.safeFileName(videoTitle);
276             }
277         }
278         br.close();
279         con.disconnect();
280         if (new_cookie == null) {
281             System.out.println("Can't getVideoHistory: cannot get cookie.");
282             return null;
283         }
284         System.out.println("ok.");
285         Cookie += "; ";
286         Cookie += new_cookie;
287
288         return videoTitle;
289     }
290
291     @Override
292     public VideoInfo getVideoInfo(String tag) throws IOException {
293         final String videoTitle = getVideoHistoryAndTitle(tag);
294
295         String url = "http://flapi.nicovideo.jp/api/getflv/" + tag;
296         if (tag.startsWith("nm")) {
297             url += "?as3=1";
298         }
299         System.out.print("Getting video informations...");
300         Map<String, String> res = new NicoApiRequest(url).get();
301         String threadId = res.get("thread_id");
302         String videoUrl = res.get("url");
303         String msgUrl = res.get("ms");
304         String userId = res.get("user_id");
305         int videoLength = -1;
306         String videoLengthStr = res.get("l");
307         try {
308             videoLength = Integer.parseInt(videoLengthStr);
309         } catch (NumberFormatException ex) {
310         }
311
312         OfficialOption oo = null;
313         if ("1".equals(res.get("needs_key"))) {
314             oo = getOfficialOption(threadId);
315         }
316
317         VideoInfo vi = new VideoInfo(videoTitle, threadId, videoUrl, msgUrl, userId, videoLength, oo);
318         System.out.println("ok.");
319         return vi;
320     }
321
322     private OfficialOption getOfficialOption(String threadId) throws IOException {
323         String url = "http://flapi.nicovideo.jp/api/getthreadkey?thread=" + threadId;
324         Map<String, String> map = new NicoApiRequest(url).get();
325         return new OfficialOption(map.get("threadkey"), map.get("force_184"));
326     }
327
328     @Override
329     public CommentInfo getWayBackKey(VideoInfo vi, String time) throws IOException {
330         if (StringUtils.isBlank(time)) {
331             return CommentInfo.DEFAULT;
332         }
333
334         System.out.print("Setting wayback time...");
335         Date date = null;
336         String waybacktime = "0";
337         try {
338             final DateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
339             date = fmt.parse(time);
340         } catch (ParseException ex2) {
341             date = null;
342         }
343         if (date == null) {
344             try {
345                 final DateFormat fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm");
346                 date = fmt.parse(time);
347             } catch (ParseException ex3) {
348                 date = null;
349             }
350         }
351         if (date != null) {
352             waybacktime = Long.toString(date.getTime() / 1000);
353             System.out.println("ok.(" + date.toString() + "):" + waybacktime);
354         } else {
355             try {
356                 long tmp_time = Long.parseLong(time);
357                 waybacktime = Long.toString(tmp_time);
358                 date = new Date(tmp_time * 1000);
359                 System.out.println("ok.(" + date.toString() + "):"
360                         + waybacktime);
361             } catch (NumberFormatException ex4) {
362                 System.out.println("ng.");
363                 System.out.println("Cannot parse wayback time.");
364                 throw new IOException("Cannot parse wayback time.", ex4);
365             }
366         }
367         System.out.print("Getting wayback key...");
368         String url = "http://flapi.nicovideo.jp/api/getwaybackkey?thread="
369                 + vi.getThreadId();
370         String ret = "";
371         try {
372             HttpURLConnection con = (HttpURLConnection) (new URL(url)).openConnection(ConProxy);
373             /* リクエストの設定 */
374             con.setRequestMethod("GET");
375             con.addRequestProperty("Cookie", Cookie);
376             con.addRequestProperty("Connection", "close");
377             con.setDoInput(true);
378             con.connect();
379             if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
380                 System.out.println("Can't get WayBackKey:" + url);
381                 throw new IOException("Can't get WayBackKey:" + url);
382             }
383             /* 戻り値の取得 */
384             BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
385             ret = br.readLine();
386             br.close();
387             con.disconnect();
388         } catch (IOException ex1) {
389             System.out.println("ng.");
390             ex1.printStackTrace();
391             throw ex1;
392         }
393         int idx = 0;
394         if ((idx = ret.indexOf(WAYBACKKEY_STR)) < 0) {
395             System.out.println("ng.");
396             System.out.println("Cannot find wayback key from response.");
397             throw new IOException("Cannot find wayback key from response.");
398         }
399         int end_idx = Math.max(ret.length(), ret.indexOf("&"));
400         String waybackkey = ret.substring(idx + WAYBACKKEY_STR.length(),
401                 end_idx);
402         if (waybackkey == null || waybackkey.equals("")) {
403             System.out.println("ng.");
404             System.out.println("Cannot get wayback key.");
405             throw new IOException("Cannot get wayback key.");
406         }
407         System.out.println("ok. key:" + waybackkey);
408         return new CommentInfo(waybackkey, waybacktime);
409     }
410
411     @Override
412     public boolean isLoggedIn() {
413         return Logged_in;
414     }
415
416     private boolean login() {
417         try {
418             HttpURLConnection con = (HttpsURLConnection) (new URL(
419                     "https://secure.nicovideo.jp/secure/login?site=niconico")).openConnection(ConProxy);
420             /* 出力のみ */
421             con.setDoOutput(true);
422             HttpURLConnection.setFollowRedirects(false);
423             con.setInstanceFollowRedirects(false);
424             con.setRequestMethod("POST");
425             con.addRequestProperty("Connection", "close");
426             con.connect();
427             StringBuffer sb = new StringBuffer(4096);
428             sb.append("next_url=&");
429             sb.append("mail=");
430             sb.append(URLEncoder.encode(User, "Shift_JIS"));
431             sb.append("&password=");
432             sb.append(URLEncoder.encode(Pass, "Shift_JIS"));
433             sb.append("&submit.x=103&submit.y=16");
434             OutputStream os = con.getOutputStream();
435             os.write(sb.substring(0).getBytes());
436             os.flush();
437             os.close();
438             int code = con.getResponseCode();
439             if (code < 200 || code >= 400) {
440                 System.out.println("Can't login:" + con.getResponseMessage());
441                 return false;
442             }
443             int i = 1;
444             String key;
445             String value;
446             while ((key = con.getHeaderFieldKey(i)) != null) {
447                 if (key.equalsIgnoreCase("Set-Cookie")) {
448                     value = con.getHeaderField(i);
449                     if (value != null) {
450                         Cookie = value.substring(0, value.indexOf(";"));
451                     }
452                 }
453                 i++;
454             }
455             con.disconnect();
456             if (Cookie == null) {
457                 System.out.println("Can't login: cannot set cookie.");
458                 return false;
459             }
460             System.out.println("Logged in.");
461         } catch (IOException ex) {
462             ex.printStackTrace();
463             return false;
464         }
465         return true;
466     }
467
468     private File appendExtension(File file, String extension) {
469         final String e = "." + extension;
470         final String defExt = ".flv";
471         String path = file.getPath();
472         if (path.endsWith(e)) {
473             return file;
474         } else if (path.endsWith(defExt)) {
475             path = path.substring(0, path.length() - defExt.length());
476         }
477         return new File(path + e);
478     }
479
480     private class NicoApiRequest {
481
482         private final String url;
483
484         private NicoApiRequest(String url) {
485             this.url = url;
486         }
487
488         private Map<String, String> get() throws IOException {
489             Map<String, String> map = new HashMap<String, String>();
490             System.out.print("Getting video informations...");
491             HttpURLConnection con = (HttpURLConnection) (new URL(url)).openConnection(ConProxy);
492             /* リクエストの設定 */
493             con.setRequestMethod("GET");
494             con.addRequestProperty("Cookie", Cookie);
495             con.addRequestProperty("Connection", "close");
496             con.connect();
497             if (con.getResponseCode() != HttpURLConnection.HTTP_OK) {
498                 throw new IOException("Can't getVideoInfo:" + url);
499             }
500             /* 戻り値の取得 */
501             BufferedReader br = new BufferedReader(new InputStreamReader(con.getInputStream()));
502             String ret = br.readLine();
503             br.close();
504             con.disconnect();
505             ret = URLDecoder.decode(ret, "Shift_JIS");
506             String[] array = ret.split("&");
507             int cnt = 0;
508             for (int i = 0; i < array.length; i++) {
509                 int idx = array[i].indexOf("=");
510                 if (idx < 0) {
511                     continue;
512                 }
513                 String key = array[i].substring(0, idx);
514                 String value = array[i].substring(idx + 1);
515                 map.put(key, value);
516             }
517             return map;
518         }
519     }
520 }