OSDN Git Service

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