Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/Filtering/Filter.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,12 @@ Filter =
else
types = ['subject', 'name', 'filename', 'comment']

watch = /(?:^|;)\s*watch/.test filter

# Hide the post (default case).
hide = !(hl or noti)
hide = !(hl or noti or watch)

filter = {isstring, regexp, boards, excludes, mask, hide, stub, hl, top, noti}
filter = {isstring, regexp, boards, excludes, mask, hide, stub, hl, top, noti, watch}
if key is 'general'
for type in types
(@filters[type] or= []).push filter
Expand Down Expand Up @@ -119,11 +121,12 @@ Filter =

test: (post, hideable=true) ->
return post.filterResults if post.filterResults
hide = false
stub = true
hl = undefined
top = false
noti = false
hide = false
stub = true
hl = undefined
top = false
noti = false
watch = false
if QuoteYou.isYou(post)
hideable = false
mask = (if post.isReply then 2 else 1)
Expand All @@ -149,14 +152,16 @@ Filter =
top or= filter.top
if filter.noti
noti = true
if filter.watch
watch = true
if hide
{hide, stub}
else
{hl, top, noti}
{hl, top, noti, watch}

node: ->
return if @isClone
{hide, stub, hl, top, noti} = Filter.test @, (!@isFetchedQuote and (@isReply or g.VIEW is 'index'))
{hide, stub, hl, top, noti, watch} = Filter.test @, (!@isFetchedQuote and (@isReply or g.VIEW is 'index'))
if hide
if @isReply
PostHiding.hide @, stub
Expand All @@ -168,6 +173,9 @@ Filter =
$.addClass @nodes.root, hl...
if noti and Unread.posts and (@ID > Unread.lastReadPost) and not QuoteYou.isYou(@)
Unread.openNotification @, ' triggered a notification filter'
if watch and !ThreadWatcher.isWatched(@thread)
ThreadWatcher.add(@thread)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This watches the thread if it sees any reply to the thread that matches the filter. Is that intentional?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I figure they could have op: only to match starting posts.



catalog: ->
return unless (url = g.SITE.urls.catalogJSON?(g.BOARD))
Expand Down
6 changes: 6 additions & 0 deletions src/General/Settings/Filter-guide.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
Show a desktop notification instead of hiding.<br>
For example: <code>notify;</code>.
</li>
<li>
Add thread to thread watcher instead of hiding.<br/>
For example: <code>watch;</code><br/>
If you specify <code>boards</code> with this flag, the boards provided will be periodically scanned in the background for new threads.<br/>
If you do not specify boards, then it will scan every page you visit for matching threads/comments.
</li>
<li>
Filters in the "General" section apply to multiple fields, by default <code>subject,name,filename,comment</code>.<br>
The fields can be specified with the <code>type</code> option, separated by commas.<br>
Expand Down
89 changes: 89 additions & 0 deletions src/Monitoring/AutoWatcher.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
AutoWatcher =
init: ->
return unless Conf['Filter']

AutoWatcher.periodicScan()

periodicScan: ->
clearTimeout AutoWatcher.timeout

interval = 5 * $.MINUTE

now = Date.now()

unless Conf['autoWatchLastScan'] and (now - interval < Conf['autoWatchLastScan'])
AutoWatcher.scan()
$.set 'autoWatchLastScan', now
AutoWatcher.timeout = setTimeout AutoWatcher.periodicScan, interval

scan: ->
sitesAndBoards = (for own _, filters of Filter.filters
Object.keys(filter.boards) for filter in filters when filter.watch and filter.boards
).flat(2).reduce (acc, i) ->
[_, k, v] = i.match(/(.*)\/(.*)/)
acc[k] ?= []
acc[k].push(v)
acc
, {}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs $.dict() [which calls Object.create(null)] instead of {} here.
Should also deduplicate boards and not just sites, and ignore "siteID/*" as a board.

for own rawSite, boards of sitesAndBoards
break unless site = g.sites[rawSite]
for boardID in boards
AutoWatcher.fetchCatalog(boardID, site, AutoWatcher.parseCatalog)

fetchCatalog: (boardID, site, cb) ->
return unless url = site.urls['catalogJSON']?({boardID})

ajax = if site.ID is g.SITE.ID then $.ajax else CrossOrigin.ajax

onLoadEnd = ->
cb.apply @, [site, boardID]

