class Post @callbacks = new Callbacks 'Post' toString: -> @ID constructor: (root, @thread, @board, that={}) -> <% if (tests_enabled) { %> 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 $$ 'a[rel=canonical]', root2 el.removeAttribute 'rel' for el in $$ 'img[src]', root2 el.src = el.src.replace /^(\w+:\/\/)[0-2](\.t\.4cdn\.org\/)/, '$10$2' el.src = el.src.replace /(spoiler-\w+)\d(\.png)$/, '$11$2' Fourchan.code.call nodes: comment: $ '.postMessage', root2 for el in $$ 'pre[style=""]', root2 el.removeAttribute 'style' # XXX /pol/ fakedoubles if board.ID is 'pol' $('a[title="Reply to this post"]', root2).textContent = +root2.id[2..] textNodes = $.X './/text()', root2 i = 0 while node = textNodes.snapshotItem i++ node.data = node.data.replace /\s+/g, ' ' $.rm node if node.data is '' @normalizedOriginal = root2 <% } %> @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 nameBlock: $ '.nameBlock', info quote: $ 'a[title*="Reply to this post"]', info comment: $ '.postMessage', post links: [] quotelinks: [] backlinks: info.getElementsByClassName 'backlink' unless @isReply = $.hasClass post, 'reply' @thread.OP = @ @thread.isArchived = !!$ '.archivedIcon', info @thread.isSticky = !!$ '.stickyIcon', info @thread.isClosed = @thread.isArchived or !!$ '.closedIcon', info @thread.kill() if @thread.isArchived @info = {} @info.nameBlock = if Conf['Anonymize'] 'Anonymous' else @nodes.nameBlock.textContent.trim() 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 @isDead = false @isHidden = false @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. #
-> \n # Remove: # 'Comment too long'... # EXIF data. (/p/) # Rolls. (/tg/) # Preceding and following new lines. # Trailing spaces. bq = @nodes.comment.cloneNode true for node in $$ '.abbr, .exif, b', bq $.rm node @info.comment = @nodesToText bq # Get the comment's text with spoilers hidden. spoilers = $$ 's', bq @info.commentSpoilered = if spoilers.length for node in spoilers $.replace node, $.tn '[spoiler]' @nodesToText bq else @info.comment nodesToText: (bq) -> text = "" nodes = $.X './/br|.//text()', bq i = 0 while node = nodes.snapshotItem i++ text += node.data or '\n' text.trim().replace /\s+$/gm, '' parseQuotes: -> @quotes = [] # XXX https://github.com/4chan/4chan-JS/issues/77 # 4chan currently creates quote links inside [code] tags; ignore them for quotelink in $$ ':not(pre) > .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 = "#{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) -> if file return if @file.isDead @file.isDead = true $.addClass @nodes.root, 'deleted-file' else return if @isDead @isDead = true $.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 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 $.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