2022-08-23 09:57:42 +00:00
#lang racket/base
2022-11-29 11:03:54 +00:00
( require racket/file
racket/list
2022-11-29 11:36:53 +00:00
racket/runtime-path
2022-10-30 10:15:26 +00:00
racket/string
2022-10-22 11:26:06 +00:00
json
2022-09-09 03:42:20 +00:00
( prefix-in easy: net/http-easy )
2022-11-29 11:03:54 +00:00
html-parsing
2022-09-09 03:42:20 +00:00
html-writing
web-server/http
2023-02-05 04:56:15 +00:00
web-server/http/bindings
2022-09-08 02:05:47 +00:00
" config.rkt "
2022-09-16 13:56:03 +00:00
" data.rkt "
2023-02-05 04:56:15 +00:00
" extwiki-data.rkt "
" extwiki-generic.rkt "
2022-10-30 10:51:15 +00:00
" static-data.rkt "
2023-02-05 04:56:15 +00:00
" ../lib/syntax.rkt "
" ../lib/pure-utils.rkt "
" ../lib/xexpr-utils.rkt "
" ../lib/url-utils.rkt " )
2022-08-23 09:57:42 +00:00
( provide
2022-10-09 10:43:21 +00:00
; headers to always send on all http responses
always-headers
2022-08-23 09:57:42 +00:00
; timeout durations for http-easy requests
timeouts
2022-09-04 13:58:45 +00:00
; generates a consistent footer
application-footer
2022-08-23 09:57:42 +00:00
; generates a consistent template for wiki page content to sit in
2022-09-09 03:42:20 +00:00
generate-wiki-page
; generates a minimal but complete redirect to another page
generate-redirect )
2022-08-23 09:57:42 +00:00
( module+ test
( require rackunit
2022-11-29 11:36:53 +00:00
html-writing
" test-utils.rkt " ) )
2022-08-23 09:57:42 +00:00
2022-10-09 10:43:21 +00:00
( define always-headers
2022-11-11 10:08:06 +00:00
( list ( header #" Referrer-Policy " #" same-origin " ) ; header to not send referers to fandom
( header #" Link " ( string->bytes/latin-1 link-header ) ) ) )
2022-09-09 03:42:20 +00:00
( define timeouts ( easy:make-timeout-config #:lease 5 #:connect 5 ) )
2022-08-23 09:57:42 +00:00
2022-11-29 11:36:53 +00:00
( define-runtime-path path-static " ../static " )
2022-11-29 11:03:54 +00:00
( define theme-icons
( for/hasheq ( [ theme ' ( default light dark ) ] )
( values theme
2022-11-29 11:36:53 +00:00
( html->xexp ( file->string ( build-path path-static ( format " icon-theme-~a.svg " theme ) ) #:mode ' binary ) ) ) ) )
2022-11-29 11:03:54 +00:00
2022-09-16 13:56:03 +00:00
( define ( application-footer source-url #:license [ license-in #f ] )
( define license ( or license-in license-default ) )
2022-09-04 13:58:45 +00:00
` ( footer ( @ ( class " custom-footer " ) )
( div ( @ ( class , ( if source-url " custom-footer__cols " " internal-footer " ) ) )
( div ( p
2022-10-30 10:51:15 +00:00
( img ( @ ( class " my-logo " ) ( src , ( get-static-url " breezewiki.svg " ) ) ) ) )
2022-09-04 13:58:45 +00:00
( p
( a ( @ ( href " https://gitdab.com/cadence/breezewiki " ) )
, ( format " ~a source code " ( config-get ' application_name ) ) ) )
( p
( a ( @ ( href " https://docs.breezewiki.com " ) )
" Documentation and more information " ) )
( p
( a ( @ ( href " https://lists.sr.ht/~cadence/breezewiki-discuss " ) )
" Discussions / Bug reports / Feature requests " ) )
, ( if ( config-true? ' instance_is_official )
` ( p , ( format " This instance is run by the ~a developer, " ( config-get ' application_name ) )
( a ( @ ( href " https://cadence.moe/contact " ) )
" Cadence. " ) )
` ( p
, ( format " This unofficial instance is based off the ~a source code, but is not controlled by the code developer. " ( config-get ' application_name ) ) ) ) )
, ( if source-url
` ( div ( p " This page displays proxied content from "
2022-12-10 05:57:05 +00:00
( a ( @ ( href , source-url ) ( rel " nofollow noreferrer " ) ) , source-url )
2022-10-09 09:50:50 +00:00
, ( format " . Text content is available under the ~a license, " ( license^-text license ) )
2022-12-10 06:16:28 +00:00
( a ( @ ( href , ( license^-url license ) ) ( rel " nofollow " ) ) " see license info. " )
2022-09-04 13:58:45 +00:00
" Media files may have different copying restrictions. " )
( p , ( format " Fandom is a trademark of Fandom, Inc. ~a is not affiliated with Fandom. " ( config-get ' application_name ) ) ) )
` ( div ( p " Text content on wikis run by Fandom is available under the Creative Commons Attribution-Share Alike License 3.0 (Unported), "
2022-12-10 05:57:05 +00:00
( a ( @ ( href " https://www.fandom.com/licensing " ) ( rel " nofollow " ) ) " see license info. " )
2022-09-04 13:58:45 +00:00
" Media files and official Fandom documents have different copying restrictions. " )
( p , ( format " Fandom is a trademark of Fandom, Inc. ~a is not affiliated with Fandom. " ( config-get ' application_name ) ) ) ) ) ) ) )
2022-10-30 10:15:26 +00:00
;; generate a notice with a link if a fandom wiki has a replacement as part of NIWA or similar
;; if the wiki has no replacement, display nothing
2023-02-05 04:56:15 +00:00
( define ( extwiki-notice wikiname title )
( define xt ( findf ( λ ( item ) ( member wikiname ( extwiki^-wikinames item ) ) ) extwikis ) )
( cond/var
[ xt
( let* ( [ group ( hash-ref extwiki-groups ( extwiki^-group xt ) ) ]
[ search-page ( format " /Special:Search?~a "
( params->query ` ( ( " search " . , title )
( " go " . " Go " ) ) ) ) ]
[ go ( if ( string-suffix? ( extwiki^-home xt ) " / " )
( regexp-replace #rx"/$" ( extwiki^-home xt ) ( λ ( _ ) search-page ) )
( let* ( [ joiner ( second ( regexp-match #rx"/(w[^./]*)/" ( extwiki^-home xt ) ) ) ] )
( regexp-replace #rx"/w[^./]*/.*$" ( extwiki^-home xt ) ( λ ( _ ) ( format " /~a~a " joiner search-page ) ) ) ) ) ]
[ props ( extwiki-props^ go ) ] )
( cond
[ ( eq? ( extwiki^-banner xt ) ' default )
` ( aside ( @ ( class " niwa__notice " ) )
( h1 ( @ ( class " niwa__header " ) ) , ( extwiki^-name xt ) " has its own website separate from Fandom. " )
( a ( @ ( class " niwa__go " ) ( href , go ) ) " Read " , title " on " , ( extwiki^-name xt ) " → " )
( div ( @ ( class " niwa__cols " ) )
( div ( @ ( class " niwa__left " ) )
( p , ( ( extwiki-group^-description group ) props ) )
( p , ( ( extwiki^-description xt ) props ) )
( p " This wiki's core community has wholly migrated away from Fandom. You should "
( a ( @ ( href , go ) ) " go to " , ( extwiki^-name xt ) " now! " ) )
( p ( @ ( class " niwa__feedback " ) )
,@ ( add-between
` ( ,@ ( for/list ( [ link ( extwiki-group^-links group ) ] )
` ( a ( @ ( href , ( cdr link ) ) ) , ( car link ) ) )
" This notice is from BreezeWiki "
( a ( @ ( href " https://docs.breezewiki.com/Reporting_Bugs.html " ) ) " Feedback? " ) )
" / " ) ) )
( div ( @ ( class " niwa__right " ) )
( img ( @ ( class " niwa__logo " ) ( src , ( extwiki^-logo xt ) ) ) ) ) ) ) ]
[ ( eq? ( extwiki^-banner xt ) ' parallel )
` ( aside ( @ ( class " niwa__parallel " ) )
( h1 ( @ ( class " niwa__header-mini " ) )
" See also "
( a ( @ ( href , go ) ) , ( extwiki^-name xt ) ) )
( p " This topic has multiple communities of editors, some active on the Fandom wiki, others active on " , ( extwiki^-name xt ) " . " )
( p " For thorough research, be sure to check both communities since they may have different information! " )
( p ( @ ( class " niwa__feedback " ) )
,@ ( add-between
` ( ,@ ( for/list ( [ link ( extwiki-group^-links group ) ] )
` ( a ( @ ( href , ( cdr link ) ) ) , ( car link ) ) )
" This notice is from BreezeWiki "
( a ( @ ( href " https://docs.breezewiki.com/Reporting_Bugs.html " ) ) " Feedback? " ) )
" / " ) ) ) ]
[ ( eq? ( extwiki^-banner xt ) ' empty )
` ( aside ( @ ( class " niwa__notice niwa__notice--alt " ) )
( h1 ( @ ( class " niwa__header " ) ) " You will be redirected to " , ( extwiki^-name xt ) " . " )
( p ( @ ( style " position: relative; top: -12px; " ) ) " This independent wiki community has its own site separate from Fandom. " )
( a ( @ ( class " niwa__go " ) ( href , go ) ) " Take me there! → " )
( p ( @ ( class " niwa__feedback " ) ( style " text-align: left " ) )
,@ ( add-between
` ( ,@ ( for/list ( [ link ( extwiki-group^-links group ) ] )
` ( a ( @ ( href , ( cdr link ) ) ) , ( car link ) ) )
" This notice is from BreezeWiki " )
" / " ) ) ) ] ) ) ]
( var fetched-callback ( get-redirect-content wikiname ) )
[ fetched-callback
( fetched-callback title ) ]
[ #t " " ] ) )
2022-10-30 10:15:26 +00:00
2022-09-16 12:56:05 +00:00
( define ( generate-wiki-page
content
2022-11-29 11:03:54 +00:00
#:req req
2022-09-16 12:56:05 +00:00
#:source-url source-url
#:wikiname wikiname
#:title title
2022-10-31 06:39:19 +00:00
#:head-data [ head-data-in #f ]
2022-11-29 11:03:54 +00:00
#:siteinfo [ siteinfo-in #f ]
2023-02-05 04:56:15 +00:00
#:user-cookies [ user-cookies-in #f ]
#:online-styles [ online-styles #t ] )
2022-10-09 09:50:50 +00:00
( define siteinfo ( or siteinfo-in siteinfo-default ) )
2022-10-31 06:39:19 +00:00
( define head-data ( or head-data-in ( ( head-data-getter wikiname ) ) ) )
2022-11-29 11:03:54 +00:00
( define user-cookies ( or user-cookies-in ( user-cookies-getter req ) ) )
2023-02-05 04:56:15 +00:00
( define origin ( format " https://~a.fandom.com " wikiname ) )
( define required-styles
( cond
[ online-styles
( define styles
( list
( format " ~a/wikia.php?controller=ThemeApi&method=themeVariables&variant=~a " origin ( user-cookies^-theme user-cookies ) )
( format " ~a/load.php?lang=en&modules=skin.fandomdesktop.styles%7Cext.fandom.PortableInfoboxFandomDesktop.css%7Cext.fandom.GlobalComponents.CommunityHeaderBackground.css%7Cext.gadget.site-styles%2Csound-styles%7Csite.styles&only=styles&skin=fandomdesktop " origin ) ) )
( if ( config-true? ' strict_proxy )
( map u-proxy-url styles )
styles ) ]
[ #t
( list
( format " /archive/~a/styles/themeVariables-~a.css " wikiname ( user-cookies^-theme user-cookies ) )
( format " /archive/~a/styles/site.css " wikiname ) ) ] ) )
2022-11-05 10:40:05 +00:00
` ( *TOP*
( *DECL* DOCTYPE html )
( html
( head
( meta ( @ ( name " viewport " ) ( content " width=device-width, initial-scale=1 " ) ) )
( title , ( format " ~a | ~a+~a "
title
( regexp-replace #rx" ?Wiki$" ( siteinfo^-sitename siteinfo ) " " )
( config-get ' application_name ) ) )
,@ ( map ( λ ( url )
` ( link ( @ ( rel " stylesheet " ) ( type " text/css " ) ( href , url ) ) ) )
2023-02-05 04:56:15 +00:00
required-styles )
2022-11-05 10:40:05 +00:00
( link ( @ ( rel " stylesheet " ) ( type " text/css " ) ( href , ( get-static-url " main.css " ) ) ) )
( script " const BWData = "
, ( jsexpr->string ( hasheq ' wikiname wikiname
' strict_proxy ( config-true? ' strict_proxy ) ) ) )
, ( if ( config-true? ' feature_search_suggestions )
` ( script ( @ ( type " module " ) ( src , ( get-static-url " search-suggestions.js " ) ) ) )
" " )
2022-11-15 07:46:56 +00:00
( script ( @ ( type " module " ) ( src , ( get-static-url " countdown.js " ) ) ) )
2022-11-05 10:40:05 +00:00
( link ( @ ( rel " icon " ) ( href , ( u ( λ ( v ) ( config-true? ' strict_proxy ) )
( λ ( v ) ( u-proxy-url v ) )
( head-data^-icon-url head-data ) ) ) ) ) )
( body ( @ ( class , ( head-data^-body-class head-data ) ) )
2023-02-05 04:56:15 +00:00
, ( if ( config-true? ' instance_is_official )
( let ( [ balloon ' ( img ( @ ( src " /static/three-balloons.png " ) ( class " bw-balloon " ) ( title " Image Source: pngimg.com/image/4955 | License: CC BY-NC 4.0 | Modifications: Resized " ) ( width " 52 " ) ( height " 56 " ) ) ) ]
[ extension-eligible? ( and req ( assq ' user-agent ( request-headers req ) ) ( string-contains? ( string-downcase ( cdr ( assq ' user-agent ( request-headers req ) ) ) ) " firefox/ " ) ) ] )
` ( div ( @ ( class " bw-top-banner " ) )
, balloon
( div
" BreezeWiki is back! Most major wikis are available. \n "
, ( if extension-eligible?
' ( div ( @ ( class " bw-top-banner-rainbow " ) )
" Try " ( a ( @ ( href " https://addons.mozilla.org/en-GB/firefox/addon/indie-wiki-buddy/ " ) ) " our affiliated browser extension " ) " - redirect to BreezeWiki automatically! \n " )
" " )
" As always, " ( a ( @ ( href " https://docs.breezewiki.com/Reporting_Bugs.html " ) ) " please go here " ) " to report problems, suggest features, or talk about the project. " )
, balloon ) )
" " )
2022-11-05 10:40:05 +00:00
( div ( @ ( class " main-container " ) )
( div ( @ ( class " fandom-community-header__background tileHorizontally header " ) ) )
( div ( @ ( class " page " ) )
( main ( @ ( class " page__main " ) )
2023-02-05 04:56:15 +00:00
, ( extwiki-notice wikiname title )
2022-11-05 10:40:05 +00:00
( div ( @ ( class " custom-top " ) )
( h1 ( @ ( class " page-title " ) ) , title )
( nav ( @ ( class " sitesearch " ) )
( form ( @ ( action , ( format " /~a/search " wikiname ) )
( class " bw-search-form " )
( id " bw-pr-search-form " ) )
( label ( @ ( for " bw-search-input " ) ) " Search " )
( div ( @ ( id " bw-pr-search-input " ) )
( input ( @ ( type " text " ) ( name " q " ) ( id " bw-search-input " ) ( autocomplete " off " ) ) ) )
2022-11-29 11:03:54 +00:00
( div ( @ ( class " bw-ss__container " ) ( id " bw-pr-search-suggestions " ) ) ) )
( div ( @ ( class " bw-theme__select " ) )
( span ( @ ( class " bw-theme__main-label " ) ) " Page theme " )
( div ( @ ( class " bw-theme__items " ) )
2023-02-05 04:56:15 +00:00
,@ ( for/list ( [ theme ' ( default light dark ) ] )
( define class
( if ( equal? theme ( user-cookies^-theme user-cookies ) )
" bw-theme__item bw-theme__item--selected "
" bw-theme__item " ) )
` ( a ( @ ( href , ( user-cookies-setter-url
req
( struct-copy user-cookies^ user-cookies
[ theme theme ] ) ) ) ( class , class ) )
( div ( @ ( class " bw-theme__icon-container " ) )
, ( hash-ref theme-icons theme ) )
, ( format " ~a " theme ) ) ) ) ) ) )
2022-11-05 10:40:05 +00:00
( div ( @ ( id " content " ) #; ( class " page-content " ) )
( div ( @ ( id " mw-content-text " ) )
, content ) )
, ( application-footer source-url #:license ( siteinfo^-license siteinfo ) ) ) ) ) ) ) ) )
2022-08-23 09:57:42 +00:00
( module+ test
2022-09-08 02:05:47 +00:00
( define page
( parameterize ( [ ( config-parameter ' strict_proxy ) " true " ] )
2022-09-16 12:56:05 +00:00
( generate-wiki-page
' ( template )
2022-11-29 11:36:53 +00:00
#:req test-req
2022-09-16 12:56:05 +00:00
#:source-url " "
#:title " test "
#:wikiname " test " ) ) )
2022-09-08 02:05:47 +00:00
; check the page is a valid xexp
( check-not-false ( xexp->html page ) )
; check the stylesheet is proxied
( check-true ( string-prefix?
( get-attribute ' href
( bits->attributes
( ( query-selector
( λ ( t a c ) ( eq? t ' link ) )
page ) ) ) )
" /proxy?dest=https%3A%2F%2Ftest.fandom.com " ) ) )
2022-09-09 03:42:20 +00:00
2022-11-29 11:03:54 +00:00
( define ( generate-redirect dest #:headers [ headers-in ' ( ) ] )
2022-09-09 03:42:20 +00:00
( define dest-bytes ( string->bytes/utf-8 dest ) )
( response/output
#:code 302
2022-11-29 11:03:54 +00:00
#:headers ( append ( list ( header #" Location " dest-bytes ) ) headers-in )
2022-09-09 03:42:20 +00:00
( λ ( out )
( write-html
` ( html
( head
( title " Redirecting... " ) )
( body
" Redirecting to "
( a ( @ ( href , dest ) ) , dest )
" ... " ) )
out ) ) ) )