OSDN Git Service

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