5 import java.net.URISyntaxException;
7 import java.util.TreeMap;
8 import java.util.regex.Matcher;
9 import nicobrowser.entity.NicoContent;
10 import nicobrowser.search.SortKind;
11 import nicobrowser.search.SortOrder;
12 import com.sun.syndication.feed.synd.SyndContentImpl;
13 import com.sun.syndication.feed.synd.SyndEntryImpl;
14 import com.sun.syndication.feed.synd.SyndFeed;
15 import com.sun.syndication.io.FeedException;
16 import com.sun.syndication.io.SyndFeedInput;
17 import java.io.BufferedInputStream;
18 import java.io.BufferedOutputStream;
19 import java.io.BufferedReader;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.InputStreamReader;
25 import java.io.Reader;
26 import java.io.StringReader;
28 import java.net.URLEncoder;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Enumeration;
32 import java.util.HashMap;
33 import java.util.LinkedHashMap;
34 import java.util.List;
36 import java.util.regex.Pattern;
37 import javax.swing.text.MutableAttributeSet;
38 import javax.swing.text.html.HTML;
39 import javax.swing.text.html.HTMLEditorKit;
40 import javax.swing.text.html.parser.ParserDelegator;
41 import javax.xml.parsers.DocumentBuilder;
42 import javax.xml.parsers.DocumentBuilderFactory;
43 import javax.xml.parsers.ParserConfigurationException;
44 import nicobrowser.entity.NicoContent.Status;
45 import nicobrowser.search.SearchKind;
46 import nicobrowser.search.SearchResult;
47 import nicobrowser.util.Result;
48 import nicobrowser.util.Util;
49 import org.apache.commons.io.FilenameUtils;
50 import org.apache.commons.lang.ArrayUtils;
51 import org.apache.commons.logging.Log;
52 import org.apache.commons.logging.LogFactory;
53 import org.apache.http.HttpEntity;
54 import org.apache.http.HttpException;
55 import org.apache.http.HttpHost;
56 import org.apache.http.HttpResponse;
57 import org.apache.http.HttpStatus;
58 import org.apache.http.NameValuePair;
59 import org.apache.http.client.entity.UrlEncodedFormEntity;
60 import org.apache.http.client.methods.HttpGet;
61 import org.apache.http.client.methods.HttpPost;
62 import org.apache.http.client.params.ClientPNames;
63 import org.apache.http.client.params.CookiePolicy;
64 import org.apache.http.conn.params.ConnRoutePNames;
65 import org.apache.http.cookie.Cookie;
66 import org.apache.http.entity.StringEntity;
67 import org.apache.http.impl.client.DefaultHttpClient;
68 import org.apache.http.impl.client.RedirectLocations;
69 import org.apache.http.message.BasicNameValuePair;
70 import org.apache.http.protocol.BasicHttpContext;
71 import org.apache.http.protocol.HttpContext;
72 import org.apache.http.util.EntityUtils;
73 import org.w3c.dom.Document;
74 import org.w3c.dom.Element;
75 import org.w3c.dom.NodeList;
76 import org.xml.sax.SAXException;
82 public class NicoHttpClient {
84 private static Log logger = LogFactory.getLog(NicoHttpClient.class);
85 private final DefaultHttpClient http;
86 private static final String LOGIN_PAGE =
87 "https://secure.nicovideo.jp/secure/login?site=niconico";
88 private static final String LOGOUT_PAGE =
89 "https://secure.nicovideo.jp/secure/logout";
90 private static final String WATCH_PAGE = "http://www.nicovideo.jp/watch/";
91 private static final String MY_LIST_PAGE_HEADER =
92 "http://www.nicovideo.jp/mylist/";
93 private static final String MOVIE_THUMBNAIL_PAGE_HEADER =
94 "http://www.nicovideo.jp/api/getthumbinfo/";
95 private static final String GET_FLV_INFO = "http://www.nicovideo.jp/api/getflv/";
96 private static final String SEARCH_HEAD = "http://www.nicovideo.jp/";
97 private static final String ADD_MYLIST_PAGE = "http://www.nicovideo.jp/mylist_add/video/";
98 private static final String GET_THREAD_KEY_PAGE = "http://www.nicovideo.jp/api/getthreadkey?thread=";
100 public NicoHttpClient() {
101 http = new DefaultHttpClient();
102 http.getParams().setParameter(
103 ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
107 * プロキシサーバを経由してアクセスする場合のコンストラクタ.
108 * @param host プロキシサーバのホスト名.
109 * @param port プロキシサーバで利用するポート番号.
111 public NicoHttpClient(String host, int port) {
113 HttpHost proxy = new HttpHost(host, port);
114 http.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
119 * @param mail ログイン識別子(登録メールアドレス).
120 * @param password パスワード.
121 * @return 認証がOKであればtrue.
123 public boolean login(String mail, String password) throws URISyntaxException, HttpException, InterruptedException {
124 boolean auth = false;
125 HttpPost post = new HttpPost(LOGIN_PAGE);
128 NameValuePair[] nvps = new NameValuePair[]{
129 new BasicNameValuePair("mail", mail),
130 new BasicNameValuePair("password", password),
131 new BasicNameValuePair("next_url", "")
133 post.setEntity(new UrlEncodedFormEntity(Arrays.asList(nvps), "UTF-8"));
135 //post.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
136 HttpResponse response = http.execute(post);
137 logger.debug("ログインステータスコード: " + response.getStatusLine().getStatusCode());
140 HttpEntity entity = response.getEntity();
141 EntityUtils.consume(entity);
142 List<Cookie> cookies = http.getCookieStore().getCookies();
143 if (!cookies.isEmpty()) {
146 } catch (IOException ex) {
147 logger.error("ログイン時に問題が発生", ex);
154 * @return ログアウトに成功すればtrue.
156 public boolean logout() throws URISyntaxException, HttpException, InterruptedException {
157 boolean result = false;
158 HttpGet method = new HttpGet(LOGOUT_PAGE);
160 HttpResponse response = http.execute(method);
161 logger.debug("ログアウトステータスコード: " + response.getStatusLine().getStatusCode());
163 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
166 EntityUtils.consume(response.getEntity());
167 } catch (IOException ex) {
168 logger.error("ログアウト時に問題が発生", ex);
175 * @param word 検索キーワード
178 * @page 検索結果ページのうち, 結果を返すページ.
181 public SearchResult search(SearchKind kind, String word, SortKind sort, SortOrder order, int page) throws
183 logger.debug("検索:" + word);
185 InputStream is = null;
186 ArrayList<NicoContent> conts = new ArrayList<NicoContent>();
187 String url = SEARCH_HEAD + kind.getKey() + "/" + URLEncoder.encode(word, "UTF-8") + "?page=" + Integer.toString(
188 page) + "&sort=" + sort.getKey() + "&order=" + order.getKey();
191 HttpGet get = new HttpGet(url);
192 HttpResponse response;
193 response = http.execute(get);
194 is = new BufferedInputStream(response.getEntity().getContent());
195 assert is.markSupported();
196 is.mark(1024 * 1024);
197 List<Result> results = Util.parseSearchResult(is);
198 for (Result r : results) {
199 NicoContent c = loadMyMovie(r.getId());
205 TreeMap<Integer, String> otherPages = Util.getOtherPages(is);
206 return new SearchResult(conts, otherPages);
207 } catch (IOException ex) {
208 logger.error("検索結果処理時に例外発生", ex);
214 } catch (IOException ex) {
221 * 「マイリスト登録数ランキング(本日)」の動画一覧を取得する。
224 public List<NicoContent> loadMyListDaily() throws URISyntaxException, HttpException, InterruptedException {
225 List<NicoContent> list = new ArrayList<NicoContent>();
226 String url = "http://www.nicovideo.jp/ranking/mylist/daily/all?rss=atom";
227 logger.debug("全動画サイトのマイリスト登録数ランキング(本日)[全体] : " + url);
229 HttpGet get = new HttpGet(url);
231 BufferedReader reader = null;
233 HttpResponse response = http.execute(get);
234 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
237 list = getNicoContents(reader);
238 deleteRankString(list);
239 EntityUtils.consume(response.getEntity());
240 } catch (FeedException ex) {
241 logger.error("", ex);
242 } catch (IOException ex) {
243 logger.error("", ex);
245 if (reader != null) {
248 } catch (IOException ex) {
249 logger.error("", ex);
257 * ニコニコ動画のRSSからコンテンツリストを取得する.
258 * @param url 取得するrssのurl.
261 public List<NicoContent> getContentsFromRss(String url) {
262 logger.debug("アクセスURL: " + url);
263 List<NicoContent> list = accessRssUrl(url);
264 if (url.contains("ranking")) {
265 deleteRankString(list);
272 * @param vi {@link #getVideoInfo(java.lang.String) }で取得したオブジェクト.
274 * @throws IOException 取得に失敗した場合.
276 public String getWayBackKey(VideoInfo vi) throws IOException {
277 final String url = "http://flapi.nicovideo.jp/api/getwaybackkey?thread=" + vi.getThreadId();
278 final HttpGet get = new HttpGet(url);
279 HttpResponse response = http.execute(get);
282 final int statusCode = response.getStatusLine().getStatusCode();
283 if (statusCode != HttpStatus.SC_OK) {
284 throw new IOException("waybackkey get error " + statusCode);
287 final BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
289 logger.debug("wayback get result text: " + res);
291 EntityUtils.consume(response.getEntity());
294 final String keyWayBackKey = "waybackkey=";
295 final String[] keyValues = res.split("&");
296 for (String s : keyValues) {
297 final String[] kv = s.split("=");
298 if (keyWayBackKey.equals(kv[0])) {
303 throw new IOException("wayback key get fail: " + res);
307 * rankingの場合、本当のタイトルの前に"第XX位:"の文字列が
309 * @param list 対象のリスト.
311 private void deleteRankString(List<NicoContent> list) {
312 for (NicoContent c : list) {
313 String title = c.getTitle();
314 int offset = title.indexOf(":") + 1;
315 c.setTitle(title.substring(offset));
321 * 「公開」設定にしていないリストからは取得できない.
323 * @param listNo マイリストNo.
326 public List<NicoContent> loadMyList(String listNo) {
327 String url = MY_LIST_PAGE_HEADER + listNo + "?rss=atom";
328 logger.debug("マイリストURL: " + url);
329 return accessRssUrl(url);
333 * コンテンツ概略のストリームを取得する.
335 * @return コンテンツ概略. 取得元でcloseすること.
336 * @throws IOException
338 public InputStream getThumbInfo(String movieNo) throws IOException {
339 String url = MOVIE_THUMBNAIL_PAGE_HEADER + movieNo;
340 logger.debug("動画サムネイルURL: " + url);
342 HttpGet get = new HttpGet(url);
343 HttpResponse response = http.execute(get);
344 return response.getEntity().getContent();
349 * 動画番号を指定したコンテンツ情報の取得.
350 * @param movieNo 動画番号.
353 public NicoContent loadMyMovie(String movieNo) {
354 NicoContent cont = null;
355 InputStream re = null;
358 re = getThumbInfo(movieNo);
359 // ドキュメントビルダーファクトリを生成
360 DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
362 DocumentBuilder builder = dbfactory.newDocumentBuilder();
363 // パースを実行してDocumentオブジェクトを取得
364 Document doc = builder.parse(re);
365 // ルート要素を取得(タグ名:site)
366 Element root = doc.getDocumentElement();
368 if ("fail".equals(root.getAttribute("status"))) {
369 logger.warn("情報取得できません: " + movieNo);
373 NodeList list2 = root.getElementsByTagName("thumb");
374 cont = new NicoContent();
375 Element element = (Element) list2.item(0);
377 String watch_url = ((Element) element.getElementsByTagName("watch_url").item(0)).getFirstChild().
379 cont.setPageLink(watch_url);
381 String title = ((Element) element.getElementsByTagName("title").item(0)).getFirstChild().getNodeValue();
382 cont.setTitle(title);
385 // String first_retrieve = ((Element) element.getElementsByTagName("first_retrieve").item(0)).getFirstChild().getNodeValue();
386 // cont.setPublishedDate(DateFormat.getInstance().parse(first_retrieve));
388 // } catch (ParseException ex) {
389 // Logger.getLogger(NicoHttpClient.class.getName()).log(Level.SEVERE, null, ex);
390 } catch (SAXException ex) {
391 logger.error("", ex);
392 } catch (IOException ex) {
393 logger.error("", ex);
394 } catch (ParserConfigurationException ex) {
395 logger.error("", ex);
401 } catch (IOException ex) {
402 logger.error("", ex);
408 private List<NicoContent> accessRssUrl(String url) {
409 List<NicoContent> contList = new ArrayList<NicoContent>();
410 HttpGet get = new HttpGet(url);
411 BufferedReader reader = null;
413 HttpResponse response = http.execute(get);
414 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
415 if (logger.isTraceEnabled()) {
416 reader.mark(1024 * 1024);
418 String str = reader.readLine();
426 contList = getNicoContents(reader);
427 } catch (FeedException ex) {
428 logger.warn("アクセスできません: " + url);
429 logger.debug("", ex);
430 } catch (IOException ex) {
431 logger.error("", ex);
433 if (reader != null) {
436 } catch (IOException ex) {
437 logger.error("", ex);
444 private List<NicoContent> getNicoContents(Reader reader) throws FeedException {
445 List<SyndEntryImpl> list = null;
446 SyndFeedInput input = new SyndFeedInput();
447 SyndFeed feed = input.build(reader);
449 list = (List<SyndEntryImpl>) feed.getEntries();
451 List<NicoContent> contList;
453 contList = new ArrayList<NicoContent>();
455 contList = createContentsList(list);
460 private List<NicoContent> createContentsList(List<SyndEntryImpl> list) {
461 class CallBack extends HTMLEditorKit.ParserCallback {
463 private boolean descFlag;
464 private String imageLink = new String();
465 private StringBuilder description = new StringBuilder();
468 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
469 logger.debug("--------<" + t.toString() + ">--------");
471 if (HTML.Tag.IMG.equals(t)) {
472 imageLink = a.getAttribute(HTML.Attribute.SRC).toString();
477 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
478 if (HTML.Tag.P.equals(t)) {
479 if ("nico-description".equals(
480 a.getAttribute(HTML.Attribute.CLASS).toString())) {
484 logger.debug("--------<" + t.toString() + ">--------");
489 public void handleEndTag(HTML.Tag t, int pos) {
490 if (HTML.Tag.P.equals(t)) {
493 logger.debug("--------</" + t.toString() + ">--------");
497 public void handleText(char[] data, int pos) {
499 description.append(data);
501 logger.debug("--------TEXT--------");
505 private void printAttributes(MutableAttributeSet a) {
506 Enumeration e = a.getAttributeNames();
507 while (e.hasMoreElements()) {
508 Object key = e.nextElement();
509 logger.debug("---- " + key.toString() + " : " + a.getAttribute(key));
513 public String getImageLink() {
517 public String getDescription() {
518 return description.toString();
522 List<NicoContent> contList = new ArrayList<NicoContent>();
524 for (SyndEntryImpl entry : list) {
525 NicoContent content = new NicoContent();
527 String title = entry.getTitle();
528 content.setTitle(title);
529 content.setPageLink(entry.getLink());
532 CallBack callBack = new CallBack();
533 for (SyndContentImpl sc : (List<SyndContentImpl>) entry.getContents()) {
535 Reader reader = new StringReader(sc.getValue());
536 new ParserDelegator().parse(reader, callBack, true);
537 } catch (IOException ex) {
538 logger.error("RSSの読み込み失敗: " + content.getTitle());
543 contList.add(content);
549 * FLVファイルのURLを取得する. ログインが必要.
550 * また, 実際にFLVファイルの実態をダウンロードするには
551 * 一度http://www.nicovideo.jp/watch/ビデオID に一度アクセスする必要があることに
553 * (参考: http://yusukebe.com/tech/archives/20070803/124356.html)
554 * @param videoId ニコニコ動画のビデオID.
555 * @return FLVファイル実体があるURL.
556 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
558 public VideoInfo getVideoInfo(String videoId) throws IOException {
559 final GetRealVideoIdResult res = accessWatchPage(videoId);
560 final String realVideoId = res.videoId;
562 String accessUrl = GET_FLV_INFO + realVideoId;
563 if (realVideoId.startsWith("nm")) {
564 accessUrl += "?as3=1";
566 Map<String, String> map = getParameterMap(accessUrl);
568 LinkedHashMap<String, String> keyMap = new LinkedHashMap<String, String>();
569 if ("1".equals(map.get("needs_key"))) {
570 // 公式動画投稿者コメント取得用パラメータ.
571 keyMap = getParameterMap(GET_THREAD_KEY_PAGE + map.get(VideoInfo.KEY_THREAD_ID));
573 return new VideoInfo(realVideoId, res.title, map, keyMap);
576 private LinkedHashMap<String, String> getParameterMap(String accessUrl) throws IOException, IllegalStateException {
577 logger.debug("アクセス: " + accessUrl);
578 HttpGet get = new HttpGet(accessUrl);
580 BufferedReader reader = null;
582 HttpResponse response = http.execute(get);
583 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
585 StringBuilder strBuilder = new StringBuilder();
586 while ((str = reader.readLine()) != null) {
587 strBuilder.append(str);
589 resultString = strBuilder.toString();
590 EntityUtils.consume(response.getEntity());
591 logger.debug(resultString);
593 if (reader != null) {
597 String[] params = resultString.split("&");
598 LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
599 for (String param : params) {
600 String[] elm = param.split("=");
601 map.put(elm[0], elm[1]);
607 * watchページコンテンツからタイトルを抽出する.
608 * @param content watchページコンテンツのストリーム.
610 private String getTitleInWatchPage(InputStream content) throws IOException {
611 final String TITLE_PARSE_STR_START = "<title>";
612 BufferedReader br = new BufferedReader(new InputStreamReader(content, "UTF-8"));
614 while ((ret = br.readLine()) != null) {
615 final int index = ret.indexOf(TITLE_PARSE_STR_START);
617 String videoTitle = ret.substring(index + TITLE_PARSE_STR_START.length(), ret.indexOf(" ‐", index));
625 private static class GetRealVideoIdResult {
627 private final String videoId;
628 private final String title;
630 private GetRealVideoIdResult(String videoId, String title) {
631 this.videoId = videoId;
637 * WATCHページへアクセスする. getflvを行うためには, 必ず事前にWATCHページへアクセスしておく必要があるため.
638 * WATCHページ参照時にリダイレクトが発生する(so動画ではスレッドIDのWATCHページにリダイレクトされる)場合には
639 * そちらのページにアクセスし、そのスレッドIDをrealIdとして返します.
640 * @param videoId 取得したいビデオのビデオID.
641 * @return 実際のアクセスに必要なIDと、タイトル. タイトルはいんきゅばす互換用です.
642 * @throws IOException アクセスに失敗した場合. 有料動画などがこれに含まれます.
644 private GetRealVideoIdResult accessWatchPage(String videoId) throws IOException {
645 String realId = videoId;
647 String watchUrl = WATCH_PAGE + videoId;
648 logger.debug("アクセス: " + watchUrl);
649 final HttpGet get = new HttpGet(watchUrl);
650 final HttpContext context = new BasicHttpContext();
651 final HttpResponse response = http.execute(get, context);
653 final RedirectLocations rl = (RedirectLocations) context.getAttribute(
654 "http.protocol.redirect-locations");
655 // 通常の動画(sm動画など)はリダイレクトが発生しないためnullになる
657 final List<URI> locations = rl.getAll();
658 logger.debug("リダイレクト数: " + locations.size());
660 // so動画はスレッドIDのページへリダイレクトされる
661 if (locations.size() == 1) {
662 realId = locations.get(0).toString().replace(WATCH_PAGE, "");
663 } else if (locations.size() > 1) {
664 throw new IOException("有料動画と思われるため処理を中断しました: " + ArrayUtils.toString(locations));
668 title = getTitleInWatchPage(response.getEntity().getContent());
670 EntityUtils.consume(response.getEntity());
672 return new GetRealVideoIdResult(realId, title);
676 * ニコニコ動画から動画ファイルをダウンロードする.
677 * @param vi getVideoInfoメソッドで取得したオブジェクト.
678 * @param saveDir ダウンロードしたファイルを保存するディレクトリ.
679 * @param np 保存するファイル名の命名規則. 拡張子は別途付与されるため不要.
680 * @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
681 * @param needLowFile エコノミー動画をダウンロードするのであればtrue.
682 * @return この処理を行った後の, 対象ファイルのステータス.
683 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
685 public GetFlvResult getFlvFile(VideoInfo vi, File saveDir, NamePattern np, Status nowStatus, boolean needLowFile,
686 ProgressListener listener) throws IOException, URISyntaxException, HttpException, InterruptedException {
688 final URL notifierUrl = vi.getSmileUrl();
690 String userName = null;
691 if (notifierUrl != null) {
692 HttpGet get = new HttpGet(notifierUrl.toString());
693 HttpResponse response = http.execute(get);
694 userName = Util.getUserName(response.getEntity().getContent());
695 EntityUtils.consume(response.getEntity());
698 final URL url = vi.getVideoUrl();
699 if (nowStatus == Status.GET_LOW || !needLowFile) {
700 if (url.toString().contains("low")) {
701 logger.info("エコノミー動画のためスキップ: " + vi.getRealVideoId());
702 return new GetFlvResult(null, nowStatus, userName);
705 final boolean isNotLow = !url.toString().contains("low");
707 final File downloadFile = new File(saveDir, np.createFileName(vi.getRealVideoId(), isNotLow));
709 HttpGet get = new HttpGet(url.toURI());
710 HttpResponse response = http.execute(get);
711 String contentType = response.getEntity().getContentType().getValue();
712 logger.debug(contentType);
713 logger.debug(downloadFile.toString());
714 if ("text/plain".equals(contentType) || "text/html".equals(contentType)) {
715 logger.error("取得できませんでした. サーバが混みあっている可能性があります: " + vi.getRealVideoId());
716 EntityUtils.consume(response.getEntity());
717 return new GetFlvResult(null, Status.GET_INFO, userName);
719 String ext = Util.getExtention(contentType);
720 final long fileSize = response.getEntity().getContentLength();
722 final int BUF_SIZE = 1024 * 32;
723 BufferedInputStream in = new BufferedInputStream(response.getEntity().getContent());
725 File file = new File(downloadFile.toString() + "." + ext);
726 logger.info("保存します(" + fileSize / 1024 + "KB): " + file.getPath());
727 FileOutputStream fos = new FileOutputStream(file);
728 BufferedOutputStream out = new BufferedOutputStream(fos);
730 long downloadSize = 0;
732 byte[] buffer = new byte[BUF_SIZE];
733 while ((i = in.read(buffer)) != -1) {
734 out.write(buffer, 0, i);
736 listener.progress(fileSize, downloadSize);
737 if (listener.getCancel()) {
738 return new GetFlvResult(null, Status.GET_INFO, userName);
742 EntityUtils.consume(response.getEntity());
745 if (url.toString().contains("low")) {
746 return new GetFlvResult(file, Status.GET_LOW, userName);
748 return new GetFlvResult(file, Status.GET_FILE, userName);
752 * ニコニコ動画から動画ファイルをダウンロードする.
753 * @param vi getVideoInfoメソッドで取得したオブジェクト.
754 * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
755 * @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
756 * @param needLowFile エコノミー動画をダウンロードするのであればtrue.
757 * @return この処理を行った後の, 対象ファイルのステータス.
758 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
760 public GetFlvResult getFlvFile(VideoInfo vi, String fileName, Status nowStatus, boolean needLowFile,
761 ProgressListener listener) throws IOException, URISyntaxException, HttpException, InterruptedException {
762 String file = FilenameUtils.getName(fileName);
763 String dir = fileName.substring(0, fileName.length() - file.length());
764 NamePattern np = new NamePattern(file, "", "", "");
765 return getFlvFile(vi, new File(dir), np, nowStatus, needLowFile, listener);
769 * ニコニコ動画から動画ファイルをダウンロードする.
770 * @param vi getVideoInfoメソッドで取得したオブジェクト.
771 * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
772 * @return この処理を行った後の, 対象ファイルのステータス.
773 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
775 public GetFlvResult getFlvFile(VideoInfo vi, String fileName, ProgressListener listener) throws IOException,
777 HttpException, InterruptedException {
778 return getFlvFile(vi, fileName, Status.GET_INFO, true, listener);
782 * ニコニコ動画から動画ファイルをダウンロードする.
784 * @param vi getVideoInfoメソッドで取得したオブジェクト.
785 * @return この処理を行った後の, 対象ファイルのステータス.
786 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
788 public GetFlvResult getFlvFile(VideoInfo vi) throws IOException, URISyntaxException, HttpException,
789 InterruptedException {
790 return getFlvFile(vi, vi.getRealVideoId(), Status.GET_INFO, true, ProgressListener.EMPTY_LISTENER);
793 public File getCommentFile(VideoInfo vi, String fileName, WayBackInfo wayback) throws Exception {
794 return downloadComment(vi, fileName, false, wayback);
797 public File getTCommentFile(VideoInfo vi, String fileName) throws Exception {
798 return downloadComment(vi, fileName, true, null, true);
801 private File downloadComment(VideoInfo vi, String fileName, boolean isTcomm, WayBackInfo wayback) throws Exception {
802 return downloadComment(vi, fileName, isTcomm, wayback, false);
805 private File downloadComment(VideoInfo vi, String fileName, boolean isTcomm, WayBackInfo wayback, boolean oldVersion)
807 HttpResponse response = null;
808 BufferedOutputStream bos = null;
811 final HttpPost post = new HttpPost(vi.getMessageUrl().toString());
813 if (oldVersion || isTcomm) {
814 param = createCommendDownloadParameter20101222(vi, isTcomm, wayback);
816 param = createCommendDownloadParameter(vi, wayback);
818 final StringEntity se = new StringEntity(param);
820 response = http.execute(post);
821 final InputStream is = response.getEntity().getContent();
822 final BufferedInputStream bis = new BufferedInputStream(is);
824 final String outputFileName = (fileName.endsWith(".xml")) ? fileName : fileName + ".xml";
825 bos = new BufferedOutputStream(new FileOutputStream(outputFileName));
827 final byte[] buf = new byte[1024 * 1024];
829 while ((read = bis.read(buf, 0, buf.length)) > 0) {
830 bos.write(buf, 0, read);
833 return new File(outputFileName);
834 } catch (Exception e) {
835 throw new Exception("コメントダウンロードに失敗しました。", e);
837 if (response != null) {
838 EntityUtils.consume(response.getEntity());
846 private String createCommendDownloadParameter(VideoInfo vi, WayBackInfo wayback) {
847 final String quote = "\"";
848 final Map<String, String> th = new HashMap<String, String>();
849 th.put("thread", vi.getThreadId());
850 th.put("version", "20090904");
851 th.put("user_id", vi.getUserId());
853 final Map<String, String> leaf = new HashMap<String, String>();
854 leaf.put("thread", vi.getThreadId());
855 leaf.put("user_id", vi.getUserId());
857 // TODO videoLengh は秒数が入っているんだっけ?
858 final int length = (int) Math.ceil(vi.getVideoLength() / 60.0);
859 final String element = "0-" + length + ":100," + vi.getResFrom();
861 final StringBuilder str = new StringBuilder();
862 str.append("<packet>");
864 str.append("<thread");
865 for (String k : th.keySet()) {
866 final String v = th.get(k);
876 str.append("<thread_leaves");
877 for (String k : leaf.keySet()) {
878 final String v = th.get(k);
888 str.append("</thread_leaves>");
890 str.append("</packet>");
892 return str.toString();
896 * 2010/12/22 までのコメント表示仕様に基づいた取得パラメータ生成.
897 * 「コメントの量を減らす」にチェックを入れた場合は現在でもこれが用いられているはず.
899 private String createCommendDownloadParameter20101222(VideoInfo vi, boolean isTcomm, WayBackInfo wayback) {
900 final String tcommStr = (isTcomm) ? "fork=\"1\" " : "";
901 // TODO wayBackStr 使用するのを忘れている
902 final String wayBackStr = (wayback != null) ? "when=" + "\"" + wayback.getTime() + "\"" + " waybackkey=" + "\""
903 + wayback.getKey() + " " : "";
904 StringBuilder builder = new StringBuilder();
905 Set<String> keySet = vi.getKeyMap().keySet();
906 for (String key : keySet) {
907 builder.append(key).append("=\"").append(vi.getKeyMap().get(key)).append("\" ");
909 final String officialOption = builder.toString();
911 return "<thread " + VideoInfo.KEY_USER_ID + "=\"" + vi.getUserId() + "\" res_from=\"" + (-1 * vi.getResFrom())
912 + "\" version=\"20061206\" thread=\"" + vi.getThreadId() + "\" " + tcommStr + officialOption + "/>";
916 * 動画をマイリストへ登録する. ログインが必要.
917 * @param myListId 登録するマイリストのID.
918 * @param videoId 登録する動画ID.
919 * @throws IOException 登録に失敗した.
921 public void addMyList(String myListId, String videoId) throws IOException {
922 String itemType = null;
923 String itemId = null;
925 HttpGet get = new HttpGet(ADD_MYLIST_PAGE + videoId);
926 HttpResponse response = http.execute(get);
927 HttpEntity entity = response.getEntity();
929 InputStream is = entity.getContent();
930 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
933 Pattern pattern = Pattern.compile("input type=\"hidden\" name=\"item_type\" value=\"(.+)\"");
934 while ((line = reader.readLine()) != null) {
935 Matcher m = pattern.matcher(line);
937 itemType = m.group(1);
942 pattern = Pattern.compile("input type=\"hidden\" name=\"item_id\" value=\"(.+)\"");
943 while ((line = reader.readLine()) != null) {
944 Matcher m = pattern.matcher(line);
951 pattern = Pattern.compile("NicoAPI\\.token = \"(.*)\";");
952 while ((line = reader.readLine()) != null) {
953 Matcher m = pattern.matcher(line);
960 EntityUtils.consume(entity);
963 if (itemType == null || itemId == null || token == null) {
964 throw new IOException("マイリスト登録に必要な情報が取得できませんでした。 "
965 + "マイリスト:" + myListId + ", 動画ID:" + videoId + ", item_type:" + itemType + ", item_id:" + itemId
966 + ", token:" + token);
969 StringEntity se = new StringEntity(
970 "group_id=" + myListId
971 + "&item_type=" + itemType
972 + "&item_id=" + itemId
973 + "&description=" + ""
974 + "&token=" + token);
976 HttpPost post = new HttpPost("http://www.nicovideo.jp/api/mylist/add");
977 post.setHeader("Content-Type", "application/x-www-form-urlencoded");
979 response = http.execute(post);
980 int statusCode = response.getStatusLine().getStatusCode();
981 EntityUtils.consume(response.getEntity());
982 if (statusCode != HttpStatus.SC_OK) {
983 throw new IOException("マイリスト登録に失敗" + "マイリスト:" + myListId + ", 動画ID:" + videoId);