Tumblr
2009年04月27日
引越ししてみました livedoor -> pixiv
2009年04月13日
就職活動が残念な状況になってきた
まあ、ある程度予想していたが素直にぶつかり過ぎて玉砕してる感じでションボリすることしきり。
もう弾数が無いのでどうしたものかと悩み中。もう今から増やすのも辛い状況で、
ほんとうにニッチもサッチもいかんです。でも無い内定だけは避けたいので、もう少し頑張るかな。
もう弾数が無いのでどうしたものかと悩み中。もう今から増やすのも辛い状況で、
ほんとうにニッチもサッチもいかんです。でも無い内定だけは避けたいので、もう少し頑張るかな。
2009年04月07日
ActiveObjectsを使っていてすっかり忘れていたこと
データベースを触るとき、コネクションプーリングという話があります。参考
ActiveObjectsには当然ながら、コネクションプーリングを使うことが出来ます。
しかし、なぜかActiveObjectsはH2Databaseには対応していません(正直、これは理解不能
EntityManagerの取得に関しては、クラスひとつ作って対応することは出来るものの、
コネクションプーリングは、各DBベンダ毎にenumでハードコーディングされているので、
クラスを作って対応みたいなことは出来ず、ソースから書き換える必要があります。
なので、プロトタイプのつもりで作ったタグ付けサービスはコネクションプーリングを使わずに動かしました。
ウン千データの中から、百程度のデータを抽出する単純な検索を実行したら、4,5秒帰ってこないということで、
何故だろうと思っていたら、後輩の『これ、毎度接続処理してるんじゃね?』と言われて気づきました。
『コネクションプーリングしてなかったじゃん…』と。
コネクションプーリングを使うようにすると、無事一瞬で帰ってくるようになりました。ちゃんちゃん。
ActiveObjectsには当然ながら、コネクションプーリングを使うことが出来ます。
しかし、なぜかActiveObjectsはH2Databaseには対応していません(正直、これは理解不能
EntityManagerの取得に関しては、クラスひとつ作って対応することは出来るものの、
コネクションプーリングは、各DBベンダ毎にenumでハードコーディングされているので、
クラスを作って対応みたいなことは出来ず、ソースから書き換える必要があります。
なので、プロトタイプのつもりで作ったタグ付けサービスはコネクションプーリングを使わずに動かしました。
ウン千データの中から、百程度のデータを抽出する単純な検索を実行したら、4,5秒帰ってこないということで、
何故だろうと思っていたら、後輩の『これ、毎度接続処理してるんじゃね?』と言われて気づきました。
『コネクションプーリングしてなかったじゃん…』と。
コネクションプーリングを使うようにすると、無事一瞬で帰ってくるようになりました。ちゃんちゃん。
2009年04月02日
Tumblrのタグ機能を云々
Tumblrのタグ機能をTagClickにさせる予定だったのだが、
TagClickの扱いにくさに辟易してきたので、タグ付けオンリーの自作サービスを作ることに。
とりあえず、試作でpermalinkに対するタグ付けと、permalinkの検索だけ実装
それに対してグリモンスクリプトを組んで、動くことを確認。
pixivのみを対象にしていたのを、URLとタグを抽出するパーサのリストを作ることで、
ある程度汎用的にタグ付けできるようにしてみた。
ブログの個別記事などは割とタグ付けされた状態のものがあるので、quoteに対してもタグ付けできるようにという考え。
後は、タグ付けの書き込みを許可してるので、書き込み操作にユーザ認証を追加することと、
タグ付けの更新(追加変更など)を簡単に出来るように、グリモンスクリプトを弄ることが必要かな。
あ、ちょうどいい題材だと思うのでタグ付けサービスはCatalyst、もしくはRailsの習作にした。
TagClickの扱いにくさに辟易してきたので、タグ付けオンリーの自作サービスを作ることに。
とりあえず、試作でpermalinkに対するタグ付けと、permalinkの検索だけ実装
それに対してグリモンスクリプトを組んで、動くことを確認。
pixivのみを対象にしていたのを、URLとタグを抽出するパーサのリストを作ることで、
ある程度汎用的にタグ付けできるようにしてみた。
ブログの個別記事などは割とタグ付けされた状態のものがあるので、quoteに対してもタグ付けできるようにという考え。
後は、タグ付けの書き込みを許可してるので、書き込み操作にユーザ認証を追加することと、
タグ付けの更新(追加変更など)を簡単に出来るように、グリモンスクリプトを弄ることが必要かな。
あ、ちょうどいい題材だと思うのでタグ付けサービスはCatalyst、もしくはRailsの習作にした。
2009年03月28日
Tumblrのタグ機能をTagClickに肩代わりさせる
Tumblrのタグ検索は日本語がうまく扱えないらしい(タグに日本語が使えない訳では無い)
普段タグを自分で付けたりしないので、あまり気にしてはいなかったのだけど、
ポスト数が増えるにつれ、後から探したいという欲求に耐えられなくなり、
『どげんかせんといけん』と、ようよう対策を講じることにしました。
とりあえず同じ不満を持つ人の対策を真似してみました。Tumblrにタグをつけてみた
ただ、これだけだとタグ付けの面倒は変わらないので、例のGreaseMonkeyを使って極力自動化することに。
1.Tumblrページ(= not Dashbord)のPixivポストを見つける
2.『タグ付けを更新する』ボタンを表示する。(赤網)
ボタンをクリックすると、必要な情報を最初から入力された状態でTagClickのタグ登録画面へ飛ぶ。
3.TagClickにそれが登録されているか探す。
3.1.無い → Pixivを読み込みタグを見つけ、TagClickへ登録する。
3.2.ある → TagClickからタグを読み込み、Tumblrページに表示する(緑網)

これで快適なタグ検索ライフを送ることが出来そうです。
まずは350ページ(1ページ10ポストなので約3500ポスト)に及ぶpixiv絵をTumblrからざっと閲覧してタグを登録させる作業を完了させないと^^
一応コードを公開しておく。
ただし、各人のTumblrのXML構造に影響されると思うので、多分汎用は無理。
そもそもTomblooを使ってTumblrにpixiv絵をポストする人専用というね。
素直にpixivのブックマーク使えって話ですよね〜
Tumblr用スクリプト
TagClick用スクリプト
普段タグを自分で付けたりしないので、あまり気にしてはいなかったのだけど、
ポスト数が増えるにつれ、後から探したいという欲求に耐えられなくなり、
『どげんかせんといけん』と、ようよう対策を講じることにしました。
とりあえず同じ不満を持つ人の対策を真似してみました。Tumblrにタグをつけてみた
ただ、これだけだとタグ付けの面倒は変わらないので、例のGreaseMonkeyを使って極力自動化することに。
1.Tumblrページ(= not Dashbord)のPixivポストを見つける
2.『タグ付けを更新する』ボタンを表示する。(赤網)
ボタンをクリックすると、必要な情報を最初から入力された状態でTagClickのタグ登録画面へ飛ぶ。
3.TagClickにそれが登録されているか探す。
3.1.無い → Pixivを読み込みタグを見つけ、TagClickへ登録する。
3.2.ある → TagClickからタグを読み込み、Tumblrページに表示する(緑網)

これで快適なタグ検索ライフを送ることが出来そうです。
まずは350ページ(1ページ10ポストなので約3500ポスト)に及ぶpixiv絵をTumblrからざっと閲覧してタグを登録させる作業を完了させないと^^
一応コードを公開しておく。
ただし、各人のTumblrのXML構造に影響されると思うので、多分汎用は無理。
そもそもTomblooを使ってTumblrにpixiv絵をポストする人専用というね。
素直にpixivのブックマーク使えって話ですよね〜
Tumblr用スクリプト
// ==UserScript==
// @name Tumblr and TagClick Tag add for pixiv
// @namespace http://blog.livedoor.jp/himachoco/
// @description Tumblr and TagClick Tag add for pixiv
// @include http://mix3.tumblr.com/*
// ==/UserScript==
var tumblrUrl = 'http://mix3.tumblr.com/';
var searchUrl = 'mix3_tumblr_search';
(function() {
var load = function(context) {
if(!context){
root = document.getElementById('container').firstChild;
}else{
root = context;
}
while(root){
var aTags = $x('div//a', root);
if(aTags.length){
var title = aTags[1].textContent;
var pixivlink = aTags[1].getAttribute('href');
var permalink = aTags[2].getAttribute('href');
if(pixivlink.match("pixiv")){
var updateUrl = createURL(permalink, title, pixivlink);
var button = buttonRender(updateUrl);
insertAfter(button, root.lastChild);
var pixiv_id= pixivlink.match(/(\&illust_id=)(\d*)$/)[2];
var url = 'http://search.tagclick.net/'+searchUrl+'/keyword?q='+pixiv_id+'&commit=%8C%9F%8D%F5';
tagRender(url, button, updateUrl);
}
}
root = root.nextSibling;
}
}
function setStyle(){
var style =
<><![CDATA[
input.btn{
border: 1px solid #333333;
color: #333333;
background-color: #edeae4;
font-family: Verdana, Geneva, sans-serif;
font-size: 11px;
font-weight: bold;
}
]]></>;
GM_addStyle(style);
}
function createURL(url, title, pixivlink){
title = encodeURI(title);
return 'http://www.tagclick.net/account/entry/post?url='+url+'&title='+title+'&pixivlink=['+pixivlink+']';
}
function buttonRender(url){
var insertButton = document.createElement('input');
insertButton.setAttribute('type', 'button');
insertButton.setAttribute('value', 'タグ付けを更新する');
insertButton.setAttribute('class', 'btn');
insertButton.setAttribute('onclick', 'window.open(\''+url+'\',\'_blank\')');
return insertButton;
}
function autoTagAdd(updateUrl){
GM_xmlhttpRequest({
method: 'GET',
url: updateUrl,
onload: function(res) {
var result = res.responseText.match(/<form[\s\S]*?<\/form>/)[0];
result = result.replace(/<script type=\"text\/javascript\">[\s\S]*?<\/script>/,"");
var formDom = (new DOMParser).parseFromString(result, "text/xml").firstChild;
GM_xmlhttpRequest({
method: 'GET',
url: updateUrl.match(/(\&pixivlink\=\[)(.*?)(\])$/)[2],
onload: function(res) {
var result = res.responseText.match(/<div id=\"tag_area\">[\s\S]*?<\/div>/);
if(result != null){
var tagDom = (new DOMParser).parseFromString(result[0], "text/xml").firstChild.firstChild.nextSibling;
var map = {};
var inputList = formDom.getElementsByTagName('input');
for(var i = 0; i < inputList.length; i++){
map[inputList[i].getAttribute('name')] = encodeURI(inputList[i].getAttribute('value'));
}
var tagString = '';
var tag = tagDom.getElementsByTagName('a');
for(var i = 0; i < tag.length; i++){
tagString += tag[i].textContent + ' ';
}
map['entry_tags'] = tagString;
var option = formDom.getElementsByTagName('option');
var count = 0;
for(var i = 0; i < option.length; i++){
if(option[i].hasAttribute('selected')){
switch (count){
case 0 :
map['posted_at[year]'] = option[i].getAttribute('value');
break;
case 1 :
map['posted_at[month]'] = option[i].getAttribute('value');
break;
case 2 :
map['posted_at[day]'] = option[i].getAttribute('value');
break;
case 3 :
map['posted_at[hour]'] = option[i].getAttribute('value');
break;
case 4 :
map['posted_at[minute]'] = option[i].getAttribute('value');
break;
}
count++;
}
}
map['entry[excerpt]'] = encodeURIComponent(updateUrl.match(/(\&pixivlink\=\[)(.*?)(\])$/)[2]);
/*
map['entry[title]'] = 'test';
map['entry_tags'] = 'test';
map['entry[excerpt]'] = 'test';
map['commit'] = encodeURI('検索');
*/
//log(map);
var mapResult = '';
for(i in map){
mapResult += '&'+i+'='+map[i];
}
//log('http://www.tagclick.net'+formDom.getAttribute('action'));
//log(mapResult.substring(1));
post('http://www.tagclick.net'+formDom.getAttribute('action'), mapResult.substring(1), function(text) {
log('tag update:'+decodeURI(map['entry[title]']));
});
//log(mapResult.substring(1));
}else{
log('illust may be removed');
}
}
});
}
});
}
function tagRender(url, button, updateUrl){
GM_xmlhttpRequest({
method: 'GET',
url: url,
onload: function(res) {
var result = res.responseText.match(/<p class=\"tags\">[\s\S]*?<\/p>/);
if(result != null){
result[0] = result[0].replace(/\ \;/g, "");
var dom = (new DOMParser).parseFromString(result, "text/xml").firstChild;
var aTag = dom.getElementsByTagName('a');
var span = document.createElement('span');
var strong = document.createElement('strong');
strong.innerHTML = ' タグ: ';
span.appendChild(strong);
if(!aTag.length){
strong.innerHTML += 'タグはありません';
}
for(var i = 0; i < aTag.length; i++){
var a = document.createElement('a');
a.setAttribute('href', 'http://search.tagclick.net/'+aTag[i].getAttribute('href'));
a.setAttribute('target', '_blank');
a.innerHTML = aTag[i].textContent + ' ';
span.appendChild(a);
}
insertAfter(span, button);
}else{
autoTagAdd(updateUrl);
}
}
});
}
function addFilter(filter, i) {
i = i || 4;
if(window.AutoPagerize && window.AutoPagerize.addFilter) {
//window.AutoPagerize.addFilter(filter);
window.AutoPagerize.addFilter(function(elements) {
filter(elements[0]);
});
} else if(i > 1) {
setTimeout(arguments.callee, 1000, filter, i -1);
}
}
// Main
setStyle();
load();
addFilter(load);
// Utility
function $x(xpath, context) {
context = context || document;
var res = document.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
for(var i, nodes = [] ; i=res.iterateNext() ; nodes.push(i));
return nodes;
}
function insertAfter(newNode, node) {
return node.parentNode.insertBefore(newNode, node.nextSibling);
}
function log(message) {
if (unsafeWindow && unsafeWindow.console) {
unsafeWindow.console.log(message);
}
}
function post(url, data, cb) {
GM_xmlhttpRequest({
method: "POST",
url: url,
headers:{'Content-type':'application/x-www-form-urlencoded'},
data: data,
onload: function(xhr) { cb(xhr.responseText); }
});
}
})();
TagClick用スクリプト
// ==UserScript==
// @name TagClick Tag add for pixiv
// @namespace http://blog.livedoor.jp/himachoco/
// @description TagClick Tag add for pixiv
// @include http://www.tagclick.net/account/entry/post*pixivlink*
// ==/UserScript==
(function() {
var url = window.location.search.match(/(\&pixivlink\=\[)(.*?)(\])$/)[2];
GM_xmlhttpRequest({
method: 'GET',
url: url,
headers: {
'User-agent': 'Mozilla/4.0 (compatible) Greasemonkey',
'Accept': 'application/atom+xml,application/xml,text/xml',
},
onload: function(res) {
var result = res.responseText.match(/<span id=\"tags\">[\s\S]*?\[\s<a/)[0];
result = result.replace(/\[\s<a/,"");
var dom = (new DOMParser).parseFromString(result, "text/xml").firstChild;
var tag = dom.getElementsByTagName('a');
var tagString = '';
for(var i = 0; i < tag.length; i++){
tagString += tag[i].textContent + ' ';
}
var tagForm = document.getElementById('entry_tags');
tagForm.setAttribute('value', tagString);
var textAreaForm = document.getElementById('entry_excerpt');
textAreaForm.innerHTML = url;
}
});
// Utility
function log(message) {
if (unsafeWindow && unsafeWindow.console) {
unsafeWindow.console.log(message);
}
}
})();
2009年03月26日
とりあえずバージョンアップ
2009年03月25日
グリモンのスクリプト完成
Tumblrのダッシュボードで『フォローはしてるけど、投稿内容がアレげな感じで、直で見るにはあまり好ましく無いような物ばかりの人』の投稿内容を非表示にするスクリプト。
ボタンで表示出来るようになっています。現状一個一個ボタンで表示させる仕様になっていますので、見たい場合はかなり面倒なスクリプトとなっています。
さすがに面倒すぎると思うので、1ページにつき1ボタンを作り、一気に表示非表示を切り替えるられるような仕様を追加しようと思います。
----------【追記】----------
使い方:firefoxのグリモンであることが前提です。スクリプトの以下の部分を、自分用に修正する必要があります。
注意:触って確かめた程度のテストしかしていないので、クラッシュしても知りません。後クロスブラウザとか一切考慮してないので、firefox以外でまともに動作するか分かりません。
------------------------------
作ったものはこれです。以下作ったスクリプトの中身。
ボタンで表示出来るようになっています。現状一個一個ボタンで表示させる仕様になっていますので、見たい場合はかなり面倒なスクリプトとなっています。
さすがに面倒すぎると思うので、1ページにつき1ボタンを作り、一気に表示非表示を切り替えるられるような仕様を追加しようと思います。
----------【追記】----------
使い方:firefoxのグリモンであることが前提です。スクリプトの以下の部分を、自分用に修正する必要があります。
// Tumblrで記事を隠すユーザのリストインストール後ユーザスクリプトの管理を開いて、編集ボタンから直接編集してください。
var userList = ['消したい人のID1', '消したい人のID2', …, '消したい人のIDN'];
// 自分のID
var mine = '自分のID';
注意:触って確かめた程度のテストしかしていないので、クラッシュしても知りません。後クロスブラウザとか一切考慮してないので、firefox以外でまともに動作するか分かりません。
------------------------------
作ったものはこれです。以下作ったスクリプトの中身。
// ==UserScript==
// @name Tumblr hidden script
// @namespace http://blog.livedoor.jp/himachoco/
// @description Selected user's blog hidden
// @include http://www.tumblr.com/dashboard*
// ==/UserScript==
// Tumblrで記事を隠すユーザのリスト
var userList = ['消したい人のID1', '消したい人のID2', …, '消したい人のIDN'];
// 自分のID
var mine = '自分のID';
(function() {
var repeat = '';
var load = function(context) {
if(!context){
root = document.getElementById('left_column');
}else{
root = context;
}
var liList = $x('ol[@id="posts"]//li', root);
var print = '';
liList.forEach(function(e){
if(checkUserList(getPostUserName(e))){
var id = getId(e);
e.setAttribute('style', 'display:none;');
e.parentNode.insertBefore(scriptRender(id), e);
e.parentNode.insertBefore(buttonRender(id), e);
}
});
}
function getPostUserName(liElement){
if(liElement.className.match(/is_mine\b/)){
return mine;
}
var res = $x('div[@class="post_info"]//a/text()', liElement);
for(var i = 0; i < res.length; i++){
if(repeat = res[i].textContent){
return repeat;
}
}
return repeat;
}
function checkUserList(userName){
for(var i = 0; i < userList.length; i++){
if(userList[i] == userName){
return true;
}
}
return false;
}
function getId(liElement){
return liElement.getAttribute('id');
}
function scriptRender(id){
var insertNode = document.createElement('script');
var textNode = document.createTextNode('function oc(elm, id) {\n'+
'var flag = (elm.innerHTML==\'隠す\');\n'+
'document.getElementById(id).style.display = flag?\'none\':\'block\';\n'+
'elm.innerHTML = flag?\'表示\':\'隠す\';\n'+
'}');
insertNode.setAttribute('type', 'text/javascript');
insertNode.appendChild(textNode);
return insertNode;
}
function buttonRender(id){
var insertNode = document.createElement('button');
var textNode = document.createTextNode('表示');
insertNode.setAttribute('id', 'btn');
insertNode.setAttribute('onclick', 'oc(this, \''+id+'\')');
insertNode.appendChild(textNode);
return insertNode;
}
function setStyle(){
var style =
<><![CDATA[
button#btn{
border: 1px solid #333333;
color: #333333;
background-color: #edeae4;
font-family: Verdana, Geneva, sans-serif;
font-size: 11px;
font-weight: bold;
}
]]></>;
GM_addStyle(style);
}
function addFilter(filter, i) {
i = i || 4;
if(window.AutoPagerize && window.AutoPagerize.addFilter) {
//window.AutoPagerize.addFilter(filter);
window.AutoPagerize.addFilter(function(elements) {
filter(elements[0]);
});
} else if(i > 1) {
setTimeout(arguments.callee, 1000, filter, i -1);
}
}
// Main
setStyle();
load();
addFilter(load);
// Utility
function $x(xpath, context) {
context = context || document;
var res = document.evaluate(xpath, context, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
for(var i, nodes = [] ; i=res.iterateNext() ; nodes.push(i));
return nodes;
}
})();
greasemonkeyのscriptを書いてた
Javascriptの書き方、作法など全然知らないので手探りでゴリゴリ。
で、一旦完成したと思ったら、AutoPagerizeを使ってるので、
AutoPagerizeが読み込んだ先までちゃんと動作するようにしないといけないことに気づいた。
functionで受け取った引数の扱い方が良く分からず…
ノードの配列らしいのだが上手く処理出来ない…
getElementById()でidからelementを取り出そうとしてもうまくいかず。
何かのサンプルでforEachを使って処理してたので、真似してみるも、getAttribute()で属性値は取り出せるのに、結局getElementById()でelementを取り出そうとすると上手くいかず。
誰かヘルプ!
で、一旦完成したと思ったら、AutoPagerizeを使ってるので、
AutoPagerizeが読み込んだ先までちゃんと動作するようにしないといけないことに気づいた。
window.AutoPagerize.addFilter(function (docs){こんな感じでaddFilterにfunctionを渡してあげれば良いみたいなのだが、
// func
});
functionで受け取った引数の扱い方が良く分からず…
ノードの配列らしいのだが上手く処理出来ない…
getElementById()でidからelementを取り出そうとしてもうまくいかず。
何かのサンプルでforEachを使って処理してたので、真似してみるも、getAttribute()で属性値は取り出せるのに、結局getElementById()でelementを取り出そうとすると上手くいかず。
誰かヘルプ!
2009年03月23日
Wicketの本をゲット
矢野勉さんが執筆なさったWicketの本が最近出版され、
今日になって本屋で見かけたので思わず買ってしまった。
さらっと流し読みしたところ、入門書としてとても良さそうな感じだった。
Wicketの生い立ちから、導入、Wicketの開発手法(コンポーネントの紹介)など、
Wicketを最初に触る上で必要なところをざっと書いてある感じ。
さらにAjax、Wicketにおけるテストの方法、
(WicketにはWicketTesterというテストツールが用意されている)
DIコンテナとの連携方法なども書いてあって、Wicketのイメージを掴むのにうってつけのように思う。
そしてなにより重要なのは、日本語w
ただ、実際にWicketを使ってどういうアプリが作れるかという具体例は少なめみたいな感じなので、、
そこはWicket in Actionなど別の資料で補う必要はあるかな。
ということで、この本を執筆した矢野さんに感謝。勉強させていただきます。
今日になって本屋で見かけたので思わず買ってしまった。
さらっと流し読みしたところ、入門書としてとても良さそうな感じだった。
Wicketの生い立ちから、導入、Wicketの開発手法(コンポーネントの紹介)など、
Wicketを最初に触る上で必要なところをざっと書いてある感じ。
さらにAjax、Wicketにおけるテストの方法、
(WicketにはWicketTesterというテストツールが用意されている)
DIコンテナとの連携方法なども書いてあって、Wicketのイメージを掴むのにうってつけのように思う。
そしてなにより重要なのは、日本語w
ただ、実際にWicketを使ってどういうアプリが作れるかという具体例は少なめみたいな感じなので、、
そこはWicket in Actionなど別の資料で補う必要はあるかな。
ということで、この本を執筆した矢野さんに感謝。勉強させていただきます。