4 import java.net.URISyntaxException;
6 import java.util.TreeMap;
7 import java.util.regex.Matcher;
8 import nicobrowser.entity.NicoContent;
9 import nicobrowser.search.SortKind;
10 import nicobrowser.search.SortOrder;
11 import com.sun.syndication.feed.synd.SyndContentImpl;
12 import com.sun.syndication.feed.synd.SyndEntryImpl;
13 import com.sun.syndication.feed.synd.SyndFeed;
14 import com.sun.syndication.io.FeedException;
15 import com.sun.syndication.io.SyndFeedInput;
16 import java.io.BufferedInputStream;
17 import java.io.BufferedOutputStream;
18 import java.io.BufferedReader;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.Reader;
25 import java.io.StringReader;
27 import java.net.URLEncoder;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Enumeration;
31 import java.util.LinkedHashMap;
32 import java.util.List;
34 import java.util.regex.Pattern;
35 import javax.swing.text.MutableAttributeSet;
36 import javax.swing.text.html.HTML;
37 import javax.swing.text.html.HTMLEditorKit;
38 import javax.swing.text.html.parser.ParserDelegator;
39 import javax.xml.parsers.DocumentBuilder;
40 import javax.xml.parsers.DocumentBuilderFactory;
41 import javax.xml.parsers.ParserConfigurationException;
42 import nicobrowser.entity.NicoContent.Status;
43 import nicobrowser.search.SearchKind;
44 import nicobrowser.search.SearchResult;
45 import nicobrowser.util.Result;
46 import nicobrowser.util.Util;
47 import org.apache.commons.io.FilenameUtils;
48 import org.apache.commons.logging.Log;
49 import org.apache.commons.logging.LogFactory;
50 import org.apache.http.HttpEntity;
51 import org.apache.http.HttpException;
52 import org.apache.http.HttpHost;
53 import org.apache.http.HttpResponse;
54 import org.apache.http.HttpStatus;
55 import org.apache.http.NameValuePair;
56 import org.apache.http.client.entity.UrlEncodedFormEntity;
57 import org.apache.http.client.methods.HttpGet;
58 import org.apache.http.client.methods.HttpPost;
59 import org.apache.http.client.params.ClientPNames;
60 import org.apache.http.client.params.CookiePolicy;
61 import org.apache.http.conn.params.ConnRoutePNames;
62 import org.apache.http.cookie.Cookie;
63 import org.apache.http.entity.StringEntity;
64 import org.apache.http.impl.client.DefaultHttpClient;
65 import org.apache.http.message.BasicNameValuePair;
66 import org.w3c.dom.Document;
67 import org.w3c.dom.Element;
68 import org.w3c.dom.NodeList;
69 import org.xml.sax.SAXException;
75 public class NicoHttpClient {
77 private static Log logger = LogFactory.getLog(NicoHttpClient.class);
78 private final DefaultHttpClient http;
79 private static final String LOGIN_PAGE =
80 "https://secure.nicovideo.jp/secure/login?site=niconico";
81 private static final String LOGOUT_PAGE =
82 "https://secure.nicovideo.jp/secure/logout";
83 private static final String WATCH_PAGE = "http://www.nicovideo.jp/watch/";
84 private static final String MY_LIST_PAGE_HEADER =
85 "http://www.nicovideo.jp/mylist/";
86 private static final String MOVIE_THUMBNAIL_PAGE_HEADER =
87 "http://www.nicovideo.jp/api/getthumbinfo/";
88 private static final String GET_FLV_INFO = "http://www.nicovideo.jp/api/getflv/";
89 private static final String SEARCH_HEAD = "http://www.nicovideo.jp/";
90 private static final String ADD_MYLIST_PAGE = "http://www.nicovideo.jp/mylist_add/video/";
91 private static final String GET_THREAD_KEY_PAGE = "http://www.nicovideo.jp/api/getthreadkey?thread=";
93 public NicoHttpClient() {
94 http = new DefaultHttpClient();
95 http.getParams().setParameter(
96 ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
100 * プロキシサーバを経由してアクセスする場合のコンストラクタ.
101 * @param host プロキシサーバのホスト名.
102 * @param port プロキシサーバで利用するポート番号.
104 public NicoHttpClient(String host, int port) {
106 HttpHost proxy = new HttpHost(host, port);
107 http.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy);
112 * @param mail ログイン識別子(登録メールアドレス).
113 * @param password パスワード.
114 * @return 認証がOKであればtrue.
116 public boolean login(String mail, String password) throws URISyntaxException, HttpException, InterruptedException {
117 boolean auth = false;
118 HttpPost post = new HttpPost(LOGIN_PAGE);
121 NameValuePair[] nvps = new NameValuePair[]{
122 new BasicNameValuePair("mail", mail),
123 new BasicNameValuePair("password", password),
124 new BasicNameValuePair("next_url", "")
126 post.setEntity(new UrlEncodedFormEntity(Arrays.asList(nvps), "UTF-8"));
128 //post.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
129 HttpResponse response = http.execute(post);
130 logger.debug("ログインステータスコード: " + response.getStatusLine().getStatusCode());
133 HttpEntity entity = response.getEntity();
134 entity.consumeContent();
135 List<Cookie> cookies = http.getCookieStore().getCookies();
136 if (!cookies.isEmpty()) {
139 } catch (IOException ex) {
140 logger.error("ログイン時に問題が発生", ex);
147 * @return ログアウトに成功すればtrue.
149 public boolean logout() throws URISyntaxException, HttpException, InterruptedException {
150 boolean result = false;
151 HttpGet method = new HttpGet(LOGOUT_PAGE);
153 HttpResponse response = http.execute(method);
154 logger.debug("ログアウトステータスコード: " + response.getStatusLine().getStatusCode());
156 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
159 response.getEntity().consumeContent();
160 } catch (IOException ex) {
161 logger.error("ログアウト時に問題が発生", ex);
168 * @param word 検索キーワード
171 * @page 検索結果ページのうち, 結果を返すページ.
174 public SearchResult search(SearchKind kind, String word, SortKind sort, SortOrder order, int page) throws
176 logger.debug("検索:" + word);
178 InputStream is = null;
179 ArrayList<NicoContent> conts = new ArrayList<NicoContent>();
180 String url = SEARCH_HEAD + kind.getKey() + "/" + URLEncoder.encode(word, "UTF-8") + "?page=" + Integer.toString(
181 page) + "&sort=" + sort.getKey() + "&order=" + order.getKey();
184 HttpGet get = new HttpGet(url);
185 HttpResponse response;
186 response = http.execute(get);
187 is = new BufferedInputStream(response.getEntity().getContent());
188 assert is.markSupported();
189 is.mark(1024 * 1024);
190 List<Result> results = Util.parseSearchResult(is);
191 for (Result r : results) {
192 NicoContent c = loadMyMovie(r.getId());
198 TreeMap<Integer, String> otherPages = Util.getOtherPages(is);
199 return new SearchResult(conts, otherPages);
200 } catch (IOException ex) {
201 logger.error("検索結果処理時に例外発生", ex);
207 } catch (IOException ex) {
214 * 「マイリスト登録数ランキング(本日)」の動画一覧を取得する。
217 public List<NicoContent> loadMyListDaily() throws URISyntaxException, HttpException, InterruptedException {
218 List<NicoContent> list = new ArrayList<NicoContent>();
219 String url = "http://www.nicovideo.jp/ranking/mylist/daily/all?rss=atom";
220 logger.debug("全動画サイトのマイリスト登録数ランキング(本日)[全体] : " + url);
222 HttpGet get = new HttpGet(url);
224 BufferedReader reader = null;
226 HttpResponse response = http.execute(get);
227 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
230 list = getNicoContents(reader);
231 deleteRankString(list);
232 response.getEntity().consumeContent();
233 } catch (FeedException ex) {
234 logger.error("", ex);
235 } catch (IOException ex) {
236 logger.error("", ex);
238 if (reader != null) {
241 } catch (IOException ex) {
242 logger.error("", ex);
250 * ニコニコ動画のRSSからコンテンツリストを取得する.
251 * @param url 取得するrssのurl.
254 public List<NicoContent> getContentsFromRss(String url) {
255 logger.debug("アクセスURL: " + url);
256 List<NicoContent> list = accessRssUrl(url);
257 if (url.contains("ranking")) {
258 deleteRankString(list);
265 * @param vi {@link #getVideoInfo(java.lang.String) }で取得したオブジェクト.
267 * @throws IOException 取得に失敗した場合.
269 public String getWayBackKey(VideoInfo vi) throws IOException {
270 final String url = "http://flapi.nicovideo.jp/api/getwaybackkey?thread=" + vi.getThreadId();
271 final HttpGet get = new HttpGet(url);
272 HttpResponse response = http.execute(get);
275 final int statusCode = response.getStatusLine().getStatusCode();
276 if (statusCode != HttpStatus.SC_OK) {
277 throw new IOException("waybackkey get error " + statusCode);
280 final BufferedReader br = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
282 logger.debug("wayback get result text: " + res);
284 response.getEntity().consumeContent();
287 final String keyWayBackKey = "waybackkey=";
288 final String[] keyValues = res.split("&");
289 for (String s : keyValues) {
290 final String[] kv = s.split("=");
291 if (keyWayBackKey.equals(kv[0])) {
296 throw new IOException("wayback key get fail: " + res);
300 * rankingの場合、本当のタイトルの前に"第XX位:"の文字列が
302 * @param list 対象のリスト.
304 private void deleteRankString(List<NicoContent> list) {
305 for (NicoContent c : list) {
306 String title = c.getTitle();
307 int offset = title.indexOf(":") + 1;
308 c.setTitle(title.substring(offset));
314 * 「公開」設定にしていないリストからは取得できない.
316 * @param listNo マイリストNo.
319 public List<NicoContent> loadMyList(String listNo) {
320 String url = MY_LIST_PAGE_HEADER + listNo + "?rss=atom";
321 logger.debug("マイリストURL: " + url);
322 return accessRssUrl(url);
326 * コンテンツ概略のストリームを取得する.
328 * @return コンテンツ概略. 取得元でcloseすること.
329 * @throws IOException
331 public InputStream getThumbInfo(String movieNo) throws IOException {
332 String url = MOVIE_THUMBNAIL_PAGE_HEADER + movieNo;
333 logger.debug("動画サムネイルURL: " + url);
335 HttpGet get = new HttpGet(url);
336 HttpResponse response = http.execute(get);
337 return response.getEntity().getContent();
342 * 動画番号を指定したコンテンツ情報の取得.
343 * @param movieNo 動画番号.
346 public NicoContent loadMyMovie(String movieNo) {
347 NicoContent cont = null;
348 InputStream re = null;
351 re = getThumbInfo(movieNo);
352 // ドキュメントビルダーファクトリを生成
353 DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
355 DocumentBuilder builder = dbfactory.newDocumentBuilder();
356 // パースを実行してDocumentオブジェクトを取得
357 Document doc = builder.parse(re);
358 // ルート要素を取得(タグ名:site)
359 Element root = doc.getDocumentElement();
361 if ("fail".equals(root.getAttribute("status"))) {
362 logger.warn("情報取得できません: " + movieNo);
366 NodeList list2 = root.getElementsByTagName("thumb");
367 cont = new NicoContent();
368 Element element = (Element) list2.item(0);
370 String watch_url = ((Element) element.getElementsByTagName("watch_url").item(0)).getFirstChild().
372 cont.setPageLink(watch_url);
374 String title = ((Element) element.getElementsByTagName("title").item(0)).getFirstChild().getNodeValue();
375 cont.setTitle(title);
378 // String first_retrieve = ((Element) element.getElementsByTagName("first_retrieve").item(0)).getFirstChild().getNodeValue();
379 // cont.setPublishedDate(DateFormat.getInstance().parse(first_retrieve));
381 // } catch (ParseException ex) {
382 // Logger.getLogger(NicoHttpClient.class.getName()).log(Level.SEVERE, null, ex);
383 } catch (SAXException ex) {
384 logger.error("", ex);
385 } catch (IOException ex) {
386 logger.error("", ex);
387 } catch (ParserConfigurationException ex) {
388 logger.error("", ex);
394 } catch (IOException ex) {
395 logger.error("", ex);
401 private List<NicoContent> accessRssUrl(String url) {
402 List<NicoContent> contList = new ArrayList<NicoContent>();
403 HttpGet get = new HttpGet(url);
404 BufferedReader reader = null;
406 HttpResponse response = http.execute(get);
407 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
408 if (logger.isTraceEnabled()) {
409 reader.mark(1024 * 1024);
411 String str = reader.readLine();
419 contList = getNicoContents(reader);
420 } catch (FeedException ex) {
421 logger.warn("アクセスできません: " + url);
422 logger.debug("", ex);
423 } catch (IOException ex) {
424 logger.error("", ex);
426 if (reader != null) {
429 } catch (IOException ex) {
430 logger.error("", ex);
437 private List<NicoContent> getNicoContents(Reader reader) throws FeedException {
438 List<SyndEntryImpl> list = null;
439 SyndFeedInput input = new SyndFeedInput();
440 SyndFeed feed = input.build(reader);
442 list = (List<SyndEntryImpl>) feed.getEntries();
444 List<NicoContent> contList;
446 contList = new ArrayList<NicoContent>();
448 contList = createContentsList(list);
453 private List<NicoContent> createContentsList(List<SyndEntryImpl> list) {
454 class CallBack extends HTMLEditorKit.ParserCallback {
456 private boolean descFlag;
457 private String imageLink = new String();
458 private StringBuilder description = new StringBuilder();
461 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
462 logger.debug("--------<" + t.toString() + ">--------");
464 if (HTML.Tag.IMG.equals(t)) {
465 imageLink = a.getAttribute(HTML.Attribute.SRC).toString();
470 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
471 if (HTML.Tag.P.equals(t)) {
472 if ("nico-description".equals(
473 a.getAttribute(HTML.Attribute.CLASS).toString())) {
477 logger.debug("--------<" + t.toString() + ">--------");
482 public void handleEndTag(HTML.Tag t, int pos) {
483 if (HTML.Tag.P.equals(t)) {
486 logger.debug("--------</" + t.toString() + ">--------");
490 public void handleText(char[] data, int pos) {
492 description.append(data);
494 logger.debug("--------TEXT--------");
498 private void printAttributes(MutableAttributeSet a) {
499 Enumeration e = a.getAttributeNames();
500 while (e.hasMoreElements()) {
501 Object key = e.nextElement();
502 logger.debug("---- " + key.toString() + " : " + a.getAttribute(key));
506 public String getImageLink() {
510 public String getDescription() {
511 return description.toString();
515 List<NicoContent> contList = new ArrayList<NicoContent>();
517 for (SyndEntryImpl entry : list) {
518 NicoContent content = new NicoContent();
520 String title = entry.getTitle();
521 content.setTitle(title);
522 content.setPageLink(entry.getLink());
525 CallBack callBack = new CallBack();
526 for (SyndContentImpl sc : (List<SyndContentImpl>) entry.getContents()) {
528 Reader reader = new StringReader(sc.getValue());
529 new ParserDelegator().parse(reader, callBack, true);
530 } catch (IOException ex) {
531 logger.error("RSSの読み込み失敗: " + content.getTitle());
536 contList.add(content);
542 * FLVファイルのURLを取得する. ログインが必要.
543 * また, 実際にFLVファイルの実態をダウンロードするには
544 * 一度http://www.nicovideo.jp/watch/ビデオID に一度アクセスする必要があることに
546 * (参考: http://yusukebe.com/tech/archives/20070803/124356.html)
547 * @param videoId ニコニコ動画のビデオID.
548 * @return FLVファイル実体があるURL.
549 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
551 public VideoInfo getVideoInfo(String videoId) throws IOException {
552 final GetRealVideoIdResult res = getRealVideoId(videoId);
553 final String realVideoId = res.videoId;
555 String accessUrl = GET_FLV_INFO + realVideoId;
556 if (realVideoId.startsWith("nm")) {
557 accessUrl += "?as3=1";
559 Map<String, String> map = getParameterMap(accessUrl);
561 LinkedHashMap<String, String> keyMap = new LinkedHashMap<String, String>();
562 if ("1".equals(map.get("needs_key"))) {
563 // 公式動画投稿者コメント取得用パラメータ.
564 keyMap = getParameterMap(GET_THREAD_KEY_PAGE + map.get(VideoInfo.KEY_THREAD_ID));
566 return new VideoInfo(realVideoId, res.title, map, keyMap);
569 private LinkedHashMap<String, String> getParameterMap(String accessUrl) throws IOException, IllegalStateException {
570 logger.debug("アクセス: " + accessUrl);
571 HttpGet get = new HttpGet(accessUrl);
573 BufferedReader reader = null;
575 HttpResponse response = http.execute(get);
576 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
578 StringBuilder strBuilder = new StringBuilder();
579 while ((str = reader.readLine()) != null) {
580 strBuilder.append(str);
582 resultString = strBuilder.toString();
583 response.getEntity().consumeContent();
584 logger.debug(resultString);
586 if (reader != null) {
590 String[] params = resultString.split("&");
591 LinkedHashMap<String, String> map = new LinkedHashMap<String, String>();
592 for (String param : params) {
593 String[] elm = param.split("=");
594 map.put(elm[0], elm[1]);
600 * watchページコンテンツからタイトルを抽出する.
601 * @param content watchページコンテンツのストリーム.
603 private String getTitleInWatchPage(InputStream content) throws IOException {
604 final String TITLE_PARSE_STR_START = "<title>";
605 BufferedReader br = new BufferedReader(new InputStreamReader(content, "UTF-8"));
607 while ((ret = br.readLine()) != null) {
608 final int index = ret.indexOf(TITLE_PARSE_STR_START);
610 String videoTitle = ret.substring(index + TITLE_PARSE_STR_START.length(), ret.indexOf("‐", index));
618 private static class GetRealVideoIdResult {
620 private final String videoId;
621 private final String title;
623 private GetRealVideoIdResult(String videoId, String title) {
624 this.videoId = videoId;
630 * watchページへアクセスし, ビデオIDを取得する.
631 * soXXXXなど, 実際のビデオIDと異なる場合がある.
632 * @param videoId 取得したいビデオのビデオID.
634 * @throws IOException
636 private GetRealVideoIdResult getRealVideoId(String videoId) throws IOException {
637 GetRealVideoIdResult res = accessWatchPage(videoId);
638 final String realId = res.videoId;
640 // ステータスコード302など、リダイレクトが必要な場合
641 if (!videoId.equals(realId)) {
642 res = getRealVideoId(realId);
648 * WATCHページへアクセスする. getflvを行うためには, 必ず事前にWATCHページへアクセスしておく必要があるため.
649 * @param videoId ビデオID.
651 * @throws IOException
653 private GetRealVideoIdResult accessWatchPage(String videoId) throws IOException {
654 String realId = videoId;
656 String watchUrl = WATCH_PAGE + videoId;
657 logger.debug("アクセス: " + watchUrl);
658 http.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
660 HttpGet get = new HttpGet(watchUrl);
661 HttpResponse response = http.execute(get);
663 if (response.containsHeader("Location")) {
664 realId = response.getFirstHeader("Location").getValue().replace("/watch/", "");
666 title = getTitleInWatchPage(response.getEntity().getContent());
668 response.getEntity().consumeContent();
671 http.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, true);
673 return new GetRealVideoIdResult(realId, title);
677 * ニコニコ動画から動画ファイルをダウンロードする.
678 * @param vi getVideoInfoメソッドで取得したオブジェクト.
679 * @param saveDir ダウンロードしたファイルを保存するディレクトリ.
680 * @param np 保存するファイル名の命名規則. 拡張子は別途付与されるため不要.
681 * @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
682 * @param needLowFile エコノミー動画をダウンロードするのであればtrue.
683 * @return この処理を行った後の, 対象ファイルのステータス.
684 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
686 public GetFlvResult getFlvFile(VideoInfo vi, File saveDir, NamePattern np, Status nowStatus, boolean needLowFile,
687 ProgressListener listener) throws IOException, URISyntaxException, HttpException, InterruptedException {
689 final URL notifierUrl = vi.getSmileUrl();
691 String userName = null;
692 if (notifierUrl != null) {
693 HttpGet get = new HttpGet(notifierUrl.toString());
694 HttpResponse response = http.execute(get);
695 userName = Util.getUserName(response.getEntity().getContent());
696 response.getEntity().consumeContent();
699 final URL url = vi.getVideoUrl();
700 if (nowStatus == Status.GET_LOW || !needLowFile) {
701 if (url.toString().contains("low")) {
702 logger.info("エコノミー動画のためスキップ: " + vi.getRealVideoId());
703 return new GetFlvResult(null, nowStatus, userName);
706 final boolean isNotLow = !url.toString().contains("low");
708 final File downloadFile = new File(saveDir, np.createFileName(vi.getRealVideoId(), isNotLow));
710 HttpGet get = new HttpGet(url.toURI());
711 HttpResponse response = http.execute(get);
712 String contentType = response.getEntity().getContentType().getValue();
713 logger.debug(contentType);
714 logger.debug(downloadFile.toString());
715 if ("text/plain".equals(contentType) || "text/html".equals(contentType)) {
716 logger.error("取得できませんでした. サーバが混みあっている可能性があります: " + vi.getRealVideoId());
717 response.getEntity().consumeContent();
718 return new GetFlvResult(null, Status.GET_INFO, userName);
720 String ext = Util.getExtention(contentType);
721 final long fileSize = response.getEntity().getContentLength();
723 final int BUF_SIZE = 1024 * 32;
724 BufferedInputStream in = new BufferedInputStream(response.getEntity().getContent());
726 File file = new File(downloadFile.toString() + "." + ext);
727 logger.info("保存します(" + fileSize / 1024 + "KB): " + file.getPath());
728 FileOutputStream fos = new FileOutputStream(file);
729 BufferedOutputStream out = new BufferedOutputStream(fos);
731 long downloadSize = 0;
733 byte[] buffer = new byte[BUF_SIZE];
734 while ((i = in.read(buffer)) != -1) {
735 out.write(buffer, 0, i);
737 listener.progress(fileSize, downloadSize);
740 response.getEntity().consumeContent();
743 if (url.toString().contains("low")) {
744 return new GetFlvResult(file, Status.GET_LOW, userName);
746 return new GetFlvResult(file, Status.GET_FILE, userName);
750 * ニコニコ動画から動画ファイルをダウンロードする.
751 * @param vi getVideoInfoメソッドで取得したオブジェクト.
752 * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
753 * @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
754 * @param needLowFile エコノミー動画をダウンロードするのであればtrue.
755 * @return この処理を行った後の, 対象ファイルのステータス.
756 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
758 public GetFlvResult getFlvFile(VideoInfo vi, String fileName, Status nowStatus, boolean needLowFile,
759 ProgressListener listener) throws IOException, URISyntaxException, HttpException, InterruptedException {
760 String file = FilenameUtils.getName(fileName);
761 String dir = fileName.substring(0, fileName.length() - file.length());
762 NamePattern np = new NamePattern(file, "", "", "");
763 return getFlvFile(vi, new File(dir), np, nowStatus, needLowFile, listener);
767 * ニコニコ動画から動画ファイルをダウンロードする.
768 * @param vi getVideoInfoメソッドで取得したオブジェクト.
769 * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
770 * @return この処理を行った後の, 対象ファイルのステータス.
771 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
773 public GetFlvResult getFlvFile(VideoInfo vi, String fileName, ProgressListener listener) throws IOException,
775 HttpException, InterruptedException {
776 return getFlvFile(vi, fileName, Status.GET_INFO, true, listener);
780 * ニコニコ動画から動画ファイルをダウンロードする.
782 * @param vi getVideoInfoメソッドで取得したオブジェクト.
783 * @return この処理を行った後の, 対象ファイルのステータス.
784 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
786 public GetFlvResult getFlvFile(VideoInfo vi) throws IOException, URISyntaxException, HttpException,
787 InterruptedException {
788 return getFlvFile(vi, vi.getRealVideoId(), Status.GET_INFO, true, ProgressListener.EMPTY_LISTENER);
791 public File getCommentFile(VideoInfo vi, String fileName) throws Exception {
792 return downloadComment(vi, fileName, false);
795 public File getTCommentFile(VideoInfo vi, String fileName) throws Exception {
796 return downloadComment(vi, fileName, true);
799 private File downloadComment(VideoInfo vi, String fileName, boolean isTcomm) throws Exception {
800 HttpResponse response = null;
801 BufferedOutputStream bos = null;
804 final HttpPost post = new HttpPost(vi.getMessageUrl().toString());
805 final String param = createCommendDownloadParameter(vi, isTcomm);
806 final StringEntity se = new StringEntity(param);
808 response = http.execute(post);
809 final InputStream is = response.getEntity().getContent();
810 final BufferedInputStream bis = new BufferedInputStream(is);
812 final String outputFileName = (fileName.endsWith(".xml")) ? fileName : fileName + ".xml";
813 bos = new BufferedOutputStream(new FileOutputStream(outputFileName));
815 final byte[] buf = new byte[1024 * 1024];
817 while ((read = bis.read(buf, 0, buf.length)) > 0) {
818 bos.write(buf, 0, read);
821 return new File(outputFileName);
822 } catch (Exception e) {
823 throw new Exception("コメントダウンロードに失敗しました。", e);
825 if (response != null) {
826 response.getEntity().consumeContent();
834 private String createCommendDownloadParameter(VideoInfo vi, boolean isTcomm) {
835 final String tcommStr = (isTcomm) ? "fork=\"1\" " : "";
836 StringBuilder builder = new StringBuilder();
837 Set<String> keySet = vi.getKeyMap().keySet();
838 for (String key : keySet) {
839 builder.append(key).append("=\"").append(vi.getKeyMap().get(key)).append("\" ");
841 final String officialOption = builder.toString();
843 return "<thread " + VideoInfo.KEY_USER_ID + "=\"" + vi.getUserId() + "\" res_from=\"" + vi.getResFrom()
844 + "\" version=\"20061206\" thread=\"" + vi.getThreadId() + "\" " + tcommStr + officialOption + "/>";
848 * 動画をマイリストへ登録する. ログインが必要.
849 * @param myListId 登録するマイリストのID.
850 * @param videoId 登録する動画ID.
851 * @throws IOException 登録に失敗した.
853 public void addMyList(String myListId, String videoId) throws IOException {
854 String itemType = null;
855 String itemId = null;
857 HttpGet get = new HttpGet(ADD_MYLIST_PAGE + videoId);
858 HttpResponse response = http.execute(get);
859 HttpEntity entity = response.getEntity();
861 InputStream is = entity.getContent();
862 BufferedReader reader = new BufferedReader(new InputStreamReader(is));
865 Pattern pattern = Pattern.compile("input type=\"hidden\" name=\"item_type\" value=\"(.+)\"");
866 while ((line = reader.readLine()) != null) {
867 Matcher m = pattern.matcher(line);
869 itemType = m.group(1);
874 pattern = Pattern.compile("input type=\"hidden\" name=\"item_id\" value=\"(.+)\"");
875 while ((line = reader.readLine()) != null) {
876 Matcher m = pattern.matcher(line);
883 pattern = Pattern.compile("NicoAPI\\.token = \"(.*)\";");
884 while ((line = reader.readLine()) != null) {
885 Matcher m = pattern.matcher(line);
892 entity.consumeContent();
895 if (itemType == null || itemId == null || token == null) {
896 throw new IOException("マイリスト登録に必要な情報が取得できませんでした。 "
897 + "マイリスト:" + myListId + ", 動画ID:" + videoId + ", item_type:" + itemType + ", item_id:" + itemId
898 + ", token:" + token);
901 StringEntity se = new StringEntity(
902 "group_id=" + myListId
903 + "&item_type=" + itemType
904 + "&item_id=" + itemId
905 + "&description=" + ""
906 + "&token=" + token);
908 HttpPost post = new HttpPost("http://www.nicovideo.jp/api/mylist/add");
909 post.setHeader("Content-Type", "application/x-www-form-urlencoded");
911 response = http.execute(post);
912 int statusCode = response.getStatusLine().getStatusCode();
913 response.getEntity().consumeContent();
914 if (statusCode != HttpStatus.SC_OK) {
915 throw new IOException("マイリスト登録に失敗" + "マイリスト:" + myListId + ", 動画ID:" + videoId);