OSDN Git Service

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