OSDN Git Service

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