4chan-x/src/site/SW.yotsuba.coffee

263 lines
9.3 KiB
CoffeeScript

SW.yotsuba =
isOPContainerThread: false
hasIPCount: true
archivedBoardsKnown: true
urls:
thread: ({boardID, threadID}) -> "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/thread/#{threadID}"
post: ({postID}) -> "#p#{postID}"
index: ({boardID}) -> "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/"
catalog: ({boardID}) -> if boardID is 'f' then undefined else "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/catalog"
archive: ({boardID}) -> if BoardConfig.isArchived(boardID) then "#{location.protocol}//#{BoardConfig.domain(boardID)}/#{boardID}/archive" else undefined
threadJSON: ({boardID, threadID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/thread/#{threadID}.json"
threadsListJSON: ({boardID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/threads.json"
archiveListJSON: ({boardID}) -> if BoardConfig.isArchived(boardID) then "#{location.protocol}//a.4cdn.org/#{boardID}/archive.json" else ''
catalogJSON: ({boardID}) -> "#{location.protocol}//a.4cdn.org/#{boardID}/catalog.json"
file: ({boardID}, filename) ->
hostname = if boardID is 'f' then ImageHost.flashHost() else ImageHost.host()
"#{location.protocol}//#{hostname}/#{boardID}/#{filename}"
thumb: ({boardID}, filename) ->
"#{location.protocol}//#{ImageHost.thumbHost()}/#{boardID}/#{filename}"
isPrunedByAge: ({boardID}) -> boardID is 'f'
areMD5sDeferred: ({boardID}) -> boardID is 'f'
isOnePage: ({boardID}) -> boardID is 'f'
noAudio: ({boardID}) -> BoardConfig.noAudio(boardID)
selectors:
board: '.board'
thread: '.thread'
threadDivider: '.board > hr'
summary: '.summary'
postContainer: '.postContainer'
replyOriginal: '.replyContainer:not([data-clone])'
sideArrows: 'div.sideArrows'
post: '.post'
infoRoot: '.postInfo'
info:
subject: '.subject'
name: '.name'
email: '.useremail'
tripcode: '.postertrip'
uniqueIDRoot: '.posteruid'
uniqueID: '.posteruid > .hand'
capcode: '.capcode.hand'
pass: '.n-pu'
flag: '.flag, .bfl'
date: '.dateTime'
nameBlock: '.nameBlock'
quote: '.postNum > a:nth-of-type(2)'
reply: '.replylink'
icons:
isSticky: '.stickyIcon'
isClosed: '.closedIcon'
isArchived: '.archivedIcon'
file:
text: '.file > :first-child'
link: '.fileText > a'
thumb: 'a.fileThumb > [data-md5]'
thumbLink: 'a.fileThumb'
highlightable:
op: '.opContainer'
reply: ' > .reply'
catalog: ''
comment: '.postMessage'
spoiler: 's'
quotelink: ':not(pre) > .quotelink' # XXX https://github.com/4chan/4chan-JS/issues/77: 4chan currently creates quote links inside [code] tags; ignore them
catalog:
board: '#threads'
thread: '.thread'
thumb: '.thumb'
boardList: '#boardNavDesktop > .boardList'
boardListBottom: '#boardNavDesktopFoot > .boardList'
styleSheet: 'link[title=switch]'
psa: '#globalMessage'
psaTop: '#globalToggle'
searchBox: '#search-box'
nav:
prev: '.prev > form > [type=submit]'
next: '.next > form > [type=submit]'
classes:
highlight: 'highlight'
xpath:
thread: 'div[contains(concat(" ",@class," ")," thread ")]'
postContainer: 'div[contains(@class,"postContainer")]'
replyContainer: 'div[contains(@class,"replyContainer")]'
regexp:
quotelink:
///
^https?://boards\.4chan(?:nel)?\.org/+
([^/]+) # boardID
/+thread/+
(\d+) # threadID
(?:[/?][^#]*)?
(?:#p
(\d+) # postID
)?
$
///
quotelinkHTML:
/<a [^>]*\bhref="(?:(?:\/\/boards\.4chan(?:nel)?\.org)?\/([^\/]+)\/thread\/)?(\d+)?(?:#p(\d+))?"/g
pass:
/^https?:\/\/www\.4chan(?:nel)?\.org\/+pass(?:$|[?#])/
captcha:
/^https?:\/\/sys\.4chan(?:nel)?\.org\/+captcha(?:$|[?#])/
bgColoredEl: ->
$.el 'div', className: 'reply'
isThisPageLegit: ->
# not 404 error page or similar.
location.hostname in ['boards.4chan.org', 'boards.4channel.org'] and
d.doctype and
!$('link[href*="favicon-status.ico"]', d.head) and
d.title not in ['4chan - Temporarily Offline', '4chan - Error', '504 Gateway Time-out', 'MathJax Equation Source']
is404: ->
# XXX Sometimes threads don't 404 but are left over as stubs containing one garbage reply post.
d.title in ['4chan - Temporarily Offline', '4chan - 404 Not Found'] or (g.VIEW is 'thread' and $('.board') and not $('.opContainer'))
isIncomplete: ->
return g.VIEW in ['index', 'thread'] and not $('.board + *')
isBoardlessPage: (url) ->
url.hostname in ['www.4chan.org', 'www.4channel.org']
isAuxiliaryPage: (url) ->
url.hostname not in ['boards.4chan.org', 'boards.4channel.org']
isFileURL: (url) ->
ImageHost.test(url.hostname)
initAuxiliary: ->
switch location.hostname
when 'www.4chan.org', 'www.4channel.org'
if SW.yotsuba.regexp.pass.test(location.href)
PassMessage.init()
else
$.onExists doc, 'body', -> $.addStyle CSS.www
Captcha.replace.init()
return
when 'sys.4chan.org', 'sys.4channel.org'
pathname = location.pathname.split /\/+/
if pathname[2] is 'imgboard.php'
if /\bmode=report\b/.test location.search
Report.init()
else if (match = location.search.match /\bres=(\d+)/)
$.ready ->
if Conf['404 Redirect'] and $.id('errmsg')?.textContent is 'Error: Specified thread does not exist.'
Redirect.navigate 'thread', {
boardID: g.BOARD.ID
postID: +match[1]
}
else if pathname[2] is 'post'
PostSuccessful.init()
return
scriptData: ->
for script in $$ 'script:not([src])', d.head
return script.textContent if /\bcooldowns *=/.test script.textContent
''
parseThreadMetadata: (thread) ->
scriptData = @scriptData()
thread.postLimit = /\bbumplimit *= *1\b/.test scriptData
thread.fileLimit = /\bimagelimit *= *1\b/.test scriptData
thread.ipCount = if (m = scriptData.match /\bunique_ips *= *(\d+)\b/) then +m[1]
if g.BOARD.ID is 'f' and thread.OP.file
{file} = thread.OP
$.ajax @urls.threadJSON({boardID: 'f', threadID: thread.ID}),
timeout: $.MINUTE
onloadend: ->
if @response
file.text.dataset.md5 = file.MD5 = @response.posts[0].md5
parseNodes: (post, nodes) ->
# Add CSS classes to sticky/closed icons on /f/ to match other boards.
if post.boardID is 'f'
for type in ['Sticky', 'Closed'] when (icon = $ "img[alt=#{type}]", nodes.info)
$.addClass icon, "#{type.toLowerCase()}Icon", 'retina'
parseDate: (node) ->
new Date(node.dataset.utc * 1000)
parseFile: (post, file) ->
{text, link, thumb} = file
return false if not (info = link.nextSibling?.textContent.match /\(([\d.]+ [KMG]?B).*\)/)
$.extend file,
name: text.title or link.title or link.textContent
size: info[1]
dimensions: info[0].match(/\d+x\d+/)?[0]
tag: info[0].match(/,[^,]*, ([a-z]+)\)/i)?[1]
MD5: text.dataset.md5
if thumb
$.extend file,
thumbURL: thumb.src
MD5: thumb.dataset.md5
isSpoiler: $.hasClass thumb.parentNode, 'imgspoiler'
if file.isSpoiler
file.thumbURL = if (m = link.href.match /\d+(?=\.\w+$)/) then "#{location.protocol}//#{ImageHost.thumbHost()}/#{post.board}/#{m[0]}s.jpg"
true
cleanComment: (bq) ->
if (abbr = $ '.abbr', bq) # 'Comment too long' or 'EXIF data available'
for node in $$ '.abbr + br, .exif', bq
$.rm node
for i in [0...2]
$.rm br if (br = abbr.previousSibling) and br.nodeName is 'BR'
$.rm abbr
cleanCommentDisplay: (bq) ->
$.rm b if (b = $ 'b', bq) and /^Rolled /.test(b.textContent)
$.rm $('.fortune', bq)
insertTags: (bq) ->
for node in $$ 's, .removed-spoiler', bq
$.replace node, [$.tn('[spoiler]'), node.childNodes..., $.tn '[/spoiler]']
for node in $$ '.prettyprint', bq
$.replace node, [$.tn('[code]'), node.childNodes..., $.tn '[/code]']
return
hasCORS: (url) ->
url.split('/')[...3].join('/') is location.protocol + '//a.4cdn.org'
sfwBoards: (sfw) ->
BoardConfig.sfwBoards(sfw)
uidColor: (uid) ->
msg = 0
i = 0
while i < 8
msg = (msg << 5) - msg + uid.charCodeAt i++
(msg >> 8) & 0xFFFFFF
isLinkified: (link) ->
ImageHost.test(link.hostname)
testNativeExtension: ->
$.global ->
@enabled = 'true' if window.Parser.postMenuIcon
transformBoardList: ->
nodes = []
spacer = -> $.el 'span', className: 'spacer'
items = $.X './/a|.//text()[not(ancestor::a)]', $(SW.yotsuba.selectors.boardList)
i = 0
while node = items.snapshotItem i++
switch node.nodeName
when '#text'
for chr in node.nodeValue
span = $.el 'span', textContent: chr
span.className = 'space' if chr is ' '
nodes.push spacer() if chr is ']'
nodes.push span
nodes.push spacer() if chr is '['
when 'A'
a = node.cloneNode true
nodes.push a
return nodes