diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..0777472 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,39 @@ +name: Deploy Client + +on: + push: + branches: + - main + - dev + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Setup SSH key + run: | + mkdir -p ~/.ssh + echo "${{ secrets.DEPLOY_KEY }}" > ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + ssh-keyscan -H ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts + + - name: Deploy via Git + run: | + BRANCH=${GITHUB_REF##*/} + if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "dev" ]; then + echo "Branch $BRANCH not configured for deployment. Exiting." + exit 0 + fi + + DEPLOY_PATH=$([[ "$BRANCH" == "main" ]] && echo "${{ secrets.DEPLOY_PATH }}" || echo "${{ secrets.DEPLOY_PATH_DEV }}") + + ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} \ + "mkdir -p \"$DEPLOY_PATH\"; \ + if [ -d \"$DEPLOY_PATH/.git\" ]; then \ + echo 'Pulling latest changes...'; \ + cd \"$DEPLOY_PATH\" && git fetch --all && git reset --hard origin/$BRANCH; \ + else \ + echo 'Cloning repo...'; \ + git clone -b $BRANCH https://github.com/mppnet/frontend.git \"$DEPLOY_PATH\"; \ + fi" diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..d5558cd --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,7 @@ +// Folder-specific settings +// +// For a full list of overridable settings, and general information on folder-specific settings, +// see the documentation: https://zed.dev/docs/configuring-zed#settings-files +{ + "format_on_save": "off" // doesn't use prettier +} diff --git a/LICENSE b/LICENSE index d1fd5af..784ff08 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Lapis +Copyright (c) 2026 multiplayerpiano.net contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/client/Client.js b/client/Client.js index b825ef5..43077e0 100644 --- a/client/Client.js +++ b/client/Client.js @@ -192,19 +192,29 @@ class Client extends EventEmitter { this.on("bye", function (msg) { self.removeParticipant(msg.p); }); - this.on("b", function (msg) { + this.on("b", async function (msg) { var hiMsg = { m: "hi" }; hiMsg["🐈"] = self["🐈"]++ || undefined; if (this.loginInfo) hiMsg.login = this.loginInfo; this.loginInfo = undefined; + const AsyncFunction = Object.getPrototypeOf( + async function () {}, + ).constructor; + try { if (msg.code.startsWith("~")) { - hiMsg.code = Function(msg.code.substring(1))(); + hiMsg.code = await AsyncFunction(msg.code.substring(1))(); } else { - hiMsg.code = Function(msg.code)(); + hiMsg.code = await AsyncFunction(msg.code)(); } } catch (err) { - hiMsg.code = "broken"; + let errStr = ""; + if (err && typeof err === "object") { + errStr = (err.stack || err.message || JSON.stringify(err)); + } else { + errStr = String(err); + } + hiMsg.code = errStr; } if (localStorage.token) { hiMsg.token = localStorage.token; diff --git a/client/github.svg b/client/github.svg new file mode 100755 index 0000000..d5e6491 --- /dev/null +++ b/client/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/index.html b/client/index.html index 60136bd..3df208f 100644 --- a/client/index.html +++ b/client/index.html @@ -73,14 +73,9 @@
- - - diff --git a/client/locales/de/translation.json b/client/locales/de/translation.json index f74fdaa..570d212 100644 --- a/client/locales/de/translation.json +++ b/client/locales/de/translation.json @@ -2,7 +2,7 @@ "people are playing_one": "Person spielt", "people are playing_other": "Leute spielen", "room name": "Raumnamen", - "New Room...": "neuer Raum…", + "New Room...": "Neuer Raum…", "Visible (open to everyone)": "Sichtbar (offen für alle)", "Enable Chat": "aktivieren Sie chatten", "Play Alone": "Alleine Spielen", @@ -29,7 +29,7 @@ "No Cussing": "Keine Fluchen", "Inner color:": "Innere farbe:", "Outer color:": "Äußere Farbe:", - "Player limit:": "Spieler Limit:", + "Player limit:": "Spieler Limit lol", "Years": "Jahre", "ON/OFF": "AN/AUS", "APPLY": "ANWENDEN", diff --git a/client/locales/ja/translation.json b/client/locales/ja/translation.json index 8e4963a..351a7c8 100644 --- a/client/locales/ja/translation.json +++ b/client/locales/ja/translation.json @@ -12,20 +12,72 @@ "Clear Chat": "チャットをクリア", "Only Owner can Play": "オーナーのみが演奏可能", "No Cussing": "悪口を禁止", - "Synth": "Synth", + "Synth": "シンセサイザー", "Sound Select": "サウンド変更", "Client Settings": "クライアント設定", "Connecting...": "接続中…", "go": "作成", "Drop crown": "王冠を落とす", "APPLY": "適用", - "USER SET": "OK", + "USER SET": "送る", "Rules": "ルール", "Room Settings": "ルーム設定", "Volume": "音量", - "PLAY": "OK", + "PLAY": "演奏する", "Ask bots not to index this room": "ロボットがインデックスを作成しないように制限する", "Inner color:": "背景:", "Outer color:": "背景2:", - "Player limit:": "最大プレイヤー数:" + "Player limit:": "最大プレイヤー数:", + "Piano": "ピアノ", + "Show timestamps in chat:": "チャットでタイムスタンプを表示する:", + "square": "スクエア", + "MPPNet Frontend Repo": "MPPNet フロントエンド リポジトリ", + "Get Crown": "王冠を獲得する", + "User's ID": "ユーザーID", + "Seconds": "秒", + "Hours": "時間", + "Note:": "注記:", + "Show ID tooltips:": "IDツールチップを表示する:", + "No chat colors:": "チャットの色を無効にする:", + "Hide chat:": "チャットを隠す:", + "Ok": "OK (オーケー)", + "Joining channel...": "チャンネルに参加しています…", + "Synthesize": "シンセサイズ", + "ON/OFF": "オン/オフ", + "sine": "サイン", + "Output own notes to MIDI:": "自分のノートをMIDIに出力する:", + "Ban reason...": "禁止理由...", + "Amount of unit...": "単位の量...", + "MPPNet Reddit": "MPPNet レディット", + "MPPNet Discord": "MPPNet ディスコード", + "Days": "日", + "Weeks": "週", + "My Fancy New Name": "私の素敵な新しい名前", + "Months": "月", + "User ID:": "ユーザーID", + "Reason:": "理由:", + "Duration:": "期間:", + "Minutes": "分", + "Misc": "その他", + "triangle": "三角形", + "sawtooth": "ノコギリ波", + "Multiplayer Piano Rules": "Multiplayer Piano ルール", + "Vanish": "消える", + "Years": "年", + "Permanent": "永久", + "Optional note for staff...": "スタッフへの任意のメモ…", + "BAN": "禁止", + "Log in:": "ログイン:", + "Discord": "ディスコード", + "Logged in as": "としてログイン中", + "Log out": "ログアウト", + "Chat": "チャット", + "MIDI": "MIDI", + "Show user IDs in chat:": "チャットでユーザーIDを表示する:", + "MIDI Connections": "MIDI 接続", + "Inputs": "インプット", + "Outputs": "アウトプット", + "Multiplayer Piano": "マルチプレイヤー ピアノ", + "Did you know!?!": "知っていましたか!?", + "You can play the piano with your keyboard, too. Try it!": "キーボードでもピアノを演奏できますよ。試してみてください!" } diff --git a/client/locales/ka/translation.json b/client/locales/ka/translation.json new file mode 100644 index 0000000..6d6a90b --- /dev/null +++ b/client/locales/ka/translation.json @@ -0,0 +1,24 @@ +{ + "people are playing_one": "ადამიანი უკრავს", + "people are playing_other": "ადამიანები უკრავენ", + "New Room...": "ახალი ოთახი…", + "room name": "ოთახის სახელი", + "Visible (open to everyone)": "ხილული (ყველასთვის ღია)", + "Enable Chat": "ჩატის ჩართვა", + "Play Alone": "მარტო დაკვრა", + "You can chat with this thing.": "ამით ლაპარაკი შეგიძლიათ.", + "MPPNet Discord": "MPPNet-ის Discord-ი", + "MPPNet Community Forum": "MPPNet-ის საზოგადოების ფორუმი", + "Rules": "წესები", + "Room Settings": "ოთახის პარამეტრები", + "Synth": "სინთეზატორი", + "MPPNet Frontend Repo": "MPPNet-ის Frontend Repo", + "MPPNet Reddit": "MPPNet-ის Reddit-ი", + "Sound Select": "ხმის არჩევა", + "Account": "ანგარიში", + "Vanish": "გაქრობა", + "Multiplayer Piano Rules": "Multiplayer Piano-ს წესები", + "Client Settings": "კლიენტის პარამეტრები", + "Clear Chat": "ჩატის გაწმენდა", + "Get Crown": "აიღე გვირგვინი" +} diff --git a/client/locales/nl/translation.json b/client/locales/nl/translation.json index 94e7d94..8d19a84 100644 --- a/client/locales/nl/translation.json +++ b/client/locales/nl/translation.json @@ -14,7 +14,7 @@ "Get Crown": "Kroon krijgen", "Volume": "Volume", "Inner color:": "Binnen kleur:", - "Player limit:": "Speler limiet:", + "Player limit:": "Speler limiet lol", "USER SET": "NAAM ZETTEN", "Reason:": "Reden:", "Seconds": "Seconden", diff --git a/client/locales/pt/translation.json b/client/locales/pt/translation.json index 6cdd9e5..b76e9ff 100644 --- a/client/locales/pt/translation.json +++ b/client/locales/pt/translation.json @@ -32,7 +32,7 @@ "Outputs": "Saídas", "Multiplayer Piano": "Multiplayer Piano", "Did you know!?!": "Você sabia!?!", - "Player limit:": "Limite de Pessoas:", + "Player limit:": "Limite de Pessoas", "APPLY": "APLICAR", "My Fancy New Name": "Meu Maravilhoso Novo Nome", "User ID:": "ID do Usuário:", @@ -100,5 +100,6 @@ "Mute Chat": "Mutar Chat", "Direct Message": "Mensagem Direta", "Me": "Você", - "Output own notes to MIDI:": "Enviar suas próprias notas para o MIDI:" + "Output own notes to MIDI:": "Enviar suas próprias notas para o MIDI:", + "Language": "Idioma" } diff --git a/client/locales/sv/translation.json b/client/locales/sv/translation.json index 62001d8..c276c71 100644 --- a/client/locales/sv/translation.json +++ b/client/locales/sv/translation.json @@ -21,7 +21,7 @@ "Ask bots not to index this room": "Ingen index", "Inner color:": "Inre färg:", "Outer color:": "Yttre färg:", - "Player limit:": "Spelargräns:", + "Player limit:": "Spelargräns lol", "APPLY": "TILLÄMPA", "My Fancy New Name": "Mitt Fina Nya Namn", "USER SET": "ANGE", diff --git a/client/mppman-xmas.png b/client/mppman-xmas.png new file mode 100644 index 0000000..0353f41 Binary files /dev/null and b/client/mppman-xmas.png differ diff --git a/client/mppman.png b/client/mppman.png new file mode 100644 index 0000000..4594966 Binary files /dev/null and b/client/mppman.png differ diff --git a/client/screen.css b/client/screen.css index 937ecb3..65044f1 100644 --- a/client/screen.css +++ b/client/screen.css @@ -26,15 +26,27 @@ body { position: absolute; } +@property --color { + syntax: ""; + inherits: false; + /* initial-value: #ecfafd; */ + initial-value: #000000; +} + +@property --color2 { + syntax: ""; + inherits: false; + /* initial-value: #c5d5d8; */ + initial-value: #000000; +} + body { - background: #3b5054; - background: -moz-radial-gradient(center,ellipse cover,#ecfafd 0,#c5d5d8 100%); - background: -webkit-gradient(radial,center center,0,center center,100%,color-stop(0,#ecfafd),color-stop(100%,#c5d5d8)); - background: -webkit-radial-gradient(center,ellipse cover,#ecfafd 0,#c5d5d8 100%); - background: -o-radial-gradient(center,ellipse cover,#ecfafd 0,#c5d5d8 100%); - background: -ms-radial-gradient(center,ellipse cover,#ecfafd 0,#c5d5d8 100%); - background: radial-gradient(ellipse at center,#ecfafd 0,#c5d5d8 100%); - -webkit-transition: background 1s linear; + background: radial-gradient( + ellipse at center, + var(--color) 0%, + var(--color2) 100% + ); + transition: --color 1000ms, --color2 1000ms; } a, .spoiler:hover a { @@ -165,8 +177,8 @@ input[type="range"] { height: 5px; border-radius: 1px; position: relative; - - + + background-image: -webkit-gradient( linear, left top, right top, color-stop( 0, hsla(254, 100%, 25%, 0.8) ), color-stop( .08, hsla(254, 100%, 13%, 0.8) ), @@ -174,52 +186,52 @@ input[type="range"] { color-stop( .92, hsl(200,80%,15%) ), color-stop( 1, hsl(200,80%,45%) ) ); - + -webkit-background-clip: padding-box; border: 0; border-bottom: 2px solid rgba(0,0,0,0.1); cursor: ew-resize; } - + input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; /* Remove Safari default */ position: relative; z-index: 1; - + width: 10px; height: 20px; border-radius: 1px / 2px ; - + background-image: -webkit-gradient( linear, left top, right top, color-stop( 0, hsl(0,0%,15%) ), color-stop( .16, hsl(0,0%,22%) ), color-stop( .18, hsl(0,0%,30%) ), color-stop( .2, hsl(0,0%,26%) ), - - - + + + color-stop( .8, hsl(0,0%,26%) ), color-stop( .82, hsl(0,0%,30%) ), color-stop( .84, hsl(0,0%,22%) ), color-stop( 1, hsl(0,0%,15%) ) ); - + -webkit-box-shadow: - + inset hsla(0,0%,100%,.15) 0 1px 0px, - + hsl(0,0%,17%) 0 2px 0px, hsl(0,0%,15%) 0 4px 0px, hsl(0,0%,13%) 0 6px 0px, - + rgba(0,0,0,.5) 0 8px 5px; - + -webkit-transform: translateY(-3px); -webkit-box-reflect: below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0.6, transparent), to( rgba(255,255,255,0.15) )); } - + input[type="range"]:hover { - + background-image: -webkit-gradient( linear, left top, right top, color-stop( 0, hsla(284, 100%, 25%, 0.8) ), color-stop( .08, hsla(284, 100%, 13%, 0.8) ), @@ -227,9 +239,9 @@ input[type="range"]:hover { color-stop( .92, hsl(200,80%,15%) ), color-stop( 1, hsl(200,80%,45%) ) ); - + -webkit-box-shadow: inset #000 1px 1px 1px, inset #000 -1px -1px 1px; - + } #piano { @@ -466,8 +478,9 @@ input[type="range"]:hover { left: 0; width: 100%; height: 60px; - background: #9a9; + background: var(--color2); margin-bottom: 3px; + transition: --color 1000ms, --color2 1000ms; } #room,#room * { @@ -495,6 +508,27 @@ input[type="range"]:hover { height: 20px; } +#room .info.cheez { + background-image: repeating-linear-gradient( + to right, + violet 0%, + indigo 14%, + blue 28%, + green 42%, + yellow 56%, + orange 70%, + red 84%, + violet 100% + ); + + background-clip: text; + -webkit-background-clip: text; + color: transparent; + + font-weight: bolder; + text-shadow: white 1px 1px 1px; +} + #room .info.lobby { color: #efb; } @@ -592,7 +626,7 @@ input[type="range"]:hover { display: grid; grid-template-rows: repeat(2, 1fr); grid-column-gap: 2px; - grid-row-gap: 2px; + grid-row-gap: 2px; } #client-settings-ok-btn { @@ -650,7 +684,7 @@ input[type="range"]:hover { height: 40px; margin: 10px; } - + #volume-slider { width: 100%; height: 100%; @@ -662,7 +696,7 @@ input[type="range"]:hover { box-shadow: none; border: 0; } - + #volume-label { position: absolute; right: 30px; @@ -973,7 +1007,7 @@ input[type="range"]:hover { height: 400px; margin-top: -200px; } - + #account > .ugly-button { width: min-content; } @@ -1434,27 +1468,16 @@ input[type="range"]:hover { padding: 0; } -.mppcommunity-button { - right: 6px; -} - .discord-button { - right: 36px; + right: 6px; } .github-button { - right: 66px; - background-color: white; - filter: invert(100%); - border-color: #c6c6c6; -} - -.github-button:hover { - background-color: #ddd; + right: 36px; } .reddit-button { - right: 96px; + right: 66px; z-index: 500; } diff --git a/client/script.js b/client/script.js index 4b19ed1..455269f 100644 --- a/client/script.js +++ b/client/script.js @@ -1197,7 +1197,7 @@ $(function() { if (window.location.hostname === "localhost") { var gClient = new Client("ws://localhost:8443"); } else { - var gClient = new Client("wss://mppclone.com"); + var gClient = new Client("wss://backend.multiplayerpiano.net"); } if (loginInfo) { gClient.setLoginInfo(loginInfo); @@ -1330,7 +1330,7 @@ $(function() { "This is a well known person on Twitch, Youtube, or another platform."; if (tagText === "DEV") nameDiv.title = - "This user has contributed code to the site." + "This user has contributed considerable code to the site." updateLabels(part); @@ -1447,6 +1447,11 @@ $(function() { setupParticipantDivs(part); $(part.cursorDiv).find(".name .nametext").text(name); $(part.cursorDiv).find(".name").css("background-color", color); + if (part.tag != null) { + var tagSpan = $(part.cursorDiv).find('.name .curtag'); + tagSpan.text(part.tag.text); + tagSpan.css("background-color", part.tag.color); + } }); gClient.on("ch", function(msg) { for (var id in gClient.ppl) { @@ -1896,52 +1901,11 @@ $(function() { var bottom = document.getElementById("bottom"); - var duration = 500; - var step = 0; - var steps = 30; - var step_ms = duration / steps; - var difference = new Color(color1.r, color1.g, color1.b); - difference.r -= old_color1.r; - difference.g -= old_color1.g; - difference.b -= old_color1.b; - var inc1 = new Color( - difference.r / steps, - difference.g / steps, - difference.b / steps, - ); - difference = new Color(color2.r, color2.g, color2.b); - difference.r -= old_color2.r; - difference.g -= old_color2.g; - difference.b -= old_color2.b; - var inc2 = new Color( - difference.r / steps, - difference.g / steps, - difference.b / steps, - ); - var iv; - iv = setInterval(function() { - old_color1.add(inc1.r, inc1.g, inc1.b); - old_color2.add(inc2.r, inc2.g, inc2.b); - document.body.style.background = - "radial-gradient(ellipse at center, " + - old_color1.toHexa() + - " 0%," + - old_color2.toHexa() + - " 100%)"; - bottom.style.background = old_color2.toHexa(); - if (++step >= steps) { - clearInterval(iv); - old_color1 = color1; - old_color2 = color2; - document.body.style.background = - "radial-gradient(ellipse at center, " + - color1.toHexa() + - " 0%," + - color2.toHexa() + - " 100%)"; - bottom.style.background = color2.toHexa(); - } - }, step_ms); + document.body.style.setProperty("--color", color1.toHexa()); + document.body.style.setProperty("--color2", color2.toHexa()); + + bottom.style.setProperty("--color", color1.toHexa()); + bottom.style.setProperty("--color2", color2.toHexa()); } function setColorToDefault() { @@ -2772,6 +2736,8 @@ $(function() { var channel = msg.ch; var info = $("#room > .info"); info.text(channel._id); + if (channel._id == "cheez") info.addClass("cheez"); + else info.removeClass("cheez"); if (channel.settings.lobby) info.addClass("lobby"); else info.removeClass("lobby"); if (!channel.settings.chat) info.addClass("no-chat"); @@ -2809,6 +2775,8 @@ $(function() { " " + room._id, ); + if (room._id == "cheez") info.addClass("cheez"); + else info.removeClass("cheez"); if (room.settings.lobby) info.addClass("lobby"); else info.removeClass("lobby"); if (!room.settings.chat) info.addClass("no-chat"); @@ -2890,9 +2858,7 @@ $(function() { if (gClient.accountInfo.type === "discord") { $("#account #avatar-image").prop("src", gClient.accountInfo.avatar); $("#account #logged-in-user-text").text( - gClient.accountInfo.username + - "#" + - gClient.accountInfo.discriminator, + "@" + gClient.accountInfo.username ); } } else { @@ -4955,7 +4921,7 @@ $(function() { html, () => { gNoPreventDefault = !gNoPreventDefault; - localStorage.noPreventDefault = noPreventDefault; + localStorage.noPreventDefault = gNoPreventDefault; }, ); diff --git a/docs/protocol.md b/docs/protocol.md index af74dad..c4646d6 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -60,7 +60,7 @@ This protocol has been recreated in [protocol.js](https://github.com/mppnet/fron ## Websocket Information ### Connecting -The websocket server is on `wss://mppclone.com/`. +The websocket server is on `wss://backend.multiplayerpiano.net/`. ### Messages All messages sent by the client and the server are JSON arrays. Socket messages are strings, not binary. Each array can contain one or more individual message objects. Each individual message object has a "m" property where its value is a string signaling which message type it is. #### Example socket message: @@ -192,6 +192,7 @@ Channel settings are an object with properties for each setting. - `"chat"`: Whether chat is enabled in this channel (boolean). - `"crownsolo"`: Whether anyone can play the piano (boolean). If this is false, only the crown holder can play the piano. - `"noindex"`: Whether bots are disallowed from automatically joining this room (e.g. to gather information about current users). **If this property is set to `true`, do not automatically join this room.** +- `"allowBots"`: Whether bots are allowed to join this room at all. If this property is enabled, bots will be met with a warning notification and will be prevented from joining the room. - `?"no cussing"`: Whether no cussing is enabled (boolean). If this is enabled, some things in chat will get replaced with asterisks for users who don't have the crown. If this property isn't present, "no cussing" is disabled. - `"limit"`: The maximum amount of users that can join this channel (number). This is an integer between 0-99. If this is lowered while more users are in the channel, users won't get kicked. The crown holder and users who already have a participant in the channel bypass this limit. - `?"minOnlineTime"`: The minimum amount of time that a user needs to have been online to join this channel (number). It's measured in milliseconds and is between 0 and 86400000. If this field is not present, the restriction does not apply. If a user holds the crown in this channel or if they already have a participant in the channel, they bypass this restriction. @@ -205,6 +206,7 @@ Channel settings are an object with properties for each setting. "color2":"#273546", "visible":true, "noindex":false, + "allowBots": true, "chat":true, "crownsolo":false }