Merge branch 'develop' into activitypub

This commit is contained in:
Barış Soner Uşaklı
2024-06-11 18:18:14 -04:00
73 changed files with 234 additions and 185 deletions

View File

@@ -3,7 +3,7 @@
"ip": "رقم الآي بي <strong> %1 </strong>", "ip": "رقم الآي بي <strong> %1 </strong>",
"nodes-responded": "عدد %1 نقطة/نقاط استجابوا خلال %2 جزء من الثانية.", "nodes-responded": "عدد %1 نقطة/نقاط استجابوا خلال %2 جزء من الثانية.",
"host": "المضيف", "host": "المضيف",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 възела отговориха в рамките на %2мсек!", "nodes-responded": "%1 възела отговориха в рамките на %2мсек!",
"host": "сървър", "host": "сървър",
"primary": "основен / изпълнение на задачите", "primary": "primary / jobs",
"pid": "ид. на процеса", "pid": "ид. на процеса",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "на линия", "online": "на линия",
@@ -19,6 +19,7 @@
"registered": "Регистрирани", "registered": "Регистрирани",
"sockets": "Сокети", "sockets": "Сокети",
"connection-count": "Connection Count",
"guests": "Гости", "guests": "Гости",
"info": "Информация" "info": "Информация"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 vazeb odpovědělo během %2ms.", "nodes-responded": "%1 vazeb odpovědělo během %2ms.",
"host": "host", "host": "host",
"primary": "primární / spuštěné úlohy", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "připojen", "online": "připojen",
@@ -19,6 +19,7 @@
"registered": "Registrován", "registered": "Registrován",
"sockets": "Sockety", "sockets": "Sockety",
"connection-count": "Connection Count",
"guests": "Hosté", "guests": "Hosté",
"info": "Informace" "info": "Informace"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP<strong>%1</strong>", "ip": "IP<strong>%1</strong>",
"nodes-responded": "%1 Knoten antworteten innerhalb von %2ms", "nodes-responded": "%1 Knoten antworteten innerhalb von %2ms",
"host": "Host", "host": "Host",
"primary": "Primärer / Laufjob", "primary": "primary / jobs",
"pid": "PID", "pid": "PID",
"nodejs": "Node.js Version", "nodejs": "Node.js Version",
"online": "Online", "online": "Online",
@@ -19,6 +19,7 @@
"registered": "Registriert", "registered": "Registriert",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Gäste", "guests": "Gäste",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "¡%1 nodos respondieron en %2ms!", "nodes-responded": "¡%1 nodos respondieron en %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "en-linea", "online": "en-linea",
@@ -19,6 +19,7 @@
"registered": "Registrado", "registered": "Registrado",
"sockets": "Toma", "sockets": "Toma",
"connection-count": "Connection Count",
"guests": "Invitados", "guests": "Invitados",
"info": "Información" "info": "Información"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 noeuds ont répondu en %2ms !", "nodes-responded": "%1 noeuds ont répondu en %2ms !",
"host": "hôte", "host": "hôte",
"primary": "Tâches principales / Exécuter", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "en ligne", "online": "en ligne",
@@ -19,6 +19,7 @@
"registered": "Enregistré", "registered": "Enregistré",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Invités", "guests": "Invités",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 צמתים הגיבו בתוך %2מילי שניות!", "nodes-responded": "%1 צמתים הגיבו בתוך %2מילי שניות!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "מקוון", "online": "מקוון",
@@ -19,6 +19,7 @@
"registered": "רשום", "registered": "רשום",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "אורחים", "guests": "אורחים",
"info": "מידע" "info": "מידע"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "Domaćin", "host": "Domaćin",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "Na mreži", "online": "Na mreži",
@@ -19,6 +19,7 @@
"registered": "Registriran", "registered": "Registriran",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Gosti", "guests": "Gosti",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 csomópont válaszolt %2mp-n belül!", "nodes-responded": "%1 csomópont válaszolt %2mp-n belül!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Regisztrált", "registered": "Regisztrált",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Vendégek", "guests": "Vendégek",
"info": "Információ" "info": "Információ"

View File

