Merge branch 'master' into v3

I really need to get back to this.
This commit is contained in:
Nicolas Stepien 2013-01-13 19:17:28 +01:00
commit cf55f670c0
11 changed files with 262 additions and 169 deletions

View File

@ -3,7 +3,7 @@
// @version 3.0.0 // @version 3.0.0
// @description Cross-browser userscript for maximum lurking on 4chan. // @description Cross-browser userscript for maximum lurking on 4chan.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com> // @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
// @copyright 2012 Nicolas Stepien <stepien.nicolas@gmail.com> // @copyright 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license // @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @match *://boards.4chan.org/* // @match *://boards.4chan.org/*
// @match *://images.4chan.org/* // @match *://images.4chan.org/*

View File

@ -3,7 +3,7 @@
// @version 3.0.0 // @version 3.0.0
// @description Cross-browser userscript for maximum lurking on 4chan. // @description Cross-browser userscript for maximum lurking on 4chan.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com> // @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
// @copyright 2012 Nicolas Stepien <stepien.nicolas@gmail.com> // @copyright 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license // @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @match *://boards.4chan.org/* // @match *://boards.4chan.org/*
// @match *://images.4chan.org/* // @match *://images.4chan.org/*
@ -20,11 +20,11 @@
// @icon https://github.com/MayhemYDG/4chan-x/raw/stable/img/icon.gif // @icon https://github.com/MayhemYDG/4chan-x/raw/stable/img/icon.gif
// ==/UserScript== // ==/UserScript==
/* 4chan X Alpha - Version 3.0.0 - 2012-10-25 /* 4chan X Alpha - Version 3.0.0 - 2013-01-13
* http://mayhemydg.github.com/4chan-x/ * http://mayhemydg.github.com/4chan-x/
* *
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
* Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com> * Copyright (c) 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
* Licensed under the MIT license. * Licensed under the MIT license.
* https://github.com/MayhemYDG/4chan-x/blob/master/LICENSE * https://github.com/MayhemYDG/4chan-x/blob/master/LICENSE
* *
@ -161,6 +161,7 @@
}, },
updater: { updater: {
checkbox: { checkbox: {
'Beep': [false, 'Beep on new post to completely read thread.'],
'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'], 'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'],
'Scroll BG': [false, 'Auto-scroll background tabs.'], 'Scroll BG': [false, 'Auto-scroll background tabs.'],
'Auto Update': [true, 'Automatically fetch new posts.'] 'Auto Update': [true, 'Automatically fetch new posts.']
@ -571,6 +572,9 @@
open: function(url) { open: function(url) {
return (GM_openInTab || window.open)(url, '_blank'); return (GM_openInTab || window.open)(url, '_blank');
}, },
hidden: function() {
return d.hidden || d.oHidden || d.mozHidden || d.webkitHidden;
},
queueTask: (function() { queueTask: (function() {
var execTask, taskChannel, taskQueue; var execTask, taskChannel, taskQueue;
taskQueue = []; taskQueue = [];
@ -691,6 +695,8 @@
return "//archive.foolz.us/" + board + "/full_image/" + filename; return "//archive.foolz.us/" + board + "/full_image/" + filename;
case 'u': case 'u':
return "//nsfw.foolz.us/" + board + "/full_image/" + filename; return "//nsfw.foolz.us/" + board + "/full_image/" + filename;
case 'po':
return "http://archive.thedarkcave.org/" + board + "/full_image/" + filename;
case 'ck': case 'ck':
case 'lit': case 'lit':
return "//fuuka.warosu.org/" + board + "/full_image/" + filename; return "//fuuka.warosu.org/" + board + "/full_image/" + filename;
@ -700,7 +706,6 @@
case 'cgl': case 'cgl':
case 'g': case 'g':
case 'mu': case 'mu':
case 'soc':
case 'w': case 'w':
return "//rbt.asia/" + board + "/full_image/" + filename; return "//rbt.asia/" + board + "/full_image/" + filename;
case 'an': case 'an':
@ -711,6 +716,8 @@
case 'toy': case 'toy':
case 'x': case 'x':
return "http://archive.heinessen.com/" + board + "/full_image/" + filename; return "http://archive.heinessen.com/" + board + "/full_image/" + filename;
case 'c':
return "//archive.nyafuu.org/" + board + "/full_image/" + filename;
} }
}, },
post: function(board, postID) { post: function(board, postID) {
@ -732,6 +739,8 @@
case 'u': case 'u':
case 'kuku': case 'kuku':
return "//nsfw.foolz.us/_/api/chan/post/?board=" + board + "&num=" + postID; return "//nsfw.foolz.us/_/api/chan/post/?board=" + board + "&num=" + postID;
case 'po':
return "http://archive.thedarkcave.org/_/api/chan/post/?board=" + board + "&num=" + postID;
} }
}, },
to: function(data) { to: function(data) {
@ -757,6 +766,9 @@
case 'kuku': case 'kuku':
url = Redirect.path('//nsfw.foolz.us', 'foolfuuka', data); url = Redirect.path('//nsfw.foolz.us', 'foolfuuka', data);
break; break;
case 'po':
url = Redirect.path('http://archive.thedarkcave.org', 'foolfuuka', data);
break;
case 'ck': case 'ck':
case 'lit': case 'lit':
url = Redirect.path('//fuuka.warosu.org', 'fuuka', data); url = Redirect.path('//fuuka.warosu.org', 'fuuka', data);
@ -768,7 +780,6 @@
case 'cgl': case 'cgl':
case 'g': case 'g':
case 'mu': case 'mu':
case 'soc':
case 'w': case 'w':
url = Redirect.path('//rbt.asia', 'fuuka', data); url = Redirect.path('//rbt.asia', 'fuuka', data);
break; break;
@ -781,6 +792,9 @@
case 'x': case 'x':
url = Redirect.path('http://archive.heinessen.com', 'fuuka', data); url = Redirect.path('http://archive.heinessen.com', 'fuuka', data);
break; break;
case 'c':
url = Redirect.path('//archive.nyafuu.org', 'fuuka', data);
break;
default: default:
if (data.threadID) { if (data.threadID) {
url = "//boards.4chan.org/" + board + "/"; url = "//boards.4chan.org/" + board + "/";
@ -807,6 +821,9 @@
postID = postID.match(/\d+/)[0]; postID = postID.match(/\d+/)[0];
} }
path = threadID ? "" + board + "/thread/" + threadID : "" + board + "/post/" + postID; path = threadID ? "" + board + "/thread/" + threadID : "" + board + "/post/" + postID;
if (archiver === 'foolfuuka') {
path += '/';
}
if (threadID && postID) { if (threadID && postID) {
path += archiver === 'foolfuuka' ? "#" + postID : "#p" + postID; path += archiver === 'foolfuuka' ? "#" + postID : "#p" + postID;
} }
@ -835,7 +852,7 @@
capcode: data.capcode, capcode: data.capcode,
tripcode: data.trip, tripcode: data.trip,
uniqueID: data.id, uniqueID: data.id,
email: data.email ? encodeURIComponent(data.email.replace(/&quot;/g, '"')) : '', email: data.email ? encodeURI(data.email.replace(/&quot;/g, '"')) : '',
subject: data.sub, subject: data.sub,
flagCode: data.country, flagCode: data.country,
flagName: data.country_name, flagName: data.country_name,
@ -1111,7 +1128,7 @@
return '</b>'; return '</b>';
} }
}); });
comment = bq.innerHTML.replace(/(^|>)(&gt;[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3'); comment = bq.innerHTML.replace(/(^|>)(&gt;[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3').replace(/((&gt;){2}(&gt;\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>');
threadID = data.thread_num; threadID = data.thread_num;
o = { o = {
postID: "" + postID, postID: "" + postID,
@ -1130,7 +1147,7 @@
})(), })(),
tripcode: data.trip, tripcode: data.trip,
uniqueID: data.poster_hash, uniqueID: data.poster_hash,
email: data.email ? encodeURIComponent(data.email) : '', email: data.email ? encodeURI(data.email) : '',
subject: data.title_processed, subject: data.title_processed,
flagCode: data.poster_country, flagCode: data.poster_country,
flagName: data.poster_country_name_processed, flagName: data.poster_country_name_processed,
@ -1171,79 +1188,73 @@
}); });
}, },
node: function() { node: function() {
var ID, a, board, data, i, index, m, node, nodes, post, quote, quoteID, quotes, snapshot, text, _i, _j, _len, _ref; var ID, a, board, deadlink, m, post, quote, quoteID, redirect, _i, _len, _ref, _ref1;
if (this.isClone) { if (this.isClone) {
return; return;
} }
snapshot = d.evaluate('.//text()[not(parent::a)]', this.nodes.comment, null, 6, null); _ref = $$('.deadlink', post.blockquote);
for (i = _i = 0, _ref = snapshot.snapshotLength; 0 <= _ref ? _i < _ref : _i > _ref; i = 0 <= _ref ? ++_i : --_i) { for (_i = 0, _len = _ref.length; _i < _len; _i++) {
node = snapshot.snapshotItem(i); deadlink = _ref[_i];
data = node.data; if (deadlink.parentNode.className === 'prettyprint') {
if (!(quotes = data.match(/>>(>\/[a-z\d]+\/)?\d+/g))) { $.replace(deadlink, Array.prototype.slice.call(deadlink.childNodes));
continue; continue;
} }
nodes = []; quote = deadlink.textContent;
for (_j = 0, _len = quotes.length; _j < _len; _j++) { if (!(ID = (_ref1 = quote.match(/\d+$/)) != null ? _ref1[0] : void 0)) {
quote = quotes[_j]; continue;
index = data.indexOf(quote); }
if (text = data.slice(0, index)) { board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
nodes.push($.tn(text)); quoteID = "" + board + "." + ID;
} if (post = g.posts[quoteID]) {
ID = quote.match(/\d+$/)[0]; if (!post.isDead) {
board = (m = quote.match(/^>>>\/([a-z\d]+)/)) ? m[1] : this.board.ID;
quoteID = "" + board + "." + ID;
if (post = g.posts[quoteID]) {
if (post.isDead) {
a = $.el('a', {
href: Redirect.to({
board: board,
threadID: 0,
postID: ID
}),
className: 'quotelink deadlink',
textContent: "" + quote + "\u00A0(Dead)",
target: '_blank'
});
a.setAttribute('data-board', board);
a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-postid', ID);
} else {
a = $.el('a', {
href: "/" + board + "/" + post.thread + "/res/#p" + ID,
className: 'quotelink',
textContent: quote
});
}
} else {
a = $.el('a', { a = $.el('a', {
href: Redirect.to({ href: "/" + board + "/" + post.thread + "/res/#p" + ID,
board: board, className: 'quotelink',
threadID: 0, textContent: quote
postID: ID });
}), } else if (redirect = Redirect.to({
className: 'deadlink', board: board,
threadID: post.thread.ID,
postID: ID
})) {
a = $.el('a', {
href: redirect,
className: 'quotelink deadlink',
target: '_blank', target: '_blank',
textContent: "" + quote + "\u00A0(Dead)" textContent: "" + quote + "\u00A0(Dead)"
}); });
if (Redirect.post(board, ID)) { a.setAttribute('data-board', board);
$.addClass(a, 'quotelink'); a.setAttribute('data-threadid', post.thread.ID);
a.setAttribute('data-board', board); a.setAttribute('data-postid', ID);
a.setAttribute('data-postid', ID);
}
} }
if (this.quotes.indexOf(quoteID) === -1) { } else if (redirect = Redirect.to({
this.quotes.push(quoteID); board: board,
threadID: 0,
postID: ID
})) {
a = $.el('a', {
href: redirect,
className: 'deadlink',
target: '_blank',
textContent: "" + quote + "\u00A0(Dead)"
});
if (Redirect.post(board, ID)) {
$.addClass(a, 'quotelink');
a.setAttribute('data-board', board);
a.setAttribute('data-postid', ID);
} }
if ($.hasClass(a, 'quotelink')) {
this.nodes.quotelinks.push(a);
}
nodes.push(a);
data = data.slice(index + quote.length);
} }
if (data) { if (a) {
nodes.push($.tn(data)); $.replace(deadlink, a);
} else {
deadlink.textContent += "\u00A0(Dead)";
}
if (this.quotes.indexOf(quoteID) === -1) {
this.quotes.push(quoteID);
}
if ($.hasClass(a, 'quotelink')) {
this.nodes.quotelinks.push(a);
} }
$.replace(node, nodes);
} }
} }
}; };
@ -1988,6 +1999,12 @@
node: function() { node: function() {
return new ThreadUpdater.Updater(this); return new ThreadUpdater.Updater(this);
}, },
/*
http://freesound.org/people/pierrecartoons1979/sounds/90112/
cc-by-nc-3.0
*/
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA',
Updater: (function() { Updater: (function() {
function _Class(thread) { function _Class(thread) {
@ -2064,9 +2081,7 @@
} }
}, },
visibility: function() { visibility: function() {
var state; if ($.hidden()) {
state = d.visibilityState || d.oVisibilityState || d.mozVisibilityState || d.webkitVisibilityState;
if (state !== 'visible') {
return; return;
} }
this.unsuccessfulFetchCount = 0; this.unsuccessfulFetchCount = 0;
@ -2085,7 +2100,7 @@
return this.scrollBG = this['Scroll BG'] ? function() { return this.scrollBG = this['Scroll BG'] ? function() {
return true; return true;
} : function() { } : function() {
return !(d.hidden || d.oHidden || d.mozHidden || d.webkitHidden); return !$.hidden();
}; };
}, },
autoUpdate: function() { autoUpdate: function() {
@ -2143,7 +2158,7 @@
var i, j; var i, j;
i = this.interval; i = this.interval;
j = Math.min(this.unsuccessfulFetchCount, 10); j = Math.min(this.unsuccessfulFetchCount, 10);
if (!(d.hidden || d.oHidden || d.mozHidden || d.webkitHidden)) { if (!$.hidden()) {
j = Math.min(j, 7); j = Math.min(j, 7);
} }
return this.seconds = Math.max(i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]); return this.seconds = Math.max(i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]);
@ -2231,6 +2246,14 @@
} }
} }
if (count) { if (count) {
if (Conf['Beep'] && $.hidden() && (Unread.replies.length === 0)) {
if (!this.audio) {
this.audio = $.el('audio', {
src: ThreadUpdater.beep
});
}
audio.play();
}
this.set('status', "+" + count); this.set('status', "+" + count);
this.status.className = 'new'; this.status.className = 'new';
this.unsuccessfulFetchCount = 0; this.unsuccessfulFetchCount = 0;
@ -2299,7 +2322,7 @@
}; };
function Post(root, thread, board, that) { function Post(root, thread, board, that) {
var alt, anchor, bq, capcode, data, date, email, file, fileInfo, flag, i, info, name, node, nodes, post, quotelink, quotes, size, subject, text, thumb, tripcode, uniqueID, unit, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2; var alt, anchor, bq, capcode, data, date, email, file, fileInfo, flag, hash, i, info, name, node, nodes, pathname, post, quotelink, quotes, size, subject, text, thumb, tripcode, uniqueID, unit, _i, _j, _k, _len, _len1, _ref, _ref1, _ref2;
this.thread = thread; this.thread = thread;
this.board = board; this.board = board;
if (that == null) { if (that == null) {
@ -2365,13 +2388,22 @@
_ref2 = $$('.quotelink', this.nodes.comment); _ref2 = $$('.quotelink', this.nodes.comment);
for (_k = 0, _len1 = _ref2.length; _k < _len1; _k++) { for (_k = 0, _len1 = _ref2.length; _k < _len1; _k++) {
quotelink = _ref2[_k]; quotelink = _ref2[_k];
if (quotelink.hash) { hash = quotelink.hash;
this.nodes.quotelinks.push(quotelink); if (!hash) {
if (quotelink.parentNode.parentNode.className === 'capcodeReplies') { continue;
continue;
}
quotes["" + (quotelink.pathname.split('/')[1]) + "." + quotelink.hash.slice(2)] = true;
} }
pathname = quotelink.pathname;
if (/catalog$/.test(pathname)) {
continue;
}
if (quotelink.hostname !== 'boards.4chan.org') {
continue;
}
this.nodes.quotelinks.push(quotelink);
if (quotelink.parentNode.parentNode.className === 'capcodeReplies') {
continue;
}
quotes["" + (pathname.split('/')[1]) + "." + hash.slice(2)] = true;
} }
this.quotes = Object.keys(quotes); this.quotes = Object.keys(quotes);
if ((file = $('.file', post)) && (thumb = $('img[data-md5]', file))) { if ((file = $('.file', post)) && (thumb = $('img[data-md5]', file))) {

View File

@ -1,5 +1,5 @@
Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
Copyright (c) 2012 Nicolas Stepien <stepien.nicolas@gmail.com> Copyright (c) 2012-2013 Nicolas Stepien <stepien.nicolas@gmail.com>
Permission is hereby granted, free of charge, to any person Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation obtaining a copy of this software and associated documentation

View File

@ -7,6 +7,47 @@ alpha
Fix Quote Highlighting not affecting inlined quotes. Fix Quote Highlighting not affecting inlined quotes.
master master
- Mayhem
Add /po/ archive redirection for threads, images and post resurrection.
2.37.3
- Mayhem
Fix successful posting causing errors.
Fix 4chan X trying to interact with >>>/board/rules links.
2.37.2
- aeosynth
Beep on new post to completely read thread
- Mayhem
Fix dead quotes.
2.37.1
- noface
Fix Anonymize not working on stubs.
- Mayhem
Fix selection quoting on Opera.
Fix history bug with Persistent QR enabled on Chrome.
Fix posting warning not displaying the reason.
Fix deadquotes showing up in code-tags.
2.37.0
- noface
Add Catalog Links toggle.
Fix Anonymize not working on hovered posts.
- Mayhem
Added catalog support.
Sync thread hiding between index and catalog.
Add /c/ archived thread and image redirection.
2.36.3
- Mayhem
Fix next/previous page keybinds.
2.36.2
- noface
Add tags support on /f/.
- Mayhem
Add /mu/ archived image redirection.
2.36.1 2.36.1
- noface - noface

View File

@ -1 +1 @@
postMessage({version:'2.36.1'},'*') postMessage({version:'2.37.3'},'*')

View File

@ -138,6 +138,8 @@ $.extend $,
return return
open: (url) -> open: (url) ->
(GM_openInTab or window.open) url, '_blank' (GM_openInTab or window.open) url, '_blank'
hidden: ->
d.hidden or d.oHidden or d.mozHidden or d.webkitHidden
queueTask: (-> queueTask: (->
# inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007 # inspired by https://www.w3.org/Bugs/Public/show_bug.cgi?id=15007
taskQueue = [] taskQueue = []

View File

@ -2,7 +2,7 @@
* http://mayhemydg.github.com/4chan-x/ * http://mayhemydg.github.com/4chan-x/
* *
* Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com> * Copyright (c) 2009-2011 James Campos <james.r.campos@gmail.com>
* Copyright (c) <%= grunt.template.today('yyyy') %> Nicolas Stepien <stepien.nicolas@gmail.com> * Copyright (c) 2012-<%= grunt.template.today('yyyy') %> Nicolas Stepien <stepien.nicolas@gmail.com>
* Licensed under the MIT license. * Licensed under the MIT license.
* <%= meta.repo %>blob/master/LICENSE * <%= meta.repo %>blob/master/LICENSE
* *

View File

@ -156,6 +156,7 @@ Config =
'hide': ['x', 'Hide thread.'] 'hide': ['x', 'Hide thread.']
updater: updater:
checkbox: checkbox:
'Beep': [false, 'Beep on new post to completely read thread.']
'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.'] 'Auto Scroll': [false, 'Scroll updated posts into view. Only enabled at bottom of page.']
'Scroll BG': [false, 'Auto-scroll background tabs.'] 'Scroll BG': [false, 'Auto-scroll background tabs.']
'Auto Update': [true, 'Automatically fetch new posts.'] 'Auto Update': [true, 'Automatically fetch new posts.']

View File

@ -7,20 +7,26 @@ Redirect =
"//archive.foolz.us/#{board}/full_image/#{filename}" "//archive.foolz.us/#{board}/full_image/#{filename}"
when 'u' when 'u'
"//nsfw.foolz.us/#{board}/full_image/#{filename}" "//nsfw.foolz.us/#{board}/full_image/#{filename}"
when 'po'
"http://archive.thedarkcave.org/#{board}/full_image/#{filename}"
when 'ck', 'lit' when 'ck', 'lit'
"//fuuka.warosu.org/#{board}/full_image/#{filename}" "//fuuka.warosu.org/#{board}/full_image/#{filename}"
when 'diy', 'sci' when 'diy', 'sci'
"//archive.installgentoo.net/#{board}/full_image/#{filename}" "//archive.installgentoo.net/#{board}/full_image/#{filename}"
when 'cgl', 'g', 'mu', 'soc', 'w' when 'cgl', 'g', 'mu', 'w'
"//rbt.asia/#{board}/full_image/#{filename}" "//rbt.asia/#{board}/full_image/#{filename}"
when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x' when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x'
"http://archive.heinessen.com/#{board}/full_image/#{filename}" "http://archive.heinessen.com/#{board}/full_image/#{filename}"
when 'c'
"//archive.nyafuu.org/#{board}/full_image/#{filename}"
post: (board, postID) -> post: (board, postID) ->
switch board switch board
when 'a', 'co', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'wsg', 'dev', 'foolz' when 'a', 'co', 'jp', 'm', 'q', 'sp', 'tg', 'tv', 'v', 'vg', 'wsg', 'dev', 'foolz'
"//archive.foolz.us/_/api/chan/post/?board=#{board}&num=#{postID}" "//archive.foolz.us/_/api/chan/post/?board=#{board}&num=#{postID}"
when 'u', 'kuku' when 'u', 'kuku'
"//nsfw.foolz.us/_/api/chan/post/?board=#{board}&num=#{postID}" "//nsfw.foolz.us/_/api/chan/post/?board=#{board}&num=#{postID}"
when 'po'
"http://archive.thedarkcave.org/_/api/chan/post/?board=#{board}&num=#{postID}"
# for fuuka-based archives: # for fuuka-based archives:
# https://github.com/eksopl/fuuka/issues/27 # https://github.com/eksopl/fuuka/issues/27
to: (data) -> to: (data) ->
@ -30,14 +36,18 @@ Redirect =
url = Redirect.path '//archive.foolz.us', 'foolfuuka', data url = Redirect.path '//archive.foolz.us', 'foolfuuka', data
when 'u', 'kuku' when 'u', 'kuku'
url = Redirect.path '//nsfw.foolz.us', 'foolfuuka', data url = Redirect.path '//nsfw.foolz.us', 'foolfuuka', data
when 'po'
url = Redirect.path 'http://archive.thedarkcave.org', 'foolfuuka', data
when 'ck', 'lit' when 'ck', 'lit'
url = Redirect.path '//fuuka.warosu.org', 'fuuka', data url = Redirect.path '//fuuka.warosu.org', 'fuuka', data
when 'diy', 'sci' when 'diy', 'sci'
url = Redirect.path '//archive.installgentoo.net', 'fuuka', data url = Redirect.path '//archive.installgentoo.net', 'fuuka', data
when 'cgl', 'g', 'mu', 'soc', 'w' when 'cgl', 'g', 'mu', 'w'
url = Redirect.path '//rbt.asia', 'fuuka', data url = Redirect.path '//rbt.asia', 'fuuka', data
when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x' when 'an', 'fit', 'k', 'mlp', 'r9k', 'toy', 'x'
url = Redirect.path 'http://archive.heinessen.com', 'fuuka', data url = Redirect.path 'http://archive.heinessen.com', 'fuuka', data
when 'c'
url = Redirect.path '//archive.nyafuu.org', 'fuuka', data
else else
if data.threadID if data.threadID
url = "//boards.4chan.org/#{board}/" url = "//boards.4chan.org/#{board}/"
@ -68,6 +78,8 @@ Redirect =
"#{board}/thread/#{threadID}" "#{board}/thread/#{threadID}"
else else
"#{board}/post/#{postID}" "#{board}/post/#{postID}"
if archiver is 'foolfuuka'
path += '/'
if threadID and postID if threadID and postID
path += path +=
if archiver is 'foolfuuka' if archiver is 'foolfuuka'
@ -98,7 +110,7 @@ Build =
capcode: data.capcode capcode: data.capcode
tripcode: data.trip tripcode: data.trip
uniqueID: data.id uniqueID: data.id
email: if data.email then encodeURIComponent data.email.replace /&quot;/g, '"' else '' email: if data.email then encodeURI data.email.replace /&quot;/g, '"' else ''
subject: data.sub subject: data.sub
flagCode: data.country flagCode: data.country
flagName: data.country_name flagName: data.country_name
@ -474,8 +486,12 @@ Get =
'<b style="color: red;">' '<b style="color: red;">'
when '[/banned]' when '[/banned]'
'</b>' '</b>'
# greentext
comment = bq.innerHTML.replace /(^|>)(&gt;[^<$]+)(<|$)/g, '$1<span class=quote>$2</span>$3' comment = bq.innerHTML
# greentext
.replace(/(^|>)(&gt;[^<$]*)(<|$)/g, '$1<span class=quote>$2</span>$3')
# quotes
.replace /((&gt;){2}(&gt;\/[a-z\d]+\/)?\d+)/g, '<span class=deadlink>$1</span>'
threadID = data.thread_num threadID = data.thread_num
o = o =
@ -491,7 +507,7 @@ Get =
when 'D' then 'developer' when 'D' then 'developer'
tripcode: data.trip tripcode: data.trip
uniqueID: data.poster_hash uniqueID: data.poster_hash
email: if data.email then encodeURIComponent data.email else '' email: if data.email then encodeURI data.email else ''
subject: data.title_processed subject: data.title_processed
flagCode: data.poster_country flagCode: data.poster_country
flagName: data.poster_country_name_processed flagName: data.poster_country_name_processed
@ -529,82 +545,63 @@ Quotify =
cb: @node cb: @node
node: -> node: ->
return if @isClone return if @isClone
for deadlink in $$ '.deadlink', post.blockquote
if deadlink.parentNode.className is 'prettyprint'
# Don't quotify deadlinks inside code tags,
# un-`span` them.
$.replace deadlink, Array::slice.call deadlink.childNodes
continue
# XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE === 6 quote = deadlink.textContent
# Get all the text nodes that are not inside an anchor. continue unless ID = quote.match(/\d+$/)?[0]
snapshot = d.evaluate './/text()[not(parent::a)]', @nodes.comment, null, 6, null board =
if m = quote.match /^>>>\/([a-z\d]+)/
for i in [0...snapshot.snapshotLength] m[1]
node = snapshot.snapshotItem i
data = node.data
# Only accept nodes with potentially valid links
continue unless quotes = data.match />>(>\/[a-z\d]+\/)?\d+/g
nodes = []
for quote in quotes
index = data.indexOf quote
if text = data[...index]
# Potential text before this valid quote.
nodes.push $.tn text
ID = quote.match(/\d+$/)[0]
board =
if m = quote.match /^>>>\/([a-z\d]+)/
m[1]
else
@board.ID
quoteID = "#{board}.#{ID}"
# \u00A0 is nbsp
if post = g.posts[quoteID]
if post.isDead
a = $.el 'a',
href: Redirect.to
board: board
threadID: 0
postID: ID
className: 'quotelink deadlink'
textContent: "#{quote}\u00A0(Dead)"
target: '_blank'
a.setAttribute 'data-board', board
a.setAttribute 'data-threadid', post.thread.ID
a.setAttribute 'data-postid', ID
else
# Don't (Dead) when quotifying in an archived post,
# and we know the post still exists.
a = $.el 'a',
href: "/#{board}/#{post.thread}/res/#p#{ID}"
className: 'quotelink'
textContent: quote
else else
@board.ID
quoteID = "#{board}.#{ID}"
# \u00A0 is nbsp
if post = g.posts[quoteID]
unless post.isDead
# Don't (Dead) when quotifying in an archived post,
# and we know the post still exists.
a = $.el 'a', a = $.el 'a',
href: Redirect.to href: "/#{board}/#{post.thread}/res/#p#{ID}"
board: board className: 'quotelink'
threadID: 0 textContent: quote
postID: ID else if redirect = Redirect.to {board: board, threadID: post.thread.ID, postID: ID}
className: 'deadlink' # Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: redirect
className: 'quotelink deadlink'
target: '_blank' target: '_blank'
textContent: "#{quote}\u00A0(Dead)" textContent: "#{quote}\u00A0(Dead)"
if Redirect.post board, ID a.setAttribute 'data-board', board
$.addClass a, 'quotelink' a.setAttribute 'data-threadid', post.thread.ID
a.setAttribute 'data-board', board a.setAttribute 'data-postid', ID
a.setAttribute 'data-postid', ID else if redirect = Redirect.to {board: board, threadID: 0, postID: ID}
# Replace the .deadlink span if we can redirect.
a = $.el 'a',
href: redirect
className: 'deadlink'
target: '_blank'
textContent: "#{quote}\u00A0(Dead)"
if Redirect.post board, ID
# Make it function as a normal quote if we can fetch the post.
$.addClass a, 'quotelink'
a.setAttribute 'data-board', board
a.setAttribute 'data-postid', ID
if @quotes.indexOf(quoteID) is -1 if a
@quotes.push quoteID $.replace deadlink, a
if $.hasClass a, 'quotelink' else
@nodes.quotelinks.push a deadlink.textContent += "\u00A0(Dead)"
nodes.push a
data = data[index + quote.length..]
if data if @quotes.indexOf(quoteID) is -1
# Potential text after the last valid quote. @quotes.push quoteID
nodes.push $.tn data if $.hasClass a, 'quotelink'
@nodes.quotelinks.push a
$.replace node, nodes
return return
QuoteInline = QuoteInline =
@ -1109,6 +1106,12 @@ ThreadUpdater =
cb: @node cb: @node
node: -> node: ->
new ThreadUpdater.Updater @ new ThreadUpdater.Updater @
###
http://freesound.org/people/pierrecartoons1979/sounds/90112/
cc-by-nc-3.0
###
beep: 'data:audio/wav;base64,UklGRjQDAABXQVZFZm10IBAAAAABAAEAgD4AAIA+AAABAAgAc21wbDwAAABBAAADAAAAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABkYXRhzAIAAGMms8em0tleMV4zIpLVo8nhfSlcPR102Ki+5JspVEkdVtKzs+K1NEhUIT7DwKrcy0g6WygsrM2k1NpiLl0zIY/WpMrjgCdbPhxw2Kq+5Z4qUkkdU9K1s+K5NkVTITzBwqnczko3WikrqM+l1NxlLF0zIIvXpsnjgydZPhxs2ay95aIrUEkdUdC3suK8N0NUIjq+xKrcz002WioppdGm091pK1w0IIjYp8jkhydXPxxq2K295aUrTkoeTs65suK+OUFUIzi7xqrb0VA0WSoootKm0t5tKlo1H4TYqMfkiydWQBxm16+85actTEseS8y7seHAPD9TIza5yKra01QyWSson9On0d5wKVk2H4DYqcfkjidUQB1j1rG75KsvSkseScu8seDCPz1TJDW2yara1FYxWSwnm9Sn0N9zKVg2H33ZqsXkkihSQR1g1bK65K0wSEsfR8i+seDEQTxUJTOzy6rY1VowWC0mmNWoz993KVc3H3rYq8TklSlRQh1d1LS647AyR0wgRMbAsN/GRDpTJTKwzKrX1l4vVy4lldWpzt97KVY4IXbUr8LZljVPRCxhw7W3z6ZISkw1VK+4sMWvXEhSPk6buay9sm5JVkZNiLWqtrJ+TldNTnquqbCwilZXU1BwpKirrpNgWFhTaZmnpquZbFlbVmWOpaOonHZcXlljhaGhpZ1+YWBdYn2cn6GdhmdhYGN3lp2enIttY2Jjco+bnJuOdGZlZXCImJqakHpoZ2Zug5WYmZJ/bGlobX6RlpeSg3BqaW16jZSVkoZ0bGtteImSk5KIeG5tbnaFkJKRinxxbm91gY2QkIt/c3BwdH6Kj4+LgnZxcXR8iI2OjIR5c3J0e4WLjYuFe3VzdHmCioyLhn52dHR5gIiKioeAeHV1eH+GiYqHgXp2dnh9hIiJh4J8eHd4fIKHiIeDfXl4eHyBhoeHhH96eHmA'
Updater: class Updater: class
constructor: (@thread) -> constructor: (@thread) ->
@ -1175,8 +1178,7 @@ ThreadUpdater =
@unsuccessfulFetchCount = 0 @unsuccessfulFetchCount = 0
setTimeout @update.bind(@), 1000 if @seconds > 2 setTimeout @update.bind(@), 1000 if @seconds > 2
visibility: -> visibility: ->
state = d.visibilityState or d.oVisibilityState or d.mozVisibilityState or d.webkitVisibilityState return if $.hidden()
return if state isnt 'visible'
# Reset the counter when we focus this tab. # Reset the counter when we focus this tab.
@unsuccessfulFetchCount = 0 @unsuccessfulFetchCount = 0
if @seconds > @interval if @seconds > @interval
@ -1191,7 +1193,7 @@ ThreadUpdater =
if @['Scroll BG'] if @['Scroll BG']
-> true -> true
else else
-> !(d.hidden or d.oHidden or d.mozHidden or d.webkitHidden) -> not $.hidden()
autoUpdate: -> autoUpdate: ->
if @['Auto Update This'] and @online if @['Auto Update This'] and @online
@timeoutID = setTimeout @timeout.bind(@), 1000 @timeoutID = setTimeout @timeout.bind(@), 1000
@ -1241,7 +1243,7 @@ ThreadUpdater =
getInterval: -> getInterval: ->
i = @interval i = @interval
j = Math.min @unsuccessfulFetchCount, 10 j = Math.min @unsuccessfulFetchCount, 10
unless d.hidden or d.oHidden or d.mozHidden or d.webkitHidden unless $.hidden()
# Lower the max refresh rate limit on visible tabs. # Lower the max refresh rate limit on visible tabs.
j = Math.min j, 7 j = Math.min j, 7
@seconds = Math.max i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j] @seconds = Math.max i, [0, 5, 10, 15, 20, 30, 60, 90, 120, 240, 300][j]
@ -1308,6 +1310,10 @@ ThreadUpdater =
post.kill true post.kill true
if count if count
if Conf['Beep'] and $.hidden() and (Unread.replies.length is 0)
unless @audio
@audio = $.el 'audio', src: ThreadUpdater.beep
audio.play()
@set 'status', "+#{count}" @set 'status', "+#{count}"
@status.className = 'new' @status.className = 'new'
@unsuccessfulFetchCount = 0 @unsuccessfulFetchCount = 0

View File

@ -88,13 +88,24 @@ class Post
quotes = {} quotes = {}
for quotelink in $$ '.quotelink', @nodes.comment for quotelink in $$ '.quotelink', @nodes.comment
# Don't add board links. (>>>/b/) # Don't add board links. (>>>/b/)
hash = quotelink.hash
continue unless hash
# Don't add catalog links. (>>>/b/catalog or >>>/b/search)
pathname = quotelink.pathname
continue if /catalog$/.test pathname
# Don't add rules links. (>>>/a/rules)
# Don't add text-board quotelinks. (>>>/img/1234) # Don't add text-board quotelinks. (>>>/img/1234)
continue if quotelink.hostname isnt 'boards.4chan.org'
@nodes.quotelinks.push quotelink
# Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...) # Don't count capcode replies as quotes. (Admin/Mod/Dev Replies: ...)
# Only add quotes that link to posts on an imageboard. continue if quotelink.parentNode.parentNode.className is 'capcodeReplies'
if quotelink.hash
@nodes.quotelinks.push quotelink # Basically, only add quotes that link to posts on an imageboard.
continue if quotelink.parentNode.parentNode.className is 'capcodeReplies' quotes["#{pathname.split('/')[1]}.#{hash[2..]}"] = true
quotes["#{quotelink.pathname.split('/')[1]}.#{quotelink.hash[2..]}"] = true
@quotes = Object.keys quotes @quotes = Object.keys quotes
if (file = $ '.file', post) and thumb = $ 'img[data-md5]', file if (file = $ '.file', post) and thumb = $ 'img[data-md5]', file

View File

@ -3,7 +3,7 @@
// @version <%= pkg.version %> // @version <%= pkg.version %>
// @description Cross-browser userscript for maximum lurking on 4chan. // @description Cross-browser userscript for maximum lurking on 4chan.
// @copyright 2009-2011 James Campos <james.r.campos@gmail.com> // @copyright 2009-2011 James Campos <james.r.campos@gmail.com>
// @copyright <%= grunt.template.today('yyyy') %> Nicolas Stepien <stepien.nicolas@gmail.com> // @copyright 2012-<%= grunt.template.today('yyyy') %> Nicolas Stepien <stepien.nicolas@gmail.com>
// @license MIT; http://en.wikipedia.org/wiki/Mit_license // @license MIT; http://en.wikipedia.org/wiki/Mit_license
// @match *://boards.4chan.org/* // @match *://boards.4chan.org/*
// @match *://images.4chan.org/* // @match *://images.4chan.org/*