|
|
|
|
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 = {'&' : '&', '<': '<', '>': '>', '"': '"'};
|
|
|
|
|
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('(…)'), 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()) + ' (' + 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 = '>>' + 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 ' + this.lastN);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
OS.expansion_links_html = function (num, omit) {
|
|
|
|
|
var html = ' ' + 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;
|
|
|
|
|
}
|