class Fetcher constructor: (@boardID, @threadID, @postID, @root, @quoter) -> if post = g.posts["#{@boardID}.#{@postID}"] @insert post return # 4chan X catalog data if (post = Index.replyData?["#{@boardID}.#{@postID}"]) and (thread = g.threads["#{@boardID}.#{@threadID}"]) board = g.boards[@boardID] post = new Post Build.postFromObject(post, @boardID), thread, board post.isFetchedQuote = true Main.callbackNodes 'Post', [post] @insert post return @root.textContent = "Loading post No.#{@postID}..." if @threadID $.cache "#{location.protocol}//a.4cdn.org/#{@boardID}/thread/#{@threadID}.json", (e, isCached) => @fetchedPost e.target, isCached else @archivedPost() insert: (post) -> # Stop here if the container has been removed while loading. return unless @root.parentNode @quoter or= post clone = post.addClone @quoter.context, ($.hasClass @root, 'dialog') Main.callbackNodes 'Post', [clone] # Get rid of the side arrows/stubs. {nodes} = clone $.rmAll nodes.root $.add nodes.root, nodes.post # Indicate links to the containing post. for quote in clone.nodes.quotelinks.concat [clone.nodes.backlinks...] {boardID, postID} = Get.postDataFromLink quote if postID is @quoter.ID and boardID is @quoter.board.ID $.addClass quote, 'forwardlink' # Set up flag CSS for cross-board links to boards with flags if clone.nodes.flag and not (Fetcher.flagCSS or= $ 'link[href^="//s.4cdn.org/css/flags."]') cssVersion = $('link[href^="//s.4cdn.org/css/"]')?.href.match(/\d+(?=\.css$)|$/)[0] or Date.now() Fetcher.flagCSS = $.el 'link', rel: 'stylesheet' href: "//s.4cdn.org/css/flags.#{cssVersion}.css" $.add d.head, Fetcher.flagCSS $.rmAll @root $.add @root, nodes.root $.event 'PostsInserted' fetchedPost: (req, isCached) -> # In case of multiple callbacks for the same request, # don't parse the same original post more than once. if post = g.posts["#{@boardID}.#{@postID}"] @insert post return {status} = req unless status is 200 # The thread can die by the time we check a quote. return if @archivedPost() $.addClass @root, 'warning' @root.textContent = if status is 404 "Thread No.#{@threadID} 404'd." else "Error #{req.statusText} (#{req.status})." return {posts} = req.response Build.spoilerRange[@boardID] = posts[0].custom_spoiler for post in posts break if post.no is @postID # we found it! if post.no isnt @postID # Cached requests can be stale and must be rechecked. if isCached api = "#{location.protocol}//a.4cdn.org/#{@boardID}/thread/#{@threadID}.json" $.cleanCache (url) -> url is api $.cache api, (e) => @fetchedPost e.target, false return # The post can be deleted by the time we check a quote. return if @archivedPost() $.addClass @root, 'warning' @root.textContent = "Post No.#{@postID} was not found." return board = g.boards[@boardID] or new Board @boardID thread = g.threads["#{@boardID}.#{@threadID}"] or new Thread @threadID, board post = new Post Build.postFromObject(post, @boardID), thread, board post.isFetchedQuote = true Main.callbackNodes 'Post', [post] @insert post archivedPost: -> return false unless Conf['Resurrect Quotes'] return false if not (url = Redirect.to 'post', {@boardID, @postID}) archive = Redirect.data.post[@boardID] if /^https:\/\//.test(url) or location.protocol is 'http:' $.cache url, (e) => @parseArchivedPost e.target.response, url, archive , responseType: 'json' withCredentials: archive.withCredentials return true else if Conf['Exempt Archives from Encryption'] CrossOrigin.json url, (response) => {media} = response if media then for key of media when /_link$/.test key # Image/thumbnail URLs loaded over HTTP can be modified in transit. # Require them to be from an HTTP host so that no referrer is sent to them from an HTTPS page. delete media[key] unless media[key]?.match /^http:\/\// @parseArchivedPost response, url, archive return true return false parseArchivedPost: (data, url, archive) -> # In case of multiple callbacks for the same request, # don't parse the same original post more than once. if post = g.posts["#{@boardID}.#{@postID}"] @insert post return unless data? $.addClass @root, 'warning' @root.textContent = "Error fetching Post No.#{@postID} from #{archive.name}." return if data.error $.addClass @root, 'warning' @root.textContent = data.error return # https://github.com/eksopl/asagi/blob/v0.4.0b74/src/main/java/net/easymodo/asagi/YotsubaAbstract.java#L82-L129 # https://github.com/FoolCode/FoolFuuka/blob/800bd090835489e7e24371186db6e336f04b85c0/src/Model/Comment.php#L368-L428 # https://github.com/bstats/b-stats/blob/6abe7bffaf6e5f523498d760e54b110df5331fbb/inc/classes/Yotsuba.php#L157-L168 comment = (data.comment or '').split /(\n|\[\/?(?:b|spoiler|code|moot|banned|fortune(?: color="#\w+")?|i|red|green|blue)\])/ comment = for text, i in comment if i % 2 is 1 tag = @archiveTags[text.replace(/\ .*\]/, ']')] if typeof tag is 'function' then tag(text) else tag else greentext = text[0] is '>' text = text.replace /(\[\/?[a-z]+):lit(\])/g, '$1$2' text = for text2, j in text.split /(>>(?:>\/[a-z\d]+\/)?\d+)/g <%= html('?{j % 2}{${text2}}{${text2}}') %> text = <%= html('?{greentext}{@{text}}{@{text}}') %> text comment = <%= html('@{comment}') %> @threadID = +data.thread_num o = ID: @postID threadID: @threadID boardID: @boardID isReply: @postID isnt @threadID o.info = subject: data.title email: data.email name: data.name or '' tripcode: data.trip capcode: switch data.capcode # https://github.com/pleebe/FoolFuuka/blob/bf4224eed04637a4d0bd4411c2bf5f9945dfec0b/assets/themes/foolz/foolfuuka-theme-fuuka/src/Partial/Board.php#L77 when 'M' then 'Mod' when 'A' then 'Admin' when 'D' then 'Developer' when 'V' then 'Verified' when 'F' then 'Founder' when 'G' then 'Manager' uniqueID: data.poster_hash flagCode: data.poster_country flagCodeTroll: data.troll_country_code flag: data.poster_country_name or data.troll_country_name dateUTC: data.timestamp dateText: data.fourchan_date commentHTML: comment delete o.info.uniqueID if o.info.capcode if data.media and !!+data.media.banned o.fileDeleted = true else if data.media?.media_filename {thumb_link} = data.media # Fix URLs missing origin thumb_link = url.split('/', 3).join('/') + thumb_link if thumb_link?[0] is '/' thumb_link = '' unless Redirect.securityCheck thumb_link media_link = Redirect.to('file', {boardID: @boardID, filename: data.media.media_orig}) media_link = '' unless Redirect.securityCheck media_link o.file = name: data.media.media_filename url: media_link or if @boardID is 'f' "#{location.protocol}//#{ImageHost.flashHost()}/#{@boardID}/#{encodeURIComponent E data.media.media_filename}" else "#{location.protocol}//#{ImageHost.host()}/#{@boardID}/#{data.media.media_orig}" height: data.media.media_h width: data.media.media_w MD5: data.media.media_hash size: $.bytesToString data.media.media_size thumbURL: thumb_link or "#{location.protocol}//#{ImageHost.thumbHost()}/#{@boardID}/#{data.media.preview_orig}" theight: data.media.preview_h twidth: data.media.preview_w isSpoiler: data.media.spoiler is '1' o.file.dimensions = "#{o.file.width}x#{o.file.height}" unless /\.pdf$/.test o.file.url o.file.tag = JSON.parse(data.media.exif).Tag if @boardID is 'f' and data.media.exif board = g.boards[@boardID] or new Board @boardID thread = g.threads["#{@boardID}.#{@threadID}"] or new Thread @threadID, board post = new Post Build.post(o), thread, board post.kill() post.file.thumbURL = o.file.thumbURL if post.file post.isFetchedQuote = true Main.callbackNodes 'Post', [post] @insert post archiveTags: '\n': <%= html('
') %> '[b]': <%= html('') %> '[/b]': <%= html('') %> '[spoiler]': <%= html('') %> '[/spoiler]': <%= html('') %> '[code]': <%= html('
') %>
    '[/code]':    <%= html('
') %> '[moot]': <%= html('
') %> '[/moot]': <%= html('
') %> '[banned]': <%= html('') %> '[/banned]': <%= html('') %> '[fortune]': (text) -> <%= html('') %> '[/fortune]': <%= html('') %> '[i]': <%= html('') %> '[/i]': <%= html('') %> '[red]': <%= html('') %> '[/red]': <%= html('') %> '[green]': <%= html('') %> '[/green]': <%= html('') %> '[blue]': <%= html('') %> '[/blue]': <%= html('') %>