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
2022-09-08 02:05:47 +00:00
" config.rkt "
2022-09-16 13:56:03 +00:00
" data.rkt "
2022-10-30 10:15:26 +00:00
" niwa-data.rkt "
2022-10-30 10:51:15 +00:00
" static-data.rkt "
2022-10-30 10:15:26 +00:00
" pure-utils.rkt "
2022-09-08 02:05:47 +00:00
" xexpr-utils.rkt "
" 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 "
( a ( @ ( href , source-url ) ( rel " noreferrer " ) ) , source-url )
2022-10-09 09:50:50 +00:00
, ( format " . Text content is available under the ~a license, " ( license^-text license ) )
( a ( @ ( href , ( license^-url license ) ) ) " 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), "
( a ( @ ( href " https://www.fandom.com/licensing " ) ) " see license info. " )
" 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
( define ( niwa-notice wikiname title )
( define ind ( findf ( λ ( item ) ( member wikiname ( first item ) ) ) niwa-data ) )
( if ind
( let* ( [ search-page ( format " /Special:Search?~a "
( params->query ` ( ( " search " . , title )
( " go " . " Go " ) ) ) ) ]
[ go ( if ( string-suffix? ( third ind ) " / " )
2022-10-31 06:39:19 +00:00
( regexp-replace #rx"/$" ( third ind ) ( λ ( _ ) search-page ) )
( let* ( [ joiner ( second ( regexp-match #rx"/(w[^./]*)/" ( third ind ) ) ) ] )
( regexp-replace #rx"/w[^./]*/.*$" ( third ind ) ( λ ( _ ) ( format " /~a~a " joiner search-page ) ) ) ) ) ] )
2022-10-30 10:15:26 +00:00
` ( aside ( @ ( class " niwa__notice " ) )
( h1 ( @ ( class " niwa__header " ) ) , ( second ind ) " has its own website separate from Fandom. " )
( a ( @ ( class " niwa__go " ) ( href , go ) ) " Read " , title " on " , ( second ind ) " → " )
( div ( @ ( class " niwa__cols " ) )
( div ( @ ( class " niwa__left " ) )
( p " Most major Nintendo wikis are part of the "
( a ( @ ( href " https://www.niwanetwork.org/about/ " ) ) " Nintendo Independent Wiki Alliance " )
" and have their own wikis off Fandom. You can help this wiki by "
( a ( @ ( href , go ) ) " visiting it directly. " ) )
( p , ( fifth ind ) )
( div ( @ ( class " niwa__divider " ) ) )
( p " Why are you seeing this message? Fandom refuses to delete or archive their copy of this wiki, so that means their pages will appear high up in search results. Fandom hopes to get clicks from readers who don't know any better. " )
2022-11-13 09:52:29 +00:00
( p ( @ ( class " niwa__feedback " ) ) " This notice brought to you by BreezeWiki / " ( a ( @ ( href " https://www.kotaku.com.au/2022/10/massive-zelda-wiki-reclaims-independence-six-months-before-tears-of-the-kingdom/ " ) ) " Info & Context " ) " / " ( a ( @ ( href " https://docs.breezewiki.com/Reporting_Bugs.html " ) ) " Feedback? " ) ) )
2022-10-30 10:15:26 +00:00
( div ( @ ( class " niwa__right " ) )
( img ( @ ( class " niwa__logo " ) ( src , ( format " https://www.niwanetwork.org~a " ( fourth ind ) ) ) ) ) ) ) ) )
" " ) )
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 ]
#:user-cookies [ user-cookies-in #f ] )
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 ) ) )
2022-08-23 09:57:42 +00:00
( define ( required-styles origin )
2022-09-08 02:05:47 +00:00
( map ( λ ( dest-path )
( define url ( format dest-path origin ) )
( if ( config-true? ' strict_proxy )
( u-proxy-url url )
url ) )
2022-11-29 11:03:54 +00:00
` ( #; " ~a/load.php?lang=en&modules=skin.fandomdesktop.styles&only=styles&skin=fandomdesktop "
2022-08-23 09:57:42 +00:00
#; " ~a/load.php?lang=en&modules=ext.gadget.dungeonsWiki%2CearthWiki%2Csite-styles%2Csound-styles&only=styles&skin=fandomdesktop "
#; " ~a/load.php?lang=en&modules=site.styles&only=styles&skin=fandomdesktop "
; combine the above entries into a single request for potentially extra speed - fandom.com doesn't even do this!
2022-11-29 11:03:54 +00:00
, ( format " ~~a/wikia.php?controller=ThemeApi&method=themeVariables&variant=~a " ( user-cookies^-theme user-cookies ) )
2022-09-11 11:21:37 +00:00
" ~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 " ) ) )
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 ) ) ) )
( required-styles ( format " https://~a.fandom.com " wikiname ) ) )
( 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 ) ) )
( div ( @ ( class " main-container " ) )
( div ( @ ( class " fandom-community-header__background tileHorizontally header " ) ) )
( div ( @ ( class " page " ) )
( main ( @ ( class " page__main " ) )
, ( niwa-notice wikiname title )
( 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 " ) )
,@ ( 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 ) ) ) )