@@ -3,7 +3,7 @@
"ip": " IP % 1", "ip": " IP % 1",
"nodes-responded": "%1 հանգույցներ արձագանքեցին %2ms-ի սահմաններում:", "nodes-responded": "%1 հանգույցներ արձագանքեցին %2ms-ի սահմաններում:",
"host": "host", "host": "host",
"primary": "առաջնային / գործարկվող աշխատատեղեր", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "առցանց", "online": "առցանց",
@@ -19,6 +19,7 @@
"registered": "Գրանցված", "registered": "Գրանցված",
"sockets": "Վարդակներ", "sockets": "Վարդակներ",
"connection-count": "Connection Count",
"guests": "Հյուրեր", "guests": "Հյուրեր",
"info": "տեղեկատվություն" "info": "տեղեկատվություն"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodi hanno risposto entro %2ms!", "nodes-responded": "%1 nodi hanno risposto entro %2ms!",
"host": "host", "host": "host",
"primary": "processi primari/eseguiti", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registrato", "registered": "Registrato",
"sockets": "Socket", "sockets": "Socket",
"connection-count": "Connection Count",
"guests": "Ospiti", "guests": "Ospiti",
"info": "Informazioni" "info": "Informazioni"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "1ードは2ms以内に応答しました", "nodes-responded": "1ードは2ms以内に応答しました",
"host": "ホスト", "host": "ホスト",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "オンライン", "online": "オンライン",
@@ -19,6 +19,7 @@
"registered": "登録数", "registered": "登録数",
"sockets": "ソケット数", "sockets": "ソケット数",
"connection-count": "Connection Count",
"guests": "ゲスト数", "guests": "ゲスト数",
"info": "情報" "info": "情報"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1개의 노드가 %2밀리초 안에 응답했습니다!", "nodes-responded": "%1개의 노드가 %2밀리초 안에 응답했습니다!",
"host": "호스트", "host": "호스트",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "PID", "pid": "PID",
"nodejs": "Node.js", "nodejs": "Node.js",
"online": "온라인", "online": "온라인",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "소켓", "sockets": "소켓",
"connection-count": "Connection Count",
"guests": "비회원", "guests": "비회원",
"info": "정보" "info": "정보"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 serveri atbildēja %2ms laikā!", "nodes-responded": "%1 serveri atbildēja %2ms laikā!",
"host": "serveris", "host": "serveris",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "tiešsaistē", "online": "tiešsaistē",
@@ -19,6 +19,7 @@
"registered": "Reģistrētie", "registered": "Reģistrētie",
"sockets": "Tīkla savienojumi", "sockets": "Tīkla savienojumi",
"connection-count": "Connection Count",
"guests": "Viesi", "guests": "Viesi",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Geregistreerd", "registered": "Geregistreerd",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Gasten", "guests": "Gasten",
"info": "Informatie" "info": "Informatie"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 maszyn odpowiedziało w ciągu %2ms!", "nodes-responded": "%1 maszyn odpowiedziało w ciągu %2ms!",
"host": "host", "host": "host",
"primary": "główne / uruchomione zadania", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "dostępny", "online": "dostępny",
@@ -19,6 +19,7 @@
"registered": "Zarejestrowane", "registered": "Zarejestrowane",
"sockets": "Sockety", "sockets": "Sockety",
"connection-count": "Connection Count",
"guests": "Goście", "guests": "Goście",
"info": "Informacja" "info": "Informacja"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes respondidos dentro de %2ms!", "nodes-responded": "%1 nodes respondidos dentro de %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registrado", "registered": "Registrado",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Visitantes", "guests": "Visitantes",
"info": "Informação" "info": "Informação"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nós responderam dentro de %2ms!", "nodes-responded": "%1 nós responderam dentro de %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registados", "registered": "Registados",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Convidados", "guests": "Convidados",
"info": "Informação" "info": "Informação"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "Узлов: %1. Время ответа %2мс!", "nodes-responded": "Узлов: %1. Время ответа %2мс!",
"host": "хост", "host": "хост",
"primary": "первичный", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "онлайн", "online": "онлайн",
@@ -19,6 +19,7 @@
"registered": "Авторизованных", "registered": "Авторизованных",
"sockets": "Сокеты", "sockets": "Сокеты",
"connection-count": "Connection Count",
"guests": "Гостей", "guests": "Гостей",
"info": "Сырые данные" "info": "Сырые данные"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 väzieb odpovedalo počas %2ms.", "nodes-responded": "%1 väzieb odpovedalo počas %2ms.",
"host": "hosť", "host": "hosť",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "pripojený", "online": "pripojený",
@@ -19,6 +19,7 @@
"registered": "Registrovaný", "registered": "Registrovaný",
"sockets": "Sockety", "sockets": "Sockety",
"connection-count": "Connection Count",
"guests": "Hostia", "guests": "Hostia",
"info": "Informácie" "info": "Informácie"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 vozlišč se je odzvalo v %2ms!", "nodes-responded": "%1 vozlišč se je odzvalo v %2ms!",
"host": "gostitelj", "host": "gostitelj",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "na spletu", "online": "na spletu",
@@ -19,6 +19,7 @@
"registered": "Registrirani", "registered": "Registrirani",
"sockets": "Vtičnice", "sockets": "Vtičnice",
"connection-count": "Connection Count",
"guests": "Gostje", "guests": "Gostje",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes responded within %2ms!", "nodes-responded": "%1 nodes responded within %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Registered", "registered": "Registered",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Guests", "guests": "Guests",
"info": "Info" "info": "Info"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nodes ตอบสนองแล้วภายใน %2ms!", "nodes-responded": "%1 nodes ตอบสนองแล้วภายใน %2ms!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "ลงทะเบียนแล้ว", "registered": "ลงทะเบียนแล้ว",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "ผู้เยี่ยมเยียน", "guests": "ผู้เยี่ยมเยียน",
"info": "ข้อมูล" "info": "ข้อมูล"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%2ms içinde %1 düğüm yanıt verdi!", "nodes-responded": "%2ms içinde %1 düğüm yanıt verdi!",
"host": "sunucu", "host": "sunucu",
"primary": "ana sunucu / işlemleri gerçekleştir", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "çevrimiçi", "online": "çevrimiçi",
@@ -19,6 +19,7 @@
"registered": "Kayıtlı", "registered": "Kayıtlı",
"sockets": "Soketler", "sockets": "Soketler",
"connection-count": "Connection Count",
"guests": "Ziyaretçiler", "guests": "Ziyaretçiler",
"info": "Bilgi" "info": "Bilgi"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 вузлів відповіли за %2мс!", "nodes-responded": "%1 вузлів відповіли за %2мс!",
"host": "host", "host": "host",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "online", "online": "online",
@@ -19,6 +19,7 @@
"registered": "Зареєстровано", "registered": "Зареєстровано",
"sockets": "Сокети", "sockets": "Сокети",
"connection-count": "Connection Count",
"guests": "Гостей", "guests": "Гостей",
"info": "Інфо" "info": "Інфо"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1 nút đã phản hồi trong %2ms!", "nodes-responded": "%1 nút đã phản hồi trong %2ms!",
"host": "máy chủ", "host": "máy chủ",
"primary": "công việc chính/điều hành", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "trực tuyến", "online": "trực tuyến",
@@ -19,6 +19,7 @@
"registered": "Đã đăng ký", "registered": "Đã đăng ký",
"sockets": "Sockets", "sockets": "Sockets",
"connection-count": "Connection Count",
"guests": "Khách", "guests": "Khách",
"info": "Thông tin" "info": "Thông tin"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1个节点在%2ms内响应", "nodes-responded": "%1个节点在%2ms内响应",
"host": "主机", "host": "主机",
"primary": "主/运行任务", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "在线", "online": "在线",
@@ -19,6 +19,7 @@
"registered": "已注册", "registered": "已注册",
"sockets": "接口", "sockets": "接口",
"connection-count": "Connection Count",
"guests": "游客", "guests": "游客",
"info": "信息" "info": "信息"

View File

@@ -3,7 +3,7 @@
"ip": "IP <strong>%1</strong>", "ip": "IP <strong>%1</strong>",
"nodes-responded": "%1個節點在%2ms內響應", "nodes-responded": "%1個節點在%2ms內響應",
"host": "主機", "host": "主機",
"primary": "primary / run jobs", "primary": "primary / jobs",
"pid": "pid", "pid": "pid",
"nodejs": "nodejs", "nodejs": "nodejs",
"online": "在線", "online": "在線",
@@ -19,6 +19,7 @@
"registered": "已註冊", "registered": "已註冊",
"sockets": "網路接口", "sockets": "網路接口",
"connection-count": "Connection Count",
"guests": "訪客", "guests": "訪客",
"info": "資訊" "info": "資訊"

View File

@@ -331,6 +331,12 @@ UserObjectFull:
example: example:
- administrators - administrators
- Staff - Staff
iconBackgrounds:
type: array
items:
type: string
description: A valid CSS colour code
example: '#fff'
muted: muted:
type: boolean type: boolean
description: Whether or not the user has been muted. description: Whether or not the user has been muted.

View File

@@ -152,12 +152,6 @@ get:
type: boolean type: boolean
enableQuickReply: enableQuickReply:
type: boolean type: boolean
iconBackgrounds:
type: array
items:
type: string
description: A valid CSS colour code
example: '#fff'
emailPrompt: emailPrompt:
type: number type: number
useragent: useragent:

View File

@@ -116,6 +116,8 @@ get:
type: number type: number
socketCount: socketCount:
type: number type: number
connectionCount:
type: number
users: users:
type: object type: object
properties: properties:

View File

@@ -152,12 +152,6 @@ get:
type: boolean type: boolean
enableQuickReply: enableQuickReply:
type: boolean type: boolean
iconBackgrounds:
type: array
items:
type: string
description: A valid CSS colour code
example: '#fff'
emailPrompt: emailPrompt:
type: number type: number
useragent: useragent:

View File

@@ -27,7 +27,7 @@ define('accounts/picture', [
icon: { text: ajaxify.data['icon:text'], bgColor: ajaxify.data['icon:bgColor'] }, icon: { text: ajaxify.data['icon:text'], bgColor: ajaxify.data['icon:bgColor'] },
defaultAvatar: ajaxify.data.defaultAvatar, defaultAvatar: ajaxify.data.defaultAvatar,
allowProfileImageUploads: ajaxify.data.allowProfileImageUploads, allowProfileImageUploads: ajaxify.data.allowProfileImageUploads,
iconBackgrounds: config.iconBackgrounds, iconBackgrounds: ajaxify.data.iconBackgrounds,
user: { user: {
uid: ajaxify.data.uid, uid: ajaxify.data.uid,
username: ajaxify.data.username, username: ajaxify.data.username,

View File

@@ -640,7 +640,7 @@ usersAPI.changePicture = async (caller, data) => {
picture = returnData && returnData.picture; picture = returnData && returnData.picture;
} }
const validBackgrounds = await user.getIconBackgrounds(caller.uid); const validBackgrounds = await user.getIconBackgrounds();
if (!validBackgrounds.includes(data.bgColor)) { if (!validBackgrounds.includes(data.bgColor)) {
data.bgColor = validBackgrounds[0]; data.bgColor = validBackgrounds[0];
} }

View File

@@ -12,9 +12,9 @@ blocksController.getBlocks = async function (req, res) {
const resultsPerPage = 50; const resultsPerPage = 50;
const start = Math.max(0, page - 1) * resultsPerPage; const start = Math.max(0, page - 1) * resultsPerPage;
const stop = start + resultsPerPage - 1; const stop = start + resultsPerPage - 1;
const payload = res.locals.userData;
const { uid, username, userslug, blocksCount } = payload;
const { uid, username, userslug, blocksCount } = await user.getUserFields(res.locals.uid, ['uid', 'username', 'userslug', 'blocksCount']);
const payload = {};
const uids = await user.blocks.list(uid); const uids = await user.blocks.list(uid);
const data = await plugins.hooks.fire('filter:user.getBlocks', { const data = await plugins.hooks.fire('filter:user.getBlocks', {
uids: uids, uids: uids,

View File

@@ -9,7 +9,8 @@ const meta = require('../../meta');
const categoriesController = module.exports; const categoriesController = module.exports;
categoriesController.get = async function (req, res) { categoriesController.get = async function (req, res) {
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']); const payload = res.locals.userData;
const { username, userslug } = payload;
const [states, allCategoriesData] = await Promise.all([ const [states, allCategoriesData] = await Promise.all([
user.getCategoryWatchState(res.locals.uid), user.getCategoryWatchState(res.locals.uid),
categories.buildForSelect(res.locals.uid, 'find', ['descriptionParsed', 'depth', 'slug']), categories.buildForSelect(res.locals.uid, 'find', ['descriptionParsed', 'depth', 'slug']),
@@ -31,7 +32,6 @@ categoriesController.get = async function (req, res) {
} }
}); });
const payload = {};
payload.categories = categoriesData; payload.categories = categoriesData;
payload.title = `[[pages:account/watched-categories, ${username}]]`; payload.title = `[[pages:account/watched-categories, ${username}]]`;
payload.breadcrumbs = helpers.buildBreadcrumbs([ payload.breadcrumbs = helpers.buildBreadcrumbs([

View File

@@ -2,7 +2,6 @@
const db = require('../../database'); const db = require('../../database');
const meta = require('../../meta'); const meta = require('../../meta');
const user = require('../../user');
const helpers = require('../helpers'); const helpers = require('../helpers');
const consentController = module.exports; const consentController = module.exports;
@@ -11,11 +10,10 @@ consentController.get = async function (req, res, next) {
if (!meta.config.gdpr_enabled) { if (!meta.config.gdpr_enabled) {
return next(); return next();
} }
const payload = res.locals.userData;
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']); const { username, userslug } = payload;
const consented = await db.getObjectField(`user:${res.locals.uid}`, 'gdpr_consent'); const consented = await db.getObjectField(`user:${res.locals.uid}`, 'gdpr_consent');
const payload = {};
payload.gdpr_consent = parseInt(consented, 10) === 1; payload.gdpr_consent = parseInt(consented, 10) === 1;
payload.digest = { payload.digest = {
frequency: meta.config.dailyDigestFreq || 'off', frequency: meta.config.dailyDigestFreq || 'off',

View File

@@ -6,13 +6,16 @@ const helpers = require('../helpers');
const groups = require('../../groups'); const groups = require('../../groups');
const privileges = require('../../privileges'); const privileges = require('../../privileges');
const plugins = require('../../plugins'); const plugins = require('../../plugins');
const accountHelpers = require('./helpers');
const file = require('../../file'); const file = require('../../file');
const editController = module.exports; const editController = module.exports;
editController.get = async function (req, res) { editController.get = async function (req, res, next) {
const [{ const { userData } = res.locals;
if (!userData) {
return next();
}
const {
username, username,
userslug, userslug,
isSelf, isSelf,
@@ -20,36 +23,36 @@ editController.get = async function (req, res) {
groups: _groups, groups: _groups,
groupTitleArray, groupTitleArray,
allowMultipleBadges, allowMultipleBadges,
}, canUseSignature, canManageUsers] = await Promise.all([ } = userData;
accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query),
const [canUseSignature, canManageUsers] = await Promise.all([
privileges.global.can('signature', req.uid), privileges.global.can('signature', req.uid),
privileges.admin.can('admin:users', req.uid), privileges.admin.can('admin:users', req.uid),
]); ]);
const payload = {}; userData.maximumSignatureLength = meta.config.maximumSignatureLength;
payload.maximumSignatureLength = meta.config.maximumSignatureLength; userData.maximumAboutMeLength = meta.config.maximumAboutMeLength;
payload.maximumAboutMeLength = meta.config.maximumAboutMeLength; userData.maximumProfileImageSize = meta.config.maximumProfileImageSize;
payload.maximumProfileImageSize = meta.config.maximumProfileImageSize; userData.allowMultipleBadges = meta.config.allowMultipleBadges === 1;
payload.allowMultipleBadges = meta.config.allowMultipleBadges === 1; userData.allowAccountDelete = meta.config.allowAccountDelete === 1;
payload.allowAccountDelete = meta.config.allowAccountDelete === 1; userData.allowWebsite = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:website'];
payload.allowWebsite = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:website']; userData.allowAboutMe = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:aboutme'];
payload.allowAboutMe = !isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:aboutme']; userData.allowSignature = canUseSignature && (!isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:signature']);
payload.allowSignature = canUseSignature && (!isSelf || !!meta.config['reputation:disabled'] || reputation >= meta.config['min:rep:signature']); userData.profileImageDimension = meta.config.profileImageDimension;
payload.profileImageDimension = meta.config.profileImageDimension; userData.defaultAvatar = user.getDefaultAvatar();
payload.defaultAvatar = user.getDefaultAvatar();
payload.groups = _groups.filter(g => g && g.userTitleEnabled && !groups.isPrivilegeGroup(g.name) && g.name !== 'registered-users'); userData.groups = _groups.filter(g => g && g.userTitleEnabled && !groups.isPrivilegeGroup(g.name) && g.name !== 'registered-users');
if (req.uid === res.locals.uid || canManageUsers) { if (req.uid === res.locals.uid || canManageUsers) {
const { associations } = await plugins.hooks.fire('filter:auth.list', { uid: res.locals.uid, associations: [] }); const { associations } = await plugins.hooks.fire('filter:auth.list', { uid: res.locals.uid, associations: [] });
payload.sso = associations; userData.sso = associations;
} }
if (!allowMultipleBadges) { if (!allowMultipleBadges) {
payload.groupTitle = groupTitleArray[0]; userData.groupTitle = groupTitleArray[0];
} }
payload.groups.sort((a, b) => { userData.groups.sort((a, b) => {
const i1 = groupTitleArray.indexOf(a.name); const i1 = groupTitleArray.indexOf(a.name);
const i2 = groupTitleArray.indexOf(b.name); const i2 = groupTitleArray.indexOf(b.name);
if (i1 === -1) { if (i1 === -1) {
@@ -59,14 +62,14 @@ editController.get = async function (req, res) {
} }
return i1 - i2; return i1 - i2;
}); });
payload.groups.forEach((group) => { userData.groups.forEach((group) => {
group.userTitle = group.userTitle || group.displayName; group.userTitle = group.userTitle || group.displayName;
group.selected = groupTitleArray.includes(group.name); group.selected = groupTitleArray.includes(group.name);
}); });
payload.groupSelectSize = Math.min(10, Math.max(5, payload.groups.length + 1)); userData.groupSelectSize = Math.min(10, Math.max(5, userData.groups.length + 1));
payload.title = `[[pages:account/edit, ${username}]]`; userData.title = `[[pages:account/edit, ${username}]]`;
payload.breadcrumbs = helpers.buildBreadcrumbs([ userData.breadcrumbs = helpers.buildBreadcrumbs([
{ {
text: username, text: username,
url: `/user/${userslug}`, url: `/user/${userslug}`,
@@ -75,9 +78,9 @@ editController.get = async function (req, res) {
text: '[[user:edit]]', text: '[[user:edit]]',
}, },
]); ]);
payload.editButtons = []; userData.editButtons = [];
res.render('account/edit', payload); res.render('account/edit', userData);
}; };
editController.password = async function (req, res, next) { editController.password = async function (req, res, next) {
@@ -102,6 +105,7 @@ editController.email = async function (req, res, next) {
}; };
async function renderRoute(name, req, res) { async function renderRoute(name, req, res) {
const { userData } = res.locals;
const [isAdmin, { username, userslug }, hasPassword] = await Promise.all([ const [isAdmin, { username, userslug }, hasPassword] = await Promise.all([
privileges.admin.can('admin:users', req.uid), privileges.admin.can('admin:users', req.uid),
user.getUserFields(res.locals.uid, ['username', 'userslug']), user.getUserFields(res.locals.uid, ['username', 'userslug']),
@@ -112,14 +116,14 @@ async function renderRoute(name, req, res) {
return helpers.notAllowed(req, res); return helpers.notAllowed(req, res);
} }
const payload = { hasPassword }; userData.hasPassword = hasPassword;
if (name === 'password') { if (name === 'password') {
payload.minimumPasswordLength = meta.config.minimumPasswordLength; userData.minimumPasswordLength = meta.config.minimumPasswordLength;
payload.minimumPasswordStrength = meta.config.minimumPasswordStrength; userData.minimumPasswordStrength = meta.config.minimumPasswordStrength;
} }
payload.title = `[[pages:account/edit/${name}, ${username}]]`; userData.title = `[[pages:account/edit/${name}, ${username}]]`;
payload.breadcrumbs = helpers.buildBreadcrumbs([ userData.breadcrumbs = helpers.buildBreadcrumbs([
{ {
text: username, text: username,
url: `/user/${userslug}`, url: `/user/${userslug}`,
@@ -133,7 +137,7 @@ async function renderRoute(name, req, res) {
}, },
]); ]);
res.render(`account/edit/${name}`, payload); res.render(`account/edit/${name}`, userData);
} }
editController.uploadPicture = async function (req, res, next) { editController.uploadPicture = async function (req, res, next) {

View File

@@ -15,18 +15,16 @@ followController.getFollowers = async function (req, res, next) {
}; };
async function getFollow(tpl, name, req, res) { async function getFollow(tpl, name, req, res) {
const { userData: payload } = res.locals;
const { const {
username, userslug, followerCount, followingCount, username, userslug, followerCount, followingCount,
} = await user.getUserFields(res.locals.uid, [ } = payload;
'username', 'userslug', 'followerCount', 'followingCount',
]);
const page = parseInt(req.query.page, 10) || 1; const page = parseInt(req.query.page, 10) || 1;
const resultsPerPage = 50; const resultsPerPage = 50;
const start = Math.max(0, page - 1) * resultsPerPage; const start = Math.max(0, page - 1) * resultsPerPage;
const stop = start + resultsPerPage - 1; const stop = start + resultsPerPage - 1;
const payload = {};
payload.title = `[[pages:${tpl}, ${username}]]`; payload.title = `[[pages:${tpl}, ${username}]]`;
const method = name === 'following' ? 'getFollowing' : 'getFollowers'; const method = name === 'following' ? 'getFollowing' : 'getFollowers';

View File

@@ -9,7 +9,7 @@ const groupsController = module.exports;
groupsController.get = async function (req, res) { groupsController.get = async function (req, res) {
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']); const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']);
const payload = {}; const payload = res.locals.userData;
let groupsData = await groups.getUserGroups([res.locals.uid]); let groupsData = await groups.getUserGroups([res.locals.uid]);
groupsData = groupsData[0]; groupsData = groupsData[0];

View File

@@ -40,11 +40,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
await parseAboutMe(results.userData); await parseAboutMe(results.userData);
let { userData } = results; let { userData } = results;
const { userSettings } = results; const { userSettings, isAdmin, isGlobalModerator, isModerator, canViewInfo } = results;
const { isAdmin } = results;
const { isGlobalModerator } = results;
const { isModerator } = results;
const { canViewInfo } = results;
const isSelf = parseInt(callerUID, 10) === parseInt(userData.uid, 10); const isSelf = parseInt(callerUID, 10) === parseInt(userData.uid, 10);
if (meta.config['reputation:disabled']) { if (meta.config['reputation:disabled']) {
@@ -93,6 +89,7 @@ helpers.getUserDataByUserSlug = async function (userslug, callerUID, query = {})
userData.isFollowing = results.isFollowing; userData.isFollowing = results.isFollowing;
userData.canChat = results.canChat; userData.canChat = results.canChat;
userData.hasPrivateChat = results.hasPrivateChat; userData.hasPrivateChat = results.hasPrivateChat;
userData.iconBackgrounds = results.iconBackgrounds;
userData.showHidden = results.canEdit; // remove in v1.19.0 userData.showHidden = results.canEdit; // remove in v1.19.0
userData.allowProfilePicture = !userData.isSelf || !!meta.config['reputation:disabled'] || userData.reputation >= meta.config['min:rep:profile-picture']; userData.allowProfilePicture = !userData.isSelf || !!meta.config['reputation:disabled'] || userData.reputation >= meta.config['min:rep:profile-picture'];
userData.allowCoverPicture = !userData.isSelf || !!meta.config['reputation:disabled'] || userData.reputation >= meta.config['min:rep:cover-picture']; userData.allowCoverPicture = !userData.isSelf || !!meta.config['reputation:disabled'] || userData.reputation >= meta.config['min:rep:cover-picture'];
@@ -169,6 +166,7 @@ async function getAllData(uid, callerUID) {
canViewInfo: privileges.global.can('view:users:info', callerUID), canViewInfo: privileges.global.can('view:users:info', callerUID),
canChat: canChat(callerUID, uid), canChat: canChat(callerUID, uid),
hasPrivateChat: messaging.hasPrivateChat(callerUID, uid), hasPrivateChat: messaging.hasPrivateChat(callerUID, uid),
iconBackgrounds: user.getIconBackgrounds(),
}); });
} }

View File

@@ -13,19 +13,17 @@ infoController.get = async function (req, res) {
const start = (page - 1) * itemsPerPage; const start = (page - 1) * itemsPerPage;
const stop = start + itemsPerPage - 1; const stop = start + itemsPerPage - 1;
const [{ username, userslug }, isPrivileged] = await Promise.all([ const payload = res.locals.userData;
user.getUserFields(res.locals.uid, ['username', 'userslug']), const { username, userslug } = payload;
const [isPrivileged, history, sessions, usernames, emails] = await Promise.all([
user.isPrivileged(req.uid), user.isPrivileged(req.uid),
]);
const [history, sessions, usernames, emails, notes] = await Promise.all([
user.getModerationHistory(res.locals.uid), user.getModerationHistory(res.locals.uid),
user.auth.getSessions(res.locals.uid, req.sessionID), user.auth.getSessions(res.locals.uid, req.sessionID),
user.getHistory(`user:${res.locals.uid}:usernames`), user.getHistory(`user:${res.locals.uid}:usernames`),
user.getHistory(`user:${res.locals.uid}:emails`), user.getHistory(`user:${res.locals.uid}:emails`),
getNotes({ uid: res.locals.uid, isPrivileged }, start, stop),
]); ]);
const payload = {}; const notes = await getNotes({ uid: res.locals.uid, isPrivileged }, start, stop);
payload.history = history; payload.history = history;
payload.sessions = sessions; payload.sessions = sessions;

View File

@@ -177,16 +177,16 @@ async function getPostsFromUserSet(template, req, res) {
const data = templateToData[template]; const data = templateToData[template];
const page = Math.max(1, parseInt(req.query.page, 10) || 1); const page = Math.max(1, parseInt(req.query.page, 10) || 1);
// exposeUid returns -2 for all remote users for ease of processing, restoring uid
let { uid } = res.locals; let { uid } = res.locals;
if (uid === -2) { if (uid === -2) {
uid = await db.getObjectField('handle:uid', req.params.userslug.toLowerCase()); uid = await db.getObjectField('handle:uid', req.params.userslug.toLowerCase());
} }
const [{ username, userslug }, settings] = await Promise.all([ const payload = res.locals.userData;
user.getUserFields(uid, ['username', 'userslug']), const { username, userslug } = uid === -2 ?
user.getSettings(req.uid), await user.getUserFields(uid, ['username', 'userslug']) :
]); payload;
const settings = await user.getSettings(req.uid);
const itemsPerPage = data.type === 'topics' ? settings.topicsPerPage : settings.postsPerPage; const itemsPerPage = data.type === 'topics' ? settings.topicsPerPage : settings.postsPerPage;
const start = (page - 1) * itemsPerPage; const start = (page - 1) * itemsPerPage;
@@ -213,7 +213,6 @@ async function getPostsFromUserSet(template, req, res) {
} }
const { itemCount, itemData } = result; const { itemCount, itemData } = result;
const payload = {};
payload[data.type] = itemData[data.type]; payload[data.type] = itemData[data.type];
payload.nextStart = itemData.nextStart; payload.nextStart = itemData.nextStart;

View File

@@ -1,6 +1,5 @@
'use strict'; 'use strict';
const nconf = require('nconf');
const _ = require('lodash'); const _ = require('lodash');
const db = require('../../database'); const db = require('../../database');
@@ -10,24 +9,13 @@ const posts = require('../../posts');
const categories = require('../../categories'); const categories = require('../../categories');
const plugins = require('../../plugins'); const plugins = require('../../plugins');
const privileges = require('../../privileges'); const privileges = require('../../privileges');
const accountHelpers = require('./helpers');
const helpers = require('../helpers'); const helpers = require('../helpers');
const utils = require('../../utils'); const utils = require('../../utils');
const profileController = module.exports; const profileController = module.exports;
profileController.get = async function (req, res, next) { profileController.get = async function (req, res, next) {
const lowercaseSlug = req.params.userslug.toLowerCase(); const { userData } = res.locals;
if (req.params.userslug !== lowercaseSlug) {
if (res.locals.isAPI) {
req.params.userslug = lowercaseSlug;
} else {
return res.redirect(`${nconf.get('relative_path')}/user/${lowercaseSlug}`);
}
}
const userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query);
if (!userData) { if (!userData) {
return next(); return next();
} }

View File

@@ -6,13 +6,15 @@ const helpers = require('../helpers');
const sessionController = module.exports; const sessionController = module.exports;
sessionController.get = async function (req, res) { sessionController.get = async function (req, res) {
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']); const payload = res.locals.userData;
const { username, userslug } = payload;
const payload = { payload.sessions = await user.auth.getSessions(res.locals.uid, req.sessionID);
sessions: await user.auth.getSessions(res.locals.uid, req.sessionID), payload.title = '[[pages:account/sessions]]';
title: '[[pages:account/sessions]]', payload.breadcrumbs = helpers.buildBreadcrumbs([
breadcrumbs: helpers.buildBreadcrumbs([{ text: username, url: `/user/${userslug}` }, { text: '[[pages:account/sessions]]' }]), { text: username, url: `/user/${userslug}` },
}; { text: '[[pages:account/sessions]]' },
]);
res.render('account/sessions', payload); res.render('account/sessions', payload);
}; };

View File

@@ -13,13 +13,12 @@ const plugins = require('../../plugins');
const notifications = require('../../notifications'); const notifications = require('../../notifications');
const db = require('../../database'); const db = require('../../database');
const helpers = require('../helpers'); const helpers = require('../helpers');
const accountHelpers = require('./helpers');
const slugify = require('../../slugify'); const slugify = require('../../slugify');
const settingsController = module.exports; const settingsController = module.exports;
settingsController.get = async function (req, res, next) { settingsController.get = async function (req, res, next) {
const userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query); const { userData } = res.locals;
if (!userData) { if (!userData) {
return next(); return next();
} }

View File

@@ -1,7 +1,6 @@
'use strict'; 'use strict';
const db = require('../../database'); const db = require('../../database');
const user = require('../../user');
const helpers = require('../helpers'); const helpers = require('../helpers');
const tagsController = module.exports; const tagsController = module.exports;
@@ -10,10 +9,10 @@ tagsController.get = async function (req, res) {
if (req.uid !== res.locals.uid) { if (req.uid !== res.locals.uid) {
return helpers.notAllowed(req, res); return helpers.notAllowed(req, res);
} }
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']); const payload = res.locals.userData;
const { username, userslug } = payload;
const tagData = await db.getSortedSetRange(`uid:${res.locals.uid}:followed_tags`, 0, -1); const tagData = await db.getSortedSetRange(`uid:${res.locals.uid}:followed_tags`, 0, -1);
const payload = {};
payload.tags = tagData; payload.tags = tagData;
payload.title = `[[pages:account/watched-tags, ${username}]]`; payload.title = `[[pages:account/watched-tags, ${username}]]`;
payload.breadcrumbs = helpers.buildBreadcrumbs([ payload.breadcrumbs = helpers.buildBreadcrumbs([

View File

@@ -6,14 +6,14 @@ const nconf = require('nconf');
const db = require('../../database'); const db = require('../../database');
const helpers = require('../helpers'); const helpers = require('../helpers');
const user = require('../../user');
const meta = require('../../meta'); const meta = require('../../meta');
const pagination = require('../../pagination'); const pagination = require('../../pagination');
const uploadsController = module.exports; const uploadsController = module.exports;
uploadsController.get = async function (req, res) { uploadsController.get = async function (req, res) {
const { username, userslug } = await user.getUserFields(res.locals.uid, ['username', 'userslug']); const payload = res.locals.userData;
const { username, userslug } = payload;
const page = Math.max(1, parseInt(req.query.page, 10) || 1); const page = Math.max(1, parseInt(req.query.page, 10) || 1);
const itemsPerPage = 25; const itemsPerPage = 25;
const start = (page - 1) * itemsPerPage; const start = (page - 1) * itemsPerPage;
@@ -23,7 +23,6 @@ uploadsController.get = async function (req, res) {
db.getSortedSetRevRange(`uid:${res.locals.uid}:uploads`, start, stop), db.getSortedSetRevRange(`uid:${res.locals.uid}:uploads`, start, stop),
]); ]);
const payload = {};
payload.uploads = uploadNames.map(uploadName => ({ payload.uploads = uploadNames.map(uploadName => ({
name: uploadName, name: uploadName,
url: path.resolve(nconf.get('upload_url'), uploadName), url: path.resolve(nconf.get('upload_url'), uploadName),

View File

@@ -88,7 +88,6 @@ apiController.loadConfig = async function (req) {
thumbs: { thumbs: {
size: meta.config.topicThumbSize, size: meta.config.topicThumbSize,
}, },
iconBackgrounds: await user.getIconBackgrounds(req.uid),
emailPrompt: meta.config.emailPrompt, emailPrompt: meta.config.emailPrompt,
useragent: req.useragent, useragent: req.useragent,
fontawesome: { fontawesome: {

View File

@@ -116,7 +116,7 @@ module.exports = function (middleware) {
} }
try { try {
await renderMethod(template, { ...res.locals.templateValues, ...options }, fn); await renderMethod(template, options, fn);
} catch (err) { } catch (err) {
next(err); next(err);
} }

View File

@@ -5,12 +5,12 @@ const meta = require('../meta');
const helpers = require('./helpers'); const helpers = require('./helpers');
const user = require('../user'); const user = require('../user');
const cache = cacheCreate({ let cache;
ttl: meta.config.uploadRateLimitCooldown * 1000,
});
exports.clearCache = function () { exports.clearCache = function () {
cache.clear(); if (cache) {
cache.clear();
}
}; };
exports.ratelimit = helpers.try(async (req, res, next) => { exports.ratelimit = helpers.try(async (req, res, next) => {
@@ -18,7 +18,11 @@ exports.ratelimit = helpers.try(async (req, res, next) => {
if (!meta.config.uploadRateLimitThreshold || (uid && await user.isAdminOrGlobalMod(uid))) { if (!meta.config.uploadRateLimitThreshold || (uid && await user.isAdminOrGlobalMod(uid))) {
return next(); return next();
} }
if (!cache) {
cache = cacheCreate({
ttl: meta.config.uploadRateLimitCooldown * 1000,
});
}
const count = (cache.get(`${req.ip}:uploaded_file_count`) || 0) + req.files.files.length; const count = (cache.get(`${req.ip}:uploaded_file_count`) || 0) + req.files.files.length;
if (count > meta.config.uploadRateLimitThreshold) { if (count > meta.config.uploadRateLimitThreshold) {
return next(new Error(['[[error:upload-ratelimit-reached]]'])); return next(new Error(['[[error:upload-ratelimit-reached]]']));

View File

@@ -248,7 +248,18 @@ module.exports = function (middleware) {
}; };
middleware.buildAccountData = async (req, res, next) => { middleware.buildAccountData = async (req, res, next) => {
res.locals.templateValues = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query); // use lowercase slug on api routes, or direct to the user/<lowercaseslug>
const lowercaseSlug = req.params.userslug.toLowerCase();
if (req.params.userslug !== lowercaseSlug) {
if (res.locals.isAPI) {
req.params.userslug = lowercaseSlug;
} else {
const newPath = req.path.replace(new RegExp(`/${req.params.userslug}`), () => `/${lowercaseSlug}`);
return res.redirect(`${nconf.get('relative_path')}${newPath}`);
}
}
res.locals.userData = await accountHelpers.getUserDataByUserSlug(req.params.userslug, req.uid, req.query);
next(); next();
}; };

View File

@@ -42,7 +42,7 @@ module.exports = function (User) {
await db.sortedSetAdd('users:banned', now, uid); await db.sortedSetAdd('users:banned', now, uid);
await db.sortedSetAdd(`uid:${uid}:bans:timestamp`, now, banKey); await db.sortedSetAdd(`uid:${uid}:bans:timestamp`, now, banKey);
await db.setObject(banKey, banData); await db.setObject(banKey, banData);
await User.setUserField(uid, 'banned:expire', banData.expire); await User.setUserFields(uid, { banned: 1, 'banned:expire': banData.expire });
if (until > now) { if (until > now) {
await db.sortedSetAdd('users:banned:expire', until, uid); await db.sortedSetAdd('users:banned:expire', until, uid);
} else { } else {
@@ -69,7 +69,7 @@ module.exports = function (User) {
uids = isArray ? uids : [uids]; uids = isArray ? uids : [uids];
const userData = await User.getUsersFields(uids, ['email:confirmed']); const userData = await User.getUsersFields(uids, ['email:confirmed']);
await db.setObject(uids.map(uid => `user:${uid}`), { 'banned:expire': 0 }); await db.setObject(uids.map(uid => `user:${uid}`), { banned: 0, 'banned:expire': 0 });
const now = Date.now(); const now = Date.now();
const unbanDataArray = []; const unbanDataArray = [];
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
@@ -124,16 +124,15 @@ module.exports = function (User) {
User.bans.unbanIfExpired = async function (uids) { User.bans.unbanIfExpired = async function (uids) {
// loading user data will unban if it has expired -barisu // loading user data will unban if it has expired -barisu
const userData = await User.getUsersFields(uids, ['banned:expire']); const userData = await User.getUsersFields(uids, ['banned', 'banned:expire']);
return User.bans.calcExpiredFromUserData(userData); return User.bans.calcExpiredFromUserData(userData);
}; };
User.bans.calcExpiredFromUserData = async function (userData) { User.bans.calcExpiredFromUserData = function (userData) {
const isArray = Array.isArray(userData); const isArray = Array.isArray(userData);
userData = isArray ? userData : [userData]; userData = isArray ? userData : [userData];
const banned = await groups.isMembers(userData.map(u => u.uid), groups.BANNED_USERS); userData = userData.map(userData => ({
userData = userData.map((userData, index) => ({ banned: !!(userData && userData.banned),
banned: banned[index],
'banned:expire': userData && userData['banned:expire'], 'banned:expire': userData && userData['banned:expire'],
banExpired: userData && userData['banned:expire'] <= Date.now() && userData['banned:expire'] !== 0, banExpired: userData && userData['banned:expire'] <= Date.now() && userData['banned:expire'] !== 0,
})); }));

View File

@@ -45,6 +45,8 @@ module.exports = function (User) {
'email:confirmed': 0, 'email:confirmed': 0,
}; };
let iconBackgrounds;
User.getUsersFields = async function (uids, fields) { User.getUsersFields = async function (uids, fields) {
if (!Array.isArray(uids) || !uids.length) { if (!Array.isArray(uids) || !uids.length) {
return []; return [];
@@ -201,8 +203,12 @@ module.exports = function (User) {
['showfullname'] ['showfullname']
)); ));
} }
if (!iconBackgrounds) {
iconBackgrounds = await User.getIconBackgrounds();
}
await Promise.all(users.map(async (user) => { const unbanUids = [];
users.forEach((user) => {
if (!user) { if (!user) {
return; return;
} }
@@ -218,7 +224,7 @@ module.exports = function (User) {
user.email = validator.escape(user.email ? user.email.toString() : ''); user.email = validator.escape(user.email ? user.email.toString() : '');
} }
if (!parseInt(user.uid, 10) && !activitypub.helpers.isUri(user.uid)) { if (!user.uid && !activitypub.helpers.isUri(user.uid)) {
for (const [key, value] of Object.entries(User.guestData)) { for (const [key, value] of Object.entries(User.guestData)) {
user[key] = value; user[key] = value;
} }
@@ -248,15 +254,12 @@ module.exports = function (User) {
} }
// User Icons // User Icons
if (requestedFields.includes('picture') && user.username && parseInt(user.uid, 10) !== 0 && !meta.config.defaultAvatar) { if (requestedFields.includes('picture') && user.username && user.uid !== 0 && !meta.config.defaultAvatar) {
const iconBackgrounds = await User.getIconBackgrounds(user.uid); if (!iconBackgrounds.includes(user['icon:bgColor'])) {
let bgColor = await User.getUserField(user.uid, 'icon:bgColor'); const nameAsIndex = Array.from(user.username).reduce((cur, next) => cur + next.charCodeAt(), 0);
if (!iconBackgrounds.includes(bgColor)) { user['icon:bgColor'] = iconBackgrounds[nameAsIndex % iconBackgrounds.length];
bgColor = Array.prototype.reduce.call(user.username, (cur, next) => cur + next.charCodeAt(), 0);
bgColor = iconBackgrounds[bgColor % iconBackgrounds.length];
} }
user['icon:text'] = (user.username[0] || '').toUpperCase(); user['icon:text'] = (user.username[0] || '').toUpperCase();
user['icon:bgColor'] = bgColor;
} }
if (user.hasOwnProperty('joindate')) { if (user.hasOwnProperty('joindate')) {
@@ -267,22 +270,25 @@ module.exports = function (User) {
user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO; user.lastonlineISO = utils.toISOString(user.lastonline) || user.joindateISO;
} }
if (user.hasOwnProperty('mutedUntil')) {
user.muted = user.mutedUntil > Date.now();
}
if (user.hasOwnProperty('banned') || user.hasOwnProperty('banned:expire')) { if (user.hasOwnProperty('banned') || user.hasOwnProperty('banned:expire')) {
const result = await User.bans.calcExpiredFromUserData(user); const result = User.bans.calcExpiredFromUserData(user);
user.banned = result.banned; user.banned = result.banned;
const unban = result.banned && result.banExpired; const unban = result.banned && result.banExpired;
user.banned_until = unban ? 0 : user['banned:expire']; user.banned_until = unban ? 0 : user['banned:expire'];
user.banned_until_readable = user.banned_until && !unban ? utils.toISOString(user.banned_until) : 'Not Banned'; user.banned_until_readable = user.banned_until && !unban ? utils.toISOString(user.banned_until) : 'Not Banned';
if (unban) { if (unban) {
await User.bans.unban(user.uid, '[[user:info.ban-expired]]'); unbanUids.push(user.uid);
user.banned = false; user.banned = false;
} }
} }
});
if (user.hasOwnProperty('mutedUntil')) { if (unbanUids.length) {
user.muted = user.mutedUntil > Date.now(); await User.bans.unban(unbanUids, '[[user:info.ban-expired]]');
} }
}));
return await plugins.hooks.fire('filter:users.get', users); return await plugins.hooks.fire('filter:users.get', users);
} }
@@ -332,14 +338,20 @@ module.exports = function (User) {
} }
} }
User.getIconBackgrounds = async (uid = 0) => {
let iconBackgrounds = [ User.getIconBackgrounds = async () => {
if (iconBackgrounds) {
return iconBackgrounds;
}
const _iconBackgrounds = [
'#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3', '#f44336', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3',
'#009688', '#1b5e20', '#33691e', '#827717', '#e65100', '#ff5722', '#009688', '#1b5e20', '#33691e', '#827717', '#e65100', '#ff5722',
'#795548', '#607d8b', '#795548', '#607d8b',
]; ];
({ iconBackgrounds } = await plugins.hooks.fire('filter:user.iconBackgrounds', { uid, iconBackgrounds })); const data = await plugins.hooks.fire('filter:user.iconBackgrounds', { iconBackgrounds: _iconBackgrounds });
iconBackgrounds = data.iconBackgrounds;
return iconBackgrounds; return iconBackgrounds;
}; };

View File

@@ -607,7 +607,7 @@ describe('User', () => {
it('should return an icon text and valid background if username and picture is explicitly requested', async () => { it('should return an icon text and valid background if username and picture is explicitly requested', async () => {
const payload = await User.getUserFields(testUid, ['username', 'picture']); const payload = await User.getUserFields(testUid, ['username', 'picture']);
const validBackgrounds = await User.getIconBackgrounds(testUid); const validBackgrounds = await User.getIconBackgrounds();
assert.strictEqual(payload['icon:text'], userData.username.slice(0, 1).toUpperCase()); assert.strictEqual(payload['icon:text'], userData.username.slice(0, 1).toUpperCase());
assert(payload['icon:bgColor']); assert(payload['icon:bgColor']);
assert(validBackgrounds.includes(payload['icon:bgColor'])); assert(validBackgrounds.includes(payload['icon:bgColor']));
@@ -616,7 +616,7 @@ describe('User', () => {
it('should return a valid background, even if an invalid background colour is set', async () => { it('should return a valid background, even if an invalid background colour is set', async () => {
await User.setUserField(testUid, 'icon:bgColor', 'teal'); await User.setUserField(testUid, 'icon:bgColor', 'teal');
const payload = await User.getUserFields(testUid, ['username', 'picture']); const payload = await User.getUserFields(testUid, ['username', 'picture']);
const validBackgrounds = await User.getIconBackgrounds(testUid); const validBackgrounds = await User.getIconBackgrounds();
assert(payload['icon:bgColor']); assert(payload['icon:bgColor']);
assert(validBackgrounds.includes(payload['icon:bgColor'])); assert(validBackgrounds.includes(payload['icon:bgColor']));