$.whenModified(
url,
'AutoWatcher'
onLoadEnd,
{timeout: $.MINUTE, ajax}
)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if it's possible to combine these periodic fetches with ThreadWatcher's periodic fetches so that less fetches have to take place, but that might be too much complexity.


parseCatalog: (site, boardID) ->
addedThreads = false
rawCatalog = @.response.reduce ((acc, i, idx) ->
threads = for thread in i.threads
thread.extraData = {
page: idx + 1,
modified: thread.last_modified,
replies: thread.replies,
unread: thread.replies
}
if thread.last_replies
thread.extraData.last = thread.last_replies[thread.last_replies.length - 1].no
thread
acc.concat(threads)
), []
for thread in rawCatalog
continue if ThreadWatcher.isWatchedRaw(boardID, thread.no)
parsedThread = site.Build.parseJSON(thread, {siteID: site.ID, boardID})

# Hacks for ThreadWatcher
parsedThread.isDead = false
parsedThread.board = {ID: boardID}
Object.assign(parsedThread, thread.extraData)

# I wish destructuring was actually pattern matching
{watch} = Filter.test(parsedThread)
continue unless watch

excerptName = (
parsedThread?.info?.subject or
parsedThread?.info?.comment.replace(/\n+/g, ' // ') or
parsedThread?.file?.name or
"No.#{parsedThread.ID}"
)
excerpt = "/#{boardID}/ - #{excerptName}"
excerpt = "#{excerpt[...70]}..." if excerpt.length > 73
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid duplicating the code in Get.threadExcerpt. Instead we should make Get.threadExcerpt handle posts from JSON. The comment could be dealt with similar to how Filter does it, in this case using parseCommentDisplay in preference to parseComment if available to filter junk out of the comment:
https://github.com/ccd0/4chan-x/blob/1.14.21.3/src/Filtering/Filter.coffee#L219

data = Object.assign(thread.extraData, {excerpt})
ThreadWatcher.addRaw(boardID, parsedThread.ID, data, null, true)
addedThreads = true
# Check to see if we added any threads. If so, trigger a refresh AFTER we're done adding them all, to avoid spamming the API
# We already give the ThreadWatcher most of what it needs, this is just to get things like lastPage coloring
ThreadWatcher.buttonFetchAll() if addedThreads
3 changes: 2 additions & 1 deletion src/Monitoring/ThreadWatcher.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -551,14 +551,15 @@ ThreadWatcher =
data.excerpt = Get.threadExcerpt thread if thread.OP
ThreadWatcher.addRaw boardID, threadID, data, cb

addRaw: (boardID, threadID, data, cb) ->
addRaw: (boardID, threadID, data, cb, skipRefresh = false) ->
oldData = ThreadWatcher.db.get {boardID, threadID, defaultValue: $.dict()}
delete oldData.last
delete oldData.modified
$.extend oldData, data
ThreadWatcher.db.set {boardID, threadID, val: oldData}, cb
ThreadWatcher.refresh()
thread = {siteID: g.SITE.ID, boardID, threadID, data, force: true}
return if skipRefresh
if Conf['Show Page'] and !data.isDead
ThreadWatcher.fetchBoard [thread]
else if ThreadWatcher.unreadEnabled and Conf['Show Unread Count']
Expand Down
4 changes: 3 additions & 1 deletion src/main/Main.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Main =
Conf['archives'] = Redirect.archives
Conf['selectedArchives'] = $.dict()
Conf['cooldowns'] = $.dict()
Conf['autoWatchLastScan'] = null
Conf['Index Sort'] = $.dict()
Conf["Last Long Reply Thresholds #{i}"] = $.dict() for i in [0...2]
Conf['siteProperties'] = $.dict()
Expand Down Expand Up @@ -521,7 +522,7 @@ Main =
unless nodes[i]
(cb() if cb)
return
setTimeout softTask, 0
setTimeout softTask, 0

softTask()

Expand Down Expand Up @@ -698,6 +699,7 @@ Main =
['Thread Updater', ThreadUpdater]
['Thread Watcher', ThreadWatcher]
['Thread Watcher (Menu)', ThreadWatcher.menu]
['Auto Watcher', AutoWatcher]
['Mark New IPs', MarkNewIPs]
['Index Navigation', Nav]
['Keybinds', Keybinds]
Expand Down