You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
doushio/common.js

1085 lines
29 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

var config = require('./config');
var imagerConfig = require('./imager/config');
var DEFINES = exports;
DEFINES.INVALID = 0;
DEFINES.INSERT_POST = 2;
DEFINES.UPDATE_POST = 3;
DEFINES.FINISH_POST = 4;
DEFINES.CATCH_UP = 5;
DEFINES.INSERT_IMAGE = 6;
DEFINES.SPOILER_IMAGES = 7;
DEFINES.DELETE_IMAGES = 8;
DEFINES.DELETE_POSTS = 9;
DEFINES.DELETE_THREAD = 10;
DEFINES.LOCK_THREAD = 11;
DEFINES.UNLOCK_THREAD = 12;
DEFINES.REPORT_POST = 13;
DEFINES.PING = 30;
DEFINES.IMAGE_STATUS = 31;
DEFINES.SYNCHRONIZE = 32;
DEFINES.EXECUTE_JS = 33;
DEFINES.MOVE_THREAD = 34;
DEFINES.UPDATE_BANNER = 35;
DEFINES.TEARDOWN = 36;
DEFINES.MODEL_SET = 50;
DEFINES.COLLECTION_RESET = 55;
DEFINES.COLLECTION_ADD = 56;
DEFINES.SUBSCRIBE = 60;
DEFINES.UNSUBSCRIBE = 61;
DEFINES.ANON = 'Anonymous';
DEFINES.INPUT_ROOM = 20;
DEFINES.MAX_POST_LINES = 30;
DEFINES.MAX_POST_CHARS = 2000;
DEFINES.WORD_LENGTH_LIMIT = 120;
/// OneeSama.state[0] flags
DEFINES.S_BOL = 1;
DEFINES.S_QUOTE = 2;
DEFINES.S_BIG = 4;
DEFINES.S_FQUOTE = 8;
function initial_state() {
// state[0] = output mode
// state[1] = number of spoiler tags we're inside
return [DEFINES.S_BOL, 0];
}
exports.initial_state = initial_state;
if (typeof mediaURL == 'undefined' || !mediaURL)
mediaURL = imagerConfig.MEDIA_URL;
function is_pubsub(t) {
return t > 0 && t < 30;
}
exports.is_pubsub = is_pubsub;
function FSM(start) {
this.state = start;
this.spec = {acts: {}, ons: {}, wilds: {}, preflights: {}};
}
exports.FSM = FSM;
FSM.prototype.clone = function () {
var second = new FSM(this.state);
second.spec = this.spec;
return second;
};
// Handlers on arriving to a new state
FSM.prototype.on = function (key, f) {
var ons = this.spec.ons[key];
if (ons)
ons.push(f);
else
this.spec.ons[key] = [f];
return this;
};
// Sanity checks before attempting a transition
FSM.prototype.preflight = function (key, f) {
var pres = this.spec.preflights[key];
if (pres)
pres.push(f);
else
this.spec.preflights[key] = [f];
};
// Specify transitions and an optional handler function
FSM.prototype.act = function (trans_spec, on_func) {
var halves = trans_spec.split('->');
if (halves.length != 2)
throw new Error("Bad FSM spec: " + trans_spec);
var parts = halves[0].split(',');
var dest = halves[1].match(/^\s*(\w+)\s*$/)[1];
var tok;
for (var i = parts.length-1; i >= 0; i--) {
var part = parts[i];
var m = part.match(/^\s*(\*|\w+)\s*(?:\+\s*(\w+)\s*)?$/);
if (!m)
throw new Error("Bad FSM spec portion: " + part);
if (m[2])
tok = m[2];
if (!tok)
throw new Error("Tokenless FSM action: " + part);
var src = m[1];
if (src == '*')
this.spec.wilds[tok] = dest;
else {
var acts = this.spec.acts[src];
if (!acts)
this.spec.acts[src] = acts = {};
acts[tok] = dest;
}
}
if (on_func)
this.on(dest, on_func);
return this;
};
FSM.prototype.feed = function (ev, param) {
var spec = this.spec;
var from = this.state, acts = spec.acts[from];
var to = (acts && acts[ev]) || spec.wilds[ev];
if (to && from != to) {
var ps = spec.preflights[to];
for (var i = 0; ps && i < ps.length; i++)
if (!ps[i].call(this, param))
return false;
this.state = to;
var fs = spec.ons[to];
for (var i = 0; fs && i < fs.length; i++)
fs[i].call(this, param);
}
return true;
};
FSM.prototype.feeder = function (ev) {
var self = this;
return function (param) {
self.feed(ev, param);
};
};
var entities = {'&' : '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;'};
function escape_html(html) {
return html.replace(/[&<>"]/g, function (c) {
return entities[c];
});
}
exports.escape_html = escape_html;
function escape_fragment(frag) {
var t = typeof(frag);
if (t == 'object' && frag && typeof(frag.safe) == 'string')
return frag.safe;
else if (t == 'string')
return escape_html(frag);
else if (t == 'number')
return frag.toString();
else
return '???';
}
exports.escape_fragment = escape_fragment;
function flatten(frags) {
var out = [];
for (var i = 0; i < frags.length; i++) {
var frag = frags[i];
if (Array.isArray(frag))
out = out.concat(flatten(frag));
else
out.push(escape_fragment(frag));
}
return out;
}
exports.flatten = flatten;
function safe(frag) {
return {safe: frag};
}
exports.safe = safe;
function is_noko(email) {
return email && email.indexOf('@') == -1 && /noko/i.test(email);
}
exports.is_noko = is_noko;
function is_sage(email) {
return config.SAGE_ENABLED && email &&
email.indexOf('@') == -1 && /sage/i.test(email);
}
exports.is_sage = is_sage;
var OneeSama = function (t) {
this.tamashii = t;
this.hooks = {};
};
exports.OneeSama = OneeSama;
var OS = OneeSama.prototype;
var break_re = new RegExp("(\\S{" + DEFINES.WORD_LENGTH_LIMIT + "})");
/* internal refs, embeds */
var ref_re = />>(\d+|>\/s\/.+|>\/music\/s\/.+|>\/watch\?v=[\w-]{11}(?:#t=[\dhms]{1,9})?|>\/soundcloud\/[\w-]{1,40}\/[\w-]{1,80}|>\/@\w{1,15}\/\d{4,20}(?:\?s=\d+)?|>\/(?:a|sysint|nap)\/\d{0,10})/;
OS.hook = function (name, func) {
var hs = this.hooks[name];
if (!hs)
this.hooks[name] = hs = [func];
else if (hs.indexOf(func) < 0)
hs.push(func);
};
OS.trigger = function (name, param) {
var hs = this.hooks[name];
if (hs)
for (var i = 0; i < hs.length; i++)
hs[i].call(this, param);
};
function override(obj, orig, upgrade) {
var origFunc = obj[orig];
obj[orig] = function () {
var args = [].slice.apply(arguments);
args.unshift(origFunc);
return upgrade.apply(this, args);
};
}
/// converts one >>ref to html
OS.red_string = function (ref) {
var prefix = ref.slice(0, 3);
var prefix2 = ref.slice(0, 5);
var dest, linkClass;
if(prefix2 == '>/sys')
{
var num = parseInt(ref.slice(4+5), 10);
dest = '../outbound/sysint/' + (num ? ''+num : '');
}
else if (prefix == '>/w') {
dest = 'https://www.youtube.com/' + ref.slice(2);
linkClass = 'embed watch';
}
else if (prefix2 == '>/sou') {
dest = 'https://soundcloud.com/' + ref.slice(13);
linkClass = 'embed soundcloud';
}
else if (prefix == '>/s') {
dest ="/"+ ref.slice(2);
linkClass = 'embed'
}
else if (prefix == '>/@') {
var bits = ref.slice(3).split('/');
dest = 'https://twitter.com/' + bits[0] + '/status/' + bits[1];
linkClass = 'embed tweet';
}
else if (prefix == '>/a') {
var num = parseInt(ref.slice(4), 10);
dest = '../outbound/a/' + (num ? ''+num : '');
}
else if (prefix == '>/n') {
var num = parseInt(ref.slice(4+2), 10);
dest = '../outbound/nap/' + (num ? ''+num : '');
}
else {
this.tamashii(parseInt(ref, 10));
return;
}
this.callback(new_tab_link(encodeURI(dest), '>>' + ref, linkClass));
};
/// 3rd tokenization stage; breaks text into chunks and >>refs
OS.break_heart = function (frag) {
if (frag.safe)
return this.callback(frag);
// break long words
var bits = frag.split(break_re);
for (var i = 0; i < bits.length; i++) {
// anchor >>refs
var morsels = bits[i].split(ref_re);
for (var j = 0; j < morsels.length; j++) {
var m = morsels[j];
if (j % 2)
this.red_string(m);
else if (i % 2) {
this.geimu(m);
this.callback(safe('<wbr>'));
}
else
this.geimu(m);
}
}
};
/// 2nd tokenization stage; as we transition our state[0] flag, emits html tags as necessary
OS.iku = function (token, to) {
var state = this.state;
if (state[0] & DEFINES.S_QUOTE && !(to & DEFINES.S_QUOTE))
this.callback(safe('</em>'));
if (state[0] & DEFINES.S_BIG && !(to & DEFINES.S_BIG)) {
if (token && token.safe == '<br>')
token = '';
this.callback(safe('</h4>'));
}
if (to & DEFINES.S_BIG && !(state[0] & DEFINES.S_BIG)) {
this.callback(safe('<h4>'));
state[0] |= DEFINES.S_BIG;
}
if (to & DEFINES.S_QUOTE && !(state[0] & DEFINES.S_QUOTE)) {
if (to & DEFINES.S_FQUOTE) {
this.callback(safe('<em class="fancy">'));
state[0] |= DEFINES.S_FQUOTE;
} else {
this.callback(safe('<em>'));
}
state[0] |= DEFINES.S_QUOTE;
}
if (to == 'SPOIL') {
if (token[1] == '/') {
state[1]--;
this.callback(safe('</del>'));
}
else {
var del = {html: '<del>'};
this.trigger('spoilerTag', del);
this.callback(safe(del.html));
state[1]++;
}
}
else {
this.break_heart(token);
}
state[0] = to;
}
/// 1st tokenization stage, breaking up [spoiler]s, >quotes, and line breaks
OS.fragment = function (frag) {
var chunks = frag.split(/(\[\/?spoiler\])/i);
var state = this.state;
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
if (i % 2) {
var to = 'SPOIL';
if (chunk[1] == '/' && state[1] < 1)
to = state[0] & (DEFINES.S_QUOTE | DEFINES.S_FQUOTE);
this.iku(chunk, to);
continue;
}
lines = chunk.split(/(\n)/);
for (var l = 0; l < lines.length; l++) {
var line = lines[l];
var is_bol = state[0] === DEFINES.S_BOL;
if (l % 2)
this.iku(safe('<br>'), DEFINES.S_BOL);
else if (is_bol && !state[1] && /^[#]{2}[^#]/.test(line)) {
var to = DEFINES.S_BIG;
if (/[>]/.test(line[2]))
to |= DEFINES.S_QUOTE;
else if (/»/.test(line[2]))
to |= DEFINES.S_QUOTE | DEFINES.S_FQUOTE;
this.iku(line.slice(2), to);
}
else if (is_bol && /^[>]/.test(line))
this.iku(line, DEFINES.S_QUOTE);
else if (is_bol && /^»/.test(line))
this.iku(line, DEFINES.S_QUOTE | DEFINES.S_FQUOTE);
else if (line)
this.iku(line, state[0] & (DEFINES.S_QUOTE | DEFINES.S_BIG | DEFINES.S_FQUOTE));
}
}
};
OS.close_out = function () {
if (this.state[0] & DEFINES.S_QUOTE) {
this.callback(safe('</em>'));
this.state[0] -= DEFINES.S_QUOTE;
if(this.state[0] & DEFINES.S_FQUOTE)
this.state[0] -= DEFINES.S_FQUOTE;
}
if (this.state[0] & DEFINES.S_BIG) {
this.callback(safe('</h4>'));
this.state[0] -= DEFINES.S_BIG;
}
while (this.state[1] > 0) {
this.callback(safe('</del>'));
this.state[1]--;
}
};
/// converts one post body to HTML
OS.karada = function (body) {
var output = [];
this.state = initial_state();
this.callback = function (frag) { output.push(frag); }
this.fragment(body);
this.close_out();
this.callback = null;
return output;
}
var dice_re = /(#tea|#dame|#tutturu|#muri|#flip|#when|#du|#fun|#fcount|#test|#sleep|#janken|#8ball|#imfey|#\?(?:\[(?:\w,*)+\])?|#\d{0,2}d\d{1,4}(?:[+-]\d{1,4})?)/i;
exports.dice_re = dice_re;
var WHEN = [
'Now', null, null, null, null,
'Later', null, null, null, null,
'In 5 minutes', null, null, null, null,
'5 minutes ago', null, null,
'In exactly 2.54 seconds', null, null,
'At midnight tonight', null, null,
'on fridey nite', null,
'Tomorrow', null, null, null,
'Next week', null, null,
'I\'m not at liberty to say', null,
'Whenever you want', null, null, null,
'Never', null, null, null,
'Immediately', null, null,
'Always', null,
'NEVER EVER', null,
'A̵҉̞͚̦̖͔̰̣̱t҉̡̤͚ ̮̱͇́͞͝t̺̺̮h̵̜̠̘̩̮̥͓̀e͇̮͎̻ ͔̪̻̙̦̭̼h̸̭̬̭͉̱̝̖̝̰͠͡e̻͍͈̕͝à̢͈͜ṭ̵̳̩̲̩͚-̤̝͓͍͜ͅḓ͍̥͉̝͜͝e͖͎͓a̸̡̘̠̯̻̩͝t̛̻̗̤͘h͍̺ ̶̧͏̗̖̺ơ̹͍f͓͓̘̻̤͉̤̺ ̨̪̗̻̝̹͞t͈̮͜͢h̰̥̞̹ͅȩ̥̹͇̩̼̻ ̸̡̤̙̯͟ͅú̺̬̘̩͞n͖̣͕̲̦̟̼͡͞į̛̻͇̹͚͡v͈̫͚̜͘̕͠é͔̯̲̤͉̱̫͟͝r̷̛͉̮s̷͔͍̰̹e̤̖̙͝',
];
var EIGHT_BALL = [
'Yes',
'No',
'Maybe',
'Ara ara',
'Why this',
'Help',
'git gud',
'Okay',
'My condolences',
'Praise be',
'EEEEEEH?!',
'Try again never',
'100%',
'Can you repeat the question',
'lol',
'0.1%',
'agreedo',
'nope.avi',
'Would you kindly?',
];
var IMFEY = [
'wowme2',
'u do u',
"go get 'em champ",
'my child...',
'✨',
'Kooloo Limpah!',
'Invigorating.',
'sproink',
'elwind!',
];
function parse_dice(frag)
{
if(/^#(dame|muri|tutturu)$/i.test(frag))
return {n:1, faces:2};
if(frag == '#loli') {
return {n:1, faces:3};
}
if(frag == '#?')
{
return {n:1, faces:2};
}
if (/^#\?/.test(frag))
{
var l = frag.slice(2).split(/(\[|\]|,)/);
var j =0;
for(var i=0;i<l.length;i++)
{
if(/(\[|\]|,)/.test(l[i])) continue;
if(l[i].length>0) j+=1;
}
return {n:1, faces: j};
}
if (frag == '#fun' || frag == '#fcount' || frag == '#du')
return {n: 1, faces: 2};
if (frag == '#tea')
return {n: 1, faces: 2};
if (frag == '#flip')
return {n: 1, faces: 2};
if (frag == '#imfey')
return {n: 1, faces: IMFEY.length};
if (frag == '#8ball')
return {n: 1, faces: EIGHT_BALL.length};
if (frag == '#when')
return {n: 1, faces: WHEN.length};
if (frag == '#test')
return {n: 1, faces: 2};
if (frag == '#sleep')
return {n: 1, faces: 2};
if (frag == '#janken')
return {n: 1, faces: 3};
var m = frag.match(/^#(\d*)d(\d+)([+-]\d+)?$/i);
if (!m)
return false;
var n = parseInt(m[1], 10) || 1, faces = parseInt(m[2], 10);
if (n < 1 || n > 10 || faces < 2 || faces > 100)
return false;
var info = {n: n, faces: faces};
if (m[3])
info.bias = parseInt(m[3], 10);
return info;
}
exports.parse_dice = parse_dice;
const audio_root = "/s/audio";
function readable_audio(bit, d) {
if (bit=='#dame')
return "dame.wav";
else if (bit=='#muri')
return "muri.wav";
else if (bit=='#tutturu')
return "tutturu.wav";
}
function readable_dice(bit, d) {
if(bit == '#loli') {
return "you should not see this";
}
if(bit == '#?')
{
if(d[0] === 0) return '#? (I have no input)';
if(d[0] === -1) return '#? (You have no post)';
return '#? ('+d[0]+')';
}
if (/^#\?/.test(bit))
{
var list = [];
var l = bit.slice(2).split(/(\[|\]|,)/);
if(!!l) {
for(var i=0;i<l.length;i++)
{
if(/(\[|\]|,)/.test(l[i])) continue;
if(l[i].length>0)
list.push(l[i]);
}
if(d[1]>list.length) return bit + " (error)";
else return bit + " (" + list[ d[1]-1] + ")";
}
}
if (bit == '#du')
{
if(d[0]>=0)
return '#du ('+d[0]+'%)';
else
return '#du (Try asking again)';
}
if (bit == '#fun')
return '#fun (' + d[0]+ ')';
if (bit == '#fcount')
return '#fcount (' + d[0] + ')';
if (bit == '#tea')
return '#tea (' + ((d[0] === true)?"now!":d[0]+" hours") + ')';
if (bit == '#flip')
return '#flip (' + (d[1] == 2) + ')';
if (bit == '#imfey')
return '#imfey (' + IMFEY[d[1] - 1] + ')';
if (bit == '#8ball')
return '#8ball (' + EIGHT_BALL[d[1] - 1] + ')';
if (bit == '#when')
{
const f = (n) => {
if(WHEN[n] === null)
return f(n-1);
else return WHEN[n];
};
return '#when (' + f(d[1]-1) + ')';
}
if (bit == '#test')
return '#test (it worked!)';
if (bit == '#sleep')
return '#sleep (Yes)';
if (bit == '#janken')
return '#janken ('+ ["Rock", "Paper", "Scissors"][d[1]-1]+')';
var f = d[0], n = d.length, b = 0;
if (d[n-1] && typeof d[n-1] == 'object') {
b = d[n-1].bias;
n--;
}
var r = d.slice(1, n);
n = r.length;
bit += ' (';
var eq = n > 1 || b;
if (eq)
bit += r.join(', ');
if (b)
bit += (b < 0 ? ' - ' + (-b) : ' + ' + b);
var sum = b;
for (var j = 0; j < n; j++)
sum += r[j];
return bit + (eq ? ' = ' : '') + sum + ')';
}
/// 4th tokenization stage; populates dice rolls
OS.geimu = function (text) {
if (!this.dice) {
this.kinpira(text);
return;
}
var bits = text.split(dice_re);
for (var i = 0; i < bits.length; i++) {
var bit = bits[i];
if (!(i % 2) || !parse_dice(bit)) {
this.kinpira(bit);
}
else if (this.queueRoll) {
this.queueRoll(bit);
}
else if (!this.dice[0]) {
this.kinpira(bit);
}
else {
var d = this.dice.shift();
this.callback(safe('<strong>'));
this.strong = true; // for client DOM insertion
var audio = readable_audio(bit,d);
if(audio)
{
this.callback(safe("<a style='text-decoration:none;' href='javascript:void(0);' onclick='this.nextSibling.play();'>"+bit+"</a>"));
this.callback(safe("<audio src='"+audio_root+"/"+audio+"'></audio>"));
}
else if(bit == "#loli") {
this.callback(safe("<a href='"+d[0]+"' target='_blank' rel='noopener noreferrer'><img src='"+d[1]+"'></img></a>"));
}
else
this.callback(readable_dice(bit, d));
this.strong = false;
this.callback(safe('</strong>'));
}
}
};
/// 5th tokenization stage; parses ^s
OS.kinpira = function (text) {
this.itameshi(text);
return;
if (!/[^]/.test(text) || /^([^]_|:[^])/.test(text)) {
this.itameshi(text);
return;
}
var bits = text.split(/[^]/);
// remove trailing ^s
while (bits.length && bits[bits.length-1] == '')
bits.pop();
var soup = safe('<sup>');
this.sup_level = 0;
for (var i = 0; i < bits.length; i++) {
if (bits[i])
this.itameshi(bits[i]);
if (i + 1 < bits.length && i < 5) {
// if there's more text, open a <sup>
this.itameshi(soup);
this.sup_level++;
}
}
// close all the sups we opened
var n = this.sup_level;
this.sup_level = 0;
soup = safe('</sup>');
for (var i = 0; i < n; i++)
this.itameshi(soup);
};
/// 6th tokenization stage; parses individual *italic* *words*
OS.itameshi = function (text) {
while (true) {
var m = /(^|[ .,;:?!(-])\*([^ *]+)\*($|[ .,;:?!)-])/.exec(text);
if (!m)
break;
if (m.index > 0) {
var before = text.slice(0, m.index);
LINKIFY ? this.linkify(before) : this.callback(before);
}
if (m[1])
this.callback(m[1]);
this.callback(safe('<i>' + escape_html(m[2]) + '</i>'));
text = text.slice(m.index + m[0].length - m[3].length);
}
if (text)
LINKIFY ? this.linkify(text) : this.callback(text);
};
// Convert text URLs to clickable links
// *Not* recommended. Use at your own risk.
var LINKIFY = false;
/// optional 7th tokenization stage
if (LINKIFY) { OS.linkify = function (text) {
var bits = text.split(/(https?:\/\/[^\s"<>^]*[^\s"<>'.,!?:;^])/);
for (var i = 0; i < bits.length; i++) {
if (i % 2) {
var e = escape_html(bits[i]);
// open in new tab, and disavow target
this.callback(safe('<a href="' + e +
'" rel="nofollow noopener noreferrer" target="_blank">' +
e + '</a>'));
}
else
this.callback(bits[i]);
}
}; }
function chibi(imgnm, src) {
var name = '', ext = '';
var m = imgnm.match(/^(.*)(\.\w{3,4})$/);
if (m) {
name = m[1];
ext = m[2];
}
var bits = [safe('<a href="'), src, safe('" download="'), imgnm];
if (name.length >= 38) {
bits.push(safe('" title="'), imgnm);
imgnm = [name.slice(0, 30), safe('(&hellip;)'), ext];
}
bits.push(safe('" rel="nofollow">'), imgnm, safe('</a>'));
return bits;
}
OS.spoiler_info = function (index, toppu) {
var large = toppu || this.thumbStyle == 'large';
var hd = toppu || this.thumbStyle != 'small';
return {
thumb: encodeURI(mediaURL + 'kana/spoiler' + (hd ? '' : 's')
+ index + '.png'),
dims: large ? imagerConfig.THUMB_DIMENSIONS
: imagerConfig.PINKY_DIMENSIONS,
};
};
var spoilerImages = imagerConfig.SPOILER_IMAGES;
function pick_spoiler(metaIndex) {
var imgs = spoilerImages;
var n = imgs.normal.length;
var count = n + imgs.trans.length;
var i;
if (metaIndex < 0)
i = Math.floor(Math.random() * count);
else
i = metaIndex % count;
var spoiler = i < n ? imgs.normal[i] : imgs.trans[i - n];
return {index: spoiler, next: (i+1) % count};
}
exports.pick_spoiler = pick_spoiler;
function new_tab_link(srcEncoded, inside, cls) {
return [safe('<a href="' + srcEncoded + '" target="_blank"' +
(cls ? ' class="'+cls+'"' : '') +
' rel="noreferrer nofollow noopener">'), inside, safe('</a>')];
}
OS.image_paths = function () {
if (!this._imgPaths) {
this._imgPaths = {
src: mediaURL + 'src/',
thumb: mediaURL + 'thumb/',
mid: mediaURL + 'mid/',
vint: mediaURL + 'vint/',
};
this.trigger('mediaPaths', this._imgPaths);
}
return this._imgPaths;
};
var audioIndicator = "\u266B"; // musical note
OS.gazou = function (info, toppu) {
var src, name, caption, video;
if (info.vint) {
src = encodeURI('../outbound/hash/' + info.MD5);
var google = encodeURI('../outbound/g/' + info.vint);
var iqdb = encodeURI('../outbound/iqdb/' + info.vint);
caption = ['Search ', new_tab_link(google, '[Google]'), ' ',
new_tab_link(iqdb, '[iqdb]'), ' ',
new_tab_link(src, '[foolz]')];
}
else {
src = encodeURI(this.image_paths().src + info.src);
video = info.video || (/\.webm$/i.test(src) && 'webm'); // webm check is legacy
caption = [video ? 'Video ' : 'Image ', new_tab_link(src, info.src)];
}
var img = this.gazou_img(info, toppu);
var dims = info.dims[0] + 'x' + info.dims[1];
return [safe('<figure data-MD5="'), info.MD5,
safe('" data-size="'), info.size,
video ? [safe('" data-video="'), video] : '',
safe('"><figcaption>'),
caption, safe(' <i>('),
info.audio ? (audioIndicator + ', ') : '',
info.duration ? (info.duration + ', ') : '',
readable_filesize(info.size), ', ',
dims, (info.apng ? ', APNG' : ''),
this.full ? [', ', chibi(info.imgnm, img.src)] : '',
safe(')</i></figcaption>'),
this.thumbStyle == 'hide' ? '' : img.html,
safe('</figure>\n\t')];
};
exports.thumbStyles = ['small', 'sharp', 'large', 'hide'];
OS.gazou_img = function (info, toppu) {
var src, thumb;
var imgPaths = this.image_paths();
if (!info.vint)
src = thumb = encodeURI(imgPaths.src + info.src);
var d = info.dims;
var w = d[0], h = d[1], tw = d[2], th = d[3];
if (info.spoiler) {
var sp = this.spoiler_info(info.spoiler, toppu);
thumb = sp.thumb;
tw = sp.dims[0];
th = sp.dims[1];
}
else if (info.vint) {
tw = tw || w;
th = th || h;
src = encodeURI('../outbound/hash/' + info.MD5);
thumb = imgPaths.vint + info.vint;
}
else if (this.thumbStyle != 'small' && info.mid) {
thumb = encodeURI(imgPaths.mid + info.mid);
if (!toppu && this.thumbStyle == 'large') {
tw *= 2;
th *= 2;
}
}
else if (info.thumb)
thumb = encodeURI(imgPaths.thumb + info.thumb);
else {
tw = w;
th = h;
}
var img = '<img src="'+thumb+'"';
if (tw && th)
img += ' width="' +tw+'" height="'+th+'">';
else
img += '>';
if (imagerConfig.IMAGE_HATS)
img = '<span class="hat"></span>' + img;
img = new_tab_link(src, safe(img));
return {html: img, src: src};
};
function readable_filesize(size) {
/* Dealt with it. */
if (size < 1024)
return size + ' B';
if (size < 1048576)
return Math.round(size / 1024) + ' KB';
size = Math.round(size / 104857.6).toString();
return size.slice(0, -1) + '.' + size.slice(-1) + ' MB';
}
exports.readable_filesize = readable_filesize;
function pad(n) {
return (n < 10 ? '0' : '') + n;
}
OS.readable_time = function (time) {
var h = this.tz_offset;
var offset;
if (h || h == 0)
offset = h * 60 * 60 * 1000;
else /* would be nice not to construct new Dates all the time */
offset = new Date().getTimezoneOffset() * -60 * 1000;
var d = new Date(time + offset);
var k = "日月火水木金土"[d.getUTCDay()];
return (d.getUTCFullYear() + '/' + pad(d.getUTCMonth()+1) + '/' +
pad(d.getUTCDate()) + '&nbsp;(' + k + ') ' +
pad(d.getUTCHours()) + ':' +
pad(d.getUTCMinutes()));
};
function datetime(time) {
var d = new Date(time);
return (d.getUTCFullYear() + '-' + pad(d.getUTCMonth()+1) + '-' +
pad(d.getUTCDate()) + 'T' + pad(d.getUTCHours()) + ':' +
pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()) + 'Z');
}
OS.post_url = function (num, op, quote) {
op = op || num;
return (this.op == op ? '' : op) + (quote ? '#q' : '#') + num;
};
OS.post_ref = function (num, op, desc_html) {
var ref = '&gt;&gt;' + num;
if (desc_html)
ref += ' ' + desc_html;
else if (this.op && this.op != op)
ref += ' \u2192';
else if (num == op && this.op == op)
ref += ' (OP)';
return safe('<a href="'+this.post_url(num, op, false)+'">'+ref+'</a>');
};
OS.post_nav = function (post) {
var n = post.num, o = post.op;
return safe('<nav><a href="' + this.post_url(n, o, false) +
'">No.</a><a href="' + this.post_url(n, o, true) +
'">' + n + '</a></nav>');
};
function action_link_html(href, name, id) {
var span = '<span ' + (id ? 'id="'+id+'" ' : '') + 'class="act">';
return span + '<a href="'+href+'">'+name+'</a></span>';
}
exports.action_link_html = action_link_html;
exports.reasonable_last_n = function (n) {
return n >= 5 && n <= 500;
};
OS.last_n_html = function (num) {
return action_link_html(num + '?last' + this.lastN,
'Last&nbsp;' + this.lastN);
};
OS.expansion_links_html = function (num, omit) {
var html = ' &nbsp; ' + action_link_html(num, 'Expand');
if (omit > this.lastN)
html += ' ' + this.last_n_html(num);
return html;
};
OS.atama = function (data) {
var auth = data.auth;
var header = auth ? [safe('<b class="'),auth.toLowerCase(),safe('">')]
: [safe('<b>')];
if (data.subject)
header.unshift(safe('<h3>「'), data.subject, safe('」</h3> '));
if (data.name || !data.trip) {
header.push(data.name || DEFINES.ANON);
if (data.trip)
header.push(' ');
}
if (data.trip)
header.push(safe('<code>' + data.trip + '</code>'));
if (auth)
header.push(' ## ' + auth);
this.trigger('headerName', {header: header, data: data});
header.push(safe('</b>'));
if (data.email) {
header.unshift(safe('<a class="email" href="mailto:'
+ encodeURI(data.email)
+ '" ref="noopener noreferrer" target="_blank">'));
header.push(safe('</a>'));
}
header.push(safe(' <time datetime="' + datetime(data.time) +
'">' + this.readable_time(data.time) + '</time> '),
this.post_nav(data));
if (!this.full && !data.op) {
var ex = this.expansion_links_html(data.num, data.omit);
header.push(safe(ex));
}
this.trigger('headerFinish', {header: header, data: data});
header.unshift(safe('<header>'));
header.push(safe('</header>\n\t'));
return header;
};
OS.monogatari = function (data, toppu) {
var tale = {header: this.atama(data)};
this.dice = data.dice;
var body = this.karada(data.body);
tale.body = [safe('<blockquote>'), body, safe('</blockquote>')];
if (data.num == MILLION) {
tale.body.splice(1, 0, safe('<script>window.gravitas=true;</script>'));
}
if (data.image && !data.hideimg)
tale.image = this.gazou(data.image, toppu);
return tale;
};
var MILLION = 1000000;
function gravitas_body() {
$('body').css({margin: 0});
}
OS.gravitas_style = function (idata, cssy) {
var src = this.image_paths().src + idata.src;
src = "url('" + encodeURI(src) + "')";
return cssy ? ("background-image: " + src + ";") : src;
};
OS.mono = function (data) {
var info = {
data: data,
classes: data.editing ? ['editing'] : [],
style: ''
};
if (data.flavor)
info.classes.push(data.flavor);
if (data.num == MILLION) {
info.classes.push('gravitas');
if (data.image)
info.style = this.gravitas_style(data.image, true);
}
this.trigger('openArticle', info);
var cls = info.classes.length && info.classes.join(' '),
o = [safe('\t<article id="'+data.num+'"'),
(cls ? [safe(' class="'), cls, safe('"')] : ''),
safe(info.style ? ' style="'+info.style+'"' : ''),
safe('>')],
c = safe('</article>\n'),
gen = this.monogatari(data, false);
return flatten([o, gen.header, gen.image || '', gen.body, c]).join('');
};
OS.monomono = function (data, cls) {
if (data.flavor)
cls = cls ? cls+' '+data.flavor : data.flavor;
if (data.locked)
cls = cls ? cls+' locked' : 'locked';
var style;
if (data.num == MILLION) {
cls = cls ? cls+' gravitas' : 'gravitas';
if (data.image)
style = this.gravitas_style(data.image, true);
}
var o = [safe('<section id="' + data.num),
(cls ? [safe('" class="'), cls] : ''),
safe(style ? '" style="' + style : ''),
safe('" data-sync="' + (data.hctr || 0)),
safe(data.full ? '' : '" data-imgs="'+data.imgctr),
safe('">')],
c = safe('</section>\n'),
gen = this.monogatari(data, true);
return flatten([o, gen.image || '', gen.header, gen.body, '\n', c]);
};
function pluralize(n, noun) {
return n + ' ' + noun + (n == 1 ? '' : 's');
}
exports.pluralize = pluralize;
exports.abbrev_msg = function (omit, img_omit) {
return omit + (omit==1 ? ' reply' : ' replies') + (img_omit
? ' and ' + pluralize(img_omit, 'image')
: '') + ' omitted.';
};
exports.parse_name = function (name) {
var tripcode = '', secure = '';
var hash = name.indexOf('#');
if (hash >= 0) {
tripcode = name.substr(hash+1);
name = name.substr(0, hash);
hash = tripcode.indexOf('#');
if (hash >= 0) {
secure = escape_html(tripcode.substr(hash+1));
tripcode = tripcode.substr(0, hash);
}
tripcode = escape_html(tripcode);
}
name = name.trim().replace(config.EXCLUDE_REGEXP, '');
return [name.substr(0, 100), tripcode.substr(0, 128),
secure.substr(0, 128)];
};
function random_id() {
return Math.floor(Math.random() * 1e16) + 1;
}