commit
894682f067
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,6 +1,16 @@
|
||||
**MayhemYDG**:
|
||||
- Fix captcha not refreshing.
|
||||
- Fix captcha submission:<br>
|
||||
Captchas were reloaded the instant a post was submitted to 4chan. Unfortunately, a recent change to reCAPTCHA made it so reloading captchas invalidates the ones that loaded but not yet used. This is now fixed by only unloading the captcha, and only load new ones after the post is submitted.<br>
|
||||
This also kills captcha caching, so the feature was removed.
|
||||
**duckness**:
|
||||
- Merge changes from Mayhem fork
|
||||
**ccd0**:
|
||||
- Embedding for direct video links
|
||||
- Merge changes from Mayhem fork
|
||||
|
||||
### v1.4.1
|
||||
*2014-03-01*
|
||||
|
||||
**Spittie**
|
||||
- Check image dimension before uploading
|
||||
|
||||
|
||||
2
LICENSE
2
LICENSE
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 4chan X - Version 1.4.1 - 2014-03-01
|
||||
* 4chan X - Version 1.4.1 - 2014-04-02
|
||||
*
|
||||
* Licensed under the MIT license.
|
||||
* https://github.com/Spittie/4chan-x/blob/master/LICENSE
|
||||
|
||||
@ -2,9 +2,11 @@ Personal fork of Seaweed's 4chan X.
|
||||
|
||||
#### [Why 4chan X needs to access data on every site?](https://github.com/Spittie/4chan-x/wiki/Why-4chan-X-needs-to-access-data-from-every-website%3F)
|
||||
|
||||
##[Install](https://github.com/Spittie/4chan-x/raw/master/builds/4chan-X.user.js) (Firefox)
|
||||
##[Install](https://github.com/Spittie/4chan-x/raw/master/builds/crx.crx) (Chrom*)
|
||||
##[Install](../../raw/master/builds/4chan-X.user.js) (Firefox)
|
||||
##[Install](../../raw/master/builds/crx.crx) (Chrom*)
|
||||
<!---
|
||||
##[Install](http://a.pomf.se/tkhuwm.xpi) (Firefox Mobile)
|
||||
-->
|
||||
|
||||
## If you have any problems, try resetting your 4chan X settings
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
builds/crx.crx
BIN
builds/crx.crx
Binary file not shown.
File diff suppressed because one or more lines are too long
@ -1260,6 +1260,10 @@ div.boardTitle {
|
||||
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/installgentoo.png", {encoding: "base64"}) %>') center left no-repeat!important;
|
||||
padding-left: 18px;
|
||||
}
|
||||
.linkify.video {
|
||||
background: transparent url('data:image/png;base64,<%= grunt.file.read("src/General/img/links/video.png", {encoding: "base64"}) %>') center left no-repeat!important;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
/* Gallery */
|
||||
#a-gallery {
|
||||
|
||||
BIN
src/General/img/links/video.png
Normal file
BIN
src/General/img/links/video.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 209 B |
@ -20,6 +20,9 @@ Linkify =
|
||||
[-\w\d.@]+@[a-z\d.-]+\.[a-z\d]
|
||||
)///i
|
||||
|
||||
@types = {}
|
||||
@types[type.key] = type for type in @ordered_types
|
||||
|
||||
if Conf['Comment Expansion']
|
||||
ExpandComment.callbacks.push @node
|
||||
|
||||
@ -146,9 +149,10 @@ Linkify =
|
||||
services: (link) ->
|
||||
href = link.href
|
||||
|
||||
for key, type of Linkify.types
|
||||
for type in Linkify.ordered_types
|
||||
continue unless match = type.regExp.exec href
|
||||
return [key, match[1], match[2], link]
|
||||
break if type.dummy
|
||||
return [type.key, match[1], match[2], link]
|
||||
|
||||
return
|
||||
|
||||
@ -254,16 +258,16 @@ Linkify =
|
||||
text = "[#{key}] #{@status}'d"
|
||||
link.textContent = text if link
|
||||
|
||||
types:
|
||||
audio:
|
||||
ordered_types: [
|
||||
key: 'audio'
|
||||
regExp: /(.*\.(mp3|ogg|wav))$/
|
||||
el: (a) ->
|
||||
$.el 'audio',
|
||||
controls: 'controls'
|
||||
preload: 'auto'
|
||||
src: a.dataset.uid
|
||||
|
||||
gist:
|
||||
,
|
||||
key: 'gist'
|
||||
regExp: /.*(?:gist.github.com.*\/)([^\/][^\/]*)$/
|
||||
el: (a) ->
|
||||
div = $.el 'iframe',
|
||||
@ -273,27 +277,27 @@ Linkify =
|
||||
api: (uid) -> "https://api.github.com/gists/#{uid}"
|
||||
text: ({files}) ->
|
||||
return file for file of files when files.hasOwnProperty file
|
||||
|
||||
image:
|
||||
,
|
||||
key: 'image'
|
||||
regExp: /(http|www).*\.(gif|png|jpg|jpeg|bmp)$/
|
||||
style: 'border: 0; width: auto; height: auto;'
|
||||
el: (a) ->
|
||||
$.el 'div',
|
||||
innerHTML: "<a target=_blank href='#{a.dataset.href}'><img src='#{a.dataset.href}'></a>"
|
||||
|
||||
InstallGentoo:
|
||||
,
|
||||
key: 'InstallGentoo'
|
||||
regExp: /.*(?:paste.installgentoo.com\/view\/)([0-9a-z_]+)/
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
src: "http://paste.installgentoo.com/view/embed/#{a.dataset.uid}"
|
||||
|
||||
Twitter:
|
||||
,
|
||||
key: 'Twitter'
|
||||
regExp: /.*twitter.com\/(.+\/status\/\d+)/
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
src: "https://twitframe.com/show?url=https://twitter.com/#{a.dataset.uid}"
|
||||
|
||||
LiveLeak:
|
||||
,
|
||||
key: 'LiveLeak'
|
||||
regExp: /.*(?:liveleak.com\/view.+i=)([0-9a-z_]+)/
|
||||
el: (a) ->
|
||||
el = $.el 'iframe',
|
||||
@ -303,8 +307,8 @@ Linkify =
|
||||
frameborder: "0"
|
||||
el.setAttribute "allowfullscreen", "true"
|
||||
el
|
||||
|
||||
MediaCrush:
|
||||
,
|
||||
key: 'MediaCrush'
|
||||
regExp: /.*(?:mediacru.sh\/)([0-9a-z_]+)/i
|
||||
style: 'border: 0;'
|
||||
el: (a) ->
|
||||
@ -335,20 +339,20 @@ Linkify =
|
||||
else
|
||||
"ERROR: No valid filetype."
|
||||
el
|
||||
|
||||
pastebin:
|
||||
,
|
||||
key: 'pastebin'
|
||||
regExp: /.*(?:pastebin.com\/(?!u\/))([^#\&\?]*).*/
|
||||
el: (a) ->
|
||||
div = $.el 'iframe',
|
||||
src: "http://pastebin.com/embed_iframe.php?i=#{a.dataset.uid}"
|
||||
|
||||
gfycat:
|
||||
,
|
||||
key: 'gfycat'
|
||||
regExp: /.*gfycat.com\/(?:iframe\/)?(\S*)/
|
||||
el: (a) ->
|
||||
div = $.el 'iframe',
|
||||
src: "http://gfycat.com/iframe/#{a.dataset.uid}"
|
||||
|
||||
SoundCloud:
|
||||
,
|
||||
key: 'SoundCloud'
|
||||
regExp: /.*(?:soundcloud.com\/|snd.sc\/)([^#\&\?]*).*/
|
||||
style: 'height: auto; width: 500px; display: inline-block;'
|
||||
el: (a) ->
|
||||
@ -364,15 +368,15 @@ Linkify =
|
||||
title:
|
||||
api: (uid) -> "//soundcloud.com/oembed?show_artwork=false&&maxwidth=500px&show_comments=false&format=json&url=https://www.soundcloud.com/#{uid}"
|
||||
text: (_) -> _.title
|
||||
|
||||
StrawPoll:
|
||||
,
|
||||
key: 'StrawPoll'
|
||||
regExp: /strawpoll\.me\/(?:embed_\d+\/)?(\d+)/
|
||||
style: 'border: 0; width: 600px; height: 406px;'
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
src: "http://strawpoll.me/embed_1/#{a.dataset.uid}"
|
||||
|
||||
TwitchTV:
|
||||
,
|
||||
key: 'TwitchTV'
|
||||
regExp: /.*(?:twitch.tv\/)([^#\&\?]*).*/
|
||||
style: "border: none; width: 640px; height: 360px;"
|
||||
el: (a) ->
|
||||
@ -396,15 +400,15 @@ Linkify =
|
||||
<param name="movie" value="http://www.twitch.tv/widgets/live_embed_player.swf" />
|
||||
<param name="flashvars" value="hostname=www.twitch.tv&channel=#{channel}&auto_play=true&start_volume=25" />
|
||||
"""
|
||||
|
||||
Vocaroo:
|
||||
,
|
||||
key: 'Vocaroo'
|
||||
regExp: /.*(?:vocaroo.com\/)([^#\&\?]*).*/
|
||||
style: 'border: 0; width: 150px; height: 45px;'
|
||||
el: (a) ->
|
||||
$.el 'object',
|
||||
innerHTML: "<embed src='http://vocaroo.com/player.swf?playMediaID=#{a.dataset.uid.replace /^i\//, ''}&autoplay=0' wmode='opaque' width='150' height='45' pluginspage='http://get.adobe.com/flashplayer/' type='application/x-shockwave-flash'></embed>"
|
||||
|
||||
Vimeo:
|
||||
,
|
||||
key: 'Vimeo'
|
||||
regExp: /.*(?:vimeo.com\/)([^#\&\?]*).*/
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
@ -412,15 +416,15 @@ Linkify =
|
||||
title:
|
||||
api: (uid) -> "https://vimeo.com/api/oembed.json?url=http://vimeo.com/#{uid}"
|
||||
text: (_) -> _.title
|
||||
|
||||
Vine:
|
||||
,
|
||||
key: 'Vine'
|
||||
regExp: /.*(?:vine.co\/)([^#\&\?]*).*/
|
||||
style: 'border: none; width: 500px; height: 500px;'
|
||||
el: (a) ->
|
||||
$.el 'iframe',
|
||||
src: "https://vine.co/#{a.dataset.uid}/card"
|
||||
|
||||
YouTube:
|
||||
,
|
||||
key: 'YouTube'
|
||||
regExp: /.*(?:youtu.be\/|youtube.*v=|youtube.*\/embed\/|youtube.*\/v\/|youtube.*videos\/)([^#\&\?]*)\??(t\=.*)?/
|
||||
el: (a) ->
|
||||
el = $.el 'iframe',
|
||||
@ -430,4 +434,22 @@ Linkify =
|
||||
title:
|
||||
api: (uid) -> "https://gdata.youtube.com/feeds/api/videos/#{uid}?alt=json&fields=title/text(),yt:noembed,app:control/yt:state/@reasonCode"
|
||||
text: (data) -> data.entry.title.$t
|
||||
,
|
||||
# dummy entries: not implemented yet but included to prevent them being wrongly embedded as a subsequent type
|
||||
key: 'Loopvid'
|
||||
regExp: /.*loopvid.appspot.com\/.*/
|
||||
dummy: true
|
||||
,
|
||||
key: 'MediaFire'
|
||||
regExp: /.*mediafire.com\/.*/
|
||||
dummy: true
|
||||
,
|
||||
key: 'video'
|
||||
regExp: /(.*\.(ogv|webm|mp4))$/
|
||||
el: (a) ->
|
||||
$.el 'video',
|
||||
controls: 'controls'
|
||||
preload: 'auto'
|
||||
src: a.dataset.uid
|
||||
]
|
||||
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
QR.captcha =
|
||||
init: ->
|
||||
return if d.cookie.indexOf('pass_enabled=1') >= 0
|
||||
container = $.id 'captchaContainer'
|
||||
return unless @isEnabled = !!container
|
||||
return unless @isEnabled = !!$.id 'captchaContainer'
|
||||
|
||||
$.globalEval 'loadRecaptcha()' if Conf['Auto-load captcha']
|
||||
|
||||
@ -10,11 +9,9 @@ QR.captcha =
|
||||
className: 'captcha-img'
|
||||
title: 'Reload reCAPTCHA'
|
||||
innerHTML: '<img>'
|
||||
hidden: true
|
||||
input = $.el 'input',
|
||||
className: 'captcha-input field'
|
||||
title: 'Verification'
|
||||
placeholder: 'Focus to load reCAPTCHA'
|
||||
autocomplete: 'off'
|
||||
spellcheck: false
|
||||
tabIndex: 45
|
||||
@ -22,17 +19,22 @@ QR.captcha =
|
||||
img: imgContainer.firstChild
|
||||
input: input
|
||||
|
||||
$.on input, 'focus', @setup
|
||||
|
||||
$.on input, 'blur', QR.focusout
|
||||
$.on input, 'focus', QR.focusin
|
||||
|
||||
$.addClass QR.nodes.el, 'has-captcha'
|
||||
$.after QR.nodes.com.parentNode, [imgContainer, input]
|
||||
|
||||
@setupObserver = new MutationObserver @afterSetup
|
||||
@setupObserver.observe container, childList: true
|
||||
|
||||
@beforeSetup()
|
||||
@afterSetup() # reCAPTCHA might have loaded before the QR.
|
||||
beforeSetup: ->
|
||||
{img, input} = @nodes
|
||||
img.parentNode.hidden = true
|
||||
input.value = ''
|
||||
input.placeholder = 'Focus to load reCAPTCHA'
|
||||
$.on input, 'focus', @setup
|
||||
@setupObserver = new MutationObserver @afterSetup
|
||||
@setupObserver.observe $.id('captchaContainer'), childList: true
|
||||
setup: ->
|
||||
$.globalEval 'loadRecaptcha()'
|
||||
afterSetup: ->
|
||||
@ -40,89 +42,37 @@ QR.captcha =
|
||||
QR.captcha.setupObserver.disconnect()
|
||||
delete QR.captcha.setupObserver
|
||||
|
||||
setLifetime = (e) -> QR.captcha.lifetime = e.detail
|
||||
$.on window, 'captcha:timeout', setLifetime
|
||||
$.globalEval 'window.dispatchEvent(new CustomEvent("captcha:timeout", {detail: RecaptchaState.timeout}))'
|
||||
$.off window, 'captcha:timeout', setLifetime
|
||||
|
||||
{img, input} = QR.captcha.nodes
|
||||
img.parentNode.hidden = false
|
||||
input.placeholder = 'Verification'
|
||||
$.off input, 'focus', QR.captcha.setup
|
||||
$.on input, 'keydown', QR.captcha.keydown.bind QR.captcha
|
||||
$.on img.parentNode, 'click', QR.captcha.reload.bind QR.captcha
|
||||
|
||||
$.get 'captchas', [], ({captchas}) ->
|
||||
QR.captcha.sync captchas
|
||||
$.sync 'captchas', QR.captcha.sync
|
||||
|
||||
QR.captcha.nodes.challenge = challenge
|
||||
new MutationObserver(QR.captcha.load.bind QR.captcha).observe challenge,
|
||||
childList: true
|
||||
subtree: true
|
||||
attributes: true
|
||||
QR.captcha.load()
|
||||
|
||||
sync: (captchas) ->
|
||||
QR.captcha.captchas = captchas
|
||||
QR.captcha.count()
|
||||
|
||||
destroy: ->
|
||||
$.globalEval 'Recaptcha.destroy()'
|
||||
@beforeSetup()
|
||||
getOne: ->
|
||||
@clear()
|
||||
if captcha = @captchas.shift()
|
||||
{challenge, response} = captcha
|
||||
@count()
|
||||
$.set 'captchas', @captchas
|
||||
else
|
||||
challenge = @nodes.img.alt
|
||||
if response = @nodes.input.value then @reload()
|
||||
if response
|
||||
response = response.trim()
|
||||
challenge = @nodes.img.alt
|
||||
response = @nodes.input.value.trim()
|
||||
if response and !/\s/.test response
|
||||
# one-word-captcha:
|
||||
# If there's only one word, duplicate it.
|
||||
response = "#{response} #{response}" unless /\s/.test response
|
||||
response = "#{response} #{response}"
|
||||
{challenge, response}
|
||||
|
||||
save: ->
|
||||
return unless response = @nodes.input.value.trim()
|
||||
@captchas.push
|
||||
challenge: @nodes.img.alt
|
||||
response: response
|
||||
timeout: @timeout
|
||||
@count()
|
||||
@reload()
|
||||
$.set 'captchas', @captchas
|
||||
|
||||
clear: ->
|
||||
return unless @captchas.length
|
||||
now = Date.now()
|
||||
for captcha, i in @captchas
|
||||
break if captcha.timeout > now
|
||||
return unless i
|
||||
@captchas = @captchas[i..]
|
||||
@count()
|
||||
$.set 'captchas', @captchas
|
||||
|
||||
load: ->
|
||||
return unless @nodes.challenge.firstChild
|
||||
# -1 minute to give upload some time.
|
||||
@timeout = Date.now() + @lifetime * $.SECOND - $.MINUTE
|
||||
challenge = @nodes.challenge.firstChild.value
|
||||
@nodes.img.alt = challenge
|
||||
@nodes.img.src = "//www.google.com/recaptcha/api/image?c=#{challenge}"
|
||||
@nodes.input.value = null
|
||||
@clear()
|
||||
|
||||
count: ->
|
||||
count = if @captchas then @captchas.length else 0
|
||||
@nodes.input.placeholder = switch count
|
||||
when 0
|
||||
'Verification (Shift + Enter to cache)'
|
||||
when 1
|
||||
'Verification (1 cached captcha)'
|
||||
else
|
||||
"Verification (#{count} cached captchas)"
|
||||
@nodes.input.alt = count # For XTRM RICE.
|
||||
|
||||
reload: (focus) ->
|
||||
# the 't' argument prevents the input from being focused
|
||||
$.globalEval 'Recaptcha.reload("t")'
|
||||
@ -132,8 +82,6 @@ QR.captcha =
|
||||
keydown: (e) ->
|
||||
if e.keyCode is 8 and not @nodes.input.value
|
||||
@reload()
|
||||
else if e.keyCode is 13 and e.shiftKey
|
||||
@save()
|
||||
else
|
||||
return
|
||||
e.preventDefault()
|
||||
|
||||
@ -663,6 +663,9 @@ QR =
|
||||
onerror: (err, url, line) ->
|
||||
# Connection error, or www.4chan.org/banned
|
||||
delete QR.req
|
||||
if QR.captcha.isEnabled
|
||||
QR.captcha.destroy()
|
||||
QR.captcha.setup()
|
||||
post.unlock()
|
||||
QR.cooldown.auto = false
|
||||
QR.status()
|
||||
@ -699,6 +702,7 @@ QR =
|
||||
{req} = QR
|
||||
delete QR.req
|
||||
|
||||
QR.captcha.destroy() if QR.captcha.isEnabled
|
||||
post = QR.posts[0]
|
||||
post.unlock()
|
||||
|
||||
@ -730,23 +734,12 @@ QR =
|
||||
err = 'You seem to have mistyped the CAPTCHA.'
|
||||
else if /expired/i.test err.textContent
|
||||
err = 'This CAPTCHA is no longer valid because it has expired.'
|
||||
# Enable auto-post if we have some cached captchas.
|
||||
QR.cooldown.auto = if QR.captcha.isEnabled
|
||||
!!QR.captcha.captchas.length
|
||||
else if err is 'Connection error with sys.4chan.org.'
|
||||
true
|
||||
else
|
||||
# Something must've gone terribly wrong if you get captcha errors without captchas.
|
||||
# Don't auto-post indefinitely in that case.
|
||||
false
|
||||
QR.cooldown.auto = false
|
||||
# Too many frequent mistyped captchas will auto-ban you!
|
||||
# On connection error, the post most likely didn't go through.
|
||||
QR.cooldown.set delay: 2
|
||||
else if err.textContent and m = err.textContent.match /wait\s+(\d+)\s+second/i
|
||||
QR.cooldown.auto = if QR.captcha.isEnabled
|
||||
!!QR.captcha.captchas.length
|
||||
else
|
||||
true
|
||||
QR.cooldown.auto = !QR.captcha.isEnabled
|
||||
QR.cooldown.set delay: m[1]
|
||||
else # stop auto-posting
|
||||
QR.cooldown.auto = false
|
||||
@ -786,18 +779,6 @@ QR =
|
||||
# Enable auto-posting if we have stuff left to post, disable it otherwise.
|
||||
postsCount = QR.posts.length - 1
|
||||
QR.cooldown.auto = postsCount and isReply
|
||||
if QR.cooldown.auto and QR.captcha.isEnabled and (captchasCount = QR.captcha.captchas.length) < 3 and captchasCount < postsCount
|
||||
notif = new Notification 'Quick reply warning',
|
||||
body: "You are running low on cached captchas. Cache count: #{captchasCount}."
|
||||
icon: Favicon.logo
|
||||
notif.onclick = ->
|
||||
QR.open()
|
||||
QR.captcha.nodes.input.focus()
|
||||
window.focus()
|
||||
notif.onshow = ->
|
||||
setTimeout ->
|
||||
notif.close()
|
||||
, 7 * $.SECOND
|
||||
|
||||
unless Conf['Persistent QR'] or QR.cooldown.auto
|
||||
QR.close()
|
||||
|
||||
@ -91,6 +91,8 @@ QR.post = class
|
||||
return unless @ is QR.selected
|
||||
for name in ['thread', 'name', 'email', 'sub', 'com', 'fileButton', 'filename', 'spoiler', 'flag'] when node = QR.nodes[name]
|
||||
node.disabled = lock
|
||||
if QR.captcha.isEnabled
|
||||
QR.captcha.nodes.input.disabled = lock
|
||||
@nodes.rm.style.visibility = if lock then 'hidden' else ''
|
||||
(if lock then $.off else $.on) QR.nodes.filename.previousElementSibling, 'click', QR.openFileInput
|
||||
@nodes.spoiler.disabled = lock
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user