4 import java.net.URISyntaxException;
5 import nicobrowser.entity.NicoContent;
6 import com.sun.syndication.feed.synd.SyndContentImpl;
7 import com.sun.syndication.feed.synd.SyndEntryImpl;
8 import com.sun.syndication.feed.synd.SyndFeed;
9 import com.sun.syndication.io.FeedException;
10 import com.sun.syndication.io.SyndFeedInput;
11 import java.io.BufferedInputStream;
12 import java.io.BufferedOutputStream;
13 import java.io.BufferedReader;
15 import java.io.FileOutputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.io.InputStreamReader;
19 import java.io.Reader;
20 import java.io.StringReader;
22 import java.net.URLDecoder;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Enumeration;
26 import java.util.List;
27 import javax.swing.text.MutableAttributeSet;
28 import javax.swing.text.html.HTML;
29 import javax.swing.text.html.HTMLEditorKit;
30 import javax.swing.text.html.parser.ParserDelegator;
31 import javax.xml.parsers.DocumentBuilder;
32 import javax.xml.parsers.DocumentBuilderFactory;
33 import javax.xml.parsers.ParserConfigurationException;
34 import nicobrowser.entity.NicoContent.Status;
35 import nicobrowser.util.Result;
36 import nicobrowser.util.Util;
37 import org.apache.commons.logging.Log;
38 import org.apache.commons.logging.LogFactory;
39 import org.apache.http.HttpEntity;
40 import org.apache.http.HttpException;
41 import org.apache.http.HttpResponse;
42 import org.apache.http.HttpStatus;
43 import org.apache.http.NameValuePair;
44 import org.apache.http.client.entity.UrlEncodedFormEntity;
45 import org.apache.http.client.methods.HttpGet;
46 import org.apache.http.client.methods.HttpPost;
47 import org.apache.http.client.params.ClientPNames;
48 import org.apache.http.client.params.CookiePolicy;
49 import org.apache.http.cookie.Cookie;
50 import org.apache.http.impl.client.DefaultHttpClient;
51 import org.apache.http.message.BasicNameValuePair;
52 import org.w3c.dom.Document;
53 import org.w3c.dom.Element;
54 import org.w3c.dom.NodeList;
55 import org.xml.sax.SAXException;
61 public class NicoHttpClient extends DefaultHttpClient {
63 private static Log log = LogFactory.getLog(NicoHttpClient.class);
64 static NicoHttpClient instance;
65 private static final String LOGIN_PAGE =
66 "https://secure.nicovideo.jp/secure/login?site=niconico";
67 private static final String LOGOUT_PAGE =
68 "https://secure.nicovideo.jp/secure/logout";
69 private static final String MY_LIST_PAGE_HEADER =
70 "http://www.nicovideo.jp/mylist/";
71 private static final String MOVIE_THUMBNAIL_PAGE_HEADER =
72 "http://www.nicovideo.jp/api/getthumbinfo/";
73 private static final String GET_FLV_INFO = "http://www.nicovideo.jp/api/getflv/";
74 private static final String SEARCH_HEAD = "http://www.nicovideo.jp/search/";
75 private static final String SEARCH_TAIL = "?sort=v";
77 private NicoHttpClient() {
79 getParams().setParameter(
80 ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY);
84 public static NicoHttpClient getInstance() {
85 if (instance == null) {
86 return new NicoHttpClient();
93 * @param mail ログイン識別子(登録メールアドレス).
94 * @param password パスワード.
95 * @return 認証がOKであればtrue.
97 public boolean login(String mail, String password) throws URISyntaxException, HttpException, InterruptedException {
99 HttpPost post = new HttpPost(LOGIN_PAGE);
102 NameValuePair[] nvps = new NameValuePair[]{
103 new BasicNameValuePair("mail", mail),
104 new BasicNameValuePair("password", password),
105 new BasicNameValuePair("next_url", "")
107 post.setEntity(new UrlEncodedFormEntity(Arrays.asList(nvps), "UTF-8"));
109 //post.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
110 HttpResponse response = execute(post);
111 log.debug("ログインステータスコード: " + response.getStatusLine().getStatusCode());
114 HttpEntity entity = response.getEntity();
115 entity.consumeContent();
116 List<Cookie> cookies = getCookieStore().getCookies();
117 if (!cookies.isEmpty()) {
120 } catch (IOException ex) {
121 log.error("ログイン時に問題が発生", ex);
128 * @return ログアウトに成功すればtrue.
130 public boolean logout() throws URISyntaxException, HttpException, InterruptedException {
131 boolean result = false;
132 HttpGet method = new HttpGet(LOGOUT_PAGE);
134 HttpResponse response = execute(method);
135 log.debug("ログアウトステータスコード: " + response.getStatusLine().getStatusCode());
137 if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
140 response.getEntity().consumeContent();
141 } catch (IOException ex) {
142 log.error("ログアウト時に問題が発生", ex);
149 * @param word 検索キーワード
152 public List<NicoContent> search(String word) {
153 log.debug("検索:" + word);
155 InputStream is = null;
156 List<NicoContent> conts = new ArrayList<NicoContent>();
157 String url = new String(SEARCH_HEAD + word + SEARCH_TAIL);
160 while (url != null) {
161 HttpGet get = new HttpGet(url);
162 HttpResponse response;
163 response = execute(get);
164 is = new BufferedInputStream(response.getEntity().getContent());
165 assert is.markSupported();
166 is.mark(1024 * 1024);
167 List<Result> results = Util.parseSerchResult(is);
168 for (Result r : results) {
169 NicoContent c = loadMyMovie(r.getId());
175 url = Util.getNextPage(is);
178 } catch (IOException ex) {
179 log.error("検索結果処理時に例外発生", ex);
185 * 「マイリスト登録数ランキング(本日)」の動画一覧を取得する。
188 public List<NicoContent> loadMyListDaily() throws URISyntaxException, HttpException, InterruptedException {
189 List<NicoContent> list = new ArrayList<NicoContent>();
190 String url = new String("http://www.nicovideo.jp/ranking/mylist/daily/all?rss=atom");
191 log.debug("全動画サイトのマイリスト登録数ランキング(本日)[全体] : " + url);
193 HttpGet get = new HttpGet(url);
195 BufferedReader reader = null;
197 HttpResponse response = execute(get);
198 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
201 list = getNicoContents(reader);
202 deleteRankString(list);
203 response.getEntity().consumeContent();
204 } catch (FeedException ex) {
206 } catch (IOException ex) {
209 if (reader != null) {
212 } catch (IOException ex) {
221 * ニコニコ動画のRSSからコンテンツリストを取得する.
222 * @param url 取得するrssのurl.
225 public List<NicoContent> getContentsFromRss(String url) {
226 log.debug("アクセスURL: " + url);
227 List<NicoContent> list = accessRssUrl(url);
228 if (url.contains("ranking")) {
229 deleteRankString(list);
235 * rankingの場合、本当のタイトルの前に"第XX位:"の文字列が
237 * @param list 対象のリスト.
239 private void deleteRankString(List<NicoContent> list) {
240 for (NicoContent c : list) {
241 String title = c.getTitle();
242 int offset = title.indexOf(":") + 1;
243 c.setTitle(title.substring(offset));
249 * 「公開」設定にしていないリストからは取得できない.
251 * @param listNo マイリストNo.
254 public List<NicoContent> loadMyList(String listNo) {
255 String url = new String(MY_LIST_PAGE_HEADER + listNo + "?rss=atom");
256 log.debug("マイリストURL: " + url);
257 return accessRssUrl(url);
261 * 動画番号を指定したコンテンツ情報の取得.
262 * @param movieNo 動画番号.
265 public NicoContent loadMyMovie(String movieNo) {
266 NicoContent cont = null;
267 InputStream re = null;
268 List<SyndEntryImpl> list = null;
269 String url = new String(MOVIE_THUMBNAIL_PAGE_HEADER + movieNo);
270 log.debug("動画サムネイルURL: " + url);
275 get = new HttpGet(url);
276 HttpResponse response = execute(get);
277 re = response.getEntity().getContent();
278 // ドキュメントビルダーファクトリを生成
279 DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance();
281 DocumentBuilder builder = dbfactory.newDocumentBuilder();
282 // パースを実行してDocumentオブジェクトを取得
283 Document doc = builder.parse(re);
284 // ルート要素を取得(タグ名:site)
285 Element root = doc.getDocumentElement();
287 if ("fail".equals(root.getAttribute("status"))) {
288 log.warn("情報取得できません: " + movieNo);
292 NodeList list2 = root.getElementsByTagName("thumb");
293 cont = new NicoContent();
294 Element element = (Element) list2.item(0);
296 String watch_url = ((Element) element.getElementsByTagName("watch_url").item(0)).getFirstChild().
298 cont.setPageLink(watch_url);
300 String title = ((Element) element.getElementsByTagName("title").item(0)).getFirstChild().getNodeValue();
301 cont.setTitle(title);
304 // String first_retrieve = ((Element) element.getElementsByTagName("first_retrieve").item(0)).getFirstChild().getNodeValue();
305 // cont.setPublishedDate(DateFormat.getInstance().parse(first_retrieve));
307 // } catch (ParseException ex) {
308 // Logger.getLogger(NicoHttpClient.class.getName()).log(Level.SEVERE, null, ex);
309 } catch (SAXException ex) {
311 } catch (IOException ex) {
313 } catch (ParserConfigurationException ex) {
320 } catch (IOException ex) {
327 private List<NicoContent> accessRssUrl(String url) {
328 List<NicoContent> contList = new ArrayList<NicoContent>();
329 HttpGet get = new HttpGet(url);
330 BufferedReader reader = null;
332 HttpResponse response = execute(get);
333 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
334 if (log.isTraceEnabled()) {
335 reader.mark(1024 * 1024);
337 String str = reader.readLine();
345 contList = getNicoContents(reader);
346 } catch (FeedException ex) {
347 log.warn("アクセスできません: " + url);
349 } catch (IOException ex) {
352 if (reader != null) {
355 } catch (IOException ex) {
363 private List<NicoContent> getNicoContents(Reader reader) throws FeedException {
364 List<SyndEntryImpl> list = null;
365 SyndFeedInput input = new SyndFeedInput();
366 SyndFeed feed = input.build(reader);
368 list = (List<SyndEntryImpl>) feed.getEntries();
370 List<NicoContent> contList;
372 contList = new ArrayList<NicoContent>();
374 contList = createContentsList(list);
379 private List<NicoContent> createContentsList(List<SyndEntryImpl> list) {
380 class CallBack extends HTMLEditorKit.ParserCallback {
382 private boolean descFlag;
383 private String imageLink = new String();
384 private StringBuilder description = new StringBuilder();
387 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) {
388 log.debug("--------<" + t.toString() + ">--------");
390 if (HTML.Tag.IMG.equals(t)) {
391 imageLink = a.getAttribute(HTML.Attribute.SRC).toString();
396 public void handleStartTag(HTML.Tag t, MutableAttributeSet a, int pos) {
397 if (HTML.Tag.P.equals(t)) {
398 if ("nico-description".equals(
399 a.getAttribute(HTML.Attribute.CLASS).toString())) {
403 log.debug("--------<" + t.toString() + ">--------");
408 public void handleEndTag(HTML.Tag t, int pos) {
409 if (HTML.Tag.P.equals(t)) {
412 log.debug("--------</" + t.toString() + ">--------");
416 public void handleText(char[] data, int pos) {
418 description.append(data);
420 log.debug("--------TEXT--------");
424 private void printAttributes(MutableAttributeSet a) {
425 Enumeration e = a.getAttributeNames();
426 while (e.hasMoreElements()) {
427 Object key = e.nextElement();
428 log.debug("---- " + key.toString() + " : " + a.getAttribute(key));
432 public String getImageLink() {
436 public String getDescription() {
437 return description.toString();
441 List<NicoContent> contList = new ArrayList<NicoContent>();
443 for (SyndEntryImpl entry : list) {
444 NicoContent content = new NicoContent();
446 String title = entry.getTitle();
447 content.setTitle(title);
448 content.setPageLink(entry.getLink());
451 CallBack callBack = new CallBack();
452 for (SyndContentImpl sc : (List<SyndContentImpl>) entry.getContents()) {
454 Reader reader = new StringReader(sc.getValue());
455 new ParserDelegator().parse(reader, callBack, true);
456 } catch (IOException ex) {
457 log.error("RSSの読み込み失敗: " + content.getTitle());
462 contList.add(content);
468 * FLVファイルのURLを取得する. ログインが必要.
469 * また, 実際にFLVファイルの実態をダウンロードするには
470 * 一度http://www.nicovideo.jp/watch/ビデオIDに一度アクセスする必要があることに
472 * (参考: http://yusukebe.com/tech/archives/20070803/124356.html)
473 * @param videoID ニコニコ動画のビデオID.
474 * @return FLVファイル実体があるURL.
475 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
477 public URL getFlvUrl(String videoID) throws IOException {
478 String accessUrl = GET_FLV_INFO + videoID;
479 if (videoID.startsWith("nm")) {
480 accessUrl += "?as3=1";
482 log.debug("アクセス: " + accessUrl);
483 HttpGet get = new HttpGet(accessUrl);
485 BufferedReader reader = null;
487 HttpResponse response = execute(get);
488 reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
491 StringBuilder strBuilder = new StringBuilder();
492 while ((str = reader.readLine()) != null) {
493 strBuilder.append(str);
495 resultString = strBuilder.toString();
496 response.getEntity().consumeContent();
497 log.debug(resultString);
499 if (reader != null) {
504 String[] urls = resultString.split("&");
505 final String marker = "url=";
506 for (String url : urls) {
507 if (url.contains(marker)) {
508 String result = url.substring(marker.length());
509 result = URLDecoder.decode(result, "UTF-8");
511 return new URL(result);
514 throw new IOException("フォーマット仕様変更? ID: " + videoID + ", パラメータ:" + resultString);
518 * ニコニコ動画から動画ファイルをダウンロードする.
519 * @param videoID smxxxx形式のビデオID.
520 * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
521 * @param nowStatus ダウンロードしようとしている動画ファイルの, 現在のステータス.
522 * @param mp4ExtIsMp4 mp4ファイルの拡張子に.mp4を用いるか. falseの場合は.flvを付与する(過去のCraving Explorer互換用).
523 * @return この処理を行った後の, 対象ファイルのステータス.
524 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
526 public GetFlvResult getFlvFile(String videoID, String fileName, Status nowStatus, boolean mp4ExtIsMp4) throws
528 URISyntaxException, HttpException, InterruptedException {
529 byte[] buffer = new byte[1024 * 32];
530 final String watchUrl = "http://www.nicovideo.jp/watch/" + videoID;
531 log.debug("アクセス: " + watchUrl);
532 HttpGet get = new HttpGet(watchUrl);
533 HttpResponse response = execute(get);
534 final String userId = Util.getUserId(response.getEntity().getContent());
535 log.debug("userId: " + userId);
536 response.getEntity().consumeContent();
538 String userName = null;
539 if (userId != null) {
540 final String userUrl = "http://www.nicovideo.jp/user/" + userId;
541 log.debug("アクセス: " + watchUrl);
542 get = new HttpGet(userUrl);
543 response = execute(get);
544 userName = Util.getUserName(response.getEntity().getContent());
545 response.getEntity().consumeContent();
548 URL url = getFlvUrl(videoID);
549 if (nowStatus == Status.GET_LOW && url.toString().contains("low")) {
550 log.info("lowファイル取得済みのためスキップ" + videoID + ":" + fileName);
551 return new GetFlvResult(nowStatus, userName);
554 get = new HttpGet(url.toURI());
555 response = execute(get);
556 String contentType = response.getEntity().getContentType().getValue();
557 log.debug(contentType);
559 if ("text/plain".equals(contentType) || "text/html".equals(contentType)) {
560 log.error("取得できませんでした. サーバが混みあっている可能性があります: " + videoID + ":" + fileName);
561 response.getEntity().consumeContent();
562 return new GetFlvResult(Status.GET_INFO, userName);
564 String ext = Util.getExtention(contentType);
566 if (ext.equals("mp4")) {
571 BufferedInputStream in = new BufferedInputStream(response.getEntity().getContent());
573 File file = new File(fileName + "." + ext);
575 // while (file.isFile()) {
577 // file = new File(fileName + "(" + postfix + ")" + "." + ext);
579 log.info("保存します: " + file.getPath());
580 FileOutputStream fos = new FileOutputStream(file);
581 BufferedOutputStream out = new BufferedOutputStream(fos);
584 while ((i = in.read(buffer)) != -1) {
585 out.write(buffer, 0, i);
588 response.getEntity().consumeContent();
591 if (url.toString().contains("low")) {
592 return new GetFlvResult(Status.GET_LOW, userName);
594 return new GetFlvResult(Status.GET_FILE, userName);
598 * ニコニコ動画から動画ファイルをダウンロードする.
599 * @param videoID smxxxx形式のビデオID.
600 * @param fileName ダウンロード後のファイル名. 拡張子は別途付与されるため不要.
601 * @return この処理を行った後の, 対象ファイルのステータス.
602 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
604 public GetFlvResult getFlvFile(String videoID, String fileName) throws IOException, URISyntaxException,
606 InterruptedException {
607 return getFlvFile(videoID, fileName, Status.GET_INFO, true);
611 * ニコニコ動画から動画ファイルをダウンロードする.
613 * @param videoID smxxxx形式のビデオID.
614 * @return この処理を行った後の, 対象ファイルのステータス.
615 * @throws java.io.IOException ファイル取得失敗. 権限の無いファイルを取得しようとした場合も.
617 public GetFlvResult getFlvFile(String videoID) throws IOException, URISyntaxException, HttpException,
618 InterruptedException {
619 return getFlvFile(videoID, videoID, Status.GET_INFO, true);