From 972ced1f10683656a5bf10a20313b91e33ebf578 Mon Sep 17 00:00:00 2001 From: Jesse Wierzbinski Date: Mon, 15 Apr 2024 08:50:06 -1000 Subject: [PATCH] feat(frontend): :sparkles: Finish glitch frontend implementation --- config/config.example.toml | 2 + index.ts | 41 ++-- packages/config-manager/config.type.ts | 4 + packages/glitch-server/glitch-languages.ts | 210 +++++++++++++++++++++ packages/glitch-server/main.ts | 111 +++++++++-- server.ts | 6 +- 6 files changed, 348 insertions(+), 26 deletions(-) create mode 100644 packages/glitch-server/glitch-languages.ts diff --git a/config/config.example.toml b/config/config.example.toml index c1798df0..6fa64df8 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -87,6 +87,8 @@ bait_ips = ["127.0.0.1", "::1"] bait_user_agents = ["curl", "wget"] [frontend] +# Enable custom frontends (warning: not enabling this or Glitch will make Lysand only accessible via the Mastodon API) +enabled = true # The URL to reach the frontend at (should be on a local network) url = "http://localhost:3000" diff --git a/index.ts b/index.ts index 647b1eb1..f399f3bc 100644 --- a/index.ts +++ b/index.ts @@ -59,21 +59,38 @@ await dualServerLogger.log( `Database is online, now serving ${postCount} posts`, ); -// Check if frontend is reachable -const response = await fetch(new URL("/", config.frontend.url)) - .then((res) => res.ok) - .catch(() => false); +if (config.frontend.enabled) { + if (!URL.canParse(config.frontend.url)) { + await dualServerLogger.log( + LogLevel.ERROR, + "Server", + `Frontend URL is not a valid URL: ${config.frontend.url}`, + ); + process.exit(1); + } -if (!response) { + // Check if frontend is reachable + const response = await fetch(new URL("/", config.frontend.url)) + .then((res) => res.ok) + .catch(() => false); + + if (!response) { + await dualServerLogger.log( + LogLevel.ERROR, + "Server", + `Frontend is unreachable at ${config.frontend.url}`, + ); + await dualServerLogger.log( + LogLevel.ERROR, + "Server", + "Please ensure the frontend is online and reachable", + ); + } +} else { await dualServerLogger.log( - LogLevel.ERROR, + LogLevel.WARNING, "Server", - `Frontend is unreachable at ${config.frontend.url}`, - ); - await dualServerLogger.log( - LogLevel.ERROR, - "Server", - "Please ensure the frontend is online and reachable", + "Frontend is disabled, skipping check", ); } diff --git a/packages/config-manager/config.type.ts b/packages/config-manager/config.type.ts index 8241e9a8..ef0aa083 100644 --- a/packages/config-manager/config.type.ts +++ b/packages/config-manager/config.type.ts @@ -121,6 +121,9 @@ export interface Config { }; frontend: { + /** @default true */ + enabled: boolean; + /** @default "http://localhost:3000" */ url: string; @@ -442,6 +445,7 @@ export const defaultConfig: Config = { }, }, frontend: { + enabled: true, url: "http://localhost:3000", glitch: { enabled: false, diff --git a/packages/glitch-server/glitch-languages.ts b/packages/glitch-server/glitch-languages.ts new file mode 100644 index 00000000..2b44d1b7 --- /dev/null +++ b/packages/glitch-server/glitch-languages.ts @@ -0,0 +1,210 @@ +export const languages = [ + ["aa", "Afar", "Afaraf"], + ["ab", "Abkhaz", "аҧсуа бызшәа"], + ["ae", "Avestan", "avesta"], + ["af", "Afrikaans", "Afrikaans"], + ["ak", "Akan", "Akan"], + ["am", "Amharic", "አማርኛ"], + ["an", "Aragonese", "aragonés"], + ["ar", "Arabic", "اللغة العربية"], + ["as", "Assamese", "অসমীয়া"], + ["av", "Avaric", "авар мацӀ"], + ["ay", "Aymara", "aymar aru"], + ["az", "Azerbaijani", "azərbaycan dili"], + ["ba", "Bashkir", "башҡорт теле"], + ["be", "Belarusian", "беларуская мова"], + ["bg", "Bulgarian", "български език"], + ["bh", "Bihari", "भोजपुरी"], + ["bi", "Bislama", "Bislama"], + ["bm", "Bambara", "bamanankan"], + ["bn", "Bengali", "বাংলা"], + ["bo", "Tibetan", "བོད་ཡིག"], + ["br", "Breton", "brezhoneg"], + ["bs", "Bosnian", "bosanski jezik"], + ["ca", "Catalan", "Català"], + ["ce", "Chechen", "нохчийн мотт"], + ["ch", "Chamorro", "Chamoru"], + ["co", "Corsican", "corsu"], + ["cr", "Cree", "ᓀᐦᐃᔭᐍᐏᐣ"], + ["cs", "Czech", "čeština"], + ["cu", "Old Church Slavonic", "ѩзыкъ словѣньскъ"], + ["cv", "Chuvash", "чӑваш чӗлхи"], + ["cy", "Welsh", "Cymraeg"], + ["da", "Danish", "dansk"], + ["de", "German", "Deutsch"], + ["dv", "Divehi", "Dhivehi"], + ["dz", "Dzongkha", "རྫོང་ཁ"], + ["ee", "Ewe", "Eʋegbe"], + ["el", "Greek", "Ελληνικά"], + ["en", "English", "English"], + ["eo", "Esperanto", "Esperanto"], + ["es", "Spanish", "Español"], + ["et", "Estonian", "eesti"], + ["eu", "Basque", "euskara"], + ["fa", "Persian", "فارسی"], + ["ff", "Fula", "Fulfulde"], + ["fi", "Finnish", "suomi"], + ["fj", "Fijian", "Vakaviti"], + ["fo", "Faroese", "føroyskt"], + ["fr", "French", "Français"], + ["fy", "Western Frisian", "Frysk"], + ["ga", "Irish", "Gaeilge"], + ["gd", "Scottish Gaelic", "Gàidhlig"], + ["gl", "Galician", "galego"], + ["gu", "Gujarati", "ગુજરાતી"], + ["gv", "Manx", "Gaelg"], + ["ha", "Hausa", "هَوُسَ"], + ["he", "Hebrew", "עברית"], + ["hi", "Hindi", "हिन्दी"], + ["ho", "Hiri Motu", "Hiri Motu"], + ["hr", "Croatian", "Hrvatski"], + ["ht", "Haitian", "Kreyòl ayisyen"], + ["hu", "Hungarian", "magyar"], + ["hy", "Armenian", "Հայերեն"], + ["hz", "Herero", "Otjiherero"], + ["ia", "Interlingua", "Interlingua"], + ["id", "Indonesian", "Bahasa Indonesia"], + ["ie", "Interlingue", "Interlingue"], + ["ig", "Igbo", "Asụsụ Igbo"], + ["ii", "Nuosu", "ꆈꌠ꒿ Nuosuhxop"], + ["ik", "Inupiaq", "Iñupiaq"], + ["io", "Ido", "Ido"], + ["is", "Icelandic", "Íslenska"], + ["it", "Italian", "Italiano"], + ["iu", "Inuktitut", "ᐃᓄᒃᑎᑐᑦ"], + ["ja", "Japanese", "日本語"], + ["jv", "Javanese", "basa Jawa"], + ["ka", "Georgian", "ქართული"], + ["kg", "Kongo", "Kikongo"], + ["ki", "Kikuyu", "Gĩkũyũ"], + ["kj", "Kwanyama", "Kuanyama"], + ["kk", "Kazakh", "қазақ тілі"], + ["kl", "Kalaallisut", "kalaallisut"], + ["km", "Khmer", "ខេមរភាសា"], + ["kn", "Kannada", "ಕನ್ನಡ"], + ["ko", "Korean", "한국어"], + ["kr", "Kanuri", "Kanuri"], + ["ks", "Kashmiri", "कश्मीरी"], + ["ku", "Kurmanji (Kurdish)", "Kurmancî"], + ["kv", "Komi", "коми кыв"], + ["kw", "Cornish", "Kernewek"], + ["ky", "Kyrgyz", "Кыргызча"], + ["la", "Latin", "latine"], + ["lb", "Luxembourgish", "Lëtzebuergesch"], + ["lg", "Ganda", "Luganda"], + ["li", "Limburgish", "Limburgs"], + ["ln", "Lingala", "Lingála"], + ["lo", "Lao", "ລາວ"], + ["lt", "Lithuanian", "lietuvių kalba"], + ["lu", "Luba-Katanga", "Tshiluba"], + ["lv", "Latvian", "latviešu valoda"], + ["mg", "Malagasy", "fiteny malagasy"], + ["mh", "Marshallese", "Kajin M̧ajeļ"], + ["mi", "Māori", "te reo Māori"], + ["mk", "Macedonian", "македонски јазик"], + ["ml", "Malayalam", "മലയാളം"], + ["mn", "Mongolian", "Монгол хэл"], + ["mr", "Marathi", "मराठी"], + ["ms", "Malay", "Bahasa Melayu"], + ["ms-Arab", "Jawi Malay", "بهاس ملايو"], + ["mt", "Maltese", "Malti"], + ["my", "Burmese", "ဗမာစာ"], + ["na", "Nauru", "Ekakairũ Naoero"], + ["nb", "Norwegian Bokmål", "Norsk bokmål"], + ["nd", "Northern Ndebele", "isiNdebele"], + ["ne", "Nepali", "नेपाली"], + ["ng", "Ndonga", "Owambo"], + ["nl", "Dutch", "Nederlands"], + ["nn", "Norwegian Nynorsk", "Norsk Nynorsk"], + ["no", "Norwegian", "Norsk"], + ["nr", "Southern Ndebele", "isiNdebele"], + ["nv", "Navajo", "Diné bizaad"], + ["ny", "Chichewa", "chiCheŵa"], + ["oc", "Occitan", "occitan"], + ["oj", "Ojibwe", "ᐊᓂᔑᓈᐯᒧᐎᓐ"], + ["om", "Oromo", "Afaan Oromoo"], + ["or", "Oriya", "ଓଡ଼ିଆ"], + ["os", "Ossetian", "ирон æвзаг"], + ["pa", "Punjabi", "ਪੰਜਾਬੀ"], + ["pi", "Pāli", "पाऴि"], + ["pl", "Polish", "Polski"], + ["ps", "Pashto", "پښتو"], + ["pt", "Portuguese", "Português"], + ["qu", "Quechua", "Runa Simi"], + ["rm", "Romansh", "rumantsch grischun"], + ["rn", "Kirundi", "Ikirundi"], + ["ro", "Romanian", "Română"], + ["ru", "Russian", "Русский"], + ["rw", "Kinyarwanda", "Ikinyarwanda"], + ["sa", "Sanskrit", "संस्कृतम्"], + ["sc", "Sardinian", "sardu"], + ["sd", "Sindhi", "सिन्धी"], + ["se", "Northern Sami", "Davvisámegiella"], + ["sg", "Sango", "yângâ tî sängö"], + ["si", "Sinhala", "සිංහල"], + ["sk", "Slovak", "slovenčina"], + ["sl", "Slovenian", "slovenščina"], + ["sn", "Shona", "chiShona"], + ["so", "Somali", "Soomaaliga"], + ["sq", "Albanian", "Shqip"], + ["sr", "Serbian", "српски језик"], + ["ss", "Swati", "SiSwati"], + ["st", "Southern Sotho", "Sesotho"], + ["su", "Sundanese", "Basa Sunda"], + ["sv", "Swedish", "Svenska"], + ["sw", "Swahili", "Kiswahili"], + ["ta", "Tamil", "தமிழ்"], + ["te", "Telugu", "తెలుగు"], + ["tg", "Tajik", "тоҷикӣ"], + ["th", "Thai", "ไทย"], + ["ti", "Tigrinya", "ትግርኛ"], + ["tk", "Turkmen", "Türkmen"], + ["tl", "Tagalog", "Wikang Tagalog"], + ["tn", "Tswana", "Setswana"], + ["to", "Tonga", "faka Tonga"], + ["tr", "Turkish", "Türkçe"], + ["ts", "Tsonga", "Xitsonga"], + ["tt", "Tatar", "татар теле"], + ["tw", "Twi", "Twi"], + ["ty", "Tahitian", "Reo Tahiti"], + ["ug", "Uyghur", "ئۇيغۇرچە‎"], + ["uk", "Ukrainian", "Українська"], + ["ur", "Urdu", "اردو"], + ["uz", "Uzbek", "Ўзбек"], + ["ve", "Venda", "Tshivenḓa"], + ["vi", "Vietnamese", "Tiếng Việt"], + ["vo", "Volapük", "Volapük"], + ["wa", "Walloon", "walon"], + ["wo", "Wolof", "Wollof"], + ["xh", "Xhosa", "isiXhosa"], + ["yi", "Yiddish", "ייִדיש"], + ["yo", "Yoruba", "Yorùbá"], + ["za", "Zhuang", "Saɯ cueŋƅ"], + ["zh", "Chinese", "中文"], + ["zu", "Zulu", "isiZulu"], + ["zh-CN", "Chinese (China)", "简体中文"], + ["zh-HK", "Chinese (Hong Kong)", "繁體中文(香港)"], + ["zh-TW", "Chinese (Taiwan)", "繁體中文(臺灣)"], + ["zh-YUE", "Cantonese", "廣東話"], + ["ast", "Asturian", "Asturianu"], + ["chr", "Cherokee", "ᏣᎳᎩ ᎦᏬᏂᎯᏍᏗ"], + ["ckb", "Sorani (Kurdish)", "سۆرانی"], + ["cnr", "Montenegrin", "crnogorski"], + ["csb", "Kashubian", "Kaszëbsczi"], + ["jbo", "Lojban", "la .lojban."], + ["kab", "Kabyle", "Taqbaylit"], + ["ldn", "Láadan", "Láadan"], + ["lfn", "Lingua Franca Nova", "lingua franca nova"], + ["moh", "Mohawk", "Kanienʼkéha"], + ["nds", "Low German", "Plattdüütsch"], + ["pdc", "Pennsylvania Dutch", "Pennsilfaani-Deitsch"], + ["sco", "Scots", "Scots"], + ["sma", "Southern Sami", "Åarjelsaemien Gïele"], + ["smj", "Lule Sami", "Julevsámegiella"], + ["szl", "Silesian", "ślůnsko godka"], + ["tok", "Toki Pona", "toki pona"], + ["vai", "Vai", "ꕙꔤ"], + ["xal", "Kalmyk", "Хальмг келн"], + ["zba", "Balaibalan", "باليبلن"], + ["zgh", "Standard Moroccan Tamazight", "ⵜⴰⵎⴰⵣⵉⵖⵜ"], +]; diff --git a/packages/glitch-server/main.ts b/packages/glitch-server/main.ts index bd7e1431..612d0fad 100644 --- a/packages/glitch-server/main.ts +++ b/packages/glitch-server/main.ts @@ -1,17 +1,12 @@ import { join } from "node:path"; -import { dualLogger } from "@loggers"; -import { errorResponse } from "@response"; import { config } from "config-manager"; -import { - LogLevel, - type LogManager, - type MultiLogManager, -} from "~packages/log-manager"; +import type { LogManager, MultiLogManager } from "~packages/log-manager"; +import { languages } from "./glitch-languages"; export const handleGlitchRequest = async ( req: Request, logger: LogManager | MultiLogManager, -): Promise => { +): Promise => { const url = new URL(req.url); let path = url.pathname; @@ -28,10 +23,102 @@ export const handleGlitchRequest = async ( const file = Bun.file(join(config.frontend.glitch.assets, path)); if (await file.exists()) { - return new Response(file); + let fileContents = await file.text(); + + for (const server of config.frontend.glitch.server) { + fileContents = fileContents.replace( + `${new URL(server).origin}/`, + "/", + ); + } + + fileContents = fileContents.replaceAll( + "Glitch-soc is free open source software forked from Mastodon.", + "Lysand is free and open-source software using the Glitch-Soc frontend.", + ); + fileContents = fileContents.replaceAll("Mastodon", "Lysand"); + fileContents = fileContents.replaceAll( + "Lysand is free, open-source software, and a trademark of Mastodon gGmbH.", + "This is not a Mastodon instance.", + ); + fileContents = fileContents.replaceAll( + "joinmastodon.org", + "lysand.org", + ); + + // Check if file is index + if (path === "/index.html") { + // Find script id="initial-state" and replace its contents with custom json + const rewriter = new HTMLRewriter() + .on("script#initial-state", { + element(element) { + element.setInnerContent( + JSON.stringify({ + meta: { + access_token: null, + activity_api_enabled: true, + admin: null, + domain: new URL(config.http.base_url).host, + limited_federation_mode: false, + locale: "en", + mascot: "https://media.tech.lgbt/site_uploads/files/000/000/004/original/1a16a73feb5c2463.png", + profile_directory: true, + registrations_open: true, + repository: "lysand-org/lysand", + search_enabled: true, + single_user_mode: false, + source_url: + "https://github.com/lysand-org/lysand", + sso_redirect: null, + status_page_url: null, + streaming_api_base_url: null, + timeline_preview: true, + title: config.instance.name, + trends_as_landing_page: false, + trends_enabled: true, + version: "4.3.0-alpha.3+glitch", + auto_play_gif: null, + display_media: null, + reduce_motion: null, + use_blurhash: null, + }, + compose: { text: "" }, + accounts: {}, + media_attachments: { + accept_content_types: + config.validation.allowed_mime_types, + }, + settings: {}, + max_feed_hashtags: 4, + poll_limits: { + max_options: + config.validation.max_poll_options, + max_option_chars: + config.validation.max_poll_option_size, + min_expiration: + config.validation.min_poll_duration, + max_expiration: + config.validation.max_poll_duration, + }, + languages: languages, + push_subscription: null, + role: null, + }), + ); + }, + }) + .transform(new Response(fileContents)); + + fileContents = await rewriter.text(); + } + return new Response(fileContents, { + headers: { + "Content-Type": `${file.type}; charset=utf-8`, + "Content-Length": String(file.size), + Date: new Date().toUTCString(), + }, + }); } - dualLogger.log(LogLevel.WARNING, "Glitch-Soc", `Asset not found: ${path}`); - - return errorResponse("Glitch-Soc route not found", 404); + return null; }; diff --git a/server.ts b/server.ts index 8b57b8b6..60c55f76 100644 --- a/server.ts +++ b/server.ts @@ -126,8 +126,10 @@ export const createServer = ( } if (config.frontend.glitch.enabled) { - // Proxy all /web requests to Glitch-Soc - return await handleGlitchRequest(req, dualLogger); + return ( + (await handleGlitchRequest(req, dualLogger)) ?? + errorResponse("Route not found", 404) + ); } const base_url_with_http = config.http.base_url.replace(