From 020a68f73ae8d514658c1654883384e723e08b99 Mon Sep 17 00:00:00 2001 From: Zixaphir Date: Tue, 9 Dec 2014 00:28:38 -0700 Subject: [PATCH] Merge ccd0's IP Count stuff --- LICENSE | 2 +- builds/appchan-x.user.js | 263 ++++++++++++++++++++++------ builds/crx/script.js | 263 ++++++++++++++++++++++------ src/General/Config.coffee | 14 +- src/General/Main.coffee | 1 + src/General/lib/thread.class | 30 +++- src/Monitoring/MarkNewIPs.coffee | 45 +++++ src/Monitoring/ThreadStats.coffee | 34 +++- src/Monitoring/ThreadUpdater.coffee | 69 ++++++-- src/Posting/QR.captcha.coffee | 5 +- 10 files changed, 570 insertions(+), 156 deletions(-) create mode 100644 src/Monitoring/MarkNewIPs.coffee diff --git a/LICENSE b/LICENSE index eeb4cc20d..a5ab91e88 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ /* -* appchan x - Version 2.9.38 - 2014-12-08 +* appchan x - Version 2.9.38 - 2014-12-09 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE diff --git a/builds/appchan-x.user.js b/builds/appchan-x.user.js index 8eccab752..4fa2b8058 100644 --- a/builds/appchan-x.user.js +++ b/builds/appchan-x.user.js @@ -28,7 +28,7 @@ // ==/UserScript== /* -* appchan x - Version 2.9.38 - 2014-12-08 +* appchan x - Version 2.9.38 - 2014-12-09 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -116,7 +116,7 @@ 'use strict'; (function() { - var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, + var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, __slice = [].slice, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, @@ -212,9 +212,11 @@ 'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.'], 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'], 'Thread Stats': [true, 'Display reply and image count.'], - 'Page Count in Stats': [false, 'Display the page count in the thread stats as well.'], + 'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.'], + 'Page Count in Stats': [true, 'Display the page count in the thread stats.'], 'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'], - 'Thread Watcher': [true, 'Bookmark threads.'] + 'Thread Watcher': [true, 'Bookmark threads.'], + 'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.'] }, 'Posting': { 'Header Shortcut': [true, 'Add a shortcut to the header to toggle the QR.'], @@ -228,7 +230,7 @@ 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'], 'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'], 'Dump List Before Comment': [false, 'Position of the QR\'s Dump List.'], - 'Auto-load captcha': [false, 'Automatically load the captcha when you open a thread, and reload it after you post.'] + 'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], @@ -3163,6 +3165,7 @@ this.isClosed = false; this.postLimit = false; this.fileLimit = false; + this.ipCount = void 0; this.OP = null; this.catalogView = null; g.threads.push(this.fullID, board.threads.push(this, this)); @@ -3192,7 +3195,7 @@ }; Thread.prototype.setStatus = function(type, status) { - var icon, name, root, typeLC; + var name, typeLC; name = "is" + type; if (this[name] === status) { return; @@ -3202,8 +3205,21 @@ return; } typeLC = type.toLowerCase(); + this.setIcon('Sticky', this.isSticky); + this.setIcon('Closed', this.isClosed && !this.isArchived); + return this.setIcon('Archived', this.isArchived); + }; + + Thread.prototype.setIcon = function(type, status) { + var icon, root, typeLC; + typeLC = type.toLowerCase(); + icon = $("." + typeLC + "Icon", this.OP.nodes.info); + if (!!icon === status) { + return; + } if (!status) { - $.rm($("." + typeLC + "Icon", this.OP.nodes.info)); + $.rm(icon.previousSibling); + $.rm(icon); if (this.catalogView) { $.rm($("." + typeLC + "Icon", this.catalogView.nodes.icons)); } @@ -3211,10 +3227,11 @@ } icon = $.el('img', { src: "" + Build.staticPath + typeLC + Build.gifIcon, + alt: type, title: type, - className: "" + typeLC + "Icon" + className: "" + typeLC + "Icon retina" }); - root = type === 'Closed' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : g.VIEW === 'index' ? $('.page-num', this.OP.nodes.info) : $('[title="Reply to this post"]', this.OP.nodes.info); + root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || $('[title="Reply to this post"]', this.OP.nodes.info); $.after(root, [$.tn(' '), icon]); if (!this.catalogView) { return; @@ -9707,29 +9724,23 @@ } }, save: function(e) { - var err, _base; - try { - if (this.needed()) { - this.shouldFocus = true; - this.reload(); - } else { - this.nodes.counter.focus(); - if ((_base = this.timeouts).destroy == null) { - _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); - } + var _base; + if (this.needed()) { + this.shouldFocus = true; + this.reload(); + } else { + this.nodes.counter.focus(); + if ((_base = this.timeouts).destroy == null) { + _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); } - console.log(e.detail); - $.forceSync('captchas'); - this.captchas.push({ - response: e.detail, - timeout: Date.now() + 2 * $.MINUTE - }); - this.count(); - return $.set('captchas', this.captchas); - } catch (_error) { - err = _error; - return console.log(err); } + $.forceSync('captchas'); + this.captchas.push({ + response: e.detail, + timeout: Date.now() + 2 * $.MINUTE + }); + this.count(); + return $.set('captchas', this.captchas); }, clear: function() { var captcha, i, now, _i, _len, _ref; @@ -11876,6 +11887,84 @@ logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAJtJREFUeF7t17ENgDAMBdGswAqswAqskP1XgUhG37JQQv25a1I4eR1CbopoS3295wIASD1SBZnecwEApO9RRurjPdLcAQDQY9UnBSDEBQAIRA3gfGkA9cfiAwDUHqCnHqDl/AGA0v8AAL4FgO0uAxUcc2cAQEtFjwQoLSMuAMBqsVwtpp4AwNDngPIGAPI5mVsCAELSuZybAQBEF/bX4X+FReD/AAAAAElFTkSuQmCC' }; + MarkNewIPs = { + init: function() { + if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) { + return; + } + return Thread.callbacks.push({ + name: 'Mark New IPs', + cb: this.node + }); + }, + node: function() { + MarkNewIPs.ipCount = this.ipCount; + MarkNewIPs.postIDs = this.posts.keys.map(function(x) { + return +x; + }); + return $.on(d, 'ThreadUpdate', MarkNewIPs.onUpdate); + }, + onUpdate: function(e) { + var added, fullID, i, ipCount, newPosts, obj, postIDs, removed, x, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1; + _ref = e.detail, ipCount = _ref.ipCount, newPosts = _ref.newPosts; + postIDs = ThreadUpdater.postIDs; + if (ipCount == null) { + return; + } + if (newPosts.length) { + obj = {}; + _ref1 = MarkNewIPs.postIDs; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + x = _ref1[_i]; + obj[x] = true; + } + added = 0; + for (_j = 0, _len1 = postIDs.length; _j < _len1; _j++) { + x = postIDs[_j]; + if (!(x in obj)) { + added++; + } + } + removed = MarkNewIPs.postIDs.length + added - postIDs.length; + switch (ipCount - MarkNewIPs.ipCount) { + case added: + i = MarkNewIPs.ipCount; + for (_k = 0, _len2 = newPosts.length; _k < _len2; _k++) { + fullID = newPosts[_k]; + MarkNewIPs.markNew(g.posts[fullID], ++i); + } + break; + case -removed: + for (_l = 0, _len3 = newPosts.length; _l < _len3; _l++) { + fullID = newPosts[_l]; + MarkNewIPs.markOld(g.posts[fullID]); + } + } + } + MarkNewIPs.ipCount = ipCount; + return MarkNewIPs.postIDs = postIDs; + }, + markNew: function(post, ipCount) { + var counter, suffix; + suffix = { + 1: 'st', + 2: 'nd', + 3: 'rd' + }[ipCount % 10] || Math.floor('th' / fuck(switches)); + counter = $.el('span', { + className: 'ip-counter', + textContent: "(" + ipCount + ")" + }); + post.nodes.nameBlock.title = "This is the " + ipCount + suffix + " IP in the thread."; + $.add(post.nodes.nameBlock, [$.tn(' '), counter]); + return $.addClass(post.nodes.root, 'new-ip'); + }, + markOld: function(post) { + post.nodes.nameBlock.title = 'Not the first post from this IP.'; + return $.addClass(post.nodes.root, 'old-ip'); + } + }; + ThreadExcerpt = { init: function() { if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) { @@ -11905,9 +11994,9 @@ } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { - innerHTML: "[0 / 0" + (Conf["Page Count in Stats"] ? " / 0" : "") + "]", + innerHTML: "[0 / \n0\n" + (Conf['IP Count in Stats'] ? ' / ?' : "") + "\n" + (Conf['Page Count in Stats'] ? ' / 0' : "") + "]", id: 'thread-stats', - title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "") + title: 'Post Count / File Count' + (Conf['IP Count in Stats'] ? " / IPs" : "") + (Conf['Page Count in Stats'] ? " / Page Count" : "") }); $.ready(function() { return Header.addShortcut(sc); @@ -11921,6 +12010,7 @@ })(this)); } this.postCountEl = $('#post-count', sc); + this.ipCountEl = $('#ip-count', sc); this.fileCountEl = $('#file-count', sc); this.pageCountEl = $('#page-count', sc); return Thread.callbacks.push({ @@ -11940,7 +12030,7 @@ }); ThreadStats.thread = this; ThreadStats.fetchPage(); - ThreadStats.update(postCount, fileCount); + ThreadStats.update(postCount, fileCount, this.ipCount); return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate); }, disconnect: function() { @@ -11963,18 +12053,30 @@ return $.off(d, 'ThreadUpdate', ThreadStats.onUpdate); }, onUpdate: function(e) { - var fileCount, postCount, _ref; + var fileCount, ipCount, newPosts, postCount, _ref, _ref1; if (e.detail[404]) { return; } - _ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount; - return ThreadStats.update(postCount, fileCount); + _ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount, ipCount = _ref.ipCount, newPosts = _ref.newPosts; + ThreadStats.update(postCount, fileCount, ipCount); + if (!Conf["Page Count in Stats"]) { + return; + } + if (newPosts.length) { + ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date; + } + if (ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((_ref1 = ThreadStats.pageCountEl) != null ? _ref1.textContent : void 0) !== '1') { + return ThreadStats.fetchPage(); + } }, - update: function(postCount, fileCount) { - var fileCountEl, postCountEl, thread; - thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl; + update: function(postCount, fileCount, ipCount) { + var fileCountEl, ipCountEl, postCountEl, thread; + thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl; postCountEl.textContent = postCount; fileCountEl.textContent = fileCount; + if ((ipCount != null) && Conf["IP Count in Stats"]) { + ipCountEl.textContent = ipCount; + } (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning'); return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning'); }, @@ -12134,8 +12236,12 @@ $.on(window, 'online offline', ThreadUpdater.cb.online); $.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost); $.on(d, 'visibilitychange', ThreadUpdater.cb.visibility); - ThreadUpdater.cb.online(); - return Rice.nodes(ThreadUpdater.dialog); + if (ThreadUpdater.thread.isArchived) { + ThreadUpdater.set('status', 'Archived', 'warning'); + } else { + ThreadUpdater.cb.online(); + } + Rice.nodes(ThreadUpdater.dialog); }, /* @@ -12208,36 +12314,72 @@ } }, load: function(e) { - var klass, req, text, _ref; + var req; req = ThreadUpdater.req; switch (req.status) { case 200: g.DEAD = false; ThreadUpdater.parse(req.response.posts); - ThreadUpdater.setInterval(); + if (ThreadUpdater.thread.isArchived) { + ThreadUpdater.set('status', 'Archived', 'warning'); + ThreadUpdater.kill(); + } else { + ThreadUpdater.setInterval(); + } break; case 404: - g.DEAD = true; - ThreadUpdater.set('timer', null); - ThreadUpdater.set('status', '404', 'warning'); - clearTimeout(ThreadUpdater.timeoutID); - ThreadUpdater.thread.kill(); - $.event('ThreadUpdate', { - 404: true, - threadID: ThreadUpdater.thread.fullID + $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + onloadend: function() { + var confirmed, page, thread, _i, _j, _len, _len1, _ref, _ref1; + if (this.status === 200) { + confirmed = true; + _ref = this.response; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + page = _ref[_i]; + _ref1 = page.threads; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + thread = _ref1[_j]; + if (thread.no === ThreadUpdater.thread.ID) { + confirmed = false; + break; + } + } + } + } else { + confirmed = false; + } + if (confirmed) { + ThreadUpdater.set('status', '404', 'warning'); + return ThreadUpdater.kill(); + } else { + return ThreadUpdater.error(req); + } + } }); break; default: - ThreadUpdater.outdateCount++; - ThreadUpdater.setInterval(); - _ref = req.status === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1]; - ThreadUpdater.set('status', text, klass); + ThreadUpdater.error(req); } if (ThreadUpdater.postID) { return ThreadUpdater.cb.checkpost(); } } }, + kill: function() { + ThreadUpdater.set('timer', ''); + clearTimeout(ThreadUpdater.timeoutID); + ThreadUpdater.thread.kill(); + return $.event('ThreadUpdate', { + 404: true, + threadID: ThreadUpdater.thread.fullID + }); + }, + error: function(req) { + var klass, text, _ref; + ThreadUpdater.setInterval(); + _ref = req.status === 304 ? ['', ''] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1]; + return ThreadUpdater.set('status', text, klass); + }, setInterval: function() { var cur, i, j, limit; i = ThreadUpdater.interval + 1; @@ -12319,6 +12461,9 @@ return; } ThreadUpdater.thread.setStatus(type, status); + if (type === 'Closed' && ThreadUpdater.thread.isArchived) { + return; + } change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore'; return new Notice('info', "The thread is " + change + ".", 30); }, @@ -12326,10 +12471,14 @@ var OP, count, files, index, node, num, post, postObject, posts, root, scroll, sendEvent, _i, _j, _len, _len1; OP = postObjects[0]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; + ThreadUpdater.thread.setStatus('Archived', !!+OP.archived); ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); ThreadUpdater.updateThreadStatus('Closed', !!OP.closed); ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit; + if (OP.unique_ips != null) { + ThreadUpdater.thread.ipCount = OP.unique_ips; + } posts = []; index = []; files = []; @@ -12370,7 +12519,8 @@ return post.fullID; }), postCount: OP.replies + 1, - fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead) + fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead), + ipCount: OP.unique_ips }); }; if (!count) { @@ -17968,6 +18118,7 @@ init('Thread Updater', ThreadUpdater); init('Thread Watcher', ThreadWatcher); init('Thread Watcher (Menu)', ThreadWatcher.menu); + init('Mark New IPs', MarkNewIPs); init('Index Navigation', Nav); init('Keybinds', Keybinds); init('Show Dice Roll', Dice); diff --git a/builds/crx/script.js b/builds/crx/script.js index 415ef2761..62baca509 100644 --- a/builds/crx/script.js +++ b/builds/crx/script.js @@ -1,6 +1,6 @@ // Generated by CoffeeScript /* -* appchan x - Version 2.9.38 - 2014-12-08 +* appchan x - Version 2.9.38 - 2014-12-09 * * Licensed under the MIT license. * https://github.com/zixaphir/appchan-x/blob/master/LICENSE @@ -88,7 +88,7 @@ 'use strict'; (function() { - var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, + var $, $$, Anonymize, ArchiveLink, Banner, Board, Build, Callbacks, CatalogLinks, CatalogThread, Clone, Color, Conf, Config, CustomCSS, DataBoard, DeleteLink, Dice, DownloadLink, ExpandComment, ExpandThread, FappeTyme, Favicon, FileInfo, Filter, Flash, Fourchan, Gallery, Get, GlobalMessage, Header, IDColor, ImageExpand, ImageHover, ImageLoader, Index, JSColor, Keybinds, Labels, Linkify, Main, MarkNewIPs, MascotTools, Mascots, Menu, Nav, Navigate, Notice, PSAHiding, Polyfill, Post, PostHiding, QR, QuoteBacklink, QuoteInline, QuoteMarkers, QuotePreview, QuoteStrikeThrough, QuoteThreading, Quotify, RandomAccessList, Recursive, Redirect, RelativeDates, RemoveSpoilers, Report, ReportLink, RevealSpoilers, Rice, Sauce, Settings, SimpleDict, Style, ThemeTools, Themes, Thread, ThreadExcerpt, ThreadStats, ThreadUpdater, ThreadWatcher, Time, TrashQueue, UI, Unread, Video, c, d, doc, editMascot, editTheme, g, userNavigation, __slice = [].slice, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, __hasProp = {}.hasOwnProperty, @@ -184,9 +184,11 @@ 'Scroll to Last Read Post': [true, 'Scroll back to the last read post when reopening a thread.'], 'Thread Excerpt': [true, 'Show an excerpt of the thread in the tab title.'], 'Thread Stats': [true, 'Display reply and image count.'], - 'Page Count in Stats': [false, 'Display the page count in the thread stats as well.'], + 'IP Count in Stats': [true, 'Display the unique IP count in the thread stats.'], + 'Page Count in Stats': [true, 'Display the page count in the thread stats.'], 'Updater and Stats in Header': [true, 'Places the thread updater and thread stats in the header instead of floating them.'], - 'Thread Watcher': [true, 'Bookmark threads.'] + 'Thread Watcher': [true, 'Bookmark threads.'], + 'Mark New IPs': [false, 'Label each post from a new IP with the thread\'s current IP count.'] }, 'Posting': { 'Header Shortcut': [true, 'Add a shortcut to the header to toggle the QR.'], @@ -200,7 +202,7 @@ 'Posting Success Notifications': [true, 'Show notifications on successful post creation or file uploading.'], 'Captcha Warning Notifications': [true, 'When disabled, shows a red border on the CAPTCHA input until a key is pressed instead of a notification.'], 'Dump List Before Comment': [false, 'Position of the QR\'s Dump List.'], - 'Auto-load captcha': [false, 'Automatically load the captcha when you open a thread, and reload it after you post.'] + 'Auto-load captcha': [false, 'Automatically load the captcha in the QR even if your post is empty.'] }, 'Quote Links': { 'Quote Backlinks': [true, 'Add quote backlinks.'], @@ -3188,6 +3190,7 @@ this.isClosed = false; this.postLimit = false; this.fileLimit = false; + this.ipCount = void 0; this.OP = null; this.catalogView = null; g.threads.push(this.fullID, board.threads.push(this, this)); @@ -3217,7 +3220,7 @@ }; Thread.prototype.setStatus = function(type, status) { - var icon, name, root, typeLC; + var name, typeLC; name = "is" + type; if (this[name] === status) { return; @@ -3227,8 +3230,21 @@ return; } typeLC = type.toLowerCase(); + this.setIcon('Sticky', this.isSticky); + this.setIcon('Closed', this.isClosed && !this.isArchived); + return this.setIcon('Archived', this.isArchived); + }; + + Thread.prototype.setIcon = function(type, status) { + var icon, root, typeLC; + typeLC = type.toLowerCase(); + icon = $("." + typeLC + "Icon", this.OP.nodes.info); + if (!!icon === status) { + return; + } if (!status) { - $.rm($("." + typeLC + "Icon", this.OP.nodes.info)); + $.rm(icon.previousSibling); + $.rm(icon); if (this.catalogView) { $.rm($("." + typeLC + "Icon", this.catalogView.nodes.icons)); } @@ -3236,10 +3252,11 @@ } icon = $.el('img', { src: "" + Build.staticPath + typeLC + Build.gifIcon, + alt: type, title: type, - className: "" + typeLC + "Icon" + className: "" + typeLC + "Icon retina" }); - root = type === 'Closed' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : g.VIEW === 'index' ? $('.page-num', this.OP.nodes.info) : $('[title="Reply to this post"]', this.OP.nodes.info); + root = type !== 'Sticky' && this.isSticky ? $('.stickyIcon', this.OP.nodes.info) : $('.page-num', this.OP.nodes.info) || $('[title="Reply to this post"]', this.OP.nodes.info); $.after(root, [$.tn(' '), icon]); if (!this.catalogView) { return; @@ -9721,29 +9738,23 @@ } }, save: function(e) { - var err, _base; - try { - if (this.needed()) { - this.shouldFocus = true; - this.reload(); - } else { - this.nodes.counter.focus(); - if ((_base = this.timeouts).destroy == null) { - _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); - } + var _base; + if (this.needed()) { + this.shouldFocus = true; + this.reload(); + } else { + this.nodes.counter.focus(); + if ((_base = this.timeouts).destroy == null) { + _base.destroy = setTimeout(this.destroy.bind(this), 3 * $.SECOND); } - console.log(e.detail); - $.forceSync('captchas'); - this.captchas.push({ - response: e.detail, - timeout: Date.now() + 2 * $.MINUTE - }); - this.count(); - return $.set('captchas', this.captchas); - } catch (_error) { - err = _error; - return console.log(err); } + $.forceSync('captchas'); + this.captchas.push({ + response: e.detail, + timeout: Date.now() + 2 * $.MINUTE + }); + this.count(); + return $.set('captchas', this.captchas); }, clear: function() { var captcha, i, now, _i, _len, _ref; @@ -11859,6 +11870,84 @@ logo: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACABAMAAAAxEHz4AAAAElBMVEX///8EZgR8ulSk0oT///8EAgQ1A88mAAAAAXRSTlMAQObYZgAAAJtJREFUeF7t17ENgDAMBdGswAqswAqskP1XgUhG37JQQv25a1I4eR1CbopoS3295wIASD1SBZnecwEApO9RRurjPdLcAQDQY9UnBSDEBQAIRA3gfGkA9cfiAwDUHqCnHqDl/AGA0v8AAL4FgO0uAxUcc2cAQEtFjwQoLSMuAMBqsVwtpp4AwNDngPIGAPI5mVsCAELSuZybAQBEF/bX4X+FReD/AAAAAElFTkSuQmCC' }; + MarkNewIPs = { + init: function() { + if (g.VIEW !== 'thread' || !Conf['Mark New IPs']) { + return; + } + return Thread.callbacks.push({ + name: 'Mark New IPs', + cb: this.node + }); + }, + node: function() { + MarkNewIPs.ipCount = this.ipCount; + MarkNewIPs.postIDs = this.posts.keys.map(function(x) { + return +x; + }); + return $.on(d, 'ThreadUpdate', MarkNewIPs.onUpdate); + }, + onUpdate: function(e) { + var added, fullID, i, ipCount, newPosts, obj, postIDs, removed, x, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1; + _ref = e.detail, ipCount = _ref.ipCount, newPosts = _ref.newPosts; + postIDs = ThreadUpdater.postIDs; + if (ipCount == null) { + return; + } + if (newPosts.length) { + obj = {}; + _ref1 = MarkNewIPs.postIDs; + for (_i = 0, _len = _ref1.length; _i < _len; _i++) { + x = _ref1[_i]; + obj[x] = true; + } + added = 0; + for (_j = 0, _len1 = postIDs.length; _j < _len1; _j++) { + x = postIDs[_j]; + if (!(x in obj)) { + added++; + } + } + removed = MarkNewIPs.postIDs.length + added - postIDs.length; + switch (ipCount - MarkNewIPs.ipCount) { + case added: + i = MarkNewIPs.ipCount; + for (_k = 0, _len2 = newPosts.length; _k < _len2; _k++) { + fullID = newPosts[_k]; + MarkNewIPs.markNew(g.posts[fullID], ++i); + } + break; + case -removed: + for (_l = 0, _len3 = newPosts.length; _l < _len3; _l++) { + fullID = newPosts[_l]; + MarkNewIPs.markOld(g.posts[fullID]); + } + } + } + MarkNewIPs.ipCount = ipCount; + return MarkNewIPs.postIDs = postIDs; + }, + markNew: function(post, ipCount) { + var counter, suffix; + suffix = { + 1: 'st', + 2: 'nd', + 3: 'rd' + }[ipCount % 10] || Math.floor('th' / fuck(switches)); + counter = $.el('span', { + className: 'ip-counter', + textContent: "(" + ipCount + ")" + }); + post.nodes.nameBlock.title = "This is the " + ipCount + suffix + " IP in the thread."; + $.add(post.nodes.nameBlock, [$.tn(' '), counter]); + return $.addClass(post.nodes.root, 'new-ip'); + }, + markOld: function(post) { + post.nodes.nameBlock.title = 'Not the first post from this IP.'; + return $.addClass(post.nodes.root, 'old-ip'); + } + }; + ThreadExcerpt = { init: function() { if (g.VIEW !== 'thread' || !Conf['Thread Excerpt']) { @@ -11888,9 +11977,9 @@ } if (Conf['Updater and Stats in Header']) { this.dialog = sc = $.el('span', { - innerHTML: "[0 / 0" + (Conf["Page Count in Stats"] ? " / 0" : "") + "]", + innerHTML: "[0 / \n0\n" + (Conf['IP Count in Stats'] ? ' / ?' : "") + "\n" + (Conf['Page Count in Stats'] ? ' / 0' : "") + "]", id: 'thread-stats', - title: 'Post Count / File Count' + (Conf["Page Count in Stats"] ? " / Page Count" : "") + title: 'Post Count / File Count' + (Conf['IP Count in Stats'] ? " / IPs" : "") + (Conf['Page Count in Stats'] ? " / Page Count" : "") }); $.ready(function() { return Header.addShortcut(sc); @@ -11904,6 +11993,7 @@ })(this)); } this.postCountEl = $('#post-count', sc); + this.ipCountEl = $('#ip-count', sc); this.fileCountEl = $('#file-count', sc); this.pageCountEl = $('#page-count', sc); return Thread.callbacks.push({ @@ -11923,7 +12013,7 @@ }); ThreadStats.thread = this; ThreadStats.fetchPage(); - ThreadStats.update(postCount, fileCount); + ThreadStats.update(postCount, fileCount, this.ipCount); return $.on(d, 'ThreadUpdate', ThreadStats.onUpdate); }, disconnect: function() { @@ -11946,18 +12036,30 @@ return $.off(d, 'ThreadUpdate', ThreadStats.onUpdate); }, onUpdate: function(e) { - var fileCount, postCount, _ref; + var fileCount, ipCount, newPosts, postCount, _ref, _ref1; if (e.detail[404]) { return; } - _ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount; - return ThreadStats.update(postCount, fileCount); + _ref = e.detail, postCount = _ref.postCount, fileCount = _ref.fileCount, ipCount = _ref.ipCount, newPosts = _ref.newPosts; + ThreadStats.update(postCount, fileCount, ipCount); + if (!Conf["Page Count in Stats"]) { + return; + } + if (newPosts.length) { + ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date; + } + if (ThreadStats.lastPost > ThreadStats.lastPageUpdate && ((_ref1 = ThreadStats.pageCountEl) != null ? _ref1.textContent : void 0) !== '1') { + return ThreadStats.fetchPage(); + } }, - update: function(postCount, fileCount) { - var fileCountEl, postCountEl, thread; - thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl; + update: function(postCount, fileCount, ipCount) { + var fileCountEl, ipCountEl, postCountEl, thread; + thread = ThreadStats.thread, postCountEl = ThreadStats.postCountEl, fileCountEl = ThreadStats.fileCountEl, ipCountEl = ThreadStats.ipCountEl; postCountEl.textContent = postCount; fileCountEl.textContent = fileCount; + if ((ipCount != null) && Conf["IP Count in Stats"]) { + ipCountEl.textContent = ipCount; + } (thread.postLimit && !thread.isSticky ? $.addClass : $.rmClass)(postCountEl, 'warning'); return (thread.fileLimit && !thread.isSticky ? $.addClass : $.rmClass)(fileCountEl, 'warning'); }, @@ -12117,8 +12219,12 @@ $.on(window, 'online offline', ThreadUpdater.cb.online); $.on(d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost); $.on(d, 'visibilitychange', ThreadUpdater.cb.visibility); - ThreadUpdater.cb.online(); - return Rice.nodes(ThreadUpdater.dialog); + if (ThreadUpdater.thread.isArchived) { + ThreadUpdater.set('status', 'Archived', 'warning'); + } else { + ThreadUpdater.cb.online(); + } + Rice.nodes(ThreadUpdater.dialog); }, /* @@ -12191,36 +12297,72 @@ } }, load: function(e) { - var klass, req, text, _ref; + var req; req = ThreadUpdater.req; switch (req.status) { case 200: g.DEAD = false; ThreadUpdater.parse(req.response.posts); - ThreadUpdater.setInterval(); + if (ThreadUpdater.thread.isArchived) { + ThreadUpdater.set('status', 'Archived', 'warning'); + ThreadUpdater.kill(); + } else { + ThreadUpdater.setInterval(); + } break; case 404: - g.DEAD = true; - ThreadUpdater.set('timer', null); - ThreadUpdater.set('status', '404', 'warning'); - clearTimeout(ThreadUpdater.timeoutID); - ThreadUpdater.thread.kill(); - $.event('ThreadUpdate', { - 404: true, - threadID: ThreadUpdater.thread.fullID + $.ajax("//a.4cdn.org/" + ThreadUpdater.thread.board + "/catalog.json", { + onloadend: function() { + var confirmed, page, thread, _i, _j, _len, _len1, _ref, _ref1; + if (this.status === 200) { + confirmed = true; + _ref = this.response; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + page = _ref[_i]; + _ref1 = page.threads; + for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { + thread = _ref1[_j]; + if (thread.no === ThreadUpdater.thread.ID) { + confirmed = false; + break; + } + } + } + } else { + confirmed = false; + } + if (confirmed) { + ThreadUpdater.set('status', '404', 'warning'); + return ThreadUpdater.kill(); + } else { + return ThreadUpdater.error(req); + } + } }); break; default: - ThreadUpdater.outdateCount++; - ThreadUpdater.setInterval(); - _ref = req.status === 304 ? [null, null] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1]; - ThreadUpdater.set('status', text, klass); + ThreadUpdater.error(req); } if (ThreadUpdater.postID) { return ThreadUpdater.cb.checkpost(); } } }, + kill: function() { + ThreadUpdater.set('timer', ''); + clearTimeout(ThreadUpdater.timeoutID); + ThreadUpdater.thread.kill(); + return $.event('ThreadUpdate', { + 404: true, + threadID: ThreadUpdater.thread.fullID + }); + }, + error: function(req) { + var klass, text, _ref; + ThreadUpdater.setInterval(); + _ref = req.status === 304 ? ['', ''] : ["" + req.statusText + " (" + req.status + ")", 'warning'], text = _ref[0], klass = _ref[1]; + return ThreadUpdater.set('status', text, klass); + }, setInterval: function() { var cur, i, j, limit; i = ThreadUpdater.interval + 1; @@ -12302,6 +12444,9 @@ return; } ThreadUpdater.thread.setStatus(type, status); + if (type === 'Closed' && ThreadUpdater.thread.isArchived) { + return; + } change = type === 'Sticky' ? status ? 'now a sticky' : 'not a sticky anymore' : status ? 'now closed' : 'not closed anymore'; return new Notice('info', "The thread is " + change + ".", 30); }, @@ -12309,10 +12454,14 @@ var OP, count, files, index, node, num, post, postObject, posts, root, scroll, sendEvent, _i, _j, _len, _len1; OP = postObjects[0]; Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler; + ThreadUpdater.thread.setStatus('Archived', !!+OP.archived); ThreadUpdater.updateThreadStatus('Sticky', !!OP.sticky); ThreadUpdater.updateThreadStatus('Closed', !!OP.closed); ThreadUpdater.thread.postLimit = !!OP.bumplimit; ThreadUpdater.thread.fileLimit = !!OP.imagelimit; + if (OP.unique_ips != null) { + ThreadUpdater.thread.ipCount = OP.unique_ips; + } posts = []; index = []; files = []; @@ -12353,7 +12502,8 @@ return post.fullID; }), postCount: OP.replies + 1, - fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead) + fileCount: OP.images + (!!ThreadUpdater.thread.OP.file && !ThreadUpdater.thread.OP.file.isDead), + ipCount: OP.unique_ips }); }; if (!count) { @@ -17953,6 +18103,7 @@ init('Thread Updater', ThreadUpdater); init('Thread Watcher', ThreadWatcher); init('Thread Watcher (Menu)', ThreadWatcher.menu); + init('Mark New IPs', MarkNewIPs); init('Index Navigation', Nav); init('Keybinds', Keybinds); init('Show Dice Roll', Dice); diff --git a/src/General/Config.coffee b/src/General/Config.coffee index d16ba38ae..f0b92bb3f 100644 --- a/src/General/Config.coffee +++ b/src/General/Config.coffee @@ -255,9 +255,13 @@ Config = true 'Display reply and image count.' ] + 'IP Count in Stats': [ + true + 'Display the unique IP count in the thread stats.' + ] 'Page Count in Stats': [ - false - 'Display the page count in the thread stats as well.' + true + 'Display the page count in the thread stats.' ] 'Updater and Stats in Header': [ true, @@ -267,6 +271,10 @@ Config = true 'Bookmark threads.' ] + 'Mark New IPs': [ + false + 'Label each post from a new IP with the thread\'s current IP count.' + ] 'Posting': 'Header Shortcut': [ @@ -315,7 +323,7 @@ Config = ] 'Auto-load captcha': [ false - 'Automatically load the captcha when you open a thread, and reload it after you post.' + 'Automatically load the captcha in the QR even if your post is empty.' ] 'Quote Links': diff --git a/src/General/Main.coffee b/src/General/Main.coffee index 84dc44b01..dd97750aa 100644 --- a/src/General/Main.coffee +++ b/src/General/Main.coffee @@ -170,6 +170,7 @@ Main = init 'Thread Updater', ThreadUpdater init 'Thread Watcher', ThreadWatcher init 'Thread Watcher (Menu)', ThreadWatcher.menu + init 'Mark New IPs', MarkNewIPs init 'Index Navigation', Nav init 'Keybinds', Keybinds init 'Show Dice Roll', Dice diff --git a/src/General/lib/thread.class b/src/General/lib/thread.class index 676ae8e4a..08adc1f94 100755 --- a/src/General/lib/thread.class +++ b/src/General/lib/thread.class @@ -13,6 +13,7 @@ class Thread @isClosed = false @postLimit = false @fileLimit = false + @ipCount = undefined @OP = null @catalogView = null @@ -35,21 +36,32 @@ class Thread @[name] = status return unless @OP typeLC = type.toLowerCase() + + @setIcon 'Sticky', @isSticky + @setIcon 'Closed', @isClosed and !@isArchived + @setIcon 'Archived', @isArchived + + setIcon: (type, status) -> + typeLC = type.toLowerCase() + icon = $ ".#{typeLC}Icon", @OP.nodes.info + return if !!icon is status + unless status - $.rm $ ".#{typeLC}Icon", @OP.nodes.info + $.rm icon.previousSibling + $.rm icon $.rm $ ".#{typeLC}Icon", @catalogView.nodes.icons if @catalogView return - icon = $.el 'img', src: "#{Build.staticPath}#{typeLC}#{Build.gifIcon}" + alt: type title: type - className: "#{typeLC}Icon" - root = if type is 'Closed' and @isSticky - $ '.stickyIcon', @OP.nodes.info - else if g.VIEW is 'index' - $ '.page-num', @OP.nodes.info - else - $ '[title="Reply to this post"]', @OP.nodes.info + className: "#{typeLC}Icon retina" + + root = + if type isnt 'Sticky' and @isSticky + $ '.stickyIcon', @OP.nodes.info + else + $('.page-num', @OP.nodes.info) or $('[title="Reply to this post"]', @OP.nodes.info) $.after root, [$.tn(' '), icon] return unless @catalogView diff --git a/src/Monitoring/MarkNewIPs.coffee b/src/Monitoring/MarkNewIPs.coffee new file mode 100644 index 000000000..d146a2f5e --- /dev/null +++ b/src/Monitoring/MarkNewIPs.coffee @@ -0,0 +1,45 @@ +MarkNewIPs = + init: -> + return if g.VIEW isnt 'thread' or !Conf['Mark New IPs'] + Thread.callbacks.push + name: 'Mark New IPs' + cb: @node + + node: -> + MarkNewIPs.ipCount = @ipCount + MarkNewIPs.postIDs = @posts.keys.map (x) -> +x + $.on d, 'ThreadUpdate', MarkNewIPs.onUpdate + + onUpdate: (e) -> + {ipCount, newPosts} = e.detail + {postIDs} = ThreadUpdater + return unless ipCount? + if newPosts.length + obj = {} + obj[x] = true for x in MarkNewIPs.postIDs + added = 0 + added++ for x in postIDs when not (x of obj) + removed = MarkNewIPs.postIDs.length + added - postIDs.length + switch ipCount - MarkNewIPs.ipCount + when added + i = MarkNewIPs.ipCount + for fullID in newPosts + MarkNewIPs.markNew g.posts[fullID], ++i + when -removed + for fullID in newPosts + MarkNewIPs.markOld g.posts[fullID] + MarkNewIPs.ipCount = ipCount + MarkNewIPs.postIDs = postIDs + + markNew: (post, ipCount) -> + suffix = {1: 'st', 2: 'nd', 3: 'rd'}[ipCount % 10] or 'th' // fuck switches + counter = $.el 'span', + className: 'ip-counter' + textContent: "(#{ipCount})" + post.nodes.nameBlock.title = "This is the #{ipCount}#{suffix} IP in the thread." + $.add post.nodes.nameBlock, [$.tn(' '), counter] + $.addClass post.nodes.root, 'new-ip' + + markOld: (post) -> + post.nodes.nameBlock.title = 'Not the first post from this IP.' + $.addClass post.nodes.root, 'old-ip' diff --git a/src/Monitoring/ThreadStats.coffee b/src/Monitoring/ThreadStats.coffee index 750f87146..9e8369d6a 100755 --- a/src/Monitoring/ThreadStats.coffee +++ b/src/Monitoring/ThreadStats.coffee @@ -4,9 +4,17 @@ ThreadStats = if Conf['Updater and Stats in Header'] @dialog = sc = $.el 'span', - innerHTML: "[0 / 0#{if Conf["Page Count in Stats"] then " / 0" else ""}]" + innerHTML: """ + [0 / + 0 + #{if Conf['IP Count in Stats'] then ' / ?' else ""} + #{if Conf['Page Count in Stats'] then ' / 0' else ""}] + """ id: 'thread-stats' - title: 'Post Count / File Count' + (if Conf["Page Count in Stats"] then " / Page Count" else "") + title: + 'Post Count / File Count' + + (if Conf['IP Count in Stats'] then " / IPs" else "") + + (if Conf['Page Count in Stats'] then " / Page Count" else "") $.ready -> Header.addShortcut sc else @@ -15,8 +23,9 @@ ThreadStats = $.ready => $.add d.body, sc - @postCountEl = $ '#post-count', sc - @fileCountEl = $ '#file-count', sc + @postCountEl = $ '#post-count', sc + @ipCountEl = $ '#ip-count', sc + @fileCountEl = $ '#file-count', sc @pageCountEl = $ '#page-count', sc Thread.callbacks.push @@ -31,7 +40,7 @@ ThreadStats = fileCount++ if post.file ThreadStats.thread = @ ThreadStats.fetchPage() - ThreadStats.update postCount, fileCount + ThreadStats.update postCount, fileCount, @ipCount $.on d, 'ThreadUpdate', ThreadStats.onUpdate disconnect: -> @@ -56,13 +65,20 @@ ThreadStats = onUpdate: (e) -> return if e.detail[404] - {postCount, fileCount} = e.detail - ThreadStats.update postCount, fileCount + {postCount, fileCount, ipCount, newPosts} = e.detail + ThreadStats.update postCount, fileCount, ipCount + return unless Conf["Page Count in Stats"] + if newPosts.length + ThreadStats.lastPost = g.posts[newPosts[newPosts.length - 1]].info.date + if ThreadStats.lastPost > ThreadStats.lastPageUpdate and ThreadStats.pageCountEl?.textContent isnt '1' + ThreadStats.fetchPage() - update: (postCount, fileCount) -> - {thread, postCountEl, fileCountEl} = ThreadStats + update: (postCount, fileCount, ipCount) -> + {thread, postCountEl, fileCountEl, ipCountEl} = ThreadStats postCountEl.textContent = postCount fileCountEl.textContent = fileCount + if ipCount? and Conf["IP Count in Stats"] + ipCountEl.textContent = ipCount (if thread.postLimit and !thread.isSticky then $.addClass else $.rmClass) postCountEl, 'warning' (if thread.fileLimit and !thread.isSticky then $.addClass else $.rmClass) fileCountEl, 'warning' diff --git a/src/Monitoring/ThreadUpdater.coffee b/src/Monitoring/ThreadUpdater.coffee index e6e565db9..5c2924c0a 100755 --- a/src/Monitoring/ThreadUpdater.coffee +++ b/src/Monitoring/ThreadUpdater.coffee @@ -104,8 +104,14 @@ ThreadUpdater = $.on d, 'QRPostSuccessful', ThreadUpdater.cb.checkpost $.on d, 'visibilitychange', ThreadUpdater.cb.visibility - ThreadUpdater.cb.online() + if ThreadUpdater.thread.isArchived + ThreadUpdater.set 'status', 'Archived', 'warning' + else + ThreadUpdater.cb.online() + Rice.nodes ThreadUpdater.dialog + + return ### http://freesound.org/people/pierrecartoons1979/sounds/90112/ @@ -160,28 +166,50 @@ ThreadUpdater = when 200 g.DEAD = false ThreadUpdater.parse req.response.posts - ThreadUpdater.setInterval() - when 404 - g.DEAD = true - ThreadUpdater.set 'timer', null - ThreadUpdater.set 'status', '404', 'warning' - clearTimeout ThreadUpdater.timeoutID - ThreadUpdater.thread.kill() - $.event 'ThreadUpdate', - 404: true - threadID: ThreadUpdater.thread.fullID - else - ThreadUpdater.outdateCount++ - ThreadUpdater.setInterval() - [text, klass] = if req.status is 304 - [null, null] + if ThreadUpdater.thread.isArchived + ThreadUpdater.set 'status', 'Archived', 'warning' + ThreadUpdater.kill() else - ["#{req.statusText} (#{req.status})", 'warning'] - ThreadUpdater.set 'status', text, klass + ThreadUpdater.setInterval() + when 404 + # XXX workaround for 4chan sending false 404s + $.ajax "//a.4cdn.org/#{ThreadUpdater.thread.board}/catalog.json", onloadend: -> + if @status is 200 + confirmed = true + for page in @response + for thread in page.threads + if thread.no is ThreadUpdater.thread.ID + confirmed = false + break + else + confirmed = false + if confirmed + ThreadUpdater.set 'status', '404', 'warning' + ThreadUpdater.kill() + else + ThreadUpdater.error req + else + ThreadUpdater.error req if ThreadUpdater.postID ThreadUpdater.cb.checkpost() + kill: -> + ThreadUpdater.set 'timer', '' + clearTimeout ThreadUpdater.timeoutID + ThreadUpdater.thread.kill() + $.event 'ThreadUpdate', + 404: true + threadID: ThreadUpdater.thread.fullID + + error: (req) -> + ThreadUpdater.setInterval() + [text, klass] = if req.status is 304 + ['', ''] + else + ["#{req.statusText} (#{req.status})", 'warning'] + ThreadUpdater.set 'status', text, klass + setInterval: -> i = ThreadUpdater.interval + 1 @@ -256,6 +284,7 @@ ThreadUpdater = updateThreadStatus: (type, status) -> return unless hasChanged = ThreadUpdater.thread["is#{type}"] isnt status ThreadUpdater.thread.setStatus type, status + return if type is 'Closed' and ThreadUpdater.thread.isArchived change = if type is 'Sticky' if status 'now a sticky' @@ -272,10 +301,13 @@ ThreadUpdater = OP = postObjects[0] Build.spoilerRange[ThreadUpdater.thread.board] = OP.custom_spoiler + # XXX Some threads such as /g/'s sticky https://a.4cdn.org/g/thread/39894014.json still use a string as the archived property. + ThreadUpdater.thread.setStatus 'Archived', !!+OP.archived ThreadUpdater.updateThreadStatus 'Sticky', !!OP.sticky ThreadUpdater.updateThreadStatus 'Closed', !!OP.closed ThreadUpdater.thread.postLimit = !!OP.bumplimit ThreadUpdater.thread.fileLimit = !!OP.imagelimit + ThreadUpdater.thread.ipCount = OP.unique_ips if OP.unique_ips? posts = [] # post objects index = [] # existing posts @@ -317,6 +349,7 @@ ThreadUpdater = newPosts: posts.map (post) -> post.fullID postCount: OP.replies + 1 fileCount: OP.images + (!!ThreadUpdater.thread.OP.file and !ThreadUpdater.thread.OP.file.isDead) + ipCount: OP.unique_ips unless count ThreadUpdater.set 'status', null, null diff --git a/src/Posting/QR.captcha.coffee b/src/Posting/QR.captcha.coffee index 0d83b9c1f..e098ecc74 100644 --- a/src/Posting/QR.captcha.coffee +++ b/src/Posting/QR.captcha.coffee @@ -98,22 +98,19 @@ QR.captcha = else null - save: (e) -> try + save: (e) -> if @needed() @shouldFocus = true @reload() else @nodes.counter.focus() @timeouts.destroy ?= setTimeout @destroy.bind(@), 3 * $.SECOND - console.log e.detail $.forceSync 'captchas' @captchas.push response: e.detail timeout: Date.now() + 2 * $.MINUTE @count() $.set 'captchas', @captchas - catch err - console.log err clear: -> return unless @captchas.length