diff --git a/breezewiki.rkt b/breezewiki.rkt index 17b619b..6b3d5eb 100644 --- a/breezewiki.rkt +++ b/breezewiki.rkt @@ -27,9 +27,7 @@ (require-reloadable "src/page-static-archive.rkt" page-static-archive) (require-reloadable "src/page-subdomain.rkt" subdomain-dispatcher) (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-jsonp.rkt" page-wiki-jsonp) (require-reloadable "src/page-file.rkt" page-file) (reload!) @@ -59,9 +57,7 @@ page-set-user-settings page-static-archive page-wiki - page-wiki-with-data page-wiki-offline - page-wiki-jsonp page-file redirect-wiki-home static-dispatcher diff --git a/dist.rkt b/dist.rkt index 491ca80..1e7096e 100644 --- a/dist.rkt +++ b/dist.rkt @@ -16,9 +16,8 @@ (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-subdomain.rkt" subdomain-dispatcher)) -(require (only-in "src/page-wiki.rkt" page-wiki page-wiki-with-data)) +(require (only-in "src/page-wiki.rkt" page-wiki)) (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)) (serve/launch/wait @@ -43,8 +42,6 @@ page-static-archive page-wiki page-wiki-offline - page-wiki-with-data - page-wiki-jsonp page-file redirect-wiki-home static-dispatcher diff --git a/lib/tree-updater.rkt b/lib/tree-updater.rkt index f941285..c1af714 100644 --- a/lib/tree-updater.rkt +++ b/lib/tree-updater.rkt @@ -12,7 +12,7 @@ update-tree-wiki) (define (preprocess-html-wiki html) - (regexp-replace* #rx"(<(?:td|figcaption)[^>]*?>\n?)(?:
(.*?)
)" + (regexp-replace* #rx"(<(?:td|figcaption)[^>]*?>\n?)(?:[ \t]*)?(?:(.*?)
)" html (λ (whole first-tag [contents #f]) (if (eq? (string-ref whole 1) #\f) ;; figcaption (string-append first-tag "" contents "") diff --git a/src/dispatcher-tree.rkt b/src/dispatcher-tree.rkt index 0bbf8c8..fb9fa1f 100644 --- a/src/dispatcher-tree.rkt +++ b/src/dispatcher-tree.rkt @@ -55,6 +55,7 @@ (define tree (sequencer:make subdomain-dispatcher + (pathprocedure:make "/buddyfight/wiki/It_Doesn't_Work!!" (page ds page-it-works)) (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))) @@ -65,16 +66,11 @@ (pathprocedure:make "/proxy" (page ds page-proxy)) (pathprocedure:make "/search" (page ds page-global-search)) (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 "/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/File:.+$" px-wikiname)) (lift:make (page ds page-file))) (if (config-true? 'feature_offline::enabled) (filter:make (pregexp (format "^/~a/wiki/.+$" px-wikiname)) (lift:make (page ds page-wiki-offline))) (λ (_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/search$" px-wikiname)) (lift:make (page ds page-search))) (filter:make (pregexp (format "^/~a(/(wiki(/)?)?)?$" px-wikiname)) (lift:make (page ds redirect-wiki-home))) diff --git a/src/endpoints.rkt b/src/endpoints.rkt new file mode 100644 index 0000000..7b2115a --- /dev/null +++ b/src/endpoints.rkt @@ -0,0 +1,248 @@ +#lang racket/base +(require racket/format + racket/match + racket/string + ; libs + (prefix-in easy: net/http-easy) + json + ; html libs + html-writing + ; web server libs + web-server/http + web-server/dispatchers/dispatch + ; my libs + "application-globals.rkt" + "config.rkt" + "data.rkt" + "fandom-request.rkt" + "static-data.rkt" + "../lib/archive-file-mappings.rkt" + "../lib/thread-utils.rkt" + "../lib/url-utils.rkt" + "../lib/xexpr-utils.rkt") +(require (for-syntax racket/base syntax/parse)) + +(provide + define-endpoint + define-standard-handler + define-post-data-handler + define-jsonp-handler + make-switch-handler + (all-from-out racket/format + racket/match + json + net/http-easy + web-server/dispatchers/dispatch + "../lib/thread-utils.rkt" + "../lib/archive-file-mappings.rkt" + "config.rkt" + "data.rkt" + "fandom-request.rkt" + "static-data.rkt")) + + +(define-for-syntax (fix-scopes here orig stx) + (define remover (make-syntax-delta-introducer here #f)) + (define introducer (make-syntax-delta-introducer orig #f)) + (introducer (remover stx 'remove))) + + +(define-syntax (define-endpoint stx) + (syntax-parse stx + [(_ name + (~and (~seq all ...) + (~seq + ((~datum variables) variable ...) + ((~datum endpoints) endpoint ...) + ((~datum render) render ...)))) + #'(define-syntax name #'(all ...))])) + + +(define ((make-switch-handler #:standard standard #:jsonp jsonp #:post post) req) + (cond + [(equal? (request-method req) #"POST") + (post req)] + [(config-true? 'feature_jsonp::enabled) + (jsonp req)] + [else + (standard req)])) + + +(define-syntax (define-standard-handler stx) + (syntax-parse stx + [(_ (~and name-args (name:id arg:id ...)) endpoint-ref:expr fn-body ...) + #:with endpoint-syntax (syntax-local-value (cadr (syntax->list #'endpoint-ref))) + (syntax-parse #'endpoint-syntax + #:context 'endpoint-data + [(((~datum variables) variable ...) + ((~datum endpoints) (endpoint-id:id (endpoint-param:expr ...)) ...) + ((~datum render) render ...)) + #:with endpoint-getters (for/list ([id (syntax-e #'(endpoint-id ...))] + [params (syntax-e #'((endpoint-param ...) ...))]) + (with-syntax ([params (append (syntax-e params) #;'(("callback" . "proxy.wikipage")))]) + (if (eq? (syntax-e id) 'siteinfo) + #'(λ () + (siteinfo-fetch wikiname)) + #'(λ () + (fandom-get-api + wikiname + `params + #:headers `#hasheq((cookie . ,(format "theme=~a" (user-cookies^-theme user-cookies))))))))) + #:with endpoint-intermediates->values (for/list ([id (syntax-e #'(endpoint-id ...))]) + (with-syntax ([id id]) + (if (eq? (syntax-e #'id) 'siteinfo) + #'id + #'(easy:response-json id)))) + + (fix-scopes + #'here stx + #'(define name-args + (response-handler + (let/cc k + variable ... + (define user-cookies (user-cookies-getter req)) + (define-values (endpoint-id ...) + (let-values ([(endpoint-id ...) (thread-values (~@ . endpoint-getters))]) + (for ([response (list endpoint-id ...)] + #:when (easy:response? response)) + (match (easy:response-status-code response) + [404 + (next-dispatcher)] + [(or 403 406) + (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 title + #:siteinfo siteinfo)) + (k (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))))] + [(not 200) + (k (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 response) + (easy:response-body response)))] + [_ (void)])) + (values (~@ . endpoint-intermediates->values)))) + fn-body ... + render ...))))])])) + + +(define-syntax (define-post-data-handler stx) + (syntax-parse stx + [(_ (~and name-args (name:id arg:id ...)) endpoint-ref:expr fn-body ...) + #:with endpoint-syntax (syntax-local-value (cadr (syntax->list #'endpoint-ref))) + (syntax-parse #'endpoint-syntax + #:context 'endpoint-data + [(((~datum variables) variable ...) + ((~datum endpoints) (endpoint-id:id (endpoint-param:expr ...)) ...) + ((~datum render) render ...)) + #:with endpoint-getters (for/list ([id (syntax-e #'(endpoint-id ...))] + [params (syntax-e #'((endpoint-param ...) ...))]) + (with-syntax ([id id]) + (if (eq? (syntax-e #'id) 'siteinfo) + #'(data->siteinfo (hash-ref post-data 'id)) + #'(hash-ref post-data 'id)))) + + (fix-scopes + #'here stx + #'(define name-args + (response-handler + (let/cc k + (define (k-validation-error message) + (k (response/jsexpr + #:code 400 + #:headers always-headers + `#hasheq((error . + #hasheq((code . "breezewiki") + (info . ,message))))))) + (define post-data/bytes (request-post-data/raw req)) + (when (not post-data/bytes) + (k-validation-error "POST requests only, please.")) + + (define origin-header + (or (headers-assq* #"origin" (request-headers/raw req)) + (headers-assq* #"referer" (request-headers/raw req)))) + (when (or (not origin-header) (not (string-prefix? (bytes->string/latin-1 (header-value origin-header)) (config-get 'canonical_origin)))) + (k-validation-error "Origin/Referer header failed validation - cross-origin requests are not allowed here")) + + (define post-data/string (bytes->string/utf-8 post-data/bytes)) + (define post-data (string->jsexpr post-data/string)) + (define-values (endpoint-id ...) (values (~@ . endpoint-getters))) + (define wikiname (hash-ref post-data 'wikiname)) + (define path (hash-ref post-data 'path)) + (define source-url (format "https://~a.fandom.com/wiki/~a" wikiname path)) + fn-body ... + render ...))))])])) + + +(define-syntax (define-jsonp-handler stx) + (syntax-parse stx + [(_ (~and name-args (name:id arg:id ...)) endpoint-ref:expr fn-body ...) + #:with endpoint-syntax (syntax-local-value (cadr (syntax->list #'endpoint-ref))) + (syntax-parse #'endpoint-syntax + #:context 'endpoint-data + [(((~datum variables) variable ...) + ((~datum endpoints) (endpoint-id:id (endpoint-param:expr ...)) ...) + ((~datum render) render ...)) + #:with endpoint-scripts (for/list ([id (syntax-e #'(endpoint-id ...))] + [params (syntax-e #'((endpoint-param ...) ...))]) + (with-syntax ([id id] + [params (append (syntax-e params) + `(("callback" . ,(format "proxy.~a" (syntax-e id)))))]) + #'(script (@ (async) + (src ,(format "https://~a.fandom.com/api.php?~a" + wikiname + (params->query `params))) + (data-jsonp-var ,(format "~a" 'id)) + (onerror ,(format "proxy.~a({error: {code: 'disconnected', info: 'Fandom connection failed or was blocked by your browser. Check any browser extensions that may block third-party scripts, then reload the page.'}})" 'id)))))) + + (fix-scopes + #'here stx + #'(define name-args + (response-handler + (let/cc k + variable ... + fn-body ... + + (define body + (generate-wiki-page + `(div + (noscript "You have to enable JavaScript to load wiki pages. Sorry!") + (div (@ (id "loading"))) + (div (@ (id "progress-bar") (style "margin-bottom: 50vh")) + (progress)) + (script #<BreezeWiki ran into an error on this page.
Try reloading the page.
If this keeps happening, you could send a public bug report. Please include the following information:
URL: ${window.location.href}${"\n"}${data}`, eContent)
} else if (data.error.code === "missingtitle") {
- render(html`This page doesn't exist on Fandom.
`, eContent) + render(html`This page doesn't exist on Fandom.
Return to the wiki's main page
`, eContent) } else { render(html`BreezeWiki wasn't able to load this page.
${data.error.code}: ${data.error.info}
`, eContent) }