226 lines
6.8 KiB
Plaintext
Executable File
226 lines
6.8 KiB
Plaintext
Executable File
class Post
|
|
@callbacks = new Callbacks 'Post'
|
|
toString: -> @ID
|
|
|
|
constructor: (root, @thread, @board, that={}) ->
|
|
root2 = root.cloneNode true
|
|
for el in $$ '.mobile', root2
|
|
$.rm el
|
|
for el in $$ 'a[href]', root2
|
|
href = el.href
|
|
href = href.replace /(^\w+:\/\/boards.4chan.org\/[^\/]+\/thread\/\d+)\/.*/, '$1'
|
|
el.setAttribute 'href', href
|
|
for el in $$ 'img[src]', root2
|
|
el.src = el.src.replace /^(\w+:\/\/)[0-2](\.t\.4cdn\.org\/)/, '$10$2'
|
|
@normalizedHTML = root2.outerHTML
|
|
|
|
@ID = +root.id[2..]
|
|
@fullID = "#{@board}.#{@ID}"
|
|
|
|
@cleanup root if that.isOriginalMarkup
|
|
post = $ '.post', root
|
|
info = $ '.postInfo', post
|
|
@nodes =
|
|
root: root
|
|
post: post
|
|
info: info
|
|
comment: $ '.postMessage', post
|
|
links: []
|
|
quotelinks: []
|
|
backlinks: info.getElementsByClassName 'backlink'
|
|
|
|
unless @isReply = $.hasClass post, 'reply'
|
|
@thread.OP = @
|
|
@thread.isSticky = !!$ '.stickyIcon', info
|
|
@thread.isClosed = !!$ '.closedIcon', info
|
|
|
|
@info = {}
|
|
if subject = $ '.subject', info
|
|
@nodes.subject = subject
|
|
@info.subject = subject.textContent
|
|
if name = $ '.name', info
|
|
@nodes.name = name
|
|
@info.name = name.textContent
|
|
if email = $ '.useremail', info
|
|
@nodes.email = email
|
|
@info.email = decodeURIComponent email.href[7..]
|
|
if tripcode = $ '.postertrip', info
|
|
@nodes.tripcode = tripcode
|
|
@info.tripcode = tripcode.textContent
|
|
if uniqueID = $ '.posteruid', info
|
|
@nodes.uniqueID = uniqueID
|
|
@info.uniqueID = uniqueID.firstElementChild.textContent
|
|
if capcode = $ '.capcode.hand', info
|
|
@nodes.capcode = capcode
|
|
@info.capcode = capcode.textContent.replace '## ', ''
|
|
if flag = $ '.flag, .countryFlag', info
|
|
@nodes.flag = flag
|
|
@info.flag = flag.title
|
|
if date = $ '.dateTime', info
|
|
@nodes.date = date
|
|
@info.date = new Date date.dataset.utc * 1000
|
|
|
|
@parseComment()
|
|
@parseQuotes()
|
|
@parseFile that
|
|
|
|
@clones = []
|
|
g.posts.push @fullID, thread.posts.push @, board.posts.push @, @
|
|
@kill() if that.isArchived
|
|
|
|
parseComment: ->
|
|
# Merge text nodes and remove empty ones.
|
|
@nodes.comment.normalize()
|
|
# Get the comment's text.
|
|
# <br> -> \n
|
|
# Remove:
|
|
# 'Comment too long'...
|
|
# EXIF data. (/p/)
|
|
# Rolls. (/tg/)
|
|
# Preceding and following new lines.
|
|
# Trailing spaces.
|
|
bq = @nodes.comment.cloneNode true
|
|
nodes = $$ '.abbr, .exif, b', bq
|
|
i = 0
|
|
while node = nodes[i++]
|
|
$.rm node
|
|
text = ""
|
|
nodes = $.X './/br|.//text()', bq
|
|
i = 0
|
|
while node = nodes.snapshotItem i++
|
|
text += node.data or '\n'
|
|
@info.comment = text.trim().replace /\s+$/gm, ''
|
|
|
|
parseQuotes: ->
|
|
@quotes = []
|
|
for quotelink in $$ '.quotelink', @nodes.comment
|
|
@parseQuote quotelink
|
|
return
|
|
|
|
parseQuote: (quotelink) ->
|
|
# Only add quotes that link to posts on an imageboard.
|
|
# Don't add:
|
|
# - board links. (>>>/b/)
|
|
# - catalog links. (>>>/b/catalog or >>>/b/search)
|
|
# - rules links. (>>>/a/rules)
|
|
# - text-board quotelinks. (>>>/img/1234)
|
|
return unless match = quotelink.href.match ///
|
|
boards\.4chan\.org/
|
|
([^/]+) # boardID
|
|
/(?:res|thread)/\d+#p
|
|
(\d+) # postID
|
|
$
|
|
///
|
|
|
|
@nodes.quotelinks.push quotelink
|
|
|
|
return if @isClone
|
|
|
|
# ES6 Set when?
|
|
fullID = "#{match[1]}.#{match[2]}"
|
|
@quotes.push fullID unless fullID in @quotes
|
|
|
|
parseFile: (that) ->
|
|
return unless (fileEl = $ '.file', @nodes.post) and thumb = $ 'img[data-md5]', fileEl
|
|
# Supports JPG/PNG/GIF/PDF.
|
|
# Flash files are not supported.
|
|
anchor = thumb.parentNode
|
|
fileText = fileEl.firstElementChild
|
|
@file =
|
|
text: fileText
|
|
thumb: thumb
|
|
URL: anchor.href
|
|
size: thumb.alt.match(/[\d.]+\s\w+/)[0]
|
|
MD5: thumb.dataset.md5
|
|
isSpoiler: $.hasClass anchor, 'imgspoiler'
|
|
size = +@file.size.match(/[\d.]+/)[0]
|
|
unit = ['B', 'KB', 'MB', 'GB'].indexOf @file.size.match(/\w+$/)[0]
|
|
size *= 1024 while unit-- > 0
|
|
@file.sizeInBytes = size
|
|
@file.thumbURL = if that.isArchived
|
|
thumb.src
|
|
else
|
|
"#{location.protocol}//t.4cdn.org/#{@board}/#{@file.URL.match(/(\d+)\./)[1]}s.jpg"
|
|
@file.isImage = /(jpg|png|gif)$/i.test @file.URL
|
|
@file.isVideo = /webm$/i.test @file.URL
|
|
nameNode = $ 'a', fileText
|
|
if @file.isImage or @file.isVideo
|
|
@file.dimensions = nameNode.nextSibling.textContent.match(/\d+x\d+/)[0]
|
|
@file.name = fileText.title or nameNode.title or nameNode.textContent
|
|
|
|
cleanup: (root) ->
|
|
for node in $$ '.mobile', root
|
|
$.rm node
|
|
for node in $$ '.desktop', root
|
|
$.rmClass node, 'desktop'
|
|
return
|
|
|
|
kill: (file, now) ->
|
|
now or= new Date()
|
|
if file
|
|
return if @file.isDead
|
|
@file.isDead = true
|
|
@file.timeOfDeath = now
|
|
$.addClass @nodes.root, 'deleted-file'
|
|
else
|
|
return if @isDead
|
|
@isDead = true
|
|
@timeOfDeath = now
|
|
$.addClass @nodes.root, 'deleted-post'
|
|
|
|
unless strong = $ 'strong.warning', @nodes.info
|
|
strong = $.el 'strong',
|
|
className: 'warning'
|
|
textContent: if @isReply then '[Deleted]' else '[Dead]'
|
|
$.after $('input', @nodes.info), strong
|
|
strong.textContent = if file then '[File deleted]' else '[Deleted]'
|
|
|
|
return if @isClone
|
|
for clone in @clones
|
|
clone.kill file, now
|
|
|
|
return if file
|
|
# Get quotelinks/backlinks to this post
|
|
# and paint them (Dead).
|
|
for quotelink in Get.allQuotelinksLinkingTo @ when not $.hasClass quotelink, 'deadlink'
|
|
quotelink.textContent = quotelink.textContent + '\u00A0(Dead)'
|
|
$.addClass quotelink, 'deadlink'
|
|
return
|
|
# XXX tmp fix for 4chan's racing condition
|
|
# giving us false-positive dead posts.
|
|
resurrect: ->
|
|
delete @isDead
|
|
delete @timeOfDeath
|
|
$.rmClass @nodes.root, 'deleted-post'
|
|
strong = $ 'strong.warning', @nodes.info
|
|
# no false-positive files
|
|
if @file and @file.isDead
|
|
strong.textContent = '[File deleted]'
|
|
else
|
|
$.rm strong
|
|
|
|
return if @isClone
|
|
for clone in @clones
|
|
clone.resurrect()
|
|
|
|
for quotelink in Get.allQuotelinksLinkingTo @
|
|
if $.hasClass quotelink, 'deadlink'
|
|
quotelink.textContent = quotelink.textContent.replace '\u00A0(Dead)', ''
|
|
$.rmClass quotelink, 'deadlink'
|
|
return
|
|
|
|
collect: ->
|
|
@kill()
|
|
g.posts.rm @fullID
|
|
@thread.posts.rm @
|
|
@board.posts.rm @
|
|
|
|
addClone: (context, contractThumb) ->
|
|
new Clone @, context, contractThumb
|
|
|
|
rmClone: (index) ->
|
|
@clones.splice index, 1
|
|
for clone in @clones[index..]
|
|
clone.nodes.root.dataset.clone = index++
|
|
return
|