OSDN Git Service

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