forked from cadence/breezewiki
Compare commits
8 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 048709b2d1 | |||
| c4e2fb00ef | |||
| 02848acfbb | |||
| 4f4c939631 | |||
| 143fadcafb | |||
| 1c675d4873 | |||
| 23a201cc84 | |||
| 443f1eecbc |
13 changed files with 505 additions and 57 deletions
|
|
@ -1,5 +1,7 @@
|
||||||
#lang racket/base
|
#lang racket/base
|
||||||
(require web-server/servlet-dispatch
|
(require racket/splicing
|
||||||
|
web-server/servlet-dispatch
|
||||||
|
web-server/safety-limits
|
||||||
"src/config.rkt"
|
"src/config.rkt"
|
||||||
"src/dispatcher-tree.rkt"
|
"src/dispatcher-tree.rkt"
|
||||||
"src/reloadable.rkt")
|
"src/reloadable.rkt")
|
||||||
|
|
@ -9,6 +11,9 @@
|
||||||
(reloadable-entry-point->procedure
|
(reloadable-entry-point->procedure
|
||||||
(make-reloadable-entry-point (quote varname) filename))))
|
(make-reloadable-entry-point (quote varname) filename))))
|
||||||
|
|
||||||
|
(require-reloadable "src/page-captcha.rkt" page-captcha)
|
||||||
|
(require-reloadable "src/page-captcha.rkt" page-captcha-image)
|
||||||
|
(require-reloadable "src/page-captcha.rkt" page-captcha-verify)
|
||||||
(require-reloadable "src/page-category.rkt" page-category)
|
(require-reloadable "src/page-category.rkt" page-category)
|
||||||
(require-reloadable "src/page-global-search.rkt" page-global-search)
|
(require-reloadable "src/page-global-search.rkt" page-global-search)
|
||||||
(require-reloadable "src/page-home.rkt" page-home)
|
(require-reloadable "src/page-home.rkt" page-home)
|
||||||
|
|
@ -22,7 +27,9 @@
|
||||||
(require-reloadable "src/page-static-archive.rkt" page-static-archive)
|
(require-reloadable "src/page-static-archive.rkt" page-static-archive)
|
||||||
(require-reloadable "src/page-subdomain.rkt" subdomain-dispatcher)
|
(require-reloadable "src/page-subdomain.rkt" subdomain-dispatcher)
|
||||||
(require-reloadable "src/page-wiki.rkt" page-wiki)
|
(require-reloadable "src/page-wiki.rkt" page-wiki)
|
||||||
|
(require-reloadable "src/page-wiki.rkt" page-wiki-with-data)
|
||||||
(require-reloadable "src/page-wiki-offline.rkt" page-wiki-offline)
|
(require-reloadable "src/page-wiki-offline.rkt" page-wiki-offline)
|
||||||
|
(require-reloadable "src/page-wiki-jsonp.rkt" page-wiki-jsonp)
|
||||||
(require-reloadable "src/page-file.rkt" page-file)
|
(require-reloadable "src/page-file.rkt" page-file)
|
||||||
|
|
||||||
(reload!)
|
(reload!)
|
||||||
|
|
@ -34,10 +41,14 @@
|
||||||
(if (config-true? 'debug) "127.0.0.1" #f)
|
(if (config-true? 'debug) "127.0.0.1" #f)
|
||||||
(config-get 'bind_host))
|
(config-get 'bind_host))
|
||||||
#:port (string->number (config-get 'port))
|
#:port (string->number (config-get 'port))
|
||||||
|
#:safety-limits (make-safety-limits #:max-request-body-length (* 8 1024 1024))
|
||||||
(λ (quit)
|
(λ (quit)
|
||||||
(channel-put ch (lambda () (semaphore-post quit)))
|
(channel-put ch (lambda () (semaphore-post quit)))
|
||||||
(dispatcher-tree
|
(dispatcher-tree
|
||||||
; order of these does not matter
|
; order of these does not matter
|
||||||
|
page-captcha
|
||||||
|
page-captcha-image
|
||||||
|
page-captcha-verify
|
||||||
page-category
|
page-category
|
||||||
page-global-search
|
page-global-search
|
||||||
page-home
|
page-home
|
||||||
|
|
@ -48,7 +59,9 @@
|
||||||
page-set-user-settings
|
page-set-user-settings
|
||||||
page-static-archive
|
page-static-archive
|
||||||
page-wiki
|
page-wiki
|
||||||
|
page-wiki-with-data
|
||||||
page-wiki-offline
|
page-wiki-offline
|
||||||
|
page-wiki-jsonp
|
||||||
page-file
|
page-file
|
||||||
redirect-wiki-home
|
redirect-wiki-home
|
||||||
static-dispatcher
|
static-dispatcher
|
||||||
|
|
|
||||||
9
dist.rkt
9
dist.rkt
|
|
@ -3,6 +3,7 @@
|
||||||
"src/config.rkt"
|
"src/config.rkt"
|
||||||
"src/dispatcher-tree.rkt")
|
"src/dispatcher-tree.rkt")
|
||||||
|
|
||||||
|
(require (only-in "src/page-captcha.rkt" page-captcha page-captcha-image page-captcha-verify))
|
||||||
(require (only-in "src/page-category.rkt" page-category))
|
(require (only-in "src/page-category.rkt" page-category))
|
||||||
(require (only-in "src/page-global-search.rkt" page-global-search))
|
(require (only-in "src/page-global-search.rkt" page-global-search))
|
||||||
(require (only-in "src/page-home.rkt" page-home))
|
(require (only-in "src/page-home.rkt" page-home))
|
||||||
|
|
@ -15,8 +16,9 @@
|
||||||
(require (only-in "src/page-static.rkt" static-dispatcher))
|
(require (only-in "src/page-static.rkt" static-dispatcher))
|
||||||
(require (only-in "src/page-static-archive.rkt" page-static-archive))
|
(require (only-in "src/page-static-archive.rkt" page-static-archive))
|
||||||
(require (only-in "src/page-subdomain.rkt" subdomain-dispatcher))
|
(require (only-in "src/page-subdomain.rkt" subdomain-dispatcher))
|
||||||
(require (only-in "src/page-wiki.rkt" page-wiki))
|
(require (only-in "src/page-wiki.rkt" page-wiki page-wiki-with-data))
|
||||||
(require (only-in "src/page-wiki-offline.rkt" page-wiki-offline))
|
(require (only-in "src/page-wiki-offline.rkt" page-wiki-offline))
|
||||||
|
(require (only-in "src/page-wiki-jsonp.rkt" page-wiki-jsonp))
|
||||||
(require (only-in "src/page-file.rkt" page-file))
|
(require (only-in "src/page-file.rkt" page-file))
|
||||||
|
|
||||||
(serve/launch/wait
|
(serve/launch/wait
|
||||||
|
|
@ -27,6 +29,9 @@
|
||||||
(λ (quit)
|
(λ (quit)
|
||||||
(dispatcher-tree
|
(dispatcher-tree
|
||||||
; order of these does not matter
|
; order of these does not matter
|
||||||
|
page-captcha
|
||||||
|
page-captcha-image
|
||||||
|
page-captcha-verify
|
||||||
page-category
|
page-category
|
||||||
page-global-search
|
page-global-search
|
||||||
page-home
|
page-home
|
||||||
|
|
@ -38,6 +43,8 @@
|
||||||
page-static-archive
|
page-static-archive
|
||||||
page-wiki
|
page-wiki
|
||||||
page-wiki-offline
|
page-wiki-offline
|
||||||
|
page-wiki-with-data
|
||||||
|
page-wiki-jsonp
|
||||||
page-file
|
page-file
|
||||||
redirect-wiki-home
|
redirect-wiki-home
|
||||||
static-dispatcher
|
static-dispatcher
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,16 @@
|
||||||
(data-src "https://static.wikia.nocookie.net/nice-image-thumbnail.png")
|
(data-src "https://static.wikia.nocookie.net/nice-image-thumbnail.png")
|
||||||
(class "thumbimage")))))
|
(class "thumbimage")))))
|
||||||
(figcaption "Test figure!"))
|
(figcaption "Test figure!"))
|
||||||
|
(div (@ (type "slideshow") (position "center") (widths "500") (mode "slideshow") (seq-no "0") (id "slideshow-0") (hash "b62d0efee427ad7dff1026e6e9dd078c") (class "wikia-slideshow wikia-gallery slideshow-center"))
|
||||||
|
(div (@ (class "wikia-slideshow-wrapper") (style "width: 500px") (data-test-outer-width))
|
||||||
|
(div (@ (class "wikia-slideshow-images-wrapper"))
|
||||||
|
(ul (@ (class "wikia-slideshow-images neutral") (style "height: 375px; width: 500px") (data-test-inner-width))
|
||||||
|
(li (@ (class "wikia-slideshow-first-image"))
|
||||||
|
(a (@ (class "image lightbox") (title "Expand slideshow") (id "slideshow-0-0") (style "width: 420px"))
|
||||||
|
(img (@ (data-src "https://static.wikia.nocookie.net/example/images/3/3d/Image.jpg/revision/latest/scale-to-width-down/500?cb=20140129105112") (class "thumbimage") (width "500") (height "281") (style "border: 0px;"))))
|
||||||
|
(div (@ (class "wikia-slideshow-overlay"))
|
||||||
|
(div (@ (class "wikia-slideshow-image-caption"))
|
||||||
|
"Example caption")))))))
|
||||||
(iframe (@ (src "https://example.com/iframe-src")))
|
(iframe (@ (src "https://example.com/iframe-src")))
|
||||||
(div (@ (class "reviews"))
|
(div (@ (class "reviews"))
|
||||||
(header "GameSpot Expert Reviews"))
|
(header "GameSpot Expert Reviews"))
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,12 @@
|
||||||
(feature_offline::only . "false")
|
(feature_offline::only . "false")
|
||||||
(feature_offline::search . "fandom")
|
(feature_offline::search . "fandom")
|
||||||
|
|
||||||
|
(feature_jsonp::enabled . "true")
|
||||||
|
|
||||||
|
(captcha::enabled . "false")
|
||||||
|
(captcha::log . "false")
|
||||||
|
(captcha::ip_header . "")
|
||||||
|
|
||||||
(access_log::enabled . "false")
|
(access_log::enabled . "false")
|
||||||
|
|
||||||
(promotions::indie_wiki_buddy . "banner home")))
|
(promotions::indie_wiki_buddy . "banner home")))
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
(struct-out license^)
|
(struct-out license^)
|
||||||
(struct-out head-data^)
|
(struct-out head-data^)
|
||||||
(struct-out user-cookies^)
|
(struct-out user-cookies^)
|
||||||
|
data->siteinfo
|
||||||
siteinfo-fetch
|
siteinfo-fetch
|
||||||
siteinfo-default
|
siteinfo-default
|
||||||
license-default
|
license-default
|
||||||
|
|
@ -66,11 +67,14 @@
|
||||||
("formatversion" . "2"))))
|
("formatversion" . "2"))))
|
||||||
(cond [(= (easy:response-status-code res) 200)
|
(cond [(= (easy:response-status-code res) 200)
|
||||||
(define data (easy:response-json res))
|
(define data (easy:response-json res))
|
||||||
|
(data->siteinfo data)]
|
||||||
|
[else siteinfo-default])]))
|
||||||
|
|
||||||
|
(define (data->siteinfo data)
|
||||||
(siteinfo^ (jp "/query/general/sitename" data)
|
(siteinfo^ (jp "/query/general/sitename" data)
|
||||||
(second (regexp-match #rx"/wiki/(.*)" (jp "/query/general/base" data)))
|
(second (regexp-match #rx"/wiki/(.*)" (jp "/query/general/base" data)))
|
||||||
(license^ (jp "/query/rightsinfo/text" data)
|
(license^ (jp "/query/rightsinfo/text" data)
|
||||||
(jp "/query/rightsinfo/url" data)))]
|
(jp "/query/rightsinfo/url" data))))
|
||||||
[else siteinfo-default])]))
|
|
||||||
|
|
||||||
(define/memoize (head-data-getter wikiname) #:hash hash
|
(define/memoize (head-data-getter wikiname) #:hash hash
|
||||||
;; data will be stored here, can be referenced by the memoized closure
|
;; data will be stored here, can be referenced by the memoized closure
|
||||||
|
|
|
||||||
|
|
@ -56,15 +56,25 @@
|
||||||
(sequencer:make
|
(sequencer:make
|
||||||
subdomain-dispatcher
|
subdomain-dispatcher
|
||||||
(pathprocedure:make "/" (page ds page-home))
|
(pathprocedure:make "/" (page ds page-home))
|
||||||
|
(filter:make #rx"^/static/" (hash-ref ds 'static-dispatcher))
|
||||||
|
(filter:make (pregexp "^/captcha/img/[0-9]+/[0-9]+$") (lift:make (page ds page-captcha-image)))
|
||||||
|
(filter:make (pregexp "^/captcha/verify/[0-9]+/[0-9]+/[0-9]+/[0-9]+$") (lift:make (page ds page-captcha-verify)))
|
||||||
|
(if (config-true? 'captcha::enabled)
|
||||||
|
(lift:make (page ds page-captcha))
|
||||||
|
(λ (_conn _req) (next-dispatcher)))
|
||||||
(pathprocedure:make "/proxy" (page ds page-proxy))
|
(pathprocedure:make "/proxy" (page ds page-proxy))
|
||||||
(pathprocedure:make "/search" (page ds page-global-search))
|
(pathprocedure:make "/search" (page ds page-global-search))
|
||||||
(pathprocedure:make "/set-user-settings" (page ds page-set-user-settings))
|
(pathprocedure:make "/set-user-settings" (page ds page-set-user-settings))
|
||||||
(pathprocedure:make "/buddyfight/wiki/It_Doesn't_Work!!" (page ds page-it-works))
|
(pathprocedure:make "/buddyfight/wiki/It_Doesn't_Work!!" (page ds page-it-works))
|
||||||
|
(pathprocedure:make "/api/render/wiki" (page ds page-wiki-with-data))
|
||||||
(filter:make (pregexp (format "^/~a/wiki/Category:.+$" px-wikiname)) (lift:make (page ds page-category)))
|
(filter:make (pregexp (format "^/~a/wiki/Category:.+$" px-wikiname)) (lift:make (page ds page-category)))
|
||||||
(filter:make (pregexp (format "^/~a/wiki/File:.+$" px-wikiname)) (lift:make (page ds page-file)))
|
(filter:make (pregexp (format "^/~a/wiki/File:.+$" px-wikiname)) (lift:make (page ds page-file)))
|
||||||
(if (config-true? 'feature_offline::enabled)
|
(if (config-true? 'feature_offline::enabled)
|
||||||
(filter:make (pregexp (format "^/~a/wiki/.+$" px-wikiname)) (lift:make (page ds page-wiki-offline)))
|
(filter:make (pregexp (format "^/~a/wiki/.+$" px-wikiname)) (lift:make (page ds page-wiki-offline)))
|
||||||
(λ (_conn _req) (next-dispatcher)))
|
(λ (_conn _req) (next-dispatcher)))
|
||||||
|
(if (config-true? 'feature_jsonp::enabled)
|
||||||
|
(filter:make (pregexp (format "^/~a/wiki/.+$" px-wikiname)) (lift:make (page ds page-wiki-jsonp)))
|
||||||
|
(λ (_conn _req) (next-dispatcher)))
|
||||||
(filter:make (pregexp (format "^/~a/wiki/.+$" px-wikiname)) (lift:make (page ds page-wiki)))
|
(filter:make (pregexp (format "^/~a/wiki/.+$" px-wikiname)) (lift:make (page ds page-wiki)))
|
||||||
(filter:make (pregexp (format "^/~a/search$" px-wikiname)) (lift:make (page ds page-search)))
|
(filter:make (pregexp (format "^/~a/search$" px-wikiname)) (lift:make (page ds page-search)))
|
||||||
(filter:make (pregexp (format "^/~a(/(wiki(/)?)?)?$" px-wikiname)) (lift:make (page ds redirect-wiki-home)))
|
(filter:make (pregexp (format "^/~a(/(wiki(/)?)?)?$" px-wikiname)) (lift:make (page ds redirect-wiki-home)))
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
#lang typed/racket/base
|
#lang typed/racket/base
|
||||||
(require "config.rkt"
|
(require racket/format
|
||||||
|
racket/string
|
||||||
|
"config.rkt"
|
||||||
"../lib/url-utils.rkt")
|
"../lib/url-utils.rkt")
|
||||||
(define-type Headers (HashTable Symbol (U Bytes String)))
|
(define-type Headers (HashTable Symbol (U Bytes String)))
|
||||||
(require/typed net/http-easy
|
(require/typed net/http-easy
|
||||||
[#:opaque Timeout-Config timeout-config?]
|
[#:opaque Timeout-Config timeout-config?]
|
||||||
[#:opaque Response response?]
|
[#:opaque Response response?]
|
||||||
[#:opaque Session session?]
|
[#:opaque Session session?]
|
||||||
|
[response-status-code (Response -> Natural)]
|
||||||
[current-session (Parameter Session)]
|
[current-session (Parameter Session)]
|
||||||
|
[current-user-agent (Parameter (U Bytes String))]
|
||||||
[make-timeout-config ([#:lease Positive-Real] [#:connect Positive-Real] -> Timeout-Config)]
|
[make-timeout-config ([#:lease Positive-Real] [#:connect Positive-Real] -> Timeout-Config)]
|
||||||
[get ((U Bytes String)
|
[get ((U Bytes String)
|
||||||
[#:close? Boolean]
|
[#:close? Boolean]
|
||||||
|
|
@ -22,19 +26,41 @@
|
||||||
fandom-get-api
|
fandom-get-api
|
||||||
timeouts)
|
timeouts)
|
||||||
|
|
||||||
|
(unless (string-contains? (~a (current-user-agent)) "BreezeWiki")
|
||||||
|
(current-user-agent
|
||||||
|
(format "BreezeWiki/1.0 (~a) ~a"
|
||||||
|
(if (config-true? 'canonical_origin)
|
||||||
|
(config-get 'canonical_origin)
|
||||||
|
"local")
|
||||||
|
(current-user-agent))))
|
||||||
|
|
||||||
(define timeouts (make-timeout-config #:lease 5 #:connect 5))
|
(define timeouts (make-timeout-config #:lease 5 #:connect 5))
|
||||||
|
|
||||||
|
(: last-failure Flonum)
|
||||||
|
(define last-failure 0.0)
|
||||||
|
(: stored-failure (Option Response))
|
||||||
|
(define stored-failure #f)
|
||||||
|
(define failure-persist-time 30000)
|
||||||
|
|
||||||
(: no-headers Headers)
|
(: no-headers Headers)
|
||||||
(define no-headers '#hasheq())
|
(define no-headers '#hasheq())
|
||||||
|
|
||||||
(: fandom-get (String String [#:headers (Option Headers)] -> Response))
|
(: fandom-get (String String [#:headers (Option Headers)] -> Response))
|
||||||
(define (fandom-get wikiname path #:headers [headers #f])
|
(define (fandom-get wikiname path #:headers [headers #f])
|
||||||
|
(or
|
||||||
|
(and ((current-inexact-milliseconds) . < . (+ last-failure failure-persist-time)) stored-failure)
|
||||||
|
(let ()
|
||||||
(define dest-url (string-append "https://www.fandom.com" path))
|
(define dest-url (string-append "https://www.fandom.com" path))
|
||||||
(define host (string-append wikiname ".fandom.com"))
|
(define host (string-append wikiname ".fandom.com"))
|
||||||
(log-outgoing wikiname path)
|
(log-outgoing wikiname path)
|
||||||
|
(define res
|
||||||
(get dest-url
|
(get dest-url
|
||||||
#:timeouts timeouts
|
#:timeouts timeouts
|
||||||
#:headers (hash-set (or headers no-headers) 'Host host)))
|
#:headers (hash-set (or headers no-headers) 'Host host)))
|
||||||
|
(when (memq (response-status-code res) '(403 406))
|
||||||
|
(set! last-failure (current-inexact-milliseconds))
|
||||||
|
(set! stored-failure res))
|
||||||
|
res)))
|
||||||
|
|
||||||
(: fandom-get-api (String (Listof (Pair String String)) [#:headers (Option Headers)] -> Response))
|
(: fandom-get-api (String (Listof (Pair String String)) [#:headers (Option Headers)] -> Response))
|
||||||
(define (fandom-get-api wikiname params #:headers [headers #f])
|
(define (fandom-get-api wikiname params #:headers [headers #f])
|
||||||
|
|
|
||||||
178
src/page-captcha.rkt
Normal file
178
src/page-captcha.rkt
Normal file
|
|
@ -0,0 +1,178 @@
|
||||||
|
#lang racket/base
|
||||||
|
(require racket/class
|
||||||
|
racket/dict
|
||||||
|
racket/draw
|
||||||
|
pict
|
||||||
|
file/convertible
|
||||||
|
racket/format
|
||||||
|
racket/list
|
||||||
|
racket/math
|
||||||
|
racket/match
|
||||||
|
web-server/http
|
||||||
|
(only-in web-server/dispatchers/dispatch next-dispatcher)
|
||||||
|
net/url
|
||||||
|
(only-in net/cookies/server cookie->set-cookie-header cookie-header->alist)
|
||||||
|
html-writing
|
||||||
|
"application-globals.rkt"
|
||||||
|
"data.rkt"
|
||||||
|
"config.rkt"
|
||||||
|
"static-data.rkt"
|
||||||
|
"../lib/url-utils.rkt"
|
||||||
|
"../lib/xexpr-utils.rkt")
|
||||||
|
|
||||||
|
(provide
|
||||||
|
page-captcha
|
||||||
|
page-captcha-image
|
||||||
|
page-captcha-verify)
|
||||||
|
|
||||||
|
(define (get-ip req)
|
||||||
|
(define header
|
||||||
|
(if (config-true? 'captcha::ip_header)
|
||||||
|
(headers-assq* (string->bytes/utf-8 (config-get 'captcha::ip_header)) (request-headers/raw req))
|
||||||
|
#f))
|
||||||
|
(if header
|
||||||
|
(~a (header-value header))
|
||||||
|
(request-client-ip req)))
|
||||||
|
|
||||||
|
(define (get-rng req)
|
||||||
|
(parameterize ([current-pseudo-random-generator (make-pseudo-random-generator)])
|
||||||
|
(define ip-segments (regexp-match* "[0-9]+" (get-ip req)))
|
||||||
|
(define seed
|
||||||
|
(modulo
|
||||||
|
(for/sum ([i (in-naturals)]
|
||||||
|
[s ip-segments])
|
||||||
|
(* (add1 i) (add1 (string->number s))))
|
||||||
|
(expt 2 32)))
|
||||||
|
(random-seed seed)
|
||||||
|
(current-pseudo-random-generator)))
|
||||||
|
|
||||||
|
(define (get-key-solution req)
|
||||||
|
(parameterize ([current-pseudo-random-generator (get-rng req)])
|
||||||
|
(random 1 (add1 9))))
|
||||||
|
|
||||||
|
(define diameter 35)
|
||||||
|
(define font (make-object font% 12 'system))
|
||||||
|
(define msg "I'm not a robot!")
|
||||||
|
(define checkbox (filled-ellipse diameter diameter #:color "Pale Goldenrod"))
|
||||||
|
(define assembly
|
||||||
|
(frame
|
||||||
|
(inset
|
||||||
|
(hc-append
|
||||||
|
8
|
||||||
|
checkbox
|
||||||
|
(text msg font))
|
||||||
|
8)))
|
||||||
|
(define-values (inner-x inner-y) (cc-find assembly checkbox))
|
||||||
|
(define-values (lt-x lt-y) (lt-find assembly checkbox))
|
||||||
|
(define-values (rb-x rb-y) (rb-find assembly checkbox))
|
||||||
|
|
||||||
|
(define (get-coordinate-solution req w h)
|
||||||
|
(parameterize ([current-pseudo-random-generator (get-rng req)])
|
||||||
|
(values (random (exact-truncate lt-x) (exact-truncate (- w rb-x)))
|
||||||
|
(random (exact-truncate lt-y) (exact-truncate (- h rb-y))))))
|
||||||
|
|
||||||
|
(define (page-captcha req)
|
||||||
|
(define cookie-header (headers-assq* #"cookie" (request-headers/raw req)))
|
||||||
|
(define cookies-alist (if cookie-header (cookie-header->alist (header-value cookie-header) bytes->string/utf-8) null))
|
||||||
|
(for ([pair cookies-alist])
|
||||||
|
(match pair
|
||||||
|
[(cons "captcha" method)
|
||||||
|
(when (config-true? 'captcha::log)
|
||||||
|
(printf "captcha skip - via ~a [~a] - ~v~n" method (get-ip req) (url->string (request-uri req))))
|
||||||
|
(next-dispatcher)]
|
||||||
|
[_ (void)]))
|
||||||
|
(response-handler
|
||||||
|
(define body
|
||||||
|
`(*TOP*
|
||||||
|
(*DECL* DOCTYPE html)
|
||||||
|
(html
|
||||||
|
(head
|
||||||
|
(meta (@ (name "viewport") (content "width=device-width, initial-scale=1")))
|
||||||
|
(title "Checking you're not a bot...")
|
||||||
|
(link (@ (rel "stylesheet") (type "text/css") (href ,(get-static-url "internal.css"))))
|
||||||
|
(link (@ (rel "stylesheet") (type "text/css") (href ,(get-static-url "main.css"))))
|
||||||
|
(link (@ (rel "icon") (href ,(head-data^-icon-url head-data-default))))
|
||||||
|
(script (@ (defer) (src "/static/captcha.js")))
|
||||||
|
(body (@ (class "skin-fandomdesktop theme-fandomdesktop-light internal"))
|
||||||
|
(div (@ (class "main-container"))
|
||||||
|
(div (@ (class "fandom-community-header__background tileBoth header")))
|
||||||
|
(div (@ (class "page"))
|
||||||
|
(main (@ (class "page__main"))
|
||||||
|
(div (@ (class "custom-top"))
|
||||||
|
(h1 (@ (class "page-title"))
|
||||||
|
"Checking you're not a bot..."))
|
||||||
|
(div (@ (id "content") #;(class "page-content"))
|
||||||
|
(div (@ (id "mw-content-text"))
|
||||||
|
(p "To confirm, please click directly in the circle, or hold down the " ,(~a (get-key-solution req)) " key on your keyboard.")
|
||||||
|
(noscript (p "JavaScript is required for the captcha. Sorry!"))
|
||||||
|
(div (@ (id "captcha-area")))))
|
||||||
|
,(application-footer #f)))))))))
|
||||||
|
(when (config-true? 'debug)
|
||||||
|
(xexp->html body))
|
||||||
|
(response/output
|
||||||
|
#:code 200
|
||||||
|
#:headers always-headers
|
||||||
|
(λ (out)
|
||||||
|
(write-html body out)))))
|
||||||
|
|
||||||
|
(define (page-captcha-image req)
|
||||||
|
(response-handler
|
||||||
|
(define w (string->number (path/param-path (third (url-path (request-uri req))))))
|
||||||
|
(define h (string->number (path/param-path (fourth (url-path (request-uri req))))))
|
||||||
|
(define-values (at-x at-y) (get-coordinate-solution req w h))
|
||||||
|
(when (config-true? 'captcha::log)
|
||||||
|
(printf "captcha show - size ~a x ~a - solution ~a x ~a [~a]~n" w h at-x at-y (get-ip req)))
|
||||||
|
#;(printf "target: ~a x ~a~ncanvas: ~a x ~a~npict size: ~a-~a ~a-~a~n" at-x at-y x y lt-x rb-x lt-y rb-y)
|
||||||
|
(define dc (make-object bitmap-dc% #f))
|
||||||
|
(send dc set-font font)
|
||||||
|
(define bm (make-object bitmap% w h #f #f))
|
||||||
|
(send dc set-bitmap bm)
|
||||||
|
(draw-pict
|
||||||
|
assembly
|
||||||
|
dc
|
||||||
|
(- at-x inner-x)
|
||||||
|
(- at-y inner-y))
|
||||||
|
(define image (convert bm 'png-bytes))
|
||||||
|
(response/output
|
||||||
|
#:mime-type #"image/png"
|
||||||
|
#:headers (list (header #"Cache-Control" #"no-cache"))
|
||||||
|
(λ (out) (display image out)))))
|
||||||
|
|
||||||
|
(define (page-captcha-verify req)
|
||||||
|
(response-handler
|
||||||
|
(match-define (list w h x y) (for/list ([segment (drop (url-path (request-uri req)) 2)])
|
||||||
|
(string->number (path/param-path segment))))
|
||||||
|
#;(printf "solution: ~a x ~a~ncoordinate: ~a x ~a~ndist^2: ~a~n" solution-x solution-y x y dist)
|
||||||
|
(define headers
|
||||||
|
(build-headers
|
||||||
|
always-headers
|
||||||
|
(cond
|
||||||
|
[(and (= y 0) (= x (get-key-solution req)))
|
||||||
|
(when (config-true? 'captcha::log)
|
||||||
|
(printf "captcha pass - key ~a [~a]~n" x (get-ip req)))
|
||||||
|
|
||||||
|
(header #"Set-Cookie" (cookie->set-cookie-header (make-cookie "captcha" "key" #:path "/" #:max-age (* 60 60 24 365 10))))]
|
||||||
|
[(= y 0)
|
||||||
|
(when (config-true? 'captcha::log)
|
||||||
|
(printf "captcha fail - key ~a instead of ~a [~a]~n" x (get-key-solution req) (get-ip req)))]
|
||||||
|
[else (void)])
|
||||||
|
(when (> y 0)
|
||||||
|
(let-values ([(solution-x solution-y) (get-coordinate-solution req w h)])
|
||||||
|
(let ([dist (+ (expt (- x solution-x) 2) (expt (- y solution-y) 2))])
|
||||||
|
(cond
|
||||||
|
[(dist . < . (expt (/ diameter 2) 2))
|
||||||
|
(when (config-true? 'captcha::log)
|
||||||
|
(printf "captcha pass - coordinate, dist^2 ~a [~a]~n" dist (get-ip req)))
|
||||||
|
(header #"Set-Cookie" (cookie->set-cookie-header (make-cookie "captcha" "coordinate" #:path "/" #:max-age (* 60 60 24 365 10))))]
|
||||||
|
[else
|
||||||
|
(when (config-true? 'captcha::log)
|
||||||
|
(printf "captcha pass - coordinate, dist^2 ~a [~a]~n" dist (get-ip req)))]))))))
|
||||||
|
(match (dict-ref (url-query (request-uri req)) 'from #f)
|
||||||
|
[(? string? dest)
|
||||||
|
(response/output
|
||||||
|
#:code 302
|
||||||
|
#:mime-type #"text/plain"
|
||||||
|
#:headers (cons (header #"Location" (string->bytes/utf-8 dest)) headers)
|
||||||
|
(λ (out)
|
||||||
|
(displayln "Checking your answer..." out)))]
|
||||||
|
[#f (next-dispatcher)])))
|
||||||
69
src/page-wiki-jsonp.rkt
Normal file
69
src/page-wiki-jsonp.rkt
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#lang racket/base
|
||||||
|
(require racket/list
|
||||||
|
racket/string
|
||||||
|
web-server/http
|
||||||
|
net/url-structs
|
||||||
|
html-writing
|
||||||
|
"application-globals.rkt"
|
||||||
|
"data.rkt"
|
||||||
|
"config.rkt"
|
||||||
|
"../lib/url-utils.rkt"
|
||||||
|
"../lib/xexpr-utils.rkt"
|
||||||
|
"../lib/archive-file-mappings.rkt"
|
||||||
|
"static-data.rkt")
|
||||||
|
|
||||||
|
(provide
|
||||||
|
page-wiki-jsonp)
|
||||||
|
|
||||||
|
(define (page-wiki-jsonp req)
|
||||||
|
(response-handler
|
||||||
|
(define wikiname (path/param-path (first (url-path (request-uri req)))))
|
||||||
|
(define segments (map path/param-path (cdr (url-path (request-uri req)))))
|
||||||
|
(define path (string-join (cdr segments) "/"))
|
||||||
|
(define source-url (format "https://~a.fandom.com/wiki/~a" wikiname path))
|
||||||
|
|
||||||
|
(define wiki-page-script-url
|
||||||
|
(format "https://~a.fandom.com/api.php?~a"
|
||||||
|
wikiname
|
||||||
|
(params->query `(("action" . "parse")
|
||||||
|
("page" . ,path)
|
||||||
|
("prop" . "text|headhtml|langlinks")
|
||||||
|
("formatversion" . "2")
|
||||||
|
("format" . "json")
|
||||||
|
("callback" . "wikiPageCallback")))))
|
||||||
|
(define siteinfo-script-url
|
||||||
|
(format "https://~a.fandom.com/api.php?~a"
|
||||||
|
wikiname
|
||||||
|
(params->query `(("action" . "query")
|
||||||
|
("meta" . "siteinfo")
|
||||||
|
("siprop" . "general|rightsinfo")
|
||||||
|
("format" . "json")
|
||||||
|
("formatversion" . "2")
|
||||||
|
("callback" . "siteinfoCallback")))))
|
||||||
|
|
||||||
|
(define body
|
||||||
|
(generate-wiki-page
|
||||||
|
`(div
|
||||||
|
(noscript "You have to enable JavaScript to load wiki pages. Sorry!")
|
||||||
|
(div (@ (id "loading")))
|
||||||
|
(progress (@ (id "progress") (style "margin-bottom: 50vh")))
|
||||||
|
(script ,(format #<<END
|
||||||
|
var wikiname = ~v;
|
||||||
|
var path = ~v;
|
||||||
|
END
|
||||||
|
wikiname path))
|
||||||
|
(script (@ (src ,(get-static-url "jsonp.js"))))
|
||||||
|
(script (@ (async) (src ,wiki-page-script-url)))
|
||||||
|
(script (@ (async) (src ,siteinfo-script-url))))
|
||||||
|
#:req req
|
||||||
|
#:source-url source-url
|
||||||
|
#:wikiname wikiname
|
||||||
|
#:title (url-segments->guess-title segments)
|
||||||
|
#:siteinfo siteinfo-default))
|
||||||
|
(when (config-true? 'debug)
|
||||||
|
(xexp->html body))
|
||||||
|
(response/output
|
||||||
|
#:code 200
|
||||||
|
#:headers always-headers
|
||||||
|
(λ (out)
|
||||||
|
(write-html body out)))))
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
racket/string
|
racket/string
|
||||||
; libs
|
; libs
|
||||||
(prefix-in easy: net/http-easy)
|
(prefix-in easy: net/http-easy)
|
||||||
|
json
|
||||||
; html libs
|
; html libs
|
||||||
"../lib/html-parsing/main.rkt"
|
"../lib/html-parsing/main.rkt"
|
||||||
html-writing
|
html-writing
|
||||||
|
|
@ -18,8 +19,7 @@
|
||||||
"config.rkt"
|
"config.rkt"
|
||||||
"data.rkt"
|
"data.rkt"
|
||||||
"fandom-request.rkt"
|
"fandom-request.rkt"
|
||||||
"../lib/pure-utils.rkt"
|
"../lib/archive-file-mappings.rkt"
|
||||||
"../lib/syntax.rkt"
|
|
||||||
"../lib/thread-utils.rkt"
|
"../lib/thread-utils.rkt"
|
||||||
"../lib/tree-updater.rkt"
|
"../lib/tree-updater.rkt"
|
||||||
"../lib/url-utils.rkt"
|
"../lib/url-utils.rkt"
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
(provide
|
(provide
|
||||||
; used by the web server
|
; used by the web server
|
||||||
page-wiki
|
page-wiki
|
||||||
|
page-wiki-with-data
|
||||||
; used by page-category, and similar pages that are partially wiki pages
|
; used by page-category, and similar pages that are partially wiki pages
|
||||||
update-tree-wiki
|
update-tree-wiki
|
||||||
preprocess-html-wiki)
|
preprocess-html-wiki)
|
||||||
|
|
@ -37,8 +38,9 @@
|
||||||
|
|
||||||
(define (page-wiki req)
|
(define (page-wiki req)
|
||||||
(define wikiname (path/param-path (first (url-path (request-uri req)))))
|
(define wikiname (path/param-path (first (url-path (request-uri req)))))
|
||||||
|
(define segments (map path/param-path (cdr (url-path (request-uri req)))))
|
||||||
(define user-cookies (user-cookies-getter req))
|
(define user-cookies (user-cookies-getter req))
|
||||||
(define path (string-join (map path/param-path (cddr (url-path (request-uri req)))) "/"))
|
(define path (string-join (cdr segments) "/"))
|
||||||
(define source-url (format "https://~a.fandom.com/wiki/~a" wikiname path))
|
(define source-url (format "https://~a.fandom.com/wiki/~a" wikiname path))
|
||||||
|
|
||||||
(define-values (dest-res siteinfo)
|
(define-values (dest-res siteinfo)
|
||||||
|
|
@ -57,14 +59,63 @@
|
||||||
|
|
||||||
(cond
|
(cond
|
||||||
[(eq? 200 (easy:response-status-code dest-res))
|
[(eq? 200 (easy:response-status-code dest-res))
|
||||||
(let* ([data (easy:response-json dest-res)]
|
(let ([data (easy:response-json dest-res)])
|
||||||
[title (jp "/parse/title" data "")]
|
|
||||||
[page-html (jp "/parse/text" data "")]
|
|
||||||
[page-html (preprocess-html-wiki page-html)]
|
|
||||||
[page (html->xexp page-html)]
|
|
||||||
[head-data ((head-data-getter wikiname) data)])
|
|
||||||
(if (equal? "missingtitle" (jp "/error/code" data #f))
|
(if (equal? "missingtitle" (jp "/error/code" data #f))
|
||||||
(next-dispatcher)
|
(next-dispatcher)
|
||||||
|
(take-json-rewrite-and-return-page data)))]
|
||||||
|
[(eq? 404 (easy:response-status-code dest-res))
|
||||||
|
(next-dispatcher)]
|
||||||
|
[(memq (easy:response-status-code dest-res) '(403 406))
|
||||||
|
(response-handler
|
||||||
|
(define body
|
||||||
|
(generate-wiki-page
|
||||||
|
`(div
|
||||||
|
(p "Sorry! Fandom isn't allowing BreezeWiki to show pages right now.")
|
||||||
|
(p "We'll automatically try again in 30 seconds, so please stay on this page and be patient.")
|
||||||
|
(p (small "In a hurry? " (a (@ (href ,source-url)) "Click here to read the page on Fandom."))))
|
||||||
|
#:req req
|
||||||
|
#:source-url source-url
|
||||||
|
#:wikiname wikiname
|
||||||
|
#:title (url-segments->guess-title segments)
|
||||||
|
#:siteinfo siteinfo))
|
||||||
|
(response/output
|
||||||
|
#:code 503
|
||||||
|
#:headers (build-headers
|
||||||
|
always-headers
|
||||||
|
(header #"Retry-After" #"30")
|
||||||
|
(header #"Cache-Control" #"max-age=30, public")
|
||||||
|
(header #"Refresh" #"35"))
|
||||||
|
(λ (out)
|
||||||
|
(write-html body out))))]
|
||||||
|
[else
|
||||||
|
(response-handler
|
||||||
|
(error 'page-wiki "Tried to load page ~a/~a~nSadly, the page didn't load because Fandom returned status code ~a with response:~n~a"
|
||||||
|
wikiname
|
||||||
|
path
|
||||||
|
(easy:response-status-code dest-res)
|
||||||
|
(easy:response-body dest-res)))]))
|
||||||
|
|
||||||
|
(define (page-wiki-with-data req)
|
||||||
|
(response-handler
|
||||||
|
(define post-data/bytes (request-post-data/raw req))
|
||||||
|
(when (not post-data/bytes)
|
||||||
|
(raise-user-error 'page-wiki-with-data "POST requests only, please."))
|
||||||
|
(define post-data/string (bytes->string/utf-8 post-data/bytes))
|
||||||
|
(define post-data (string->jsexpr post-data/string))
|
||||||
|
(define wikiname (jp "/wikiname" post-data))
|
||||||
|
(define path (jp "/path" post-data))
|
||||||
|
(take-json-rewrite-and-return-page
|
||||||
|
#:req req
|
||||||
|
#:wikiname wikiname
|
||||||
|
#:source-url (format "https://~a.fandom.com/wiki/~a" wikiname path)
|
||||||
|
#:data (jp "/data" post-data)
|
||||||
|
#:siteinfo (data->siteinfo (jp "/siteinfo" post-data)))))
|
||||||
|
|
||||||
|
(define (take-json-rewrite-and-return-page #:req req #:wikiname wikiname #:source-url source-url #:data data #:siteinfo siteinfo)
|
||||||
|
(define title (jp "/parse/title" data ""))
|
||||||
|
(define page-html (preprocess-html-wiki (jp "/parse/text" data "")))
|
||||||
|
(define page (html->xexp page-html))
|
||||||
|
(define head-data ((head-data-getter wikiname) data))
|
||||||
(response-handler
|
(response-handler
|
||||||
(define body
|
(define body
|
||||||
(generate-wiki-page
|
(generate-wiki-page
|
||||||
|
|
@ -80,6 +131,7 @@
|
||||||
(define redirect-msg-a (if redirect-msg
|
(define redirect-msg-a (if redirect-msg
|
||||||
((query-selector (λ (t a c) (eq? t 'a)) redirect-msg))
|
((query-selector (λ (t a c) (eq? t 'a)) redirect-msg))
|
||||||
#f))
|
#f))
|
||||||
|
(define html (xexp->html-bytes body))
|
||||||
(define headers
|
(define headers
|
||||||
(build-headers
|
(build-headers
|
||||||
always-headers
|
always-headers
|
||||||
|
|
@ -90,21 +142,10 @@
|
||||||
(let* ([dest (get-attribute 'href (bits->attributes redirect-msg-a))]
|
(let* ([dest (get-attribute 'href (bits->attributes redirect-msg-a))]
|
||||||
[value (bytes-append #"0;url=" (string->bytes/utf-8 dest))])
|
[value (bytes-append #"0;url=" (string->bytes/utf-8 dest))])
|
||||||
(header #"Refresh" value)))))
|
(header #"Refresh" value)))))
|
||||||
(when (config-true? 'debug)
|
(response/full
|
||||||
; used for its side effects
|
200
|
||||||
; convert to string with error checking, error will be raised if xexp is invalid
|
#"OK"
|
||||||
(xexp->html body))
|
(current-seconds)
|
||||||
(response/output
|
#"text/html; charset=utf-8"
|
||||||
#:code 200
|
headers
|
||||||
#:headers headers
|
(list html))))
|
||||||
(λ (out)
|
|
||||||
(write-html body out))))))]
|
|
||||||
[(eq? 404 (easy:response-status-code dest-res))
|
|
||||||
(next-dispatcher)]
|
|
||||||
[else
|
|
||||||
(response-handler
|
|
||||||
(error 'page-wiki "Tried to load page ~a/~v~nSadly, the page didn't load because Fandom returned status code ~a with response:~n~a"
|
|
||||||
wikiname
|
|
||||||
path
|
|
||||||
(easy:response-status-code dest-res)
|
|
||||||
(easy:response-body dest-res)))]))
|
|
||||||
|
|
|
||||||
24
static/captcha.js
Normal file
24
static/captcha.js
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
const u = new URL(location)
|
||||||
|
const from = u.searchParams.get("from") || location.href
|
||||||
|
let answered = false
|
||||||
|
|
||||||
|
const area = document.getElementById("captcha-area")
|
||||||
|
const areaBox = area.getBoundingClientRect()
|
||||||
|
const width = Math.floor(areaBox.width)
|
||||||
|
const height = Math.floor(window.innerHeight - areaBox.bottom - areaBox.left)
|
||||||
|
const img = document.createElement("img")
|
||||||
|
img.src = `/captcha/img/${width}/${height}`
|
||||||
|
img.addEventListener("click", event => {
|
||||||
|
if (answered) return
|
||||||
|
answered = true
|
||||||
|
location = `/captcha/verify/${width}/${height}/${event.offsetX}/${event.offsetY}?` + new URLSearchParams({from})
|
||||||
|
})
|
||||||
|
area.appendChild(img)
|
||||||
|
|
||||||
|
document.addEventListener("keydown", event => {
|
||||||
|
if (event.repeat) {
|
||||||
|
if (answered) return
|
||||||
|
answered = true
|
||||||
|
location = `/captcha/verify/0/0/${event.key}/0?` + new URLSearchParams({from})
|
||||||
|
}
|
||||||
|
})
|
||||||
59
static/jsonp.js
Normal file
59
static/jsonp.js
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
const loading = document.getElementById("loading")
|
||||||
|
loading.textContent = "Loading, please wait..."
|
||||||
|
const progress = document.getElementById("progress")
|
||||||
|
|
||||||
|
let wikiPage = null
|
||||||
|
function wikiPageCallback(data) {
|
||||||
|
wikiPage = data
|
||||||
|
cont()
|
||||||
|
}
|
||||||
|
|
||||||
|
let siteinfo = null
|
||||||
|
function siteinfoCallback(data) {
|
||||||
|
siteinfo = data
|
||||||
|
cont()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cont() {
|
||||||
|
if (!(wikiPage && siteinfo)) return
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
const uploadFraction = 0.7
|
||||||
|
|
||||||
|
// Upload progress
|
||||||
|
xhr.upload.addEventListener("progress", event => {
|
||||||
|
if (event.lengthComputable) {
|
||||||
|
progress.value = (event.loaded / event.total) * uploadFraction
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Download progress
|
||||||
|
xhr.addEventListener("progress", event => {
|
||||||
|
if (event.lengthComputable) {
|
||||||
|
progress.value = (event.loaded / event.total) * (1 - uploadFraction) + uploadFraction
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
xhr.addEventListener("load", () => {
|
||||||
|
console.log(xhr)
|
||||||
|
const imported = document.importNode(xhr.responseXML.getElementById("content"), true)
|
||||||
|
document.getElementById("content").replaceWith(imported)
|
||||||
|
document.title = xhr.responseXML.title
|
||||||
|
for (const e of xhr.responseXML.head.children) {
|
||||||
|
if (["LINK"].includes(e.tagName)) {
|
||||||
|
const imported = document.importNode(e, true)
|
||||||
|
document.head.appendChild(imported)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
xhr.open("POST", "/api/render/wiki")
|
||||||
|
xhr.responseType = "document"
|
||||||
|
xhr.send(JSON.stringify({
|
||||||
|
data: wikiPage,
|
||||||
|
siteinfo,
|
||||||
|
wikiname,
|
||||||
|
path
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
@ -2,3 +2,4 @@ User-Agent: *
|
||||||
Disallow: /*/wiki/*
|
Disallow: /*/wiki/*
|
||||||
Disallow: /proxy
|
Disallow: /proxy
|
||||||
Disallow: /set-user-settings
|
Disallow: /set-user-settings
|
||||||
|
Disallow: /captcha
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue