Hatena::Groupgeneration1991

gigi-net@G91

2009-05-14

[][]ニコニコ大百科のリンク先をポップアップするGreasemonkeyスクリプト『Popup NicoDict』改良版リリースしました! 00:13

f:id:gigi-net:20090514000939j:image

これは何?

ニコニコ大百科キーワードマウスオーバーでポップアップ表示させます。

先日公開したスクリプトに機能追加し、不具合を修正した物です。

ニコニコ大百科のキーワードをポップアップ表示させるGreaseMonkeyスクリプト『Popup Nico Dict』リリースしました - 5.1さらうどん


インストール

userscript.orgからどうぞ。

Popup Nico Dict for Greasemonkey


どうぞご利用ください。

バグなどのご指摘をお待ちしております。

インストールにはFirefoxGreasemonkeyが必要です。

Greasemonkey固有の関数を使用しているため、他の環境では動かないと思われます。

以前のバージョンからの修正点

ざっと書くと

ニコニコ動画内の大百科へのリンクからもポップアップが開けるようになった

f:id:gigi-net:20090514000936j:image

こんな感じ。

おそらく待望の機能かと。

この機能がないとこのスクリプトの意味があんまりない気すらするw


ただ、これだけちょっぴり反応が悪い感じ。

コツを言うと大百科アイコンの左上の辺りにカーソルを乗せることですかね。


ニコニコ大百科内での対応ページを記事ページのみから検索結果やトップ含め全ページへ拡張

f:id:gigi-net:20090514000938j:image

検索ページからもこの通り!

その他、トップだろうがコメント欄だろうが大百科内からでは、どこでもどんな記事でもポップアップできます。


ポップアップ先のページの取得範囲を従来の単語記事ページのみから、動画、市場、コミュニティ、ユーザー記事からも取得できるように拡張

URL

単語:/a/

動画:/v/

ユーザー:/u/

市場:/i/

コミュニティ/c/

と言った感じで分かれているらしいので全てに対応させてみた。

ページの構造は同じなのですぐに取得できました!


ポップアップが消えなくなったり表示されなくなるバグを修正

コメントでも指摘があったバグ

いろいろ使ってみて、自分でもこれはひどいという状態だったのを直してみた。

誤爆はほとんどなくなった感じ。

まだ問題あったらご指摘お願いします。


ポップアップがウィンドウの表示範囲外にはみ出さないように修正

f:id:gigi-net:20090514000940j:image

普通にマウスカーソルの真下に出して、はみ出すようであればポップアップの底辺がウィンドウの底辺に接触する位置になるようにポップアップを移動するようにしてみた。

少しは見やすくなったはず。

ちなみに、コメントで教えていただいた物のソースを見てもよくわからなかったので自己流で搭載してみた。


必要性を感じなかったので、縦方向にしか回避しません。

横方向ではみ出してしまった場合は、そのままです。

ただ、横にはみ出すこと自体あんまりないので不要かなー。


・リンク先の記事がリダイレクト設定されていても、リダイレクト先の記事を取得できるように修正

f:id:gigi-net:20090514000937j:image

誤植していても正しい記事が表示されます。

コメントの方でGM_xmlhttpRequestのFinalUrlプロパティを参照すると良いと教えていただいたのですが、上手く取得できなかった。

結局、ニコニコ大百科リダイレクトページを見てみると、ヘッダ部分にリダイレクト先のURLが埋め込まれていたので、それを取得してくることで対応しました。


・取得文字数を300文字から500文字に拡張

ちょっぴり増やしてみた。

これは好みの問題かなー?

ソースコード16行目のARTICLE_LENGTHという変数値が取得する文字数なので、お好きに変更してください。


・タイトルの取得方式を変更し、タイトル部の英字が小文字に変換されてしまうバグを修正

そのまんま。細かく言うと、リンク先のページのh1要素から取得させてみた


・【】で囲まれた文字が自動的に改行される不具合を修正

不具合というか前バージョンのリリース以前から既知の問題でしたが、あんまり気にならないの前回はそのままリリース。

今回はこっそりと修正してみた。


・記事整形後に中身がなくなった小見出しを削除

例えば【関連動画】のような小見出しがあって、その見出し以下の項目が動画へのリンクのみだった場合、ポップアップには何も表示されないのが不自然なので消してみた。


