Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
KuroShiro furigana for Pentadactyl

KuroShiro furigana for Pentadactyl

Place serve.js in a directory.

Open a terminal there and do npm install kuroshiro kuroshiro-analyzer-kuromoji.

Run serve.js as a Node.js background service (i.e. using nssm).

Place kuroshiro.js in ~/pentadactyl/plugins (tested on Windows only).

Use command :kuroshiro to place furigana over all Japanese text on page.

Update: Now also available as a UserScript for greasemonkey!

// ==UserScript==
// @name auto kuroshiro
// @namespace jkss
// @include http://*.co.jp
// @version 1
// @grant none
// ==/UserScript==
var kspool = 0;
function textNodesUnder(el) {
var n, a = [],
walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
while (n = walk.nextNode()) a.push(n);
var b = [];
for (var i in a) {
if (b.indexOf(a[i].parentNode) < 0 && a[i].textContent && a[i].textContent.trim().length > 0)
b.push(a[i].parentNode);
}
return b;
}
function do_ksh() {
let nodes = textNodesUnder(document.body);
let errors = 0;
console.log("performing kuroshiro...");
for (var i in nodes) {
let node = nodes[i];
let http = new XMLHttpRequest();
var url = 'http://127.0.0.1:9615/';
if ('undefined' == typeof node) continue;
var prm = {
text: node.innerHTML
};
var params = JSON.stringify(prm);
http.open('POST', url, true);
http.onreadystatechange = function() { //Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) {
let response = JSON.parse(http.responseText);
if (response.need) {
node.innerHTML = response.new;
}
kspool--;
if (kspool == 0) {
console.log("kuroshiro: done");
} else {
// console.log("kuroshiro: response ["+kspool+"]");
}
}
};
kspool++;
// console.log("kuroshiro: request ["+kspool+"]");
http.send(params);
}
console.log("kuroshiro: request sent");
}
do_ksh();
"use strict";
var INFO = [
"plugin",
{
name: "kuroshiro",
version: "1.1",
href: "http://genjit.su",
summary: "Add furigana to Japanese text",
xmlns: "dactyl"
},
[
"author",
{
email: "akasaka@coolsoftware.ru"
},
"Akasaka Ryuunosuke"
],
[
"project",
{
name: "Pentadactyl",
"min-version": "1.0"
}
],
[
"p", {},
"Adds furigana to Japanese text"
],
];
function info_add_description(tags, description) {
INFO = INFO.concat(
[
"item", {},
["tags", {}, tags],
["spec", {}, tags],
["description", {},
["p", {}, description]
]
]
);
}
function textNodesUnder(el) {
var n, a = [],
walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
while (n = walk.nextNode()) a.push(n);
var b = [];
for (var i in a) {
if (b.indexOf(a[i].parentNode) < 0 && a[i].textContent && a[i].textContent.trim().length > 0)
b.push(a[i].parentNode);
}
return b;
}
var kspool = 0;
function do_ksh() {
dactyl.echo("performing kuroshiro...");
let nodes = textNodesUnder(gBrowser.mCurrentTab.linkedBrowser.contentDocument.body);
let errors = 0;
for (var i in nodes) {
let node = nodes[i];
let http = new XMLHttpRequest();
var url = 'http://127.0.0.1:9615/';
if ('undefined' == typeof node) continue;
var prm = {
text: node.innerHTML
};
var params = JSON.stringify(prm);
http.open('POST', url, true);
//Send the proper header information along with the request
http.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
http.onreadystatechange = () => { //Call a function when the state changes.
if (http.readyState == 4 && http.status == 200) {
let response = JSON.parse(http.responseText);
if (response.need) {
node.innerHTML = response.new;
}
kspool--;
if (kspool == 0) {
dactyl.echo("kuroshiro: done");
} else {
dactyl.echo("kuroshiro: response [" + kspool + "]");
}
}
};
kspool++;
http.send(params);
}
dactyl.echo("kuroshiro: request sent");
}
function create_command_and_mapping(command, description, funcref, mapping, command_option = {}) {
group.commands.add([command], description, funcref, command_option, true);
if (mapping != "")
group.mappings.add([modes.NORMAL], [mapping], description, funcref);
info_add_description(":" + command + " " + mapping, description);
}
create_command_and_mapping("kuroshiro", "Add furigana to page",
function() {
do_ksh();
}, "");
var http = require('http');
var url = require('url');
console.log("Starting Kuroshiro");
var Kuroshiro = require('kuroshiro');
var KuromojiAnalyzer = require('kuroshiro-analyzer-kuromoji');
const ks = new Kuroshiro();
ks.init(new KuromojiAnalyzer());
http.createServer(function(req, res) {
if (req.method == 'POST') {
var body = '';
req.on('data', function(data) {
body += data;
});
req.on('end', function() {
// console.log("Body: " + body);
let query = JSON.parse(body);
res.writeHead(200, {
'Content-Type': 'text/html',
'Access-Control-Allow-Origin': '*'
});
let response = {
need: false
};
try {
if (Kuroshiro.Util.hasKanji(query.text)) {
ks.convert(query.text, {
mode: "furigana"
}).then((r) => {
response.need = true;
response.new = r;
//console.log("converted ");
}).catch((e) => {
response.error = e;
}).finally(() => {
let r = JSON.stringify(response);
//console.log("responding",r );
res.end(r);
});
} else {
res.end(JSON.stringify(response));
}
} catch (e) {
response.error = e;
res.end(JSON.stringify(response));
}
});
} else {
res.writeHead(200, {
'Content-Type': 'text/html',
'Access-Control-Allow-Origin': '*'
});
ks.convert("感じ取れたら手を繋ごう、重なるのは人生のライン and レミリア最高!", {
mode: "furigana"
}).then((r) => {
res.end("kuroshiro server by akasaka - " + r.toString());
});
}
}).listen(9615);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.