reorganize routes
add adminapi remove body-parser in favor of express methods add id to captchas add testmodule for matrix-synapse to login with ppfun credentials (just a test)
This commit is contained in:
parent
ee62c57b34
commit
2bc1aa9591
4
API.md
4
API.md
|
@ -1,9 +1,9 @@
|
|||
# API Websocket
|
||||
|
||||
This websocket provides unlimited access to many functions of the site, it is used for Discord Chat Bridge and Minecraft Bridge.
|
||||
This websocket provides unlimited access to many functions of the site, it is used for Discord Chat Bridge and similar stuff.
|
||||
|
||||
Websocket url:
|
||||
`https://[old.]pixelplanet.fun/mcws`
|
||||
`https://[dev.]pixelplanet.fun/mcws`
|
||||
|
||||
Connection just possible with header:
|
||||
|
||||
|
|
100
i18n/ssr-de.po
100
i18n/ssr-de.po
|
@ -64,30 +64,6 @@ msgstr "Dein Land ist temporär stummgeschaltet"
|
|||
msgid "Stop flooding."
|
||||
msgstr "Stoppe zu spamen."
|
||||
|
||||
#: src/ssr-components/Globe.jsx:44
|
||||
msgid "Double click on globe to go back."
|
||||
msgstr "Doppelklick um zurück zu gehen."
|
||||
|
||||
#: src/ssr-components/Globe.jsx:45
|
||||
msgid "Loading..."
|
||||
msgstr "Lade..."
|
||||
|
||||
#: src/ssr-components/Globe.jsx:51
|
||||
msgid "PixelPlanet.Fun 3DGlobe"
|
||||
msgstr "PixelPlanet.Fun 3D Globus"
|
||||
|
||||
#: src/ssr-components/Globe.jsx:52
|
||||
msgid "A 3D globe of our whole map"
|
||||
msgstr "Ein interaktvier 3D Globus unserer gesamten Karte"
|
||||
|
||||
#: src/ssr-components/Main.jsx:70
|
||||
msgid "PixelPlanet.fun"
|
||||
msgstr "PixelPlanet.Fun"
|
||||
|
||||
#: src/ssr-components/Main.jsx:72
|
||||
msgid "Place color pixels on an map styled canvas with other players online"
|
||||
msgstr "Zeichne mit farbigen Pixel auf einer Weltkarte mit anderen Spielern"
|
||||
|
||||
#: src/routes/reset_password.js:41
|
||||
msgid "You sent an empty password or invalid data :("
|
||||
msgstr "Du hast ein ungültiges Passwort oder Daten gesendet :("
|
||||
|
@ -120,6 +96,30 @@ msgstr ""
|
|||
"Dieser Passwort Wiederherstellungslink ist falsch oder abgelaufen, bitte "
|
||||
"beantrage einen neuen (Hinweis: Du kannst diese Links nur einmal verwenden)"
|
||||
|
||||
#: src/ssr-components/Globe.jsx:44
|
||||
msgid "Double click on globe to go back."
|
||||
msgstr "Doppelklick um zurück zu gehen."
|
||||
|
||||
#: src/ssr-components/Globe.jsx:45
|
||||
msgid "Loading..."
|
||||
msgstr "Lade..."
|
||||
|
||||
#: src/ssr-components/Globe.jsx:51
|
||||
msgid "PixelPlanet.Fun 3DGlobe"
|
||||
msgstr "PixelPlanet.Fun 3D Globus"
|
||||
|
||||
#: src/ssr-components/Globe.jsx:52
|
||||
msgid "A 3D globe of our whole map"
|
||||
msgstr "Ein interaktvier 3D Globus unserer gesamten Karte"
|
||||
|
||||
#: src/ssr-components/Main.jsx:70
|
||||
msgid "PixelPlanet.fun"
|
||||
msgstr "PixelPlanet.Fun"
|
||||
|
||||
#: src/ssr-components/Main.jsx:72
|
||||
msgid "Place color pixels on an map styled canvas with other players online"
|
||||
msgstr "Zeichne mit farbigen Pixel auf einer Weltkarte mit anderen Spielern"
|
||||
|
||||
#: src/core/mail.js:65
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
|
@ -243,6 +243,38 @@ msgstr "PixelPlanet.Fun Passwort Wiederherstellung"
|
|||
msgid "Reset your password here"
|
||||
msgstr "Setze hier dein Passwort zurück"
|
||||
|
||||
#: src/routes/api/captcha.js:22
|
||||
msgid "No captcha text given"
|
||||
msgstr "Keinen Captcha Text eingegeben"
|
||||
|
||||
#: src/routes/api/captcha.js:36
|
||||
msgid "You took too long, try again."
|
||||
msgstr "Do hast zu lange benötigt."
|
||||
|
||||
#: src/routes/api/captcha.js:42
|
||||
msgid "You failed your captcha"
|
||||
msgstr "Captcha Text ist falsch"
|
||||
|
||||
#: src/routes/api/captcha.js:48
|
||||
msgid "Unknown Captcha Error"
|
||||
msgstr "Unbekannter Captcha Fehler"
|
||||
|
||||
#: src/routes/api/captcha.js:55
|
||||
msgid "Server error occured"
|
||||
msgstr "Server Fehler"
|
||||
|
||||
#: src/routes/api/modtools.js:51
|
||||
msgid "You are not logged in"
|
||||
msgstr "Du bist nicht angemeldet."
|
||||
|
||||
#: src/routes/api/modtools.js:63
|
||||
msgid "You are not allowed to access this page"
|
||||
msgstr "Du hast keinen Zugriff zu diese Seite"
|
||||
|
||||
#: src/routes/api/modtools.js:129
|
||||
msgid "Just admins can do that"
|
||||
msgstr "Nur Administratoren können dies tun"
|
||||
|
||||
#: src/utils/validation.js:18
|
||||
msgid "Email can't be empty."
|
||||
msgstr "E-Mail Feld kann nicht leer sein."
|
||||
|
@ -291,26 +323,6 @@ msgstr "Passwort muss mindestens 6 Zeichen lang sein."
|
|||
msgid "Password must be shorter than 60 characters."
|
||||
msgstr "Passwort muss kürzer als 60 Zeichen sein."
|
||||
|
||||
#: src/routes/api/captcha.js:22
|
||||
msgid "No captcha text given"
|
||||
msgstr "Keinen Captcha Text eingegeben"
|
||||
|
||||
#: src/routes/api/captcha.js:36
|
||||
msgid "You took too long, try again."
|
||||
msgstr "Do hast zu lange benötigt."
|
||||
|
||||
#: src/routes/api/captcha.js:42
|
||||
msgid "You failed your captcha"
|
||||
msgstr "Captcha Text ist falsch"
|
||||
|
||||
#: src/routes/api/captcha.js:48
|
||||
msgid "Unknown Captcha Error"
|
||||
msgstr "Unbekannter Captcha Fehler"
|
||||
|
||||
#: src/routes/api/captcha.js:55
|
||||
msgid "Server error occured"
|
||||
msgstr "Server Fehler"
|
||||
|
||||
#: src/routes/api/auth/register.js:31
|
||||
msgid "E-Mail already in use."
|
||||
msgstr "E-Mail wird bereits verwendet."
|
||||
|
|
|
@ -53,6 +53,44 @@ msgstr ""
|
|||
msgid "Stop flooding."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:40
|
||||
msgid "You sent an empty password or invalid data :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:52
|
||||
msgid "This password-reset link isn't valid anymore :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:63
|
||||
msgid "Your passwords do not match :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:78
|
||||
msgid "User doesn't exist in our database :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:90
|
||||
msgid "Passowrd successfully changed."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:109
|
||||
msgid "Invalid url :( Please check your mail again."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:122
|
||||
msgid ""
|
||||
"This passwort reset link is wrong or already expired, please request a new "
|
||||
"one (Note: you can use those links just once)"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/Main.jsx:70
|
||||
msgid "PixelPlanet.fun"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/Main.jsx:72
|
||||
msgid "Place color pixels on an map styled canvas with other players online"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/Globe.jsx:44
|
||||
msgid "Double click on globe to go back."
|
||||
msgstr ""
|
||||
|
@ -69,44 +107,6 @@ msgstr ""
|
|||
msgid "A 3D globe of our whole map"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/Main.jsx:70
|
||||
msgid "PixelPlanet.fun"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/Main.jsx:72
|
||||
msgid "Place color pixels on an map styled canvas with other players online"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:41
|
||||
msgid "You sent an empty password or invalid data :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:53
|
||||
msgid "This password-reset link isn't valid anymore :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:64
|
||||
msgid "Your passwords do not match :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:79
|
||||
msgid "User doesn't exist in our database :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:91
|
||||
msgid "Passowrd successfully changed."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:110
|
||||
msgid "Invalid url :( Please check your mail again."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/reset_password.js:123
|
||||
msgid ""
|
||||
"This passwort reset link is wrong or already expired, please request a new "
|
||||
"one (Note: you can use those links just once)"
|
||||
msgstr ""
|
||||
|
||||
#: src/core/mail.js:65
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
|
@ -218,26 +218,6 @@ msgstr ""
|
|||
msgid "Reset your password here"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:22
|
||||
msgid "No captcha text given"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:36
|
||||
msgid "You took too long, try again."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:42
|
||||
msgid "You failed your captcha"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:48
|
||||
msgid "Unknown Captcha Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:55
|
||||
msgid "Server error occured"
|
||||
msgstr ""
|
||||
|
||||
#: src/utils/validation.js:18
|
||||
msgid "Email can't be empty."
|
||||
msgstr ""
|
||||
|
@ -286,6 +266,38 @@ msgstr ""
|
|||
msgid "Password must be shorter than 60 characters."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:22
|
||||
msgid "No captcha text given"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:36
|
||||
msgid "You took too long, try again."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:42
|
||||
msgid "You failed your captcha"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:48
|
||||
msgid "Unknown Captcha Error"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/captcha.js:55
|
||||
msgid "Server error occured"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/modtools.js:50
|
||||
msgid "You are not logged in"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/modtools.js:62
|
||||
msgid "You are not allowed to access this page"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/modtools.js:128
|
||||
msgid "Just admins can do that"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/register.js:31
|
||||
msgid "E-Mail already in use."
|
||||
msgstr ""
|
||||
|
@ -302,6 +314,10 @@ msgstr ""
|
|||
msgid "Failed to establish session after register :("
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/logout.js:13
|
||||
msgid "You are not even logged in."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/change_mail.js:41
|
||||
#: src/routes/api/auth/change_passwd.js:37
|
||||
#: src/routes/api/auth/delete_account.js:38
|
||||
|
@ -314,23 +330,6 @@ msgstr ""
|
|||
msgid "Incorrect password!"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/RedirectionPage.jsx:20
|
||||
msgid "You will be automatically redirected after 15s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/RedirectionPage.jsx:21
|
||||
#, javascript-format
|
||||
msgid "Or ${ clickHere } to go back to pixelplanet"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/RedirectionPage.jsx:25
|
||||
msgid "PixelPlanet.fun Accounts"
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/logout.js:13
|
||||
msgid "You are not even logged in."
|
||||
msgstr ""
|
||||
|
||||
#: src/routes/api/auth/verify.js:25
|
||||
#: src/routes/api/auth/verify.js:32
|
||||
msgid "Mail verification"
|
||||
|
@ -346,6 +345,19 @@ msgid ""
|
|||
"request a new one."
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/RedirectionPage.jsx:20
|
||||
msgid "You will be automatically redirected after 15s"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/RedirectionPage.jsx:21
|
||||
#, javascript-format
|
||||
msgid "Or ${ clickHere } to go back to pixelplanet"
|
||||
msgstr ""
|
||||
|
||||
#: src/ssr-components/RedirectionPage.jsx:25
|
||||
msgid "PixelPlanet.fun Accounts"
|
||||
msgstr ""
|
||||
|
||||
#: src/canvasesDesc.js:18
|
||||
msgid "Earth"
|
||||
msgstr ""
|
||||
|
|
|
@ -45,22 +45,6 @@ msgstr ""
|
|||
msgid "Hide Hidden Canvases"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:632
|
||||
msgid "Register New Account"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:639
|
||||
msgid "Restore my Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:646
|
||||
msgid "Welcome to PixelPlanet.fun"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:652
|
||||
msgid "Look at past Canvases"
|
||||
msgstr ""
|
||||
|
||||
#: src/ui/placePixel.js:53
|
||||
msgid "Error :("
|
||||
msgstr ""
|
||||
|
@ -170,6 +154,22 @@ msgstr ""
|
|||
msgid "Can't render 3D canvas, do you have WebGL2 disabled?"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:628
|
||||
msgid "Register New Account"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:635
|
||||
msgid "Restore my Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:642
|
||||
msgid "Welcome to PixelPlanet.fun"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/index.js:648
|
||||
msgid "Look at past Canvases"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:559
|
||||
#: src/components/CoordinatesBox.jsx:32
|
||||
msgid "Copy to Clipboard"
|
||||
|
@ -187,8 +187,8 @@ msgstr ""
|
|||
msgid "Pixels placed"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:224
|
||||
#: src/components/ModalRoot.jsx:69
|
||||
#: src/components/Modtools.jsx:224
|
||||
#: src/components/Window.jsx:138
|
||||
#: src/components/contextmenus/ChannelContextMenu.jsx:67
|
||||
msgid "Close"
|
||||
|
@ -198,27 +198,6 @@ msgstr ""
|
|||
msgid "Restore"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/CanvasSwitchButton.jsx:22
|
||||
#: src/components/windows/index.js:22
|
||||
msgid "Canvas Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ChatButton.jsx:92
|
||||
msgid "Close Chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ChatButton.jsx:92
|
||||
msgid "Open Chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ExpandMenuButton.jsx:23
|
||||
msgid "Close Menu"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ExpandMenuButton.jsx:23
|
||||
msgid "Open Menu"
|
||||
msgstr ""
|
||||
|
||||
#: src/actions/fetch.js:39
|
||||
msgid "You made too many requests"
|
||||
msgstr ""
|
||||
|
@ -248,6 +227,27 @@ msgstr ""
|
|||
msgid "Server answered with gibberish :("
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/CanvasSwitchButton.jsx:22
|
||||
#: src/components/windows/index.js:22
|
||||
msgid "Canvas Selection"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ChatButton.jsx:92
|
||||
msgid "Close Chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ChatButton.jsx:92
|
||||
msgid "Open Chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ExpandMenuButton.jsx:23
|
||||
msgid "Close Menu"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/ExpandMenuButton.jsx:23
|
||||
msgid "Open Menu"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/HistorySelect.jsx:144
|
||||
msgid "Loading"
|
||||
msgstr ""
|
||||
|
@ -256,6 +256,25 @@ msgstr ""
|
|||
msgid "Select Date above"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/HelpButton.jsx:23
|
||||
#: src/components/windows/index.js:16
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/SettingsButton.jsx:23
|
||||
#: src/components/windows/index.js:17
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/LogInButton.jsx:23
|
||||
#: src/components/windows/index.js:18
|
||||
msgid "User Area"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/DownloadButton.jsx:37
|
||||
msgid "Make Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Window.jsx:117
|
||||
msgid "Clone"
|
||||
msgstr ""
|
||||
|
@ -272,29 +291,6 @@ msgstr ""
|
|||
msgid "Resize"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/HelpButton.jsx:23
|
||||
#: src/components/windows/index.js:16
|
||||
msgid "Help"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/LogInButton.jsx:23
|
||||
#: src/components/windows/index.js:18
|
||||
msgid "User Area"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/SettingsButton.jsx:23
|
||||
#: src/components/windows/index.js:17
|
||||
msgid "Settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/GlobeButton.jsx:35
|
||||
msgid "Globe View"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/DownloadButton.jsx:37
|
||||
msgid "Make Screenshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/PalselButton.jsx:31
|
||||
msgid "Close Palette"
|
||||
msgstr ""
|
||||
|
@ -303,6 +299,10 @@ msgstr ""
|
|||
msgid "Open Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/buttons/GlobeButton.jsx:35
|
||||
msgid "Globe View"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/contextmenus/ChannelContextMenu.jsx:55
|
||||
msgid "Mute"
|
||||
msgstr ""
|
||||
|
@ -384,6 +384,225 @@ msgstr ""
|
|||
msgid "Send"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Register.jsx:81
|
||||
msgid "Register new account here"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Register.jsx:92
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:82
|
||||
#: src/components/windows/Register.jsx:100
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:80
|
||||
#: src/components/DeleteAccount.jsx:62
|
||||
#: src/components/LogInForm.jsx:83
|
||||
#: src/components/windows/Register.jsx:108
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Register.jsx:116
|
||||
msgid "Confirm Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Modtools.jsx:311
|
||||
#: src/components/Modtools.jsx:392
|
||||
#: src/components/Modtools.jsx:467
|
||||
#: src/components/Modtools.jsx:512
|
||||
#: src/components/Modtools.jsx:595
|
||||
#: src/components/windows/ForgotPassword.jsx:86
|
||||
#: src/components/windows/Register.jsx:119
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:133
|
||||
msgid "Show Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:138
|
||||
msgid "Turn on grid to highlight pixel borders."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:141
|
||||
msgid "Show Pixel Activity"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:146
|
||||
msgid "Show circles where pixels are placed."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:149
|
||||
msgid "Disable Game Sounds"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:155
|
||||
msgid "All sound effects will be disabled."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:159
|
||||
msgid ""
|
||||
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
|
||||
"have some privacy feature blocking us?"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:165
|
||||
msgid "Enable chat notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:169
|
||||
msgid "Play a sound when new chat messages arrive"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:172
|
||||
msgid "Auto Zoom In"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:177
|
||||
msgid ""
|
||||
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
|
||||
"small."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:180
|
||||
msgid "Compact Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:185
|
||||
msgid "Display Palette in a compact form that takes less screen space."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:188
|
||||
msgid "Potato Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:192
|
||||
msgid "For when you are playing on a potato."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:374
|
||||
#: src/components/windows/Settings.jsx:195
|
||||
msgid "Light Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:199
|
||||
msgid "Show Grid in white instead of black."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:203
|
||||
msgid "Historical View"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:208
|
||||
msgid "Check out past versions of the canvas."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:213
|
||||
msgid "Themes"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:218
|
||||
msgid "How pixelplanet should look like."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:225
|
||||
msgid "Select Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/CanvasSelect.jsx:33
|
||||
msgid ""
|
||||
"Select the canvas you want to use. Every canvas is unique and has "
|
||||
"different palettes, cooldown and requirements. Archive of closed canvases "
|
||||
"can be accessed here:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/CanvasSelect.jsx:41
|
||||
msgid "Archive"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:146
|
||||
msgid "Channel settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:161
|
||||
msgid "Start chatting here"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:198
|
||||
msgid "Chat here"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:221
|
||||
msgid "You must be logged in to chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:20
|
||||
msgid ""
|
||||
"While we tend to not delete canvases, some canvases are started for fun or "
|
||||
"as a request by users who currently like a meme. Those canvases can get "
|
||||
"boring after a while and after weeks of no major change and if they really "
|
||||
"aren't worth being kept active, we decide to remove them."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:22
|
||||
msgid ""
|
||||
"Here we collect those canvases to archive them in a proper way (which is "
|
||||
"currently just one)."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:24
|
||||
msgid "Political Compass Canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:31
|
||||
msgid ""
|
||||
"This canvas got requested during a time of political conflicts on the main "
|
||||
"Earth canvas. It was a 1024x1024 representation of the political compass "
|
||||
"with a 5s cooldown and 60s stacking. It got launched on May 11th and "
|
||||
"remained active for months till it got shut down on November 30th."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:32
|
||||
msgid ""
|
||||
"We decided to archive it as a timelapse with lossless encoded webm. Taking "
|
||||
"a screenshot from the timelapse results in a perfect 1:1 representation of "
|
||||
"how the canvas was at that time."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:60
|
||||
msgid "Sent you a mail with instructions to reset your password."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:71
|
||||
msgid "Enter your mail address and we will send you a new password:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:27
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:30
|
||||
msgid "Ranking"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:33
|
||||
msgid "Converter"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:39
|
||||
msgid "Modtools"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:40
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:47
|
||||
msgid "Consider joining us on Guilded:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:36
|
||||
msgid "your IP"
|
||||
msgstr ""
|
||||
|
@ -561,229 +780,6 @@ msgstr ""
|
|||
msgid "Credit for the Palette of the Top10 canvas goes to ${ vinikLink }."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Register.jsx:81
|
||||
msgid "Register new account here"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Register.jsx:92
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:82
|
||||
#: src/components/windows/Register.jsx:100
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:80
|
||||
#: src/components/DeleteAccount.jsx:62
|
||||
#: src/components/LogInForm.jsx:83
|
||||
#: src/components/windows/Register.jsx:108
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Register.jsx:116
|
||||
msgid "Confirm Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:311
|
||||
#: src/components/Admintools.jsx:392
|
||||
#: src/components/Admintools.jsx:467
|
||||
#: src/components/Admintools.jsx:512
|
||||
#: src/components/Admintools.jsx:595
|
||||
#: src/components/windows/ForgotPassword.jsx:86
|
||||
#: src/components/windows/Register.jsx:119
|
||||
msgid "Submit"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/CanvasSelect.jsx:33
|
||||
msgid ""
|
||||
"Select the canvas you want to use. Every canvas is unique and has "
|
||||
"different palettes, cooldown and requirements. Archive of closed canvases "
|
||||
"can be accessed here:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/CanvasSelect.jsx:41
|
||||
msgid "Archive"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:20
|
||||
msgid ""
|
||||
"While we tend to not delete canvases, some canvases are started for fun or "
|
||||
"as a request by users who currently like a meme. Those canvases can get "
|
||||
"boring after a while and after weeks of no major change and if they really "
|
||||
"aren't worth being kept active, we decide to remove them."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:22
|
||||
msgid ""
|
||||
"Here we collect those canvases to archive them in a proper way (which is "
|
||||
"currently just one)."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:24
|
||||
msgid "Political Compass Canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:31
|
||||
msgid ""
|
||||
"This canvas got requested during a time of political conflicts on the main "
|
||||
"Earth canvas. It was a 1024x1024 representation of the political compass "
|
||||
"with a 5s cooldown and 60s stacking. It got launched on May 11th and "
|
||||
"remained active for months till it got shut down on November 30th."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Archive.jsx:32
|
||||
msgid ""
|
||||
"We decided to archive it as a timelapse with lossless encoded webm. Taking "
|
||||
"a screenshot from the timelapse results in a perfect 1:1 representation of "
|
||||
"how the canvas was at that time."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:146
|
||||
msgid "Channel settings"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:161
|
||||
msgid "Start chatting here"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:198
|
||||
msgid "Chat here"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Chat.jsx:221
|
||||
msgid "You must be logged in to chat"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:60
|
||||
msgid "Sent you a mail with instructions to reset your password."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/ForgotPassword.jsx:71
|
||||
msgid "Enter your mail address and we will send you a new password:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:133
|
||||
msgid "Show Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:138
|
||||
msgid "Turn on grid to highlight pixel borders."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:141
|
||||
msgid "Show Pixel Activity"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:146
|
||||
msgid "Show circles where pixels are placed."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:149
|
||||
msgid "Disable Game Sounds"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:155
|
||||
msgid "All sound effects will be disabled."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:159
|
||||
msgid ""
|
||||
"Your Browser doesn't allow us to use AudioContext to play sounds. Do you "
|
||||
"have some privacy feature blocking us?"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:165
|
||||
msgid "Enable chat notifications"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:169
|
||||
msgid "Play a sound when new chat messages arrive"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:172
|
||||
msgid "Auto Zoom In"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:177
|
||||
msgid ""
|
||||
"Zoom in instead of placing a pixel when you tap the canvas and your zoom is "
|
||||
"small."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:180
|
||||
msgid "Compact Palette"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:185
|
||||
msgid "Display Palette in a compact form that takes less screen space."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:188
|
||||
msgid "Potato Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:192
|
||||
msgid "For when you are playing on a potato."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Converter.jsx:374
|
||||
#: src/components/windows/Settings.jsx:195
|
||||
msgid "Light Grid"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:199
|
||||
msgid "Show Grid in white instead of black."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:203
|
||||
msgid "Historical View"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:208
|
||||
msgid "Check out past versions of the canvas."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:213
|
||||
msgid "Themes"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:218
|
||||
msgid "How pixelplanet should look like."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:225
|
||||
msgid "Select Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:27
|
||||
msgid "Profile"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:30
|
||||
msgid "Ranking"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:33
|
||||
msgid "Converter"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:39
|
||||
msgid "Admintools"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:39
|
||||
msgid "Modtools"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:40
|
||||
msgid "Loading..."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/UserArea.jsx:47
|
||||
msgid "Consider joining us on Guilded:"
|
||||
msgstr ""
|
||||
|
||||
#: src/utils/validation.js:18
|
||||
msgid "Email can't be empty."
|
||||
msgstr ""
|
||||
|
@ -832,6 +828,13 @@ msgstr ""
|
|||
msgid "Password must be shorter than 60 characters."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:91
|
||||
#: src/components/ChangeName.jsx:68
|
||||
#: src/components/ChangePassword.jsx:110
|
||||
#: src/components/LanguageSelect.jsx:73
|
||||
msgid "Save"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/CanvasItem.jsx:30
|
||||
msgid "Online Users"
|
||||
msgstr ""
|
||||
|
@ -877,30 +880,6 @@ msgstr ""
|
|||
msgid "Dimensions"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:21
|
||||
msgid "Login to access more features and stats."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:23
|
||||
msgid "Login with Name or Mail:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:30
|
||||
msgid "I forgot my Password."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:31
|
||||
msgid "or login with:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:72
|
||||
msgid "or register here:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInArea.jsx:79
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserAreaContent.jsx:63
|
||||
msgid "Todays Placed Pixels"
|
||||
msgstr ""
|
||||
|
@ -958,81 +937,81 @@ msgstr ""
|
|||
msgid "Ranking updates every 5 min. Daily rankings get reset at midnight UTC."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:184
|
||||
#: src/components/Modtools.jsx:184
|
||||
msgid "Build image on canvas."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:187
|
||||
#: src/components/Modtools.jsx:187
|
||||
msgid "Build image and set it to protected."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:190
|
||||
#: src/components/Modtools.jsx:190
|
||||
msgid "Build image, but reset cooldown to unset-pixel cd."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:253
|
||||
#: src/components/Modtools.jsx:253
|
||||
msgid "Image Upload"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:254
|
||||
#: src/components/Modtools.jsx:254
|
||||
msgid "Upload images to canvas"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:256
|
||||
#: src/components/Modtools.jsx:256
|
||||
msgid "File"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:276
|
||||
#: src/components/Modtools.jsx:276
|
||||
msgid "Coordinates in X_Y format:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:316
|
||||
#: src/components/Modtools.jsx:316
|
||||
msgid "Pixel Protection"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:318
|
||||
#: src/components/Modtools.jsx:318
|
||||
msgid ""
|
||||
"Set protection of areas (if you need finer grained control, "
|
||||
"use protect with image upload and alpha layers)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:398
|
||||
#: src/components/Modtools.jsx:398
|
||||
msgid "Rollback to Date"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:400
|
||||
#: src/components/Modtools.jsx:400
|
||||
msgid "Rollback an area of the canvas to a set date (00:00 UTC)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:475
|
||||
#: src/components/Modtools.jsx:475
|
||||
msgid "IP Actions"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:477
|
||||
#: src/components/Modtools.jsx:477
|
||||
msgid "Do stuff with IPs (one IP per line)"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:516
|
||||
#: src/components/Modtools.jsx:516
|
||||
msgid "Manage Moderators"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:518
|
||||
#: src/components/Modtools.jsx:518
|
||||
msgid "Remove Moderator"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:550
|
||||
#: src/components/Modtools.jsx:550
|
||||
msgid "There are no mods"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:555
|
||||
#: src/components/Modtools.jsx:555
|
||||
msgid "Assign new Mod"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:558
|
||||
#: src/components/Modtools.jsx:558
|
||||
msgid "Enter UserName of new Mod"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/Admintools.jsx:567
|
||||
#: src/components/Modtools.jsx:567
|
||||
msgid "User Name"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1114,33 +1093,28 @@ msgstr ""
|
|||
msgid "Download Template"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:91
|
||||
#: src/components/ChangeName.jsx:68
|
||||
#: src/components/ChangePassword.jsx:110
|
||||
#: src/components/LanguageSelect.jsx:73
|
||||
msgid "Save"
|
||||
#: src/components/LogInArea.jsx:21
|
||||
msgid "Login to access more features and stats."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInForm.jsx:76
|
||||
msgid "Name or Email"
|
||||
#: src/components/LogInArea.jsx:23
|
||||
msgid "Login with Name or Mail:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInForm.jsx:87
|
||||
msgid "LogIn"
|
||||
#: src/components/LogInArea.jsx:30
|
||||
msgid "I forgot my Password."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserMessages.jsx:28
|
||||
msgid ""
|
||||
"Please verify your mail address \n"
|
||||
"or your account could get deleted after a few days."
|
||||
#: src/components/LogInArea.jsx:31
|
||||
msgid "or login with:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserMessages.jsx:49
|
||||
msgid "A new verification mail is getting sent to you."
|
||||
#: src/components/LogInArea.jsx:72
|
||||
msgid "or register here:"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserMessages.jsx:53
|
||||
msgid "Click here to request a new verification mail."
|
||||
#: src/components/LogInArea.jsx:79
|
||||
msgid "Register"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangePassword.jsx:22
|
||||
|
@ -1163,10 +1137,6 @@ msgstr ""
|
|||
msgid "Confirm New Password"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeName.jsx:64
|
||||
msgid "New Username"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeMail.jsx:59
|
||||
msgid ""
|
||||
"Changed Mail successfully. We sent you a verification mail, "
|
||||
|
@ -1177,6 +1147,10 @@ msgstr ""
|
|||
msgid "New Mail"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/DeleteAccount.jsx:66
|
||||
msgid "Yes, Delete My Account!"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/SocialSettings.jsx:38
|
||||
msgid "Block all Private Messages"
|
||||
msgstr ""
|
||||
|
@ -1189,8 +1163,30 @@ msgstr ""
|
|||
msgid "You have no users blocked"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/DeleteAccount.jsx:66
|
||||
msgid "Yes, Delete My Account!"
|
||||
#: src/components/UserMessages.jsx:28
|
||||
msgid ""
|
||||
"Please verify your mail address \n"
|
||||
"or your account could get deleted after a few days."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserMessages.jsx:49
|
||||
msgid "A new verification mail is getting sent to you."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/UserMessages.jsx:53
|
||||
msgid "Click here to request a new verification mail."
|
||||
msgstr ""
|
||||
|
||||
#: src/components/ChangeName.jsx:64
|
||||
msgid "New Username"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInForm.jsx:76
|
||||
msgid "Name or Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/LogInForm.jsx:87
|
||||
msgid "LogIn"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:14
|
||||
|
@ -1205,6 +1201,11 @@ msgctxt "keybinds"
|
|||
msgid "X"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:150
|
||||
msgctxt "keybinds"
|
||||
msgid "M"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Help.jsx:16
|
||||
#: src/components/windows/Settings.jsx:205
|
||||
msgctxt "keybinds"
|
||||
|
@ -1254,9 +1255,4 @@ msgstr ""
|
|||
#: src/components/windows/Help.jsx:31
|
||||
msgctxt "keybinds"
|
||||
msgid "C"
|
||||
msgstr ""
|
||||
|
||||
#: src/components/windows/Settings.jsx:150
|
||||
msgctxt "keybinds"
|
||||
msgid "M"
|
||||
msgstr ""
|
|
@ -10,7 +10,6 @@
|
|||
"dependencies": {
|
||||
"bcrypt": "^5.0.1",
|
||||
"bluebird": "^3.5.0",
|
||||
"body-parser": "^1.19.1",
|
||||
"bufferutil": "^4.0.6",
|
||||
"compression": "^1.7.3",
|
||||
"connect-redis": "^6.0.0",
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"dependencies": {
|
||||
"bcrypt": "^5.0.1",
|
||||
"bluebird": "^3.5.0",
|
||||
"body-parser": "^1.19.1",
|
||||
"bufferutil": "^4.0.6",
|
||||
"compression": "^1.7.3",
|
||||
"connect-redis": "^6.0.0",
|
||||
|
|
|
@ -177,10 +177,10 @@ export async function requestLeaveChan(channelId) {
|
|||
return t`Unknown Error`;
|
||||
}
|
||||
|
||||
export async function requestSolveCaptcha(text) {
|
||||
export async function requestSolveCaptcha(text, captchaid) {
|
||||
const res = await makeAPIPOSTRequest(
|
||||
'api/captcha',
|
||||
{ text },
|
||||
{ text, id: captchaid },
|
||||
);
|
||||
if (!res.errors && !res.success) {
|
||||
return {
|
||||
|
@ -193,7 +193,7 @@ export async function requestSolveCaptcha(text) {
|
|||
export async function requestHistoricalTimes(day, canvasId) {
|
||||
try {
|
||||
const date = dateToString(day);
|
||||
const url = `api/history?day=${date}&id=${canvasId}`;
|
||||
const url = `history?day=${date}&id=${canvasId}`;
|
||||
const response = await fetchWithTimeout(url);
|
||||
if (response.status !== 200) {
|
||||
return [];
|
||||
|
@ -271,3 +271,15 @@ export function requestDeleteAccount(password) {
|
|||
{ password },
|
||||
);
|
||||
}
|
||||
|
||||
export function requestRankings() {
|
||||
return makeAPIGETRequest(
|
||||
'ranking',
|
||||
);
|
||||
}
|
||||
|
||||
export function requestMe() {
|
||||
return makeAPIGETRequest(
|
||||
'api/me',
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import {
|
|||
requestBlock,
|
||||
requestBlockDm,
|
||||
requestLeaveChan,
|
||||
requestRankings,
|
||||
requestMe,
|
||||
} from './fetch';
|
||||
|
||||
export function sweetAlert(
|
||||
|
@ -446,10 +448,8 @@ export function remFromMessages(
|
|||
|
||||
export function fetchStats() {
|
||||
return async (dispatch) => {
|
||||
const response = await fetch('api/ranking', { credentials: 'include' });
|
||||
if (response.ok) {
|
||||
const rankings = await response.json();
|
||||
|
||||
const rankings = await requestRankings();
|
||||
if (!rankings.errors) {
|
||||
dispatch(receiveStats(rankings));
|
||||
}
|
||||
};
|
||||
|
@ -457,12 +457,8 @@ export function fetchStats() {
|
|||
|
||||
export function fetchMe() {
|
||||
return async (dispatch) => {
|
||||
const response = await fetch('api/me', {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const me = await response.json();
|
||||
const me = await requestMe();
|
||||
if (!me.errors) {
|
||||
dispatch(receiveMe(me));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -113,7 +113,6 @@ export type Action =
|
|||
dailyTotalPixels: number,
|
||||
ranking: number,
|
||||
dailyRanking: number,
|
||||
minecraftname: string,
|
||||
blockDm: boolean,
|
||||
canvases: Object,
|
||||
channels: Object,
|
||||
|
@ -129,7 +128,6 @@ export type Action =
|
|||
dailyTotalPixels: number,
|
||||
ranking: number,
|
||||
dailyRanking: number,
|
||||
minecraftname: string,
|
||||
blockDm: boolean,
|
||||
canvases: Object,
|
||||
channels: Object,
|
||||
|
|
|
@ -8,10 +8,12 @@ import path from 'path';
|
|||
import fs from 'fs';
|
||||
import process from 'process';
|
||||
import http from 'http';
|
||||
import url from 'url';
|
||||
import ppfunCaptcha from 'ppfun-captcha';
|
||||
|
||||
import { getIPFromRequest } from './utils/ip';
|
||||
import { setCaptchaSolution } from './utils/captcha';
|
||||
import { getRandomString } from './core/utils';
|
||||
|
||||
const PORT = process.env.PORT || 8080;
|
||||
const HOST = process.env.HOST || 'localhost';
|
||||
|
@ -23,30 +25,51 @@ const font = fs.readdirSync(path.resolve(__dirname, 'captchaFonts'))
|
|||
));
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
const captcha = ppfunCaptcha.create({
|
||||
width: 500,
|
||||
height: 300,
|
||||
fontSize: 180,
|
||||
stroke: 'black',
|
||||
fill: 'none',
|
||||
nodeDeviation: 2.5,
|
||||
connectionPathDeviation: 10.0,
|
||||
style: 'stroke-width: 4;',
|
||||
background: '#EFEFEF',
|
||||
font,
|
||||
console.log(req.url);
|
||||
|
||||
req.on('error', (err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
const ip = getIPFromRequest(req);
|
||||
const urlObject = url.parse(req.url, true);
|
||||
|
||||
setCaptchaSolution(captcha.text, ip);
|
||||
console.log(`Serving ${captcha.text} to ${ip}`);
|
||||
if (req.method === 'GET' && urlObject.pathname.endsWith('.svg')) {
|
||||
const captcha = ppfunCaptcha.create({
|
||||
width: 500,
|
||||
height: 300,
|
||||
fontSize: 180,
|
||||
stroke: 'black',
|
||||
fill: 'none',
|
||||
nodeDeviation: 2.5,
|
||||
connectionPathDeviation: 10.0,
|
||||
style: 'stroke-width: 4;',
|
||||
background: '#EFEFEF',
|
||||
font,
|
||||
});
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'image/svg+xml',
|
||||
'Cache-Control': 'no-cache',
|
||||
});
|
||||
res.write(captcha.data);
|
||||
res.end();
|
||||
const ip = getIPFromRequest(req);
|
||||
const captchaid = getRandomString();
|
||||
|
||||
setCaptchaSolution(captcha.text, ip, captchaid);
|
||||
console.log(`Serving ${captcha.text} to ${ip} / ${captchaid}`);
|
||||
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'image/svg+xml',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Captcha-Id': captchaid,
|
||||
});
|
||||
res.write(captcha.data);
|
||||
res.end();
|
||||
} else {
|
||||
res.writeHead(404, {
|
||||
'Content-Type': 'text/html',
|
||||
'Cache-Control': 'no-cache',
|
||||
});
|
||||
res.end(
|
||||
// eslint-disable-next-line max-len
|
||||
'<html><body><h1>Captchaserver: 404 Not Found</h1>Captchas are accessible via *.svp paths</body></html>',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PORT, HOST, () => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
/* @flow */
|
||||
/*
|
||||
* Entrypoint for main client script
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
import fetch from 'isomorphic-fetch'; // TODO put in the beggining with webpack!
|
||||
|
@ -117,6 +119,8 @@ function init() {
|
|||
ProtocolClient.connect();
|
||||
|
||||
store.dispatch(fetchStats());
|
||||
// TODO: We don't have to do this this often
|
||||
// the client might not even look at it
|
||||
setInterval(() => { store.dispatch(fetchStats()); }, 300000);
|
||||
}
|
||||
init();
|
||||
|
@ -156,7 +160,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
|
||||
|
||||
// on captcha received
|
||||
window.onCaptcha = async function onCaptcha(token: string) {
|
||||
window.onCaptcha = async function onCaptcha(token) {
|
||||
const body = JSON.stringify({
|
||||
token,
|
||||
});
|
||||
|
|
|
@ -53,7 +53,7 @@ const Alert = () => {
|
|||
<p className="modaltext">
|
||||
{alertMessage}
|
||||
</p>
|
||||
<p>
|
||||
<div>
|
||||
{(alertType === 'captcha')
|
||||
? <Captcha close={close} />
|
||||
: (
|
||||
|
@ -64,7 +64,7 @@ const Alert = () => {
|
|||
{alertBtn}
|
||||
</button>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -8,29 +8,47 @@
|
|||
|
||||
/* eslint-disable jsx-a11y/no-autofocus */
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { t } from 'ttag';
|
||||
|
||||
import { IoReloadCircleSharp } from 'react-icons/io5';
|
||||
import { requestSolveCaptcha } from '../actions/fetch';
|
||||
|
||||
function getUrl() {
|
||||
return `${window.ssv.captchaurl}/captcha.svg?${new Date().getTime()}`;
|
||||
async function getUrlAndId() {
|
||||
const url = window.ssv.captchaurl;
|
||||
const resp = await fetch(url, {
|
||||
cache: 'no-cache',
|
||||
});
|
||||
if (resp.ok) {
|
||||
const captchaid = resp.headers.get('captcha-id');
|
||||
const svgBlob = await resp.blob();
|
||||
return [URL.createObjectURL(svgBlob), captchaid];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const Captcha = ({ callback, close }) => {
|
||||
const [captchaUrl, setCaptchaUrl] = useState(getUrl());
|
||||
const [captchaData, setCaptchaData] = useState({});
|
||||
const [text, setText] = useState('');
|
||||
const [errors, setErrors] = useState([]);
|
||||
const [imgLoaded, setImgLoaded] = useState(false);
|
||||
|
||||
useEffect(async () => {
|
||||
const [svgUrl, captchaid] = await getUrlAndId();
|
||||
setCaptchaData({ url: svgUrl, id: captchaid });
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={async (e) => {
|
||||
e.preventDefault();
|
||||
const { errors: resErrors } = await requestSolveCaptcha(text);
|
||||
const { errors: resErrors } = await requestSolveCaptcha(
|
||||
text,
|
||||
captchaData.id,
|
||||
);
|
||||
if (resErrors) {
|
||||
setCaptchaUrl(getUrl());
|
||||
const [svgUrl, captchaid] = await getUrlAndId();
|
||||
setCaptchaData({ url: svgUrl, id: captchaid });
|
||||
setText('');
|
||||
setErrors(resErrors);
|
||||
} else {
|
||||
|
@ -58,21 +76,23 @@ const Captcha = ({ callback, close }) => {
|
|||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
opacity: (imgLoaded) ? 1 : 0,
|
||||
transform: 'translate(-50%,-50%)',
|
||||
transition: '100ms',
|
||||
}}
|
||||
src={captchaUrl}
|
||||
alt="CAPTCHA"
|
||||
onLoad={() => { setImgLoaded(true); }}
|
||||
onError={() => setErrors([t`Could not load captcha`])}
|
||||
/>
|
||||
{(captchaData.url) && (
|
||||
<img
|
||||
style={{
|
||||
width: '100%',
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
opacity: (imgLoaded) ? 1 : 0,
|
||||
transform: 'translate(-50%,-50%)',
|
||||
transition: '100ms',
|
||||
}}
|
||||
src={captchaData.url}
|
||||
alt="CAPTCHA"
|
||||
onLoad={() => { setImgLoaded(true); }}
|
||||
onError={() => setErrors([t`Could not load captcha`])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p className="modaltext">
|
||||
{t`Can't read? Reload:`}
|
||||
|
@ -82,9 +102,10 @@ const Captcha = ({ callback, close }) => {
|
|||
title={t`Reload`}
|
||||
className="modallink"
|
||||
style={{ fontSize: 28 }}
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
setImgLoaded(false);
|
||||
setCaptchaUrl(getUrl());
|
||||
const [svgUrl, captchaid] = await getUrlAndId();
|
||||
setCaptchaData({ url: svgUrl, id: captchaid });
|
||||
}}
|
||||
>
|
||||
<IoReloadCircleSharp />
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Admintools
|
||||
* Modtools
|
||||
* @flow
|
||||
*/
|
||||
|
||||
|
@ -31,7 +31,7 @@ async function submitImageAction(
|
|||
data.append('image', file);
|
||||
data.append('canvasid', canvas);
|
||||
data.append('coords', coords);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -51,7 +51,7 @@ async function submitProtAction(
|
|||
data.append('canvasid', canvas);
|
||||
data.append('ulcoor', tlcoords);
|
||||
data.append('brcoor', brcoords);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -72,7 +72,7 @@ async function submitRollback(
|
|||
data.append('canvasid', canvas);
|
||||
data.append('ulcoor', tlcoords);
|
||||
data.append('brcoor', brcoords);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -88,7 +88,7 @@ async function submitIPAction(
|
|||
const iplist = document.getElementById('iparea').value;
|
||||
data.append('ip', iplist);
|
||||
data.append('ipaction', action);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -101,7 +101,7 @@ async function getModList(
|
|||
) {
|
||||
const data = new FormData();
|
||||
data.append('modlist', true);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -119,7 +119,7 @@ async function submitRemMod(
|
|||
) {
|
||||
const data = new FormData();
|
||||
data.append('remmod', userId);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -133,7 +133,7 @@ async function submitMakeMod(
|
|||
) {
|
||||
const data = new FormData();
|
||||
data.append('makemod', userName);
|
||||
const resp = await fetch('./admintools', {
|
||||
const resp = await fetch('./api/modtools', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
body: data,
|
||||
|
@ -146,7 +146,7 @@ async function submitMakeMod(
|
|||
}
|
||||
|
||||
|
||||
function Admintools() {
|
||||
function Modtools() {
|
||||
const maxDate = getToday();
|
||||
|
||||
const [selectedCanvas, selectCanvas] = useState(0);
|
||||
|
@ -603,4 +603,4 @@ function Admintools() {
|
|||
);
|
||||
}
|
||||
|
||||
export default React.memo(Admintools);
|
||||
export default React.memo(Modtools);
|
|
@ -15,7 +15,7 @@ import Rankings from '../Rankings';
|
|||
// eslint-disable-next-line max-len
|
||||
const Converter = React.lazy(() => import(/* webpackChunkName: "converter" */ '../Converter'));
|
||||
// eslint-disable-next-line max-len
|
||||
const Admintools = React.lazy(() => import(/* webpackChunkName: "admintools" */ '../Admintools'));
|
||||
const Modtools = React.lazy(() => import(/* webpackChunkName: "modtools" */ '../Modtools'));
|
||||
|
||||
const UserArea = ({ windowId }) => {
|
||||
const name = useSelector((state) => state.user.name);
|
||||
|
@ -36,9 +36,9 @@ const UserArea = ({ windowId }) => {
|
|||
</Suspense>
|
||||
</div>
|
||||
{userlvl && (
|
||||
<div label={(userlvl === 1) ? t`Admintools` : t`Modtools`}>
|
||||
<div label={(userlvl === 1) ? t`Modtools` : t`Modtools`}>
|
||||
<Suspense fallback={<div>{t`Loading...`}</div>}>
|
||||
<Admintools />
|
||||
<Modtools />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -10,7 +10,7 @@ import sharp from 'sharp';
|
|||
import Sequelize from 'sequelize';
|
||||
import redis from '../data/redis';
|
||||
|
||||
import { admintoolsLogger } from './logger';
|
||||
import { modtoolsLogger } from './logger';
|
||||
import { getIPv6Subnet } from '../utils/ip';
|
||||
import { Blacklist, Whitelist, RegUser } from '../data/models';
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
|
@ -46,7 +46,7 @@ export async function executeIPAction(action: string, ips: string): string {
|
|||
const ipKey = getIPv6Subnet(ip);
|
||||
const key = `isprox:${ipKey}`;
|
||||
|
||||
admintoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`);
|
||||
modtoolsLogger.info(`ADMINTOOLS: ${action} ${ip}`);
|
||||
switch (action) {
|
||||
case 'ban':
|
||||
await Blacklist.findOrCreate({
|
||||
|
@ -150,7 +150,7 @@ export async function executeImageAction(
|
|||
);
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
admintoolsLogger.info(`ADMINTOOLS: Loaded image wth ${pxlCount} pixels to ${x}/${y}`);
|
||||
modtoolsLogger.info(`ADMINTOOLS: Loaded image wth ${pxlCount} pixels to ${x}/${y}`);
|
||||
return [
|
||||
200,
|
||||
`Successfully loaded image wth ${pxlCount} pixels to ${x}/${y}`,
|
||||
|
@ -238,7 +238,7 @@ export async function executeProtAction(
|
|||
height,
|
||||
protect,
|
||||
);
|
||||
admintoolsLogger.info(
|
||||
modtoolsLogger.info(
|
||||
// eslint-disable-next-line max-len
|
||||
`ADMINTOOLS: Set protect to ${protect} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`,
|
||||
);
|
||||
|
@ -333,7 +333,7 @@ export async function executeRollback(
|
|||
height,
|
||||
date,
|
||||
);
|
||||
admintoolsLogger.info(
|
||||
modtoolsLogger.info(
|
||||
// eslint-disable-next-line max-len
|
||||
`ADMINTOOLS: Rollback to ${date} for ${pxlCount} pixels at ${x} / ${y} with dimension ${width}x${height}`,
|
||||
);
|
||||
|
|
|
@ -48,7 +48,7 @@ export const LOG_MYSQL = parseInt(process.env.LOG_MYSQL, 10) || false;
|
|||
export const HOURLY_EVENT = parseInt(process.env.HOURLY_EVENT, 10) || false;
|
||||
|
||||
// Accounts
|
||||
export const APISOCKET_KEY = process.env.APISOCKET_KEY || 'changethis';
|
||||
export const APISOCKET_KEY = process.env.APISOCKET_KEY || null;
|
||||
// Comma seperated list of user ids of Admins
|
||||
export const ADMIN_IDS = (process.env.ADMIN_IDS)
|
||||
? process.env.ADMIN_IDS.split(',').map((z) => parseInt(z, 10)) : [];
|
||||
|
|
|
@ -48,12 +48,12 @@ export const proxyLogger = createLogger({
|
|||
],
|
||||
});
|
||||
|
||||
export const admintoolsLogger = createLogger({
|
||||
export const modtoolsLogger = createLogger({
|
||||
format: format.printf(({ message }) => message),
|
||||
transports: [
|
||||
new DailyRotateFile({
|
||||
level: 'info',
|
||||
filename: './log/admintools-%DATE%.log',
|
||||
filename: './log/modtools-%DATE%.log',
|
||||
maxSize: '20m',
|
||||
maxFiles: '14d',
|
||||
colorize: false,
|
||||
|
|
|
@ -14,21 +14,17 @@ export default async function getMe(user, lang = 'default') {
|
|||
const userdata = user.getUserData();
|
||||
// sanitize data
|
||||
const {
|
||||
name, mailVerified, minecraftname, mcVerified,
|
||||
name, mailVerified,
|
||||
} = userdata;
|
||||
if (!name) userdata.name = null;
|
||||
const messages = [];
|
||||
if (name && !mailVerified) {
|
||||
messages.push('not_verified');
|
||||
}
|
||||
if (minecraftname && !mcVerified) {
|
||||
messages.push('not_mc_verified');
|
||||
}
|
||||
if (messages.length > 0) {
|
||||
userdata.messages = messages;
|
||||
}
|
||||
delete userdata.mailVerified;
|
||||
delete userdata.mcVerified;
|
||||
|
||||
userdata.canvases = getLocalicedCanvases(lang);
|
||||
userdata.channels = {
|
||||
|
|
|
@ -26,6 +26,14 @@ export function getRandomInt(min, max) {
|
|||
return min + (Math.floor(Math.random() * range));
|
||||
}
|
||||
|
||||
/*
|
||||
* generates random string with a-z,0-9
|
||||
* 11 chars length
|
||||
*/
|
||||
export function getRandomString() {
|
||||
return Math.random().toString(36).substring(2, 15);
|
||||
}
|
||||
|
||||
export function distMax([x1, y1], [x2, y2]) {
|
||||
return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
|
||||
}
|
||||
|
|
|
@ -88,16 +88,6 @@ const RegUser = Model.define('User', {
|
|||
allowNull: true,
|
||||
},
|
||||
|
||||
minecraftid: {
|
||||
type: DataType.CHAR(36),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
minecraftname: {
|
||||
type: DataType.CHAR(16),
|
||||
allowNull: true,
|
||||
},
|
||||
|
||||
// when mail verification got requested,
|
||||
// used for purging unverified accounts
|
||||
verificationReqAt: {
|
||||
|
@ -125,10 +115,6 @@ const RegUser = Model.define('User', {
|
|||
return this.verified & 0x01;
|
||||
},
|
||||
|
||||
mcVerified(): boolean {
|
||||
return this.verified & 0x02;
|
||||
},
|
||||
|
||||
blockDm(): boolean {
|
||||
return this.blocks & 0x01;
|
||||
},
|
||||
|
@ -144,11 +130,6 @@ const RegUser = Model.define('User', {
|
|||
this.setDataValue('verified', val);
|
||||
},
|
||||
|
||||
mcVerified(num: boolean) {
|
||||
const val = (num) ? (this.verified | 0x02) : (this.verified & ~0x02);
|
||||
this.setDataValue('verified', val);
|
||||
},
|
||||
|
||||
blockDm(num: boolean) {
|
||||
const val = (num) ? (this.blocks | 0x01) : (this.blocks & ~0x01);
|
||||
this.setDataValue('blocks', val);
|
||||
|
|
|
@ -223,8 +223,6 @@ class User {
|
|||
return {
|
||||
name: null,
|
||||
mailVerified: false,
|
||||
mcVerified: false,
|
||||
minecraftname: null,
|
||||
blockDm: false,
|
||||
totalPixels: 0,
|
||||
dailyTotalPixels: 0,
|
||||
|
@ -242,8 +240,6 @@ class User {
|
|||
return {
|
||||
name: regUser.name,
|
||||
mailVerified: regUser.mailVerified,
|
||||
mcVerified: regUser.mcVerified,
|
||||
minecraftname: regUser.minecraftname,
|
||||
blockDm: regUser.blockDm,
|
||||
totalPixels: regUser.totalPixels,
|
||||
dailyTotalPixels: regUser.dailyTotalPixels,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# AdminAPI
|
||||
|
||||
API which is used for connecting with external applications and bridges to fetch informations.
|
||||
Modtools are in ./api
|
||||
|
||||
Connections to here just possible with header:
|
||||
|
||||
```
|
||||
Authorization: "Bearer APISOCKETKEY"
|
||||
```
|
|
@ -0,0 +1,143 @@
|
|||
import express from 'express';
|
||||
|
||||
import logger from '../../core/logger';
|
||||
import RegUser from '../../data/models/RegUser';
|
||||
import { getIPFromRequest } from '../../utils/ip';
|
||||
import { compareToHash } from '../../utils/hash';
|
||||
import { APISOCKET_KEY } from '../../core/config';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/*
|
||||
* Need APISOCKETKEY to access
|
||||
*/
|
||||
router.use(async (req, res, next) => {
|
||||
const { headers } = req;
|
||||
if (!headers.authorization
|
||||
|| !APISOCKET_KEY
|
||||
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
|
||||
const ip = getIPFromRequest(req);
|
||||
logger.warn(`API adminapi request from ${ip} rejected`);
|
||||
res.status(401);
|
||||
res.json({
|
||||
success: false,
|
||||
errors: ['No or invalid authorization header'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
router.use(express.json());
|
||||
|
||||
/*
|
||||
* check login credentials
|
||||
* useful for 3rd party login
|
||||
*/
|
||||
router.post('/checklogin', async (req, res) => {
|
||||
const errors = [];
|
||||
|
||||
const { password } = req.body;
|
||||
if (!password) {
|
||||
errors.push('No password given');
|
||||
}
|
||||
|
||||
const query = {
|
||||
attributes: [
|
||||
'id',
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
],
|
||||
};
|
||||
let userString;
|
||||
if (req.body.name) {
|
||||
query.where = { name: req.body.name };
|
||||
userString = req.body.name;
|
||||
} else if (req.body.email) {
|
||||
query.where = { email: req.body.email };
|
||||
userString = req.body.email;
|
||||
} else if (req.body.id) {
|
||||
query.where = { id: req.body.id };
|
||||
userString = String(req.body.id);
|
||||
} else {
|
||||
errors.push('No name or email given');
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
res.status(400);
|
||||
res.json({
|
||||
success: false,
|
||||
errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const reguser = await RegUser.findOne(query);
|
||||
if (!reguser) {
|
||||
res.json({
|
||||
success: false,
|
||||
errors: [`User ${userString} does not exist`],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!compareToHash(password, reguser.password)) {
|
||||
logger.info(
|
||||
`ADMINAPI: User ${reguser.name} / ${reguser.id} entered wronng password`,
|
||||
);
|
||||
res.json({
|
||||
success: false,
|
||||
errors: [`Password wrong for user ${userString}`],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`ADMINAPI: User ${reguser.name} / ${reguser.id} got loged in`);
|
||||
res.json({
|
||||
success: true,
|
||||
userdata: {
|
||||
id: reguser.id,
|
||||
name: reguser.name,
|
||||
email: reguser.email,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* get user data
|
||||
*/
|
||||
router.post('/userdata', async (req, res) => {
|
||||
const { id } = req.body;
|
||||
if (!id) {
|
||||
res.status(400);
|
||||
res.json({
|
||||
success: false,
|
||||
errors: ['No id given'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
const reguser = await RegUser.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
});
|
||||
if (!reguser) {
|
||||
res.json({
|
||||
success: false,
|
||||
errors: ['No such user'],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
userdata: {
|
||||
id: reguser.id,
|
||||
name: reguser.name,
|
||||
email: reguser.email,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,3 @@
|
|||
# API
|
||||
|
||||
api for accounts, requests that do not require login data or accounts should not be here
|
|
@ -16,14 +16,19 @@ export default async (req: Request, res: Response) => {
|
|||
const { t } = req.ttag;
|
||||
|
||||
try {
|
||||
const { text } = req.body;
|
||||
const { text, id } = req.body;
|
||||
if (!text) {
|
||||
res.status(400)
|
||||
.json({ errors: [t`No captcha text given`] });
|
||||
return;
|
||||
}
|
||||
if (!id) {
|
||||
res.status(400)
|
||||
.json({ errors: [t`No captcha id given`] });
|
||||
return;
|
||||
}
|
||||
|
||||
const ret = await checkCaptchaSolution(text, ip);
|
||||
const ret = await checkCaptchaSolution(text, ip, id);
|
||||
|
||||
switch (ret) {
|
||||
case 0:
|
||||
|
|
|
@ -1,13 +1,7 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
import session from '../../core/session';
|
||||
import passport from '../../core/passport';
|
||||
import { expressTTag } from '../../core/ttag';
|
||||
import logger from '../../core/logger';
|
||||
import User from '../../data/models/User';
|
||||
import { getIPFromRequest } from '../../utils/ip';
|
||||
|
@ -15,23 +9,17 @@ import { getIPFromRequest } from '../../utils/ip';
|
|||
import me from './me';
|
||||
import captcha from './captcha';
|
||||
import auth from './auth';
|
||||
import ranking from './ranking';
|
||||
import history from './history';
|
||||
import chatHistory from './chathistory';
|
||||
import startDm from './startdm';
|
||||
import leaveChan from './leavechan';
|
||||
import block from './block';
|
||||
import blockdm from './blockdm';
|
||||
import modtools from './modtools';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// this route doesn't need passport
|
||||
router.get('/ranking', ranking);
|
||||
|
||||
router.get('/history', history);
|
||||
|
||||
router.use(bodyParser.json());
|
||||
router.use(express.json());
|
||||
|
||||
router.use((err, req, res, next) => {
|
||||
if (err) {
|
||||
|
@ -43,11 +31,6 @@ router.use((err, req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* make localisations available
|
||||
*/
|
||||
router.use(expressTTag);
|
||||
|
||||
// captcah doesn't need a user
|
||||
router.post('/captcha', captcha);
|
||||
|
||||
|
@ -72,6 +55,12 @@ router.use(session);
|
|||
router.use(passport.initialize());
|
||||
router.use(passport.session());
|
||||
|
||||
/*
|
||||
* modtools
|
||||
* (does not json bodies, but urlencoded)
|
||||
*/
|
||||
router.use('/modtools', modtools);
|
||||
|
||||
/*
|
||||
* create dummy user with just ip if not
|
||||
* logged in
|
||||
|
|
|
@ -2,15 +2,12 @@
|
|||
*
|
||||
* starts a DM session
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import logger from '../../core/logger';
|
||||
import socketEvents from '../../socket/SocketEvents';
|
||||
|
||||
async function leaveChan(req: Request, res: Response) {
|
||||
async function leaveChan(req, res) {
|
||||
const channelId = parseInt(req.body.channelId, 10);
|
||||
const { user } = req;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* basic admin api
|
||||
* is used by ../components/Admintools
|
||||
* basic mod api
|
||||
* is used by ../components/Modtools
|
||||
*
|
||||
* @flow
|
||||
*
|
||||
|
@ -8,13 +8,10 @@
|
|||
|
||||
import express from 'express';
|
||||
import type { Request, Response } from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
import multer from 'multer';
|
||||
|
||||
import { getIPFromRequest } from '../utils/ip';
|
||||
import session from '../core/session';
|
||||
import passport from '../core/passport';
|
||||
import { admintoolsLogger } from '../core/logger';
|
||||
import { getIPFromRequest } from '../../utils/ip';
|
||||
import { modtoolsLogger } from '../../core/logger';
|
||||
import {
|
||||
executeIPAction,
|
||||
executeImageAction,
|
||||
|
@ -23,7 +20,7 @@ import {
|
|||
getModList,
|
||||
removeMod,
|
||||
makeMod,
|
||||
} from '../core/adminfunctions';
|
||||
} from '../../core/adminfunctions';
|
||||
|
||||
|
||||
const router = express.Router();
|
||||
|
@ -32,7 +29,7 @@ const router = express.Router();
|
|||
* multer middleware for getting POST parameters
|
||||
* into req.file (if file) and req.body for text
|
||||
*/
|
||||
router.use(bodyParser.urlencoded({ extended: true }));
|
||||
router.use(express.urlencoded({ extended: true }));
|
||||
const upload = multer({
|
||||
limits: {
|
||||
fileSize: 5 * 1024 * 1024,
|
||||
|
@ -41,18 +38,16 @@ const upload = multer({
|
|||
|
||||
|
||||
/*
|
||||
* make sure User is logged in and mod or admin
|
||||
* make sure User is logged in and mod or mod
|
||||
*/
|
||||
router.use(session);
|
||||
router.use(passport.initialize());
|
||||
router.use(passport.session());
|
||||
router.use(async (req, res, next) => {
|
||||
const ip = getIPFromRequest(req);
|
||||
if (!req.user) {
|
||||
admintoolsLogger.info(
|
||||
`ADMINTOOLS: ${ip} tried to access admintools without login`,
|
||||
modtoolsLogger.info(
|
||||
`MODTOOLS: ${ip} tried to access modtools without login`,
|
||||
);
|
||||
res.status(403).send('You are not logged in');
|
||||
const { t } = req.ttag;
|
||||
res.status(403).send(t`You are not logged in`);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
|
@ -60,14 +55,15 @@ router.use(async (req, res, next) => {
|
|||
* 2 = Mod
|
||||
*/
|
||||
if (!req.user.userlvl) {
|
||||
admintoolsLogger.info(
|
||||
`ADMINTOOLS: ${ip} / ${req.user.id} tried to access admintools`,
|
||||
modtoolsLogger.info(
|
||||
`MODTOOLS: ${ip} / ${req.user.id} tried to access modtools`,
|
||||
);
|
||||
res.status(403).send('You are not allowed to access this page');
|
||||
const { t } = req.ttag;
|
||||
res.status(403).send(t`You are not allowed to access this page`);
|
||||
return;
|
||||
}
|
||||
admintoolsLogger.info(
|
||||
`ADMINTOOLS: ${req.user.id} / ${req.user.regUser.name} is using admintools`,
|
||||
modtoolsLogger.info(
|
||||
`MODTOOLS: ${req.user.id} / ${req.user.regUser.name} is using modtools`,
|
||||
);
|
||||
|
||||
next();
|
||||
|
@ -128,7 +124,8 @@ router.post('/', upload.single('image'), async (req, res, next) => {
|
|||
*/
|
||||
router.use(async (req, res, next) => {
|
||||
if (req.user.userlvl !== 1) {
|
||||
res.status(403).send('Just admins can do that');
|
||||
const { t } = req.ttag;
|
||||
res.status(403).send(t`Just admins can do that`);
|
||||
return;
|
||||
}
|
||||
next();
|
|
@ -6,7 +6,7 @@
|
|||
import fs from 'fs';
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import { BACKUP_DIR } from '../../core/config';
|
||||
import { BACKUP_DIR } from '../core/config';
|
||||
|
||||
async function history(req: Request, res: Response) {
|
||||
const { day, id } = req.query;
|
|
@ -3,16 +3,133 @@
|
|||
* @flow
|
||||
*/
|
||||
|
||||
import api from './api';
|
||||
import express from 'express';
|
||||
import etag from 'etag';
|
||||
import path from 'path';
|
||||
|
||||
import ranking from './ranking';
|
||||
import history from './history';
|
||||
import tiles from './tiles';
|
||||
import chunks from './chunks';
|
||||
import admintools from './admintools';
|
||||
import resetPassword from './reset_password';
|
||||
import api from './api';
|
||||
import adminapi from './adminapi';
|
||||
|
||||
export {
|
||||
api,
|
||||
tiles,
|
||||
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
|
||||
import { expressTTag } from '../core/ttag';
|
||||
import generateGlobePage from '../ssr-components/Globe';
|
||||
import generateMainPage from '../ssr-components/Main';
|
||||
|
||||
import { MONTH } from '../core/constants';
|
||||
import { GUILDED_INVITE } from '../core/config';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/*
|
||||
* ranking of pixels placed
|
||||
* daily and total
|
||||
*/
|
||||
router.get('/ranking', ranking);
|
||||
|
||||
/*
|
||||
* give: date per query
|
||||
* returns: array of HHMM backups available
|
||||
*/
|
||||
router.get('/history', history);
|
||||
|
||||
/*
|
||||
* zoomed tiles
|
||||
*/
|
||||
router.use('/tiles', tiles);
|
||||
|
||||
/*
|
||||
* adminapi
|
||||
*/
|
||||
router.use('/adminapi', adminapi);
|
||||
|
||||
//
|
||||
// public folder
|
||||
// (this should be served with nginx or other webserver)
|
||||
// -----------------------------------------------------------------------------
|
||||
router.use(express.static(path.join(__dirname, 'public'), {
|
||||
maxAge: 3 * MONTH,
|
||||
extensions: ['html'],
|
||||
}));
|
||||
|
||||
/*
|
||||
* Redirect to guilded
|
||||
*/
|
||||
router.use('/guilded', (req, res) => {
|
||||
res.redirect(GUILDED_INVITE);
|
||||
});
|
||||
|
||||
/*
|
||||
* Serving Chunks
|
||||
*/
|
||||
router.get(
|
||||
'/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+)(/)?:z([0-9]+)?.bmp',
|
||||
chunks,
|
||||
admintools,
|
||||
resetPassword,
|
||||
};
|
||||
);
|
||||
|
||||
/*
|
||||
* Following with translations
|
||||
* ---------------------------------------------------------------------------
|
||||
*/
|
||||
router.use(expressTTag);
|
||||
|
||||
/*
|
||||
* API calls
|
||||
*/
|
||||
router.use('/api', api);
|
||||
|
||||
/*
|
||||
* Password Reset Link
|
||||
*/
|
||||
router.use('/reset_password', resetPassword);
|
||||
|
||||
//
|
||||
// 3D Globe (react generated)
|
||||
// -----------------------------------------------------------------------------
|
||||
const globeEtag = etag(
|
||||
assets.globe.js.join('_'),
|
||||
{ weak: true },
|
||||
);
|
||||
router.get('/globe', async (req, res) => {
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
ETag: globeEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === globeEtag) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(generateGlobePage(req.lang));
|
||||
});
|
||||
|
||||
//
|
||||
// Main Page (react generated)
|
||||
// -----------------------------------------------------------------------------
|
||||
const indexEtag = etag(
|
||||
assets.client.js.join('_'),
|
||||
{ weak: true },
|
||||
);
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
ETag: indexEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === indexEtag) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(generateMainPage(req.lang));
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
import rankings from '../../core/ranking';
|
||||
import rankings from '../core/ranking';
|
||||
|
||||
|
||||
export default async (req: Request, res: Response) => {
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import express from 'express';
|
||||
import bodyParser from 'body-parser';
|
||||
|
||||
import type { Request, Response } from 'express';
|
||||
|
||||
|
@ -21,7 +20,7 @@ const router = express.Router();
|
|||
/*
|
||||
* decode form data to req.body
|
||||
*/
|
||||
router.use(bodyParser.urlencoded({ extended: true }));
|
||||
router.use(express.urlencoded({ extended: true }));
|
||||
|
||||
|
||||
/*
|
||||
|
|
125
src/server.js
125
src/server.js
|
@ -1,43 +1,31 @@
|
|||
/* @flow */
|
||||
/*
|
||||
* Entrypoint for main server script
|
||||
*/
|
||||
|
||||
import url from 'url';
|
||||
import path from 'path';
|
||||
import compression from 'compression';
|
||||
import express from 'express';
|
||||
import http from 'http';
|
||||
import etag from 'etag';
|
||||
|
||||
|
||||
// import baseCss from './components/base.tcss';
|
||||
import forceGC from './core/forceGC';
|
||||
import assets from './assets.json'; // eslint-disable-line import/no-unresolved
|
||||
import logger from './core/logger';
|
||||
import rankings from './core/ranking';
|
||||
import models from './data/models';
|
||||
import routes from './routes';
|
||||
import chatProvider from './core/ChatProvider';
|
||||
import { expressTTag } from './core/ttag';
|
||||
|
||||
import SocketServer from './socket/SocketServer';
|
||||
import APISocketServer from './socket/APISocketServer';
|
||||
|
||||
import {
|
||||
api,
|
||||
tiles,
|
||||
chunks,
|
||||
admintools,
|
||||
resetPassword,
|
||||
} from './routes';
|
||||
import generateGlobePage from './ssr-components/Globe';
|
||||
import generateMainPage from './ssr-components/Main';
|
||||
|
||||
import { SECOND, MONTH } from './core/constants';
|
||||
import { PORT, HOST, GUILDED_INVITE } from './core/config';
|
||||
import { PORT, HOST } from './core/config';
|
||||
import { SECOND } from './core/constants';
|
||||
|
||||
import { startAllCanvasLoops } from './core/tileserver';
|
||||
|
||||
startAllCanvasLoops();
|
||||
|
||||
|
||||
const app = express();
|
||||
app.disable('x-powered-by');
|
||||
|
||||
|
@ -70,19 +58,6 @@ function wsupgrade(request, socket, head) {
|
|||
}
|
||||
server.on('upgrade', wsupgrade);
|
||||
|
||||
|
||||
//
|
||||
// API
|
||||
// -----------------------------------------------------------------------------
|
||||
app.use('/api', api);
|
||||
|
||||
|
||||
//
|
||||
// Serving Zoomed Tiless
|
||||
// -----------------------------------------------------------------------------
|
||||
app.use('/tiles', tiles);
|
||||
|
||||
|
||||
/*
|
||||
* use gzip compression for following calls
|
||||
/* level from -1 (default, 6) to 0 (no) from 1 (fastest) to 9 (best)
|
||||
|
@ -98,93 +73,7 @@ app.use(compression({
|
|||
},
|
||||
}));
|
||||
|
||||
|
||||
//
|
||||
// public folder
|
||||
// (this should be served with nginx or other webserver)
|
||||
// -----------------------------------------------------------------------------
|
||||
app.use(express.static(path.join(__dirname, 'public'), {
|
||||
maxAge: 3 * MONTH,
|
||||
extensions: ['html'],
|
||||
}));
|
||||
|
||||
|
||||
//
|
||||
// Redirecct to guilded
|
||||
// -----------------------------------------------------------------------------
|
||||
app.use('/guilded', (req, res) => {
|
||||
res.redirect(GUILDED_INVITE);
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// Serving Chunks
|
||||
// -----------------------------------------------------------------------------
|
||||
app.get('/chunks/:c([0-9]+)/:x([0-9]+)/:y([0-9]+)(/)?:z([0-9]+)?.bmp', chunks);
|
||||
|
||||
//
|
||||
// Admintools
|
||||
// -----------------------------------------------------------------------------
|
||||
app.use('/admintools', admintools);
|
||||
|
||||
/*
|
||||
* decide which language to use
|
||||
*/
|
||||
app.use(expressTTag);
|
||||
|
||||
|
||||
//
|
||||
// Password Reset Link
|
||||
// -----------------------------------------------------------------------------
|
||||
app.use('/reset_password', resetPassword);
|
||||
|
||||
|
||||
//
|
||||
// 3D Globe (react generated)
|
||||
// -----------------------------------------------------------------------------
|
||||
const globeEtag = etag(
|
||||
assets.globe.js.join('_'),
|
||||
{ weak: true },
|
||||
);
|
||||
app.get('/globe', async (req, res) => {
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
ETag: globeEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === globeEtag) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(generateGlobePage(req.lang));
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// Main Page (react generated)
|
||||
// -----------------------------------------------------------------------------
|
||||
const indexEtag = etag(
|
||||
assets.client.js.join('_'),
|
||||
{ weak: true },
|
||||
);
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
res.set({
|
||||
'Cache-Control': `private, max-age=${15 * 60}`, // seconds
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
ETag: indexEtag,
|
||||
});
|
||||
|
||||
if (req.headers['if-none-match'] === indexEtag) {
|
||||
res.status(304).end();
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).send(generateMainPage(req.lang));
|
||||
});
|
||||
|
||||
app.use(routes);
|
||||
|
||||
//
|
||||
// ip config
|
||||
|
|
|
@ -26,6 +26,7 @@ async function verifyClient(info, done) {
|
|||
const ip = getIPFromRequest(req);
|
||||
|
||||
if (!headers.authorization
|
||||
|| !APISOCKET_KEY
|
||||
|| headers.authorization !== `Bearer ${APISOCKET_KEY}`) {
|
||||
logger.warn(`API ws request from ${ip} authenticated`);
|
||||
return done(false);
|
||||
|
|
|
@ -71,13 +71,17 @@ function evaluateResult(captchaText, userText) {
|
|||
*
|
||||
* @param text Solution of captcha
|
||||
* @param ip
|
||||
* @param ttl time to be valid in seconds
|
||||
* @param captchaid
|
||||
*/
|
||||
export function setCaptchaSolution(
|
||||
text,
|
||||
ip,
|
||||
captchaid = null,
|
||||
) {
|
||||
const key = `capt:${ip}`;
|
||||
let key = `capt:${ip}`;
|
||||
if (captchaid) {
|
||||
key += `:${captchaid}`;
|
||||
}
|
||||
return redis.setAsync(key, text, 'EX', CAPTCHA_TIMEOUT);
|
||||
}
|
||||
|
||||
|
@ -93,9 +97,13 @@ export function setCaptchaSolution(
|
|||
export async function checkCaptchaSolution(
|
||||
text,
|
||||
ip,
|
||||
captchaid = null,
|
||||
) {
|
||||
const ipn = getIPv6Subnet(ip);
|
||||
const key = `capt:${ip}`;
|
||||
let key = `capt:${ip}`;
|
||||
if (captchaid) {
|
||||
key += `:${captchaid}`;
|
||||
}
|
||||
const solution = await redis.getAsync(key);
|
||||
if (solution) {
|
||||
if (evaluateResult(solution.toString('utf8'), text)) {
|
||||
|
@ -109,7 +117,7 @@ export async function checkCaptchaSolution(
|
|||
);
|
||||
return 2;
|
||||
}
|
||||
logger.info(`CAPTCHA ${ip} timed out`);
|
||||
logger.info(`CAPTCHA ${ip}:${captchaid} timed out`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
scp ./testmodule.py root@pixelplanet:/etc/matrix-synapse/testmodule.py
|
||||
ssh root@pixelplanet 'systemctl restart matrix-synapse'
|
|
@ -0,0 +1,141 @@
|
|||
#
|
||||
# Python3 Module for matrix-synapse
|
||||
#
|
||||
|
||||
from typing import Awaitable, Callable, Optional, Tuple
|
||||
import logging
|
||||
|
||||
import synapse
|
||||
from synapse import module_api
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MyAuthProvider:
|
||||
def __init__(self, config: dict, api: module_api):
|
||||
self.api = api
|
||||
if 'ppfunurl' not in config:
|
||||
raise Exception('Pixelplanet ppfunurl not configured')
|
||||
self.ppfunurl = config["ppfunurl"]
|
||||
if 'apisocketkey' not in config:
|
||||
raise Exception('Pixelplanet apisocketkey not configured')
|
||||
self.apisocketkey = config["apisocketkey"]
|
||||
|
||||
self.credentials = {
|
||||
"bob": "building",
|
||||
"@scoop:matrix.org": "digging",
|
||||
}
|
||||
|
||||
api.register_password_auth_provider_callbacks(
|
||||
check_3pid_auth = self.check_3pid_pass,
|
||||
auth_checkers = {
|
||||
("m.login.password", ("password",)): self.check_pass,
|
||||
},
|
||||
)
|
||||
|
||||
async def check_credentials(
|
||||
self,
|
||||
query,
|
||||
) -> Optional[
|
||||
Tuple[
|
||||
int,
|
||||
str,
|
||||
Optional[str]
|
||||
]
|
||||
]:
|
||||
try:
|
||||
resp = await self.api.http_client.post_json_get_json(
|
||||
self.ppfunurl + '/adminapi/checklogin',
|
||||
query,
|
||||
[[ "authorization", ['Bearer ' + self.apisocketkey] ]],
|
||||
)
|
||||
if not resp["success"]:
|
||||
raise Exception(resp["errors"][0])
|
||||
userdata = resp['userdata']
|
||||
return (userdata['id'], userdata['name'], userdata['email'])
|
||||
except Exception as e:
|
||||
logger.warning('Could not login via ppfun: %s', e)
|
||||
return None
|
||||
|
||||
async def login(
|
||||
self,
|
||||
ppfun_id,
|
||||
ppfun_name,
|
||||
ppfun_email,
|
||||
) -> Optional[
|
||||
Tuple[
|
||||
str,
|
||||
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
|
||||
]
|
||||
]:
|
||||
localpart = f'pp_{ppfun_id}'
|
||||
user_id = self.api.get_qualified_user_id(localpart)
|
||||
logger.info('check if user %s exists', user_id)
|
||||
does_exist = await self.api.check_user_exists(user_id)
|
||||
if does_exist is None:
|
||||
logger.info('User %s does not exist yet, registering new user', user_id)
|
||||
emails = None
|
||||
if ppfun_email:
|
||||
emails = [ppfun_email]
|
||||
try:
|
||||
user_id = await self.api.register_user(localpart, ppfun_name, emails)
|
||||
except Exception as e:
|
||||
logger.warning('Could not create user %s, because %s', user_id, e)
|
||||
return None
|
||||
logger.info('User %s logged in via ppfun %s', user_id, ppfun_name)
|
||||
return user_id, None
|
||||
|
||||
async def check_3pid_pass(
|
||||
self,
|
||||
medium: str,
|
||||
address: str,
|
||||
password: str,
|
||||
) -> Optional[
|
||||
Tuple[
|
||||
str,
|
||||
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
|
||||
]
|
||||
]:
|
||||
if medium != "email" or not address:
|
||||
return None
|
||||
ppfun_userdata = await self.check_credentials({
|
||||
"email": address,
|
||||
"password": password
|
||||
})
|
||||
if ppfun_userdata is not None:
|
||||
logger.info('User %s logging in with ppfun credentials', ppfun_userdata)
|
||||
ret = await self.login(*ppfun_userdata)
|
||||
return ret
|
||||
return None
|
||||
|
||||
async def check_pass(
|
||||
self,
|
||||
username: str,
|
||||
login_type: str,
|
||||
login_dict: "synapse.module_api.JsonDict",
|
||||
) -> Optional[
|
||||
Tuple[
|
||||
str,
|
||||
Optional[Callable[["synapse.module_api.LoginResponse"], Awaitable[None]]],
|
||||
]
|
||||
]:
|
||||
if login_type != "m.login.password":
|
||||
return None
|
||||
|
||||
query = {
|
||||
"password": login_dict.get('password')
|
||||
}
|
||||
if username.startswith('@pp_'):
|
||||
query['id'] = username[4: username.index(':')]
|
||||
elif username.startswith('pp_') and username.endswith(f':{self.api._server_name}'):
|
||||
query['id'] = username[3: username.index(':')]
|
||||
else:
|
||||
query['name'] = username
|
||||
|
||||
ppfun_userdata = await self.check_credentials(query)
|
||||
|
||||
if ppfun_userdata is not None:
|
||||
logger.info('User %s logging in with ppfun credentials', ppfun_userdata)
|
||||
ret = await self.login(*ppfun_userdata)
|
||||
return ret
|
||||
return None
|
Loading…
Reference in New Issue