技術的なおはなし

関連エントリの奇跡 - 5.1さらうどん

上記の記事で、大百科APIがあることを発見し、仕様に文句を垂れた挙げ句、中の人に仕様変更までしていただきました。

しかし、わざわざ修正していただいたにも関わらず今回は大百科APIは使っていません。

我ながら非常に申し訳ないw


致命的な問題として、やはり取得できる文字数が少ないというのが大きかったです。

このAPIを使わずとも、自前のスクレイピングで上手いこと記事が取得できてしまったので特に使う理由がありませんでした。


ソースコード

// ==UserScript==
// @name           Popup Nico Dict
// @namespace      http://gigi-net.net
// @include        http://dic.nicovideo.jp/*
// @include        http://www.nicovideo.jp/*
// ==/UserScript==
(function(){
//変数定義一覧
var xpos =0;
var ypos =0;
var flag=0;
var now_target_title="";
var fadeInIntervalID = null;
var fadeOutIntervalID = null;
var show ="";
ARTICLE_LENGTH=500;

//リダイレクトされたかどうか判定する関数
function CheckRedirect(x){
	var redirect_url =x.responseText.match(/location.replace(.*);?/);
	//リダイレクトされてない
	if(redirect_url==null){
		GetDOM(x);
	//リダイレクトされた
	}else{
		var r_url =redirect_url[0].match(/http[^']*/)+"";		
		GM_xmlhttpRequest({
  			method:"GET", 
  			url:r_url,
  			onload:GetDOM
		});
	}
}


//ポップアップをフェードイン
function fadein(){
	popup = document.getElementById("ndtkeywordpopup");
	var op = parseFloat(popup.style.opacity);
	var newop =op+0.1;
	popup.style.opacity=newop;
	if(op>=1){
		clearInterval(fadeInIntervalID);
	}
}

//ポップアップをフェードアウト
function fadeout(){
	popup = document.getElementById("ndtkeywordpopup");
	var op = parseFloat(popup.style.opacity);
	var newop =op-0.1;
	popup.style.opacity=newop;
	if(op<0){
		clearInterval(fadeOutIntervalID);
		flag=0;
		document.getElementsByTagName("body")[0].removeChild(popup);
		shadow =document.getElementById("ndtkeywordpopup_shadow");
		document.getElementsByTagName("body")[0].removeChild(shadow);
	}
}

//ポップアップを生成する関数
function createPopup(text){
		if(flag==0){
		var popup = document.createElement('div');
		popup.id ="ndtkeywordpopup";
		with(popup.style) {
			visibility = 'visible';
			fontSize = '10pt';
			fontFamily = 'sans-serif';
			textAlign = 'left';
			lineHeight = '110%';
			color = '#333333';
			paddingLeft = '5px';
			paddingRight = '5px';
			backgroundColor = 'cornsilk';
			border = '1px solid #333333';
			opacity = '0';
			position = 'absolute';
			left = xpos+10+"px";
			top = ypos+25+"px";
			width = '300px';
		}
		//影を生成
		var shadow = document.createElement('div');
		shadow.id ="ndtkeywordpopup_shadow";
		with(shadow.style) {
			visibility = 'visible';
			fontFamily = 'sans-serif';
			fontSize = '10pt'
			textAlign = 'left';
			color = '#333';
			paddingLeft = '5px';
			paddingRight = '5px';
			lineHeight = '110%';
			backgroundColor = '#333';
			border = '1px solid #000';
			opacity = '0.5';
			position = 'absolute';
			left = xpos+5+10+"px";
			top = ypos+25+5+"px";
			width = '300px';
		}
		popup.innerHTML =text;
		shadow.innerHTML =text;
		flag =1;
		document.getElementsByTagName("body")[0].appendChild(shadow);
		document.getElementsByTagName("body")[0].appendChild(popup);
		//はみ出さないように処理する
		var scroll =parseInt(pageYOffset);
		var popup_height =parseInt(popup.offsetHeight);
		var popup_bottom =parseInt(popup.style.top)+parseInt(popup_height);
		if(popup_bottom-scroll>innerHeight){
			shadow.style.top =scroll+parseInt(innerHeight)-parseInt(popup.offsetHeight)+"px";
			popup.style.top =scroll+parseInt(innerHeight)-parseInt(popup.offsetHeight)+"px";
		}
		fadeInIntervalID =setInterval(fadein,30);
		}
}
//マウスがキーワードから外れたとき
function outKeyword(){
	shadow =document.getElementById("ndtkeywordpopup_shadow");
	shadow.style.opacity =0;
	fadeOutIntervalID =setInterval(fadeout,30);
}


//記事を取得し、整形する関数
function GetDOM(x){
	var dict = x.responseText;
	//タイトルの取得
	var now_target_title =dict.match(/<h1>.*>/);
	now_target_title =now_target_title.toString();
	now_target_title =now_target_title.replace(/<.*?>/mg,"");
	//正規表現でarticle以降を取り出す。
	var start =dict.search(/<div class..article.* id..article.*>/);
	var end =dict.search("<hr>");
	var article =dict.substring(start,end);
	//置き換え用の適当な文字列生成
	var rand_st ="st"+(new Date()).getTime();
	var rand_ed ="ed"+(new Date()).getTime();
	//見出しを適当な文字に変換
	article =article.replace(/<h2.*?>/gm,rand_st);
	article =article.replace(/<.h2>/gm,rand_ed);
	//ページメニューを消去
	article =article.replace(/<div id..page-menu.>.*<.div>/,"");
	//タグを全消去
	article =article.replace(/<.*?>/mg,"");
	//先頭から一定文字のみ抽出し、省略された場合は...をくわえる
	show =article.substring(0,ARTICLE_LENGTH-1);
	if(article.length>=ARTICLE_LENGTH){
		show +="..."
		}
	//適当な文字を見出しに戻す
	show =show.replace(new RegExp(rand_st,"gm"),"<br>【");
	show =show.replace(new RegExp(rand_ed,"gm"),"】<br>");
	//空の見出しを削除
	show =show.replace(/<br>【.*】<br>\s*<br>【/gm,"【");
	//タイトルを追加
	show ="<h1>"+now_target_title+"</h1>"+show;
	createPopup(show);
}

//キーワードにマウスが乗ったときの関数
function onKeyword(e){
	if(flag==1){
		popup =document.getElementById("ndtkeywordpopup");
		document.getElementsByTagName("body")[0].removeChild(popup);
		shadow =document.getElementById("ndtkeywordpopup_shadow");
		document.getElementsByTagName("body")[0].removeChild(shadow);
		clearInterval(fadeInIntervalID);
		clearInterval(fadeOutIntervalID);
		flag=0;	
	}
	document.addEventListener('mousemove', setPos, false);
	var article_url =e.target+"";
	GM_xmlhttpRequest({
  		method:"GET", 
  		url:article_url,
  		onload:CheckRedirect
	});
}

function SetEvent(){
//キーワードリンクを配列にまとめる
var links =document.getElementsByTagName("a");
var keywords =new Array;
var urls =new Array;
//現在のページがニコニコ大百科の場合
if(document.domain=="dic.nicovideo.jp"){
	//トップページは除外する
		for(var i=0;i<links.length;i++){
			if(links[i].getAttribute("class") =="auto"){
				keywords.push(links[i]);
			}else{
				if(links[i].getAttribute("href")!=null){
					if(links[i].getAttribute("href").match(/^\/[avciu]\/.*/)!=null){
						keywords.push(links[i]);
					}
				}
			}
		}
}else{
//現在のページがニコニコ動画の場合
	for(var j=0;j<links.length;j++){	
		if(links[j].getAttribute("href") !=null){
			if(links[j].getAttribute("href").match(/http:\/\/dic.nicovideo.jp\/[av]\/.+/)!=null){
			links[j].removeAttribute("title");
			keywords.push(links[j]);
			}
		}
	}
}

//キーワードにマウスが乗ったときのイベント追加
for(i=0;i<keywords.length;i++){
	keywords[i].addEventListener('mouseover',onKeyword,false);
	keywords[i].addEventListener('mouseout', outKeyword, false);
}
clearTimeout(timer);
}
var timer = setTimeout(SetEvent,1000);
//マウスカーソルの座標を同期
function setPos(e) {
		xpos = e.pageX;
		ypos = e.pageY;
}

})();