diff --git a/CHANGELOG.md b/CHANGELOG.md
index 61183ce87..a37858942 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,29 @@
+## [1.68.0](https://github.com/homarr-labs/homarr/compare/v1.67.0...v1.68.0) (2026-06-26)
+
+### Features
+
+* add VPN status widget ([#5812](https://github.com/homarr-labs/homarr/issues/5812)) ([a2bb53a](https://github.com/homarr-labs/homarr/commit/a2bb53a8b8fd17da3d2e23851b75d4e179f66225))
+* re-add Docker quick-access button in user menu ([#6054](https://github.com/homarr-labs/homarr/issues/6054)) ([abd1635](https://github.com/homarr-labs/homarr/commit/abd16354bc9ef3dddbb497fd16b03476535046ac)), closes [#2165](https://github.com/homarr-labs/homarr/issues/2165)
+* ship source maps for readable production stack traces ([#6031](https://github.com/homarr-labs/homarr/issues/6031)) ([89380f8](https://github.com/homarr-labs/homarr/commit/89380f81ddd132f5b1041a8f2c3b6c6b138923dd))
+
+### Bug Fixes
+
+* **beszel:** harden cache layer and prevent blank widgets ([#6052](https://github.com/homarr-labs/homarr/issues/6052)) ([49c2663](https://github.com/homarr-labs/homarr/commit/49c266374df12fea2404710232ced42e2de11dbf)), closes [#6030](https://github.com/homarr-labs/homarr/issues/6030)
+* **beszel:** improve stats date display, add click-to-stats modal, fix grid ordering ([#6026](https://github.com/homarr-labs/homarr/issues/6026)) ([55fb227](https://github.com/homarr-labs/homarr/commit/55fb227305fe51963e486ffb25c4ce6811a2f0e4))
+* **beszel:** leverage TanStack Query for stale-while-revalidate caching ([#6055](https://github.com/homarr-labs/homarr/issues/6055)) ([bbc4589](https://github.com/homarr-labs/homarr/commit/bbc4589c9e1608721fb58a6f9b03cca8d65c1c4a))
+* **beszel:** use 480m interval for 1w/30d stats views ([4383da3](https://github.com/homarr-labs/homarr/commit/4383da38ef634327c288626c58480cc1e5a2f805))
+* **beszel:** widget transparency, error handling, and cache invalidation ([#5993](https://github.com/homarr-labs/homarr/issues/5993)) ([39e4245](https://github.com/homarr-labs/homarr/commit/39e424563a3187dcf0dba5f654e2225be76aef51))
+* **icon-picker:** keep URL visible in edit mode, add SVG-first ordering ([#6051](https://github.com/homarr-labs/homarr/issues/6051)) ([e01571b](https://github.com/homarr-labs/homarr/commit/e01571b221812cd19a6d67ebe3cf44432104ec27))
+* **integrations:** prevent certificate trust button from submitting the form ([#6020](https://github.com/homarr-labs/homarr/issues/6020)) ([0f66bfd](https://github.com/homarr-labs/homarr/commit/0f66bfd6c128253ba644d80d7c908de5314cf997))
+* **manage/about:** bypass self-fetch + add manage error boundary (LLM exploration) ([#6009](https://github.com/homarr-labs/homarr/issues/6009)) ([19f9769](https://github.com/homarr-labs/homarr/commit/19f9769d9e669d56d793e9f6a0d265316113d743)), closes [#6008](https://github.com/homarr-labs/homarr/issues/6008) [#6008](https://github.com/homarr-labs/homarr/issues/6008) [#6008](https://github.com/homarr-labs/homarr/issues/6008)
+* **plex:** use series title for media releases instead of season label ([#6083](https://github.com/homarr-labs/homarr/issues/6083)) ([2443b89](https://github.com/homarr-labs/homarr/commit/2443b89b46f88fddbf41f0382a9f3bc5469c6fa8))
+* show app first in add item modal ([#6087](https://github.com/homarr-labs/homarr/issues/6087)) ([915c8a3](https://github.com/homarr-labs/homarr/commit/915c8a3501551fdccf786d0c34dc01107d873ce0))
+* **truenas:** report actual memory usage and pool free space ([#6017](https://github.com/homarr-labs/homarr/issues/6017)) ([f69e545](https://github.com/homarr-labs/homarr/commit/f69e545d6429aece0e55975c3b2426f20e91e545))
+* **unraid:** report disk free space instead of total size ([#6018](https://github.com/homarr-labs/homarr/issues/6018)) ([1fb97ee](https://github.com/homarr-labs/homarr/commit/1fb97ee9c6fbe656a7dd3a9d1e436125982533f4))
+* **widgets:** clean up health monitoring disk and size display ([#6019](https://github.com/homarr-labs/homarr/issues/6019)) ([e29c79a](https://github.com/homarr-labs/homarr/commit/e29c79a9028d9311dcb3d8323e0950a444589c7a))
+* **widgets:** hide health monitoring default tab option for non-proxmox integrations ([#6021](https://github.com/homarr-labs/homarr/issues/6021)) ([52df56b](https://github.com/homarr-labs/homarr/commit/52df56b19da8e0bcb6c2e5df47b7936ec63f3149))
+* **widgets:** hide system disk usage value when it overflows and show it in a tooltip ([#6022](https://github.com/homarr-labs/homarr/issues/6022)) ([a19c9d8](https://github.com/homarr-labs/homarr/commit/a19c9d88caacc86b42854c732818f0b5e5cc8dd5))
+
## [1.67.0](https://github.com/homarr-labs/homarr/compare/v1.66.1...v1.67.0) (2026-06-19)
### Features
diff --git a/CODEOWNERS b/CODEOWNERS
index b798cfada..b3e2214ac 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -1,6 +1,11 @@
# ── CODEOWNERS ───────────────────────────────────────
* @homarr-labs/maintainers
+# Specific features / integrations
+# Glueten
+apps/docs/docs/integrations/glueten @Lok3sh-kumar
+packages/integrations/src/gluetun @Lok3sh-kumar
+
# Exempt Renovate‑managed files (no owners)
package.json
**/package.json
diff --git a/apps/docs/docs/integrations/gluetun/index.mdx b/apps/docs/docs/integrations/gluetun/index.mdx
new file mode 100644
index 000000000..7c936fc1f
--- /dev/null
+++ b/apps/docs/docs/integrations/gluetun/index.mdx
@@ -0,0 +1,52 @@
+---
+title: 'Gluetun'
+description: 'Connect Gluetun to Homarr as a VPN integration.'
+hide_title: true
+---
+
+import { IntegrationHeader } from '@site/src/components/integrations/header';
+import { IntegrationCapabilites } from '@site/src/components/integrations/widgets';
+import { AddingIntegration } from '@site/src/components/integrations/adding';
+import { IntegrationSecrets } from '@site/src/components/integrations/secrets';
+import { gluetunIntegration } from '.';
+import { vpnWidget } from '@site/docs/widgets/vpn';
+
+
+
+[Gluetun](https://github.com/qdm12/gluetun) exposes a control server that Homarr can query to report the VPN tunnel
+status, DNS status, public IP address, and active provider details in the VPN widget.
+
+### Widgets & Capabilities
+
+
+
+### Setting up the Gluetun control server
+
+Refer to the [Gluetun control server documentation](https://github.com/qdm12/gluetun-wiki/blob/main/setup/advanced/control-server.md#authentication) to enable and configure authentication.
+
+### Adding the integration
+
+
+
+### Secrets
+
+Gluetun's control server can be left unprotected or secured with either an API key or HTTP Basic authentication.
+Choose the option that matches how your Gluetun instance integration is set up.
+
+
diff --git a/apps/docs/docs/integrations/gluetun/index.ts b/apps/docs/docs/integrations/gluetun/index.ts
new file mode 100644
index 000000000..6d2489a62
--- /dev/null
+++ b/apps/docs/docs/integrations/gluetun/index.ts
@@ -0,0 +1,8 @@
+import { IntegrationDefinition } from "@site/src/types";
+
+export const gluetunIntegration: IntegrationDefinition = {
+ name: "Gluetun",
+ description: "VPN control server integration for Homarr's VPN widget.",
+ iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/gluetun.svg",
+ path: "../../integrations/gluetun",
+};
diff --git a/apps/docs/docs/widgets/vpn/img/single.png b/apps/docs/docs/widgets/vpn/img/single.png
new file mode 100644
index 000000000..ea4b423e7
Binary files /dev/null and b/apps/docs/docs/widgets/vpn/img/single.png differ
diff --git a/apps/docs/docs/widgets/vpn/index.mdx b/apps/docs/docs/widgets/vpn/index.mdx
new file mode 100644
index 000000000..0aa6a1a16
--- /dev/null
+++ b/apps/docs/docs/widgets/vpn/index.mdx
@@ -0,0 +1,40 @@
+---
+title: 'VPN'
+description: 'Monitor the status of your VPN integrations.'
+hide_title: true
+---
+
+import { WidgetHeader } from '@site/src/components/widgets/header';
+import { AddingWidget } from '@site/src/components/widgets/adding';
+import { WidgetIntegrations } from '@site/src/components/widgets/integrations';
+import { vpnWidget } from '.';
+import { gluetunIntegration } from '@site/docs/integrations/gluetun';
+
+
+
+The VPN widget provides a compact overview of each configured VPN integration. It shows whether the tunnel and DNS
+services are up, the currently exposed public IP address, and the VPN provider and protocol being used.
+
+When multiple VPN integrations are attached, the widget renders one card per integration so you can compare their
+current status at a glance.
+
+### Screenshots
+
+import singleIntegrationImage from './img/single.png';
+
+
+
+
+
+### Supported Integrations
+
+
+
+### Adding the widget
+
+
diff --git a/apps/docs/docs/widgets/vpn/index.ts b/apps/docs/docs/widgets/vpn/index.ts
new file mode 100644
index 000000000..0f1590e5b
--- /dev/null
+++ b/apps/docs/docs/widgets/vpn/index.ts
@@ -0,0 +1,9 @@
+import { WidgetDefinition } from "@site/src/types";
+import { IconShieldLock } from "@tabler/icons-react";
+
+export const vpnWidget: WidgetDefinition = {
+ icon: IconShieldLock,
+ name: "VPN",
+ description: "Monitor the connection status, public IP, and provider details of your VPN integrations.",
+ path: "../../widgets/vpn",
+};
diff --git a/apps/nextjs/src/app/[locale]/manage/about/page.tsx b/apps/nextjs/src/app/[locale]/manage/about/page.tsx
index 3f49db49c..9577d39a0 100644
--- a/apps/nextjs/src/app/[locale]/manage/about/page.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/about/page.tsx
@@ -1,4 +1,3 @@
-import { headers } from "next/headers";
import Image from "next/image";
import {
Accordion,
@@ -41,11 +40,10 @@ import { getScopedI18n } from "@homarr/translation/server";
import { homarrLogoPath } from "~/components/layout/logo/homarr-logo";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { createMetaTitle } from "~/metadata";
-import type { PackageJsonDependencies } from "~/versions/package-reader";
-import { getPackageVersion } from "~/versions/package-reader";
-import type githubContributorsJson from "../../../../../../../static-data/contributors.json";
-import type openCollectiveContributorsJson from "../../../../../../../static-data/opencollective-contributors.json";
-import type crowdinContributorsJson from "../../../../../../../static-data/translators.json";
+import { getDependenciesAsync, getPackageVersion } from "~/versions/package-reader";
+import githubContributors from "@static-data/contributors.json";
+import openCollectiveContributors from "@static-data/opencollective-contributors.json";
+import crowdinContributors from "@static-data/translators.json";
import classes from "./about.module.css";
export async function generateMetadata() {
@@ -56,32 +54,10 @@ export async function generateMetadata() {
};
}
-const getHostAsync = async () => {
- if (process.env.HOSTNAME) {
- return `${process.env.HOSTNAME}:3000`;
- }
-
- return (await headers()).get("host");
-};
-
export default async function AboutPage() {
- const baseServerUrl = `http://${await getHostAsync()}`;
const t = await getScopedI18n("management.page.about");
const version = getPackageVersion();
- const dependencies = (await fetch(`${baseServerUrl}/api/about/dependencies`).then((res) =>
- res.json(),
- )) as PackageJsonDependencies;
- const githubContributors = (await fetch(`${baseServerUrl}/api/about/contributors/github`).then((res) =>
- res.json(),
- )) as typeof githubContributorsJson;
-
- const crowdinContributors = (await fetch(`${baseServerUrl}/api/about/contributors/crowdin`).then((res) =>
- res.json(),
- )) as typeof crowdinContributorsJson;
-
- const openCollectiveContributors = (await fetch(`${baseServerUrl}/api/about/contributors/opencollective`).then(
- (res) => res.json(),
- )) as typeof openCollectiveContributorsJson;
+ const dependencies = await getDependenciesAsync();
return (
@@ -185,7 +161,7 @@ export default async function AboutPage() {
{Object.entries(dependencies)
.filter(([, value]) => !value.includes("workspace:"))
- .sort(([key1], [key2]) => key1.localeCompare(key2))
+ .toSorted(([key1], [key2]) => key1.localeCompare(key2))
.map(([name, version]) => (
void;
+}) {
+ const tError = useScopedI18n("management.error");
+ const t = useI18n();
+
+ useEffect(() => {
+ console.error(error);
+ }, [error]);
+
+ return (
+
+
+
+ {tError("title")}
+
+ {tError("text")}
+
+ {error.digest && (
+
+ {tError("digest", { digest: error.digest })}
+
+ )}
+
+ {t("common.action.tryAgain")}
+
+
+
+ );
+}
diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx
index 61f8d6136..e3cfae9c9 100644
--- a/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/docker-table.tsx
@@ -150,14 +150,18 @@ const createColumns = (t: TranslationFunction): MRT_ColumnDef[]
},
];
-export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["getContainers"]) {
+interface DockerTableProps {
+ initialData?: RouterOutputs["docker"]["getContainers"];
+}
+
+export function DockerTable({ initialData }: DockerTableProps) {
const t = useI18n();
const tDocker = useScopedI18n("docker");
const { data, isFetching, refetch } = clientApi.docker.getContainers.useQuery(undefined, {
- initialData: { containers, timestamp },
+ initialData,
refetchOnMount: false,
});
- const relativeTime = useTimeAgo(data.timestamp);
+ const relativeTime = useTimeAgo(data?.timestamp ?? new Date());
const mutate = useCallback(() => {
void refetch()
.then(() => {
@@ -174,8 +178,11 @@ export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["
});
}, [refetch, tDocker]);
+ const containers = data?.containers ?? [];
+
const table = useTranslatedMantineReactTable({
- data: data.containers,
+ data: containers,
+ state: { isLoading: isFetching },
enableDensityToggle: false,
enableColumnActions: false,
enableColumnFilters: false,
@@ -188,7 +195,7 @@ export function DockerTable({ containers, timestamp }: RouterOutputs["docker"]["
enableBottomToolbar: false,
positionGlobalFilter: "right",
mantineSearchTextInputProps: {
- placeholder: tDocker("table.search", { count: String(data.containers.length) }),
+ placeholder: tDocker("table.search", { count: String(containers.length) }),
style: { minWidth: 300 },
autoFocus: true,
},
diff --git a/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx b/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx
index 1da3db128..97544314d 100644
--- a/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx
+++ b/apps/nextjs/src/app/[locale]/manage/tools/docker/page.tsx
@@ -23,7 +23,7 @@ export default async function DockerPage() {
{tDocker("title")}
-
+
>
);
diff --git a/apps/nextjs/src/app/api/about/contributors/crowdin/route.ts b/apps/nextjs/src/app/api/about/contributors/crowdin/route.ts
index a149c082d..d55165906 100644
--- a/apps/nextjs/src/app/api/about/contributors/crowdin/route.ts
+++ b/apps/nextjs/src/app/api/about/contributors/crowdin/route.ts
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
-import crowdinContributors from "../../../../../../../../static-data/translators.json";
+import crowdinContributors from "@static-data/translators.json";
export const GET = () => {
return NextResponse.json(crowdinContributors);
diff --git a/apps/nextjs/src/app/api/about/contributors/github/route.ts b/apps/nextjs/src/app/api/about/contributors/github/route.ts
index a6fc81173..fa93d2881 100644
--- a/apps/nextjs/src/app/api/about/contributors/github/route.ts
+++ b/apps/nextjs/src/app/api/about/contributors/github/route.ts
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
-import githubContributors from "../../../../../../../../static-data/contributors.json";
+import githubContributors from "@static-data/contributors.json";
export const GET = () => {
return NextResponse.json(githubContributors);
diff --git a/apps/nextjs/src/app/api/about/contributors/opencollective/route.ts b/apps/nextjs/src/app/api/about/contributors/opencollective/route.ts
index 369f14a8c..eac1b58c4 100644
--- a/apps/nextjs/src/app/api/about/contributors/opencollective/route.ts
+++ b/apps/nextjs/src/app/api/about/contributors/opencollective/route.ts
@@ -1,6 +1,6 @@
import { NextResponse } from "next/server";
-import openCollectiveContributors from "../../../../../../../../static-data/opencollective-contributors.json";
+import openCollectiveContributors from "@static-data/opencollective-contributors.json";
export const GET = () => {
return NextResponse.json(openCollectiveContributors);
diff --git a/apps/nextjs/src/app/api/backup/test/backup.spec.ts b/apps/nextjs/src/app/api/backup/test/backup.spec.ts
index 0a394abf7..15706ecb8 100644
--- a/apps/nextjs/src/app/api/backup/test/backup.spec.ts
+++ b/apps/nextjs/src/app/api/backup/test/backup.spec.ts
@@ -211,7 +211,17 @@ describe("SQLite backup", () => {
const decryptedAfter = decryptWithKey(updatedRow.value as `${string}.${string}`, newKey);
expect(decryptedAfter).toBe(plaintext);
- expect(() => decryptWithKey(updatedRow.value as `${string}.${string}`, oldKey)).toThrow();
+ // The old key must no longer recover the plaintext. With AES-256-CBC this
+ // usually fails PKCS7 padding validation (throws), but ~1/256 of the time the
+ // wrong key yields valid padding and returns garbage instead of throwing, so
+ // assert on the actual property (plaintext is unrecoverable) rather than throwing.
+ let decryptedWithOldKey: string | null = null;
+ try {
+ decryptedWithOldKey = decryptWithKey(updatedRow.value as `${string}.${string}`, oldKey);
+ } catch {
+ // Expected for most wrong keys: padding validation fails.
+ }
+ expect(decryptedWithOldKey).not.toBe(plaintext);
sqlite.close();
});
diff --git a/apps/nextjs/src/components/backup/use-backup-analysis.ts b/apps/nextjs/src/components/backup/use-backup-analysis.ts
index a59e4335b..ee92a4464 100644
--- a/apps/nextjs/src/components/backup/use-backup-analysis.ts
+++ b/apps/nextjs/src/components/backup/use-backup-analysis.ts
@@ -142,7 +142,8 @@ export const useBackupAnalysis = () => {
phase: "error",
});
throw new Error(
- `Migration ${migration.tag} failed: ${migrationErr instanceof Error ? migrationErr.message : "Unknown error"}`, { cause: migrationErr },
+ `Migration ${migration.tag} failed: ${migrationErr instanceof Error ? migrationErr.message : "Unknown error"}`,
+ { cause: migrationErr },
);
}
diff --git a/apps/nextjs/src/components/board/items/item-select-modal.tsx b/apps/nextjs/src/components/board/items/item-select-modal.tsx
index 726f135fa..2ac349c6d 100644
--- a/apps/nextjs/src/components/board/items/item-select-modal.tsx
+++ b/apps/nextjs/src/components/board/items/item-select-modal.tsx
@@ -41,7 +41,12 @@ export const ItemSelectModal = createModal(({ actions }) => {
name: t(`widget.${kind}.name`),
description: t(`widget.${kind}.description`),
}))
- .sort((itemA, itemB) => itemA.name.localeCompare(itemB.name)),
+ .sort((itemA, itemB) => {
+ if (itemA.kind === "app") return -1;
+ if (itemB.kind === "app") return 1;
+
+ return itemA.name.localeCompare(itemB.name);
+ }),
[t],
);
diff --git a/apps/nextjs/src/components/integration/integration-grid-shared.ts b/apps/nextjs/src/components/integration/integration-grid-shared.ts
index 4b43a4299..2fc7228c8 100644
--- a/apps/nextjs/src/components/integration/integration-grid-shared.ts
+++ b/apps/nextjs/src/components/integration/integration-grid-shared.ts
@@ -57,6 +57,7 @@ export const categoryTranslationKeys: Record = {
mediaMonitoring: "integration.category.mediaMonitoring",
speedtest: "integration.category.speedtest",
analytics: "integration.category.analytics",
+ vpn: "integration.category.vpn",
documents: "integration.category.documents",
mediaLibrary: "integration.category.mediaLibrary",
uptimeMonitoring: "integration.category.uptimeMonitoring",
diff --git a/apps/nextjs/src/components/layout/header/docker-quick-access-modal.tsx b/apps/nextjs/src/components/layout/header/docker-quick-access-modal.tsx
new file mode 100644
index 000000000..c9826b8a9
--- /dev/null
+++ b/apps/nextjs/src/components/layout/header/docker-quick-access-modal.tsx
@@ -0,0 +1,10 @@
+"use client";
+
+import { createModal } from "@homarr/modals";
+
+import { DockerTable } from "~/app/[locale]/manage/tools/docker/docker-table";
+
+export const DockerQuickAccessModal = createModal(() => ).withOptions({
+ defaultTitle: (t) => t("docker.title"),
+ size: 1400,
+});
diff --git a/apps/nextjs/src/components/layout/header/user.tsx b/apps/nextjs/src/components/layout/header/user.tsx
index 6ec2cf306..af3d72121 100644
--- a/apps/nextjs/src/components/layout/header/user.tsx
+++ b/apps/nextjs/src/components/layout/header/user.tsx
@@ -3,6 +3,7 @@ import { UnstyledButton } from "@mantine/core";
import { api } from "@homarr/api/server";
import { auth } from "@homarr/auth/next";
+import { env } from "@homarr/docker/env";
import { CurrentUserAvatar } from "~/components/user-avatar";
import { UserAvatarMenu } from "~/components/user-avatar-menu";
@@ -11,9 +12,10 @@ import { UpdateIndicator } from "./update";
export const UserButton = async () => {
const session = await auth();
const isAdmin = session?.user.permissions.includes("admin");
+ const isDockerEnabled = isAdmin && env.ENABLE_DOCKER;
const availableUpdatesPromise = isAdmin ? api.updateChecker.getAvailableUpdates() : undefined;
return (
-
+
}>
diff --git a/apps/nextjs/src/components/user-avatar-menu.tsx b/apps/nextjs/src/components/user-avatar-menu.tsx
index 71ec2b218..cf6d013a2 100644
--- a/apps/nextjs/src/components/user-avatar-menu.tsx
+++ b/apps/nextjs/src/components/user-avatar-menu.tsx
@@ -5,7 +5,15 @@ import { useCallback, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Center, Menu, Stack, Text, useMantineColorScheme } from "@mantine/core";
import { useHotkeys, useTimeout } from "@mantine/hooks";
-import { IconCheck, IconHome, IconLogin, IconLogout, IconSettings, IconTool } from "@tabler/icons-react";
+import {
+ IconBrandDocker,
+ IconCheck,
+ IconHome,
+ IconLogin,
+ IconLogout,
+ IconSettings,
+ IconTool,
+} from "@tabler/icons-react";
import type { RouterOutputs } from "@homarr/api";
import { signOut, useSession } from "@homarr/auth/client";
@@ -17,14 +25,16 @@ import { Link } from "@homarr/ui";
import { useAuthContext } from "~/app/[locale]/_client-providers/session";
import { CurrentColorSchemeCombobox } from "./color-scheme/current-color-scheme-combobox";
import { CurrentLanguageCombobox } from "./language/current-language-combobox";
+import { DockerQuickAccessModal } from "./layout/header/docker-quick-access-modal";
import { AvailableUpdatesMenuItem } from "./layout/header/update";
interface UserAvatarMenuProps {
children: ReactNode;
availableUpdatesPromise?: Promise;
+ isDockerEnabled?: boolean;
}
-export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatarMenuProps) => {
+export const UserAvatarMenu = ({ children, availableUpdatesPromise, isDockerEnabled }: UserAvatarMenuProps) => {
const t = useScopedI18n("common.userAvatar.menu");
const { toggleColorScheme } = useMantineColorScheme();
useHotkeys([[hotkeys.toggleColorScheme, toggleColorScheme]]);
@@ -34,6 +44,7 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatar
const { logoutUrl } = useAuthContext();
const { openModal } = useModalAction(LogoutModal);
+ const { openModal: openDockerModal } = useModalAction(DockerQuickAccessModal);
const handleSignout = useCallback(async () => {
await signOut({
@@ -80,6 +91,11 @@ export const UserAvatarMenu = ({ children, availableUpdatesPromise }: UserAvatar
}>
{t("management")}
+ {isDockerEnabled && (
+ } onClick={() => openDockerModal()}>
+ {t("docker")}
+
+ )}
>
)}
diff --git a/apps/nextjs/tsconfig.json b/apps/nextjs/tsconfig.json
index 470a11e34..59c34075e 100644
--- a/apps/nextjs/tsconfig.json
+++ b/apps/nextjs/tsconfig.json
@@ -3,6 +3,7 @@
"compilerOptions": {
"paths": {
"~/*": ["./src/*"],
+ "@static-data/*": ["../../static-data/*"],
"@homarr/node-unifi": ["../../node_modules/@types/node-unifi"]
},
"plugins": [
diff --git a/package.json b/package.json
index bd36b5962..040b93355 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "homarr",
- "version": "1.67.0",
+ "version": "1.68.0",
"private": true,
"scripts": {
"build": "cross-env CI=true turbo build",
diff --git a/packages/api/src/router/widgets/index.ts b/packages/api/src/router/widgets/index.ts
index 70c1aa997..80776db45 100644
--- a/packages/api/src/router/widgets/index.ts
+++ b/packages/api/src/router/widgets/index.ts
@@ -36,6 +36,7 @@ export const widgetRouter = createTRPCRouter({
uptimeKuma: lazy(() => import("./uptime-kuma").then((mod) => mod.uptimeKumaRouter)),
audioStats: lazy(() => import("./audio-stats").then((mod) => mod.audioStatsRouter)),
umami: lazy(() => import("./umami").then((mod) => mod.umamiRouter)),
+ vpn: lazy(() => import("./vpn").then((mod) => mod.vpnRouter)),
ups: lazy(() => import("./ups").then((mod) => mod.upsRouter)),
customApi: lazy(() => import("./custom-api").then((mod) => mod.customApiRouter)),
});
diff --git a/packages/api/src/router/widgets/vpn.ts b/packages/api/src/router/widgets/vpn.ts
new file mode 100644
index 000000000..407e4986f
--- /dev/null
+++ b/packages/api/src/router/widgets/vpn.ts
@@ -0,0 +1,25 @@
+import { getIntegrationKindsByCategory } from "@homarr/definitions";
+import { vpnSummaryHandler } from "@homarr/request-handler/vpn";
+
+import { createManyIntegrationMiddleware } from "../../middlewares/integration";
+import { settleIntegrationQueries } from "../../settle-integrations";
+import { createTRPCRouter, publicProcedure } from "../../trpc";
+
+export const vpnRouter = createTRPCRouter({
+ getSummaries: publicProcedure
+ .unstable_concat(createManyIntegrationMiddleware("query", ...getIntegrationKindsByCategory("vpn")))
+ .query(async ({ ctx }) => {
+ return await settleIntegrationQueries(ctx.integrations, async (integration) => {
+ const { data, timestamp } = await vpnSummaryHandler.handler(integration, {}).getDataAsync();
+ return {
+ integration: {
+ id: integration.id,
+ name: integration.name,
+ kind: integration.kind,
+ updatedAt: timestamp,
+ },
+ summary: data,
+ };
+ });
+ }),
+});
diff --git a/packages/definitions/src/docs/homarr-docs-sitemap.ts b/packages/definitions/src/docs/homarr-docs-sitemap.ts
index 1af579b5f..4dbb8757a 100644
--- a/packages/definitions/src/docs/homarr-docs-sitemap.ts
+++ b/packages/definitions/src/docs/homarr-docs-sitemap.ts
@@ -179,6 +179,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/github"
| "/docs/integrations/gitlab"
| "/docs/integrations/glances"
+ | "/docs/integrations/gluetun"
| "/docs/integrations/gotify"
| "/docs/integrations/home-assistant"
| "/docs/integrations/ical"
@@ -280,6 +281,7 @@ export type HomarrDocumentationPath =
| "/docs/widgets/ups"
| "/docs/widgets/uptime-kuma"
| "/docs/widgets/video"
+ | "/docs/widgets/vpn"
| "/docs/widgets/weather"
| ""
| "/sitemap.xml"
@@ -337,6 +339,7 @@ export type HomarrDocumentationPath =
| "/docs/integrations/audiobookshelf"
| "/docs/integrations/navidrome"
| "/docs/integrations/umami"
+ | "/docs/integrations/gluetun"
| "/docs/integrations/peanut"
| "/docs/integrations/beszel"
| "/docs/widgets/clock"
@@ -380,6 +383,7 @@ export type HomarrDocumentationPath =
| "/docs/widgets/uptime-kuma"
| "/docs/widgets/audio-stats"
| "/docs/widgets/umami"
+ | "/docs/widgets/vpn"
| "/docs/widgets/ups"
| "/docs/widgets/beszel-system-table"
| "/docs/widgets/beszel-system-grid"
diff --git a/packages/definitions/src/docs/integration-doc-slugs.ts b/packages/definitions/src/docs/integration-doc-slugs.ts
index 99ec6ebb0..90c3cae8b 100644
--- a/packages/definitions/src/docs/integration-doc-slugs.ts
+++ b/packages/definitions/src/docs/integration-doc-slugs.ts
@@ -55,6 +55,7 @@ export const integrationDocSlugs: Record = {
audiobookshelf: "audiobookshelf",
navidrome: "navidrome",
umami: "umami",
+ gluetun: "gluetun",
peaNut: "peanut",
beszel: "beszel",
archiveTeamWarrior: null,
diff --git a/packages/definitions/src/docs/widget-doc-slugs.ts b/packages/definitions/src/docs/widget-doc-slugs.ts
index 3ffae6408..2e34a4843 100644
--- a/packages/definitions/src/docs/widget-doc-slugs.ts
+++ b/packages/definitions/src/docs/widget-doc-slugs.ts
@@ -43,6 +43,7 @@ export const widgetDocSlugs: Record = {
uptimeKuma: "uptime-kuma",
audioStats: "audio-stats",
umami: "umami",
+ vpn: "vpn",
ups: "ups",
archiveTeamWarrior: null,
beszelSystemTable: "beszel-system-table",
diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts
index 8efa552e4..abb4ffdfd 100644
--- a/packages/definitions/src/integration.ts
+++ b/packages/definitions/src/integration.ts
@@ -489,6 +489,13 @@ export const integrationDefs = {
documentationUrl: createDocumentationLink("/docs/integrations/beszel"),
defaultPort: 8090,
},
+ gluetun: {
+ name: "Gluetun",
+ secretKinds: [["username", "password"], ["apiKey"], []],
+ iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/gluetun.svg",
+ category: ["vpn"],
+ documentationUrl: createDocumentationLink("/docs/integrations/gluetun"),
+ },
archiveTeamWarrior: {
name: "ArchiveTeam Warrior",
secretKinds: [[], ["username", "password"]],
@@ -604,6 +611,7 @@ export const integrationCategories = [
"mediaMonitoring",
"speedtest",
"analytics",
+ "vpn",
"archiving",
"ups",
"documents",
diff --git a/packages/definitions/src/widget-integration-map.ts b/packages/definitions/src/widget-integration-map.ts
index 87898a49e..a560f2979 100644
--- a/packages/definitions/src/widget-integration-map.ts
+++ b/packages/definitions/src/widget-integration-map.ts
@@ -38,6 +38,7 @@ export const widgetIntegrationSupport: Partial {
+ const authHeaders = this.getAuthHeaders();
+ const url = this.url("/v1/vpn/status");
+ const response = await input.fetchAsync(url, {
+ headers: authHeaders,
+ });
+
+ if (!response.ok) {
+ return TestConnectionError.StatusResult(response);
+ }
+
+ return { success: true };
+ }
+
+ public async getSummaryAsync(): Promise {
+ const authHeaders = this.getAuthHeaders();
+ const [vpnStatus, dnsStatus, publicIp, vpnSettings] = await Promise.all([
+ this.getVpnStatusAsync(authHeaders),
+ this.getDnsStatusAsync(authHeaders),
+ this.getPublicIpAsync(authHeaders),
+ this.getVpnSettingAsync(authHeaders),
+ ]);
+
+ return {
+ vpnStatus: vpnStatus.status,
+ dnsStatus: dnsStatus.status,
+ publicIp: publicIp.public_ip,
+ country: publicIp.country,
+ city: publicIp.city,
+ vpnProvider: {
+ protocol: vpnSettings.type,
+ provider: vpnSettings.provider.name,
+ },
+ };
+ }
+
+ private async getVpnStatusAsync(authHeaders: Record): Promise {
+ const url = this.url("/v1/vpn/status");
+
+ const response = await fetchWithTrustedCertificatesAsync(url, {
+ headers: authHeaders,
+ });
+
+ if (!response.ok) throw new ResponseError(response);
+ return gluetunVpnStatusSchema.parse(await response.json());
+ }
+
+ private async getDnsStatusAsync(authHeaders: Record): Promise {
+ const url = this.url("/v1/dns/status");
+
+ const response = await fetchWithTrustedCertificatesAsync(url, {
+ headers: authHeaders,
+ });
+
+ if (!response.ok) throw new ResponseError(response);
+ return gluetunDnsStatusSchema.parse(await response.json());
+ }
+
+ private async getPublicIpAsync(authHeaders: Record): Promise {
+ const url = this.url("/v1/publicip/ip");
+
+ const response = await fetchWithTrustedCertificatesAsync(url, {
+ headers: authHeaders,
+ });
+
+ if (!response.ok) throw new ResponseError(response);
+ return gluetunPublicIpSchema.parse(await response.json());
+ }
+
+ private async getVpnSettingAsync(authHeaders: Record): Promise {
+ const url = this.url("/v1/vpn/settings");
+
+ const response = await fetchWithTrustedCertificatesAsync(url, {
+ headers: authHeaders,
+ });
+
+ if (!response.ok) throw new ResponseError(response);
+ return gluetunVpnSettingsSchema.parse(await response.json());
+ }
+
+ private getAuthHeaders(): Record {
+ if (this.hasSecretValue("apiKey")) {
+ return { "X-API-Key": this.getSecretValue("apiKey") };
+ }
+
+ if (this.hasSecretValue("username") && this.hasSecretValue("password")) {
+ const username = this.getSecretValue("username");
+ const password = this.getSecretValue("password");
+
+ const encodedCredentials = btoa(`${username}:${password}`);
+
+ return { Authorization: `Basic ${encodedCredentials}` };
+ }
+
+ return {};
+ }
+}
diff --git a/packages/integrations/src/gluetun/gluetun-types.ts b/packages/integrations/src/gluetun/gluetun-types.ts
new file mode 100644
index 000000000..6886d72a7
--- /dev/null
+++ b/packages/integrations/src/gluetun/gluetun-types.ts
@@ -0,0 +1,116 @@
+import { z } from "zod/v4";
+
+export const gluetunVpnStatusSchema = z.object({
+ status: z.string(),
+});
+
+export type GluetunVpnStatus = z.infer;
+
+export const gluetunDnsStatusSchema = gluetunVpnStatusSchema;
+
+export type GluetunDnsStatus = z.infer;
+
+export const gluetunPublicIpSchema = z.object({
+ public_ip: z.string(),
+ region: z.string(),
+ country: z.string(),
+ city: z.string(),
+ location: z.string(),
+ organization: z.string(),
+ postal_code: z.string(),
+ timezone: z.string(),
+});
+
+export type GluetunPublicIp = z.infer;
+
+const gluetunServerSelectionOpenvpnSchema = z.object({
+ config_file_path: z.string(),
+ protocol: z.string(),
+ endpoint_ip: z.string(),
+ custom_port: z.number(),
+ pia_encryption_preset: z.string(),
+});
+
+const gluetunServerSelectionWireguardSchema = z.object({
+ endpoint_ip: z.string(),
+ endpoint_port: z.number(),
+ public_key: z.string(),
+});
+
+const gluetunServerSelectionSchema = z.object({
+ vpn: z.string(),
+ countries: z.array(z.string()).nullable(),
+ categories: z.array(z.string()).nullable(),
+ regions: z.array(z.string()).nullable(),
+ cities: z.array(z.string()).nullable(),
+ isps: z.array(z.string()).nullable(),
+ names: z.array(z.string()).nullable(),
+ numbers: z.array(z.number()).nullable(),
+ hostnames: z.array(z.string()),
+ owned_only: z.boolean(),
+ free_only: z.boolean(),
+ premium_only: z.boolean(),
+ stream_only: z.boolean(),
+ multi_hop_only: z.boolean(),
+ port_forward_only: z.boolean(),
+ secure_core_only: z.boolean(),
+ tor_only: z.boolean(),
+ openvpn: gluetunServerSelectionOpenvpnSchema,
+ wireguard: gluetunServerSelectionWireguardSchema,
+});
+
+const gluetunPortForwardingSchema = z.object({
+ enabled: z.boolean(),
+ provider: z.string(),
+ status_file_path: z.string(),
+ up_command: z.string(),
+ down_command: z.string(),
+ listening_port: z.number(),
+ username: z.string(),
+ password: z.string(),
+});
+
+const gluetunProviderSchema = z.object({
+ name: z.string(),
+ server_selection: gluetunServerSelectionSchema,
+ port_forwarding: gluetunPortForwardingSchema,
+});
+
+const gluetunOpenvpnSettingsSchema = z.object({
+ version: z.string(),
+ user: z.string(),
+ password: z.string(),
+ config_file_path: z.string(),
+ ciphers: z.array(z.string()).nullable(),
+ auth: z.string(),
+ cert: z.string(),
+ key: z.string(),
+ encrypted_key: z.string(),
+ key_passphrase: z.string(),
+ pia_encryption_preset: z.string(),
+ mssfix: z.number(),
+ interface: z.string(),
+ process_user: z.string(),
+ verbosity: z.number(),
+ flags: z.array(z.string()).nullable(),
+});
+
+const gluetunWireguardSettingsSchema = z.object({
+ private_key: z.string(),
+ pre_shared_key: z.string(),
+ addresses: z.array(z.string()),
+ allowed_ips: z.array(z.string()),
+ interface: z.string(),
+ persistent_keep_alive_interval: z.number(),
+ mtu: z.number(),
+ implementation: z.string(),
+});
+
+export const gluetunVpnSettingsSchema = z.object({
+ type: z.string(),
+ provider: gluetunProviderSchema,
+ openvpn: gluetunOpenvpnSettingsSchema,
+ wireguard: gluetunWireguardSettingsSchema,
+});
+
+export type GluetunVpnSettings = z.infer;
diff --git a/packages/integrations/src/gluetun/test/gluetun-integration.spec.ts b/packages/integrations/src/gluetun/test/gluetun-integration.spec.ts
new file mode 100644
index 000000000..1f84005f3
--- /dev/null
+++ b/packages/integrations/src/gluetun/test/gluetun-integration.spec.ts
@@ -0,0 +1,371 @@
+import { Response } from "undici";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+import { createDb } from "@homarr/db/test";
+
+import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
+
+import type { IntegrationTestingInput } from "../../base/integration";
+import { GluetunIntegration } from "../gluetun-integration";
+import {
+ gluetunDnsStatusSchema,
+ gluetunPublicIpSchema,
+ gluetunVpnSettingsSchema,
+ gluetunVpnStatusSchema,
+} from "../gluetun-types";
+
+vi.mock("@homarr/db", async (importActual) => {
+ // eslint-disable-next-line @typescript-eslint/consistent-type-imports
+ const actual = await importActual();
+ return {
+ ...actual,
+ db: createDb(),
+ };
+});
+
+vi.mock("@homarr/core/infrastructure/http", () => ({
+ fetchWithTrustedCertificatesAsync: vi.fn(),
+}));
+
+const TEST_URL = "https://gluetun.example.com";
+const TEST_API_KEY = "test-api-key";
+const TEST_USERNAME = "user";
+const TEST_PASSWORD = "p@ss";
+
+const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
+
+type FetchResponse = Awaited>;
+
+const VPN_STATUS_PAYLOAD = { status: "running" };
+const DNS_STATUS_PAYLOAD = { status: "running" };
+const PUBLIC_IP_PAYLOAD = {
+ public_ip: "203.0.113.42",
+ region: "Europe",
+ country: "Germany",
+ city: "Berlin",
+ location: "52.5200,13.4050",
+ organization: "Example Org",
+ postal_code: "10115",
+ timezone: "Europe/Berlin",
+};
+const VPN_SETTINGS_PAYLOAD = {
+ type: "wireguard",
+ provider: {
+ name: "mullvad",
+ server_selection: {
+ vpn: "wireguard",
+ countries: ["Germany"],
+ categories: null,
+ regions: null,
+ cities: ["Berlin"],
+ isps: null,
+ names: null,
+ numbers: null,
+ hostnames: ["de-ber-wg-001"],
+ owned_only: false,
+ free_only: false,
+ premium_only: false,
+ stream_only: false,
+ multi_hop_only: false,
+ port_forward_only: false,
+ secure_core_only: false,
+ tor_only: false,
+ openvpn: {
+ config_file_path: "",
+ protocol: "udp",
+ endpoint_ip: "",
+ custom_port: 0,
+ pia_encryption_preset: "",
+ },
+ wireguard: {
+ endpoint_ip: "203.0.113.1",
+ endpoint_port: 51820,
+ public_key: "pubkey",
+ },
+ },
+ port_forwarding: {
+ enabled: false,
+ provider: "mullvad",
+ status_file_path: "",
+ up_command: "",
+ down_command: "",
+ listening_port: 0,
+ username: "",
+ password: "",
+ },
+ },
+ openvpn: {
+ version: "",
+ user: "",
+ password: "",
+ config_file_path: "",
+ ciphers: null,
+ auth: "",
+ cert: "",
+ key: "",
+ encrypted_key: "",
+ key_passphrase: "",
+ pia_encryption_preset: "",
+ mssfix: 0,
+ interface: "tun0",
+ process_user: "",
+ verbosity: 0,
+ flags: null,
+ },
+ wireguard: {
+ private_key: "",
+ pre_shared_key: "",
+ addresses: ["10.0.0.2/32"],
+ allowed_ips: ["0.0.0.0/0"],
+ interface: "wg0",
+ persistent_keep_alive_interval: 25,
+ mtu: 1420,
+ implementation: "auto",
+ },
+};
+
+const jsonResponse = (body: unknown, init: ResponseInit = { status: 200 }): FetchResponse =>
+ new Response(JSON.stringify(body), {
+ ...init,
+ headers: { "content-type": "application/json" },
+ }) as unknown as FetchResponse;
+
+const setupHappyPath = () => {
+ mockFetch.mockImplementation((url) => {
+ const urlString = typeof url === "string" ? url : url.toString();
+ const { pathname } = new URL(urlString);
+
+ switch (pathname) {
+ case "/v1/vpn/status":
+ return Promise.resolve(jsonResponse(VPN_STATUS_PAYLOAD));
+ case "/v1/dns/status":
+ return Promise.resolve(jsonResponse(DNS_STATUS_PAYLOAD));
+ case "/v1/publicip/ip":
+ return Promise.resolve(jsonResponse(PUBLIC_IP_PAYLOAD));
+ case "/v1/vpn/settings":
+ return Promise.resolve(jsonResponse(VPN_SETTINGS_PAYLOAD));
+ default:
+ return Promise.resolve(jsonResponse({ error: "not found" }, { status: 404 }));
+ }
+ });
+};
+
+const createIntegrationWithApiKey = () =>
+ new GluetunIntegration({
+ id: "gluetun-1",
+ name: "Gluetun",
+ url: TEST_URL,
+ externalUrl: null,
+ decryptedSecrets: [{ kind: "apiKey", value: TEST_API_KEY }],
+ });
+
+const createIntegrationWithBasicAuth = () =>
+ new GluetunIntegration({
+ id: "gluetun-2",
+ name: "Gluetun",
+ url: TEST_URL,
+ externalUrl: null,
+ decryptedSecrets: [
+ { kind: "username", value: TEST_USERNAME },
+ { kind: "password", value: TEST_PASSWORD },
+ ],
+ });
+
+describe("GluetunIntegration.getSummaryAsync", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ test("maps the four upstream responses into a flat VPN summary", async () => {
+ setupHappyPath();
+
+ const result = await createIntegrationWithApiKey().getSummaryAsync();
+
+ expect(result).toEqual({
+ vpnStatus: "running",
+ dnsStatus: "running",
+ publicIp: "203.0.113.42",
+ country: "Germany",
+ city: "Berlin",
+ vpnProvider: {
+ protocol: "wireguard",
+ provider: "mullvad",
+ },
+ });
+ });
+
+ test("hits each of the four control endpoints exactly once", async () => {
+ setupHappyPath();
+
+ await createIntegrationWithApiKey().getSummaryAsync();
+
+ const calledPaths = mockFetch.mock.calls.map(([url]) => {
+ const urlString = typeof url === "string" ? url : url.toString();
+ return new URL(urlString).pathname;
+ });
+
+ expect(calledPaths).toHaveLength(4);
+ expect(new Set(calledPaths)).toEqual(
+ new Set(["/v1/vpn/status", "/v1/dns/status", "/v1/publicip/ip", "/v1/vpn/settings"]),
+ );
+ });
+
+ test("sends X-API-Key header when an apiKey secret is configured", async () => {
+ setupHappyPath();
+
+ await createIntegrationWithApiKey().getSummaryAsync();
+
+ for (const [, init] of mockFetch.mock.calls) {
+ const headers = (init?.headers ?? {}) as Record;
+ expect(headers["X-API-Key"]).toBe(TEST_API_KEY);
+ expect(headers.Authorization).toBeUndefined();
+ }
+ });
+
+ test("sends Basic auth header when username/password are configured", async () => {
+ setupHappyPath();
+
+ await createIntegrationWithBasicAuth().getSummaryAsync();
+
+ const expected = `Basic ${btoa(`${TEST_USERNAME}:${TEST_PASSWORD}`)}`;
+ for (const [, init] of mockFetch.mock.calls) {
+ const headers = (init?.headers ?? {}) as Record;
+ expect(headers.Authorization).toBe(expected);
+ expect(headers["X-API-Key"]).toBeUndefined();
+ }
+ });
+
+ test("throws when any upstream endpoint returns a non-OK status", async () => {
+ mockFetch.mockImplementation((url) => {
+ const urlString = typeof url === "string" ? url : url.toString();
+ const { pathname } = new URL(urlString);
+
+ if (pathname === "/v1/publicip/ip") {
+ return Promise.resolve(jsonResponse({ error: "unauthorized" }, { status: 401 }));
+ }
+ return Promise.resolve(jsonResponse(VPN_STATUS_PAYLOAD));
+ });
+
+ await expect(createIntegrationWithApiKey().getSummaryAsync()).rejects.toThrow();
+ });
+
+ test("throws when an upstream payload fails schema validation", async () => {
+ mockFetch.mockImplementation((url) => {
+ const urlString = typeof url === "string" ? url : url.toString();
+ const { pathname } = new URL(urlString);
+
+ if (pathname === "/v1/publicip/ip") {
+ return Promise.resolve(jsonResponse({ public_ip: 123 }));
+ }
+ return Promise.resolve(jsonResponse(VPN_STATUS_PAYLOAD));
+ });
+
+ await expect(createIntegrationWithApiKey().getSummaryAsync()).rejects.toThrow();
+ });
+});
+
+describe("GluetunIntegration testing endpoint", () => {
+ type TestingFn = (input: IntegrationTestingInput) => Promise<{ success: boolean }>;
+
+ const invokeTesting = (integration: GluetunIntegration, input: IntegrationTestingInput) => {
+ const testing = (integration as unknown as { testingAsync: TestingFn }).testingAsync.bind(integration);
+ return testing(input);
+ };
+
+ const makeInput = (response: Response): IntegrationTestingInput =>
+ ({
+ fetchAsync: vi.fn().mockResolvedValue(response),
+ }) as unknown as IntegrationTestingInput;
+
+ test("returns success when /v1/vpn/status responds 200", async () => {
+ const input = makeInput(new Response(JSON.stringify(VPN_STATUS_PAYLOAD), { status: 200 }));
+
+ const result = await invokeTesting(createIntegrationWithApiKey(), input);
+
+ expect(result.success).toBe(true);
+ });
+
+ test("returns a status-code failure when /v1/vpn/status responds non-OK", async () => {
+ const input = makeInput(new Response("unauthorized", { status: 401 }));
+
+ const result = await invokeTesting(createIntegrationWithApiKey(), input);
+
+ expect(result.success).toBe(false);
+ });
+
+ test("passes the api-key header through to fetchAsync", async () => {
+ const response = new Response(JSON.stringify(VPN_STATUS_PAYLOAD), { status: 200 });
+ const input = makeInput(response);
+
+ await invokeTesting(createIntegrationWithApiKey(), input);
+
+ const fetchAsyncMock = vi.mocked(input.fetchAsync);
+ const init = fetchAsyncMock.mock.calls[0]?.[1];
+ const headers = (init?.headers ?? {}) as Record;
+ expect(headers["X-API-Key"]).toBe(TEST_API_KEY);
+ });
+
+ test("passes the Basic auth header through to fetchAsync when username/password are configured", async () => {
+ const response = new Response(JSON.stringify(VPN_STATUS_PAYLOAD), { status: 200 });
+ const input = makeInput(response);
+
+ await invokeTesting(createIntegrationWithBasicAuth(), input);
+
+ const fetchAsyncMock = vi.mocked(input.fetchAsync);
+ const init = fetchAsyncMock.mock.calls[0]?.[1];
+ const headers = (init?.headers ?? {}) as Record;
+ const expected = `Basic ${btoa(`${TEST_USERNAME}:${TEST_PASSWORD}`)}`;
+ expect(headers.Authorization).toBe(expected);
+ expect(headers["X-API-Key"]).toBeUndefined();
+ });
+});
+
+describe("gluetun schemas", () => {
+ test("vpn status schema accepts a string status", () => {
+ expect(gluetunVpnStatusSchema.parse({ status: "running" })).toEqual({ status: "running" });
+ });
+
+ test("vpn status schema rejects a missing status", () => {
+ expect(() => gluetunVpnStatusSchema.parse({})).toThrow();
+ });
+
+ test("dns status schema mirrors vpn status schema", () => {
+ expect(gluetunDnsStatusSchema.parse({ status: "stopped" })).toEqual({ status: "stopped" });
+ expect(() => gluetunDnsStatusSchema.parse({ status: 42 })).toThrow();
+ });
+
+ test("public ip schema parses the full payload", () => {
+ expect(gluetunPublicIpSchema.parse(PUBLIC_IP_PAYLOAD)).toEqual(PUBLIC_IP_PAYLOAD);
+ });
+
+ test("public ip schema rejects a missing required field", () => {
+ const { city: _city, ...partial } = PUBLIC_IP_PAYLOAD;
+ expect(() => gluetunPublicIpSchema.parse(partial)).toThrow();
+ });
+
+ test("vpn settings schema parses a wireguard payload", () => {
+ const parsed = gluetunVpnSettingsSchema.parse(VPN_SETTINGS_PAYLOAD);
+ expect(parsed.type).toBe("wireguard");
+ expect(parsed.provider.name).toBe("mullvad");
+ });
+
+ test("vpn settings schema accepts nullable server-selection arrays", () => {
+ const payloadWithNulls = {
+ ...VPN_SETTINGS_PAYLOAD,
+ provider: {
+ ...VPN_SETTINGS_PAYLOAD.provider,
+ server_selection: {
+ ...VPN_SETTINGS_PAYLOAD.provider.server_selection,
+ countries: null,
+ cities: null,
+ },
+ },
+ };
+
+ expect(() => gluetunVpnSettingsSchema.parse(payloadWithNulls)).not.toThrow();
+ });
+
+ test("vpn settings schema rejects a non-string type", () => {
+ expect(() => gluetunVpnSettingsSchema.parse({ ...VPN_SETTINGS_PAYLOAD, type: 1 })).toThrow();
+ });
+});
diff --git a/packages/integrations/src/gotify/gotify-integration.ts b/packages/integrations/src/gotify/gotify-integration.ts
index b7fd9361e..1d0f5eb8c 100644
--- a/packages/integrations/src/gotify/gotify-integration.ts
+++ b/packages/integrations/src/gotify/gotify-integration.ts
@@ -15,9 +15,9 @@ export class GotifyIntegration extends Integration implements INotificationsInte
}
private getHeaders() {
- const credentials = Buffer.from(
- `${super.getSecretValue("username")}:${super.getSecretValue("password")}`,
- ).toString("base64");
+ const credentials = Buffer.from(`${super.getSecretValue("username")}:${super.getSecretValue("password")}`).toString(
+ "base64",
+ );
return { Authorization: `Basic ${credentials}` };
}
@@ -31,11 +31,13 @@ export class GotifyIntegration extends Integration implements INotificationsInte
if (!result.success) throw new Error(`Failed to parse Gotify response: ${result.error.message}`);
const parsed = result.data;
- return parsed.messages.map((message): Notification => ({
- id: String(message.id),
- time: new Date(message.date),
- title: message.title,
- body: message.message,
- }));
+ return parsed.messages.map(
+ (message): Notification => ({
+ id: String(message.id),
+ time: new Date(message.date),
+ title: message.title,
+ body: message.message,
+ }),
+ );
}
}
diff --git a/packages/integrations/src/interfaces/vpn-summary/vpn-summary-integration.ts b/packages/integrations/src/interfaces/vpn-summary/vpn-summary-integration.ts
new file mode 100644
index 000000000..39e500d30
--- /dev/null
+++ b/packages/integrations/src/interfaces/vpn-summary/vpn-summary-integration.ts
@@ -0,0 +1,5 @@
+import type { VpnSummary } from "./vpn-summary-types";
+
+export interface VpnSummaryIntegration {
+ getSummaryAsync(): Promise;
+}
diff --git a/packages/integrations/src/interfaces/vpn-summary/vpn-summary-types.ts b/packages/integrations/src/interfaces/vpn-summary/vpn-summary-types.ts
new file mode 100644
index 000000000..91e70454c
--- /dev/null
+++ b/packages/integrations/src/interfaces/vpn-summary/vpn-summary-types.ts
@@ -0,0 +1,13 @@
+export interface VpnProviderDetails {
+ protocol: string;
+ provider: string;
+}
+
+export interface VpnSummary {
+ vpnStatus: string;
+ dnsStatus: string;
+ publicIp: string;
+ country: string;
+ city: string;
+ vpnProvider: VpnProviderDetails;
+}
diff --git a/packages/integrations/src/plex/plex-integration.ts b/packages/integrations/src/plex/plex-integration.ts
index 9c2cb8a4f..a031343c0 100644
--- a/packages/integrations/src/plex/plex-integration.ts
+++ b/packages/integrations/src/plex/plex-integration.ts
@@ -127,11 +127,18 @@ export class PlexIntegration extends Integration implements IMediaServerIntegrat
const media =
data.MediaContainer.Metadata?.filter((item) => item.Image).map((item) => {
+ const title =
+ item.type === "episode"
+ ? (item.grandparentTitle ?? item.title)
+ : item.type === "season"
+ ? (item.parentTitle ?? item.title)
+ : item.title;
+
return {
id: item.Media?.at(0)?.id.toString() ?? item.key,
type: mapType(item.type),
- title: item.title,
- subtitle: item.tagline,
+ title,
+ subtitle: title === item.title ? item.tagline : item.title,
description: item.summary,
releaseDate: item.originallyAvailableAt
? new Date(item.originallyAvailableAt)
@@ -222,6 +229,8 @@ const recentlyAddedSchema = z.object({
studio: z.string().optional(),
type: z.string(), // For example "movie", "album"
title: z.string(),
+ parentTitle: z.string().optional(),
+ grandparentTitle: z.string().optional(),
summary: z.string().optional(),
duration: z.number().optional(),
addedAt: z.number(),
diff --git a/packages/integrations/src/plex/test/plex-integration.spec.ts b/packages/integrations/src/plex/test/plex-integration.spec.ts
new file mode 100644
index 000000000..64e046256
--- /dev/null
+++ b/packages/integrations/src/plex/test/plex-integration.spec.ts
@@ -0,0 +1,140 @@
+// @vitest-environment node
+import { Response } from "undici";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http";
+
+import type { IntegrationSecret } from "../../base/types";
+import { PlexIntegration } from "../plex-integration";
+
+const imageProxyMocks = vi.hoisted(() => ({
+ createImageAsync: vi.fn((url: string) => Promise.resolve(`proxied:${url}`)),
+}));
+
+vi.mock("@homarr/core/infrastructure/http", () => ({
+ fetchWithTrustedCertificatesAsync: vi.fn(),
+}));
+
+vi.mock("@homarr/image-proxy", () => ({
+ ImageProxy: class {
+ createImageAsync = imageProxyMocks.createImageAsync;
+ },
+}));
+
+const TEST_URL = "https://plex.example.com";
+const mockFetch = vi.mocked(fetchWithTrustedCertificatesAsync);
+
+const secrets: IntegrationSecret[] = [{ kind: "apiKey", value: "plex-token" }];
+
+const createIntegration = () =>
+ new PlexIntegration({
+ id: "test-plex",
+ name: "Test Plex",
+ url: TEST_URL,
+ externalUrl: null,
+ decryptedSecrets: secrets,
+ });
+
+const createMetadataItem = (key: string, overrides: Record) => ({
+ key: `/library/metadata/${key}`,
+ type: "movie",
+ title: "Movie",
+ addedAt: 1_735_689_600,
+ Image: [
+ {
+ type: "coverPoster",
+ url: `/library/metadata/${key}/thumb`,
+ },
+ ],
+ ...overrides,
+});
+
+beforeEach(() => {
+ mockFetch.mockReset();
+ imageProxyMocks.createImageAsync.mockClear();
+});
+
+describe("PlexIntegration.getMediaReleasesAsync", () => {
+ test("uses the series title for TV seasons and episodes", async () => {
+ mockFetch.mockImplementation((url) => {
+ const urlString = String(url);
+
+ if (urlString.endsWith("/identity")) {
+ return Promise.resolve(
+ new Response(JSON.stringify({ MediaContainer: { machineIdentifier: "server-id" } }), { status: 200 }),
+ ) as unknown as ReturnType;
+ }
+
+ if (urlString.endsWith("/library/recentlyAdded")) {
+ return Promise.resolve(
+ new Response(
+ JSON.stringify({
+ MediaContainer: {
+ Metadata: [
+ createMetadataItem("season", {
+ type: "season",
+ title: "Season 1",
+ parentTitle: "Breaking Bad",
+ }),
+ createMetadataItem("episode", {
+ type: "episode",
+ title: "Review",
+ parentTitle: "Season 1",
+ grandparentTitle: "The Bear",
+ }),
+ createMetadataItem("movie", {
+ type: "movie",
+ title: "Inception",
+ tagline: "Dreams feel real",
+ }),
+ createMetadataItem("show", {
+ type: "show",
+ title: "The Last of Us",
+ tagline: "A series tagline",
+ }),
+ createMetadataItem("missing-parent", {
+ type: "season",
+ title: "Season 3",
+ }),
+ ],
+ },
+ }),
+ { status: 200 },
+ ),
+ ) as unknown as ReturnType;
+ }
+
+ throw new Error(`Unexpected Plex request: ${urlString}`);
+ });
+
+ const releases = await createIntegration().getMediaReleasesAsync();
+
+ expect(releases).toMatchObject([
+ {
+ title: "Breaking Bad",
+ subtitle: "Season 1",
+ type: "tv",
+ },
+ {
+ title: "The Bear",
+ subtitle: "Review",
+ type: "tv",
+ },
+ {
+ title: "Inception",
+ subtitle: "Dreams feel real",
+ type: "movie",
+ },
+ {
+ title: "The Last of Us",
+ subtitle: "A series tagline",
+ type: "tv",
+ },
+ {
+ title: "Season 3",
+ subtitle: undefined,
+ type: "tv",
+ },
+ ]);
+ });
+});
\ No newline at end of file
diff --git a/packages/integrations/src/types.ts b/packages/integrations/src/types.ts
index 1ee395dfa..afeaf1db2 100644
--- a/packages/integrations/src/types.ts
+++ b/packages/integrations/src/types.ts
@@ -4,6 +4,7 @@ export * from "./interfaces/network-controller-summary/network-controller-summar
export * from "./interfaces/firewall-summary/firewall-summary-types";
export * from "./interfaces/health-monitoring/health-monitoring-types";
export * from "./interfaces/ups-summary/ups-summary-types";
+export * from "./interfaces/vpn-summary/vpn-summary-types";
export * from "./interfaces/indexer-manager/indexer-manager-types";
export * from "./interfaces/media-requests/media-request-types";
export * from "./base/searchable-integration";
@@ -22,4 +23,5 @@ export * from "./audiobookshelf/audiobookshelf-types";
export * from "./navidrome/navidrome-types";
export * from "./paperless-ngx/paperless-ngx-types";
export * from "./umami/umami-types";
+export * from "./gluetun/gluetun-types";
export * from "./beszel/beszel-types";
diff --git a/packages/integrations/test/technitium.spec.ts b/packages/integrations/test/technitium.spec.ts
index 99c9d2610..77189b556 100644
--- a/packages/integrations/test/technitium.spec.ts
+++ b/packages/integrations/test/technitium.spec.ts
@@ -2,15 +2,7 @@
import type { StartedTestContainer } from "testcontainers";
import { GenericContainer, Wait } from "testcontainers";
-import {
- beforeAll,
- beforeEach,
- afterAll,
- describe,
- expect,
- test,
- vi,
-} from "vitest";
+import { beforeAll, beforeEach, afterAll, describe, expect, test, vi } from "vitest";
import { createDb } from "@homarr/db/test";
@@ -31,15 +23,10 @@ vi.mock("@homarr/db", async (importActual) => {
vi.mock("@homarr/core/infrastructure/certificates", async (importActual) => {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
- const actual =
- await importActual<
- typeof import("@homarr/core/infrastructure/certificates")
- >();
+ const actual = await importActual();
return {
...actual,
- getTrustedCertificateHostnamesAsync: vi
- .fn()
- .mockImplementation(() => Promise.resolve([])),
+ getTrustedCertificateHostnamesAsync: vi.fn().mockImplementation(() => Promise.resolve([])),
};
});
@@ -90,9 +77,7 @@ const INTEGRATION_ID = "test-technitium";
const makeInput = (
baseUrl: string,
- auth:
- | { kind: "credentials"; password?: string }
- | { kind: "apiKey"; value: string },
+ auth: { kind: "credentials"; password?: string } | { kind: "apiKey"; value: string },
): IntegrationInput => ({
id: INTEGRATION_ID,
name: "Technitium DNS",
@@ -109,10 +94,7 @@ const makeInput = (
// Fetch an API token appropriate for the server version.
// v15 has a dedicated createToken endpoint; legacy reuses the session token from login.
-async function fetchApiToken(
- baseUrl: string,
- apiVersion: TechnitiumVersion,
-): Promise {
+async function fetchApiToken(baseUrl: string, apiVersion: TechnitiumVersion): Promise {
if (apiVersion === "v15") {
// v15 exposes a dedicated endpoint for non-expiring API tokens.
const res = await fetch(
@@ -122,9 +104,7 @@ async function fetchApiToken(
}
// Legacy: /api/user/login exists on all tested versions (v11–v14) and returns a session
// token that can be used as a long-lived API key via the ?token= query parameter.
- const res = await fetch(
- `${baseUrl}/api/user/login?user=${DEFAULT_USERNAME}&pass=${DEFAULT_PASSWORD}`,
- );
+ const res = await fetch(`${baseUrl}/api/user/login?user=${DEFAULT_USERNAME}&pass=${DEFAULT_PASSWORD}`);
return ((await res.json()) as { token: string }).token;
}
@@ -144,16 +124,12 @@ const pool: Record = {};
beforeAll(async () => {
await Promise.all(
VERSIONS.map(async ({ tag, apiVersion }) => {
- const container = await new GenericContainer(
- `technitium/dns-server:${tag}`,
- )
+ const container = await new GenericContainer(`technitium/dns-server:${tag}`)
.withExposedPorts(5380)
// Explicitly set the admin password so all versions use the same credentials.
// Without this, older versions may generate a random password on first run.
.withEnvironment({ DNS_SERVER_ADMIN_PASSWORD: DEFAULT_PASSWORD })
- .withWaitStrategy(
- Wait.forLogMessage("Technitium DNS Server was started successfully."),
- )
+ .withWaitStrategy(Wait.forLogMessage("Technitium DNS Server was started successfully."))
.start();
const baseUrl = `http://${container.getHost()}:${container.getMappedPort(5380)}`;
const apiToken = await fetchApiToken(baseUrl, apiVersion);
@@ -163,238 +139,209 @@ beforeAll(async () => {
}, 120_000);
afterAll(async () => {
- await Promise.all(
- Object.values(pool).map(({ container }) => container.stop()),
- );
+ await Promise.all(Object.values(pool).map(({ container }) => container.stop()));
});
// ─── version-parameterised suites ────────────────────────────────────────────
-describe.each(VERSIONS)(
- "Technitium DNS $tag ($apiVersion)",
- ({ tag, apiVersion }) => {
- let baseUrl: string;
- let apiToken: string;
+describe.each(VERSIONS)("Technitium DNS $tag ($apiVersion)", ({ tag, apiVersion }) => {
+ let baseUrl: string;
+ let apiToken: string;
- beforeAll(() => {
- const entry = pool[tag];
- if (!entry)
- throw new Error(`Container pool entry for ${tag} was not initialized`);
- ({ baseUrl, apiToken } = entry);
- });
+ beforeAll(() => {
+ const entry = pool[tag];
+ if (!entry) throw new Error(`Container pool entry for ${tag} was not initialized`);
+ ({ baseUrl, apiToken } = entry);
+ });
- beforeEach(() => {
- sessionState[INTEGRATION_ID] = null;
- });
+ beforeEach(() => {
+ sessionState[INTEGRATION_ID] = null;
+ });
- // ── factory ────────────────────────────────────────────────────────────────
+ // ── factory ────────────────────────────────────────────────────────────────
- describe("createTechnitiumDnsIntegrationAsync (factory)", () => {
- test("factory creates a working integration (auto-detects version)", async () => {
- const integration = await createTechnitiumDnsIntegrationAsync(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ describe("createTechnitiumDnsIntegrationAsync (factory)", () => {
+ test("factory creates a working integration (auto-detects version)", async () => {
+ const integration = await createTechnitiumDnsIntegrationAsync(makeInput(baseUrl, { kind: "credentials" }));
- const result = await integration.getSummaryAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("enabled");
- expect(result.dnsQueriesToday).toBeGreaterThanOrEqual(0);
- }, 20_000);
- });
+ expect(result.status).toBe("enabled");
+ expect(result.dnsQueriesToday).toBeGreaterThanOrEqual(0);
+ }, 20_000);
+ });
- // ── getSummaryAsync ────────────────────────────────────────────────────────
+ // ── getSummaryAsync ────────────────────────────────────────────────────────
- describe("getSummaryAsync", () => {
- test("returns valid summary with credentials", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ describe("getSummaryAsync", () => {
+ test("returns valid summary with credentials", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- const result = await integration.getSummaryAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.dnsQueriesToday).toBeGreaterThanOrEqual(0);
- expect(result.adsBlockedToday).toBeGreaterThanOrEqual(0);
- expect(result.adsBlockedTodayPercentage).toBeGreaterThanOrEqual(0);
- expect(result.domainsBeingBlocked).toBeGreaterThanOrEqual(0);
- expect(result.status).toBe("enabled");
- }, 20_000);
+ expect(result.dnsQueriesToday).toBeGreaterThanOrEqual(0);
+ expect(result.adsBlockedToday).toBeGreaterThanOrEqual(0);
+ expect(result.adsBlockedTodayPercentage).toBeGreaterThanOrEqual(0);
+ expect(result.domainsBeingBlocked).toBeGreaterThanOrEqual(0);
+ expect(result.status).toBe("enabled");
+ }, 20_000);
- test("returns valid summary with API key", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "apiKey", value: apiToken }),
- apiVersion,
- );
+ test("returns valid summary with API key", async () => {
+ const integration = new TechnitiumDnsIntegration(
+ makeInput(baseUrl, { kind: "apiKey", value: apiToken }),
+ apiVersion,
+ );
- const result = await integration.getSummaryAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("enabled");
- expect(result.dnsQueriesToday).toBeGreaterThanOrEqual(0);
- }, 20_000);
+ expect(result.status).toBe("enabled");
+ expect(result.dnsQueriesToday).toBeGreaterThanOrEqual(0);
+ }, 20_000);
- test("domainsBeingBlocked is a non-negative number (blockedZones + blockListZones)", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ test("domainsBeingBlocked is a non-negative number (blockedZones + blockListZones)", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- const result = await integration.getSummaryAsync();
+ const result = await integration.getSummaryAsync();
- expect(typeof result.domainsBeingBlocked).toBe("number");
- expect(result.domainsBeingBlocked).toBeGreaterThanOrEqual(0);
- }, 20_000);
- });
+ expect(typeof result.domainsBeingBlocked).toBe("number");
+ expect(result.domainsBeingBlocked).toBeGreaterThanOrEqual(0);
+ }, 20_000);
+ });
- // ── session caching ────────────────────────────────────────────────────────
+ // ── session caching ────────────────────────────────────────────────────────
- describe("session caching", () => {
- test("login is performed once and token is cached for subsequent calls", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ describe("session caching", () => {
+ test("login is performed once and token is cached for subsequent calls", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- await integration.getSummaryAsync();
- const tokenAfterFirst = sessionState[INTEGRATION_ID]?.token;
- expect(tokenAfterFirst).toBeDefined();
+ await integration.getSummaryAsync();
+ const tokenAfterFirst = sessionState[INTEGRATION_ID]?.token;
+ expect(tokenAfterFirst).toBeDefined();
- await integration.getSummaryAsync();
- const tokenAfterSecond = sessionState[INTEGRATION_ID]?.token;
+ await integration.getSummaryAsync();
+ const tokenAfterSecond = sessionState[INTEGRATION_ID]?.token;
- expect(tokenAfterSecond).toBe(tokenAfterFirst);
- }, 20_000);
+ expect(tokenAfterSecond).toBe(tokenAfterFirst);
+ }, 20_000);
- test("recovers from an expired/invalid stored token by re-logging in", async () => {
- // Plant a bad token with the correct version so it hits the right API path before failing
- sessionState[INTEGRATION_ID] = {
- token: "deliberately-invalid-token",
- version: apiVersion,
- };
+ test("recovers from an expired/invalid stored token by re-logging in", async () => {
+ // Plant a bad token with the correct version so it hits the right API path before failing
+ sessionState[INTEGRATION_ID] = {
+ token: "deliberately-invalid-token",
+ version: apiVersion,
+ };
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- apiVersion,
- );
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }), apiVersion);
- const result = await integration.getSummaryAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("enabled");
- expect(sessionState[INTEGRATION_ID]).not.toBeNull();
- expect(sessionState[INTEGRATION_ID]?.token).not.toBe(
- "deliberately-invalid-token",
- );
- }, 20_000);
- });
+ expect(result.status).toBe("enabled");
+ expect(sessionState[INTEGRATION_ID]).not.toBeNull();
+ expect(sessionState[INTEGRATION_ID]?.token).not.toBe("deliberately-invalid-token");
+ }, 20_000);
+ });
- // ── blocking state changes ─────────────────────────────────────────────────
+ // ── blocking state changes ─────────────────────────────────────────────────
- describe("blocking state changes", () => {
- test("disableAsync permanently disables blocking", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ describe("blocking state changes", () => {
+ test("disableAsync permanently disables blocking", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- await integration.disableAsync();
- const result = await integration.getSummaryAsync();
+ await integration.disableAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("disabled");
+ expect(result.status).toBe("disabled");
- await integration.enableAsync(); // restore
- }, 20_000);
+ await integration.enableAsync(); // restore
+ }, 20_000);
- test("enableAsync re-enables blocking after permanent disable", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ test("enableAsync re-enables blocking after permanent disable", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- await integration.disableAsync();
- await integration.enableAsync();
- const result = await integration.getSummaryAsync();
+ await integration.disableAsync();
+ await integration.enableAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("enabled");
- }, 20_000);
+ expect(result.status).toBe("enabled");
+ }, 20_000);
- test("disableAsync with duration temporarily disables blocking", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ test("disableAsync with duration temporarily disables blocking", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- await integration.disableAsync(120); // 120s → 2 min
- const result = await integration.getSummaryAsync();
+ await integration.disableAsync(120); // 120s → 2 min
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("disabled");
+ expect(result.status).toBe("disabled");
- await integration.enableAsync(); // restore
- }, 20_000);
+ await integration.enableAsync(); // restore
+ }, 20_000);
- test("disableAsync with small duration (1s) rounds up to 1 minute and disables", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ test("disableAsync with small duration (1s) rounds up to 1 minute and disables", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- await integration.disableAsync(1);
- const result = await integration.getSummaryAsync();
+ await integration.disableAsync(1);
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("disabled");
+ expect(result.status).toBe("disabled");
- await integration.enableAsync(); // restore
- }, 20_000);
+ await integration.enableAsync(); // restore
+ }, 20_000);
- test("enableAsync cancels an active timed disable immediately", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- );
+ test("enableAsync cancels an active timed disable immediately", async () => {
+ const integration = new TechnitiumDnsIntegration(makeInput(baseUrl, { kind: "credentials" }));
- await integration.disableAsync(600); // 10 minutes
- await integration.enableAsync();
- const result = await integration.getSummaryAsync();
+ await integration.disableAsync(600); // 10 minutes
+ await integration.enableAsync();
+ const result = await integration.getSummaryAsync();
- expect(result.status).toBe("enabled");
- }, 20_000);
- });
+ expect(result.status).toBe("enabled");
+ }, 20_000);
+ });
- // ── testConnectionAsync ────────────────────────────────────────────────────
+ // ── testConnectionAsync ────────────────────────────────────────────────────
- describe("testConnectionAsync", () => {
- test("succeeds with valid credentials", async () => {
- const result = await new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "credentials" }),
- ).testConnectionAsync();
- expect(result.success).toBe(true);
- }, 20_000);
+ describe("testConnectionAsync", () => {
+ test("succeeds with valid credentials", async () => {
+ const result = await new TechnitiumDnsIntegration(
+ makeInput(baseUrl, { kind: "credentials" }),
+ ).testConnectionAsync();
+ expect(result.success).toBe(true);
+ }, 20_000);
- test("succeeds with valid API key", async () => {
- const result = await new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "apiKey", value: apiToken }),
- apiVersion,
- ).testConnectionAsync();
- expect(result.success).toBe(true);
- }, 20_000);
+ test("succeeds with valid API key", async () => {
+ const result = await new TechnitiumDnsIntegration(
+ makeInput(baseUrl, { kind: "apiKey", value: apiToken }),
+ apiVersion,
+ ).testConnectionAsync();
+ expect(result.success).toBe(true);
+ }, 20_000);
- test("fails with wrong password — returns authorization error", async () => {
- const integration = new TechnitiumDnsIntegration(
- makeInput(baseUrl, {
- kind: "credentials",
- password: "wrong-password",
- }),
- );
+ test("fails with wrong password — returns authorization error", async () => {
+ const integration = new TechnitiumDnsIntegration(
+ makeInput(baseUrl, {
+ kind: "credentials",
+ password: "wrong-password",
+ }),
+ );
- const result = await integration.testConnectionAsync();
+ const result = await integration.testConnectionAsync();
- expect(result.success).toBe(false);
- if (result.success) return;
- expect(result.error).toBeInstanceOf(TestConnectionError);
- expect(result.error.type).toBe("authorization");
- }, 20_000);
+ expect(result.success).toBe(false);
+ if (result.success) return;
+ expect(result.error).toBeInstanceOf(TestConnectionError);
+ expect(result.error.type).toBe("authorization");
+ }, 20_000);
- test("fails with invalid API key — returns authorization error", async () => {
- const result = await new TechnitiumDnsIntegration(
- makeInput(baseUrl, { kind: "apiKey", value: "invalid-api-key-xyz" }),
- apiVersion,
- ).testConnectionAsync();
+ test("fails with invalid API key — returns authorization error", async () => {
+ const result = await new TechnitiumDnsIntegration(
+ makeInput(baseUrl, { kind: "apiKey", value: "invalid-api-key-xyz" }),
+ apiVersion,
+ ).testConnectionAsync();
- expect(result.success).toBe(false);
- if (result.success) return;
- expect(result.error).toBeInstanceOf(TestConnectionError);
- expect(result.error.type).toBe("authorization");
- }, 20_000);
- });
- },
-);
+ expect(result.success).toBe(false);
+ if (result.success) return;
+ expect(result.error).toBeInstanceOf(TestConnectionError);
+ expect(result.error.type).toBe("authorization");
+ }, 20_000);
+ });
+});
diff --git a/packages/request-handler/src/vpn.ts b/packages/request-handler/src/vpn.ts
new file mode 100644
index 000000000..ebca0fbeb
--- /dev/null
+++ b/packages/request-handler/src/vpn.ts
@@ -0,0 +1,28 @@
+import { createLogger } from "@homarr/core/infrastructure/logs";
+import type { IntegrationKindByCategory } from "@homarr/definitions";
+import { createIntegrationAsync } from "@homarr/integrations";
+import type { VpnSummary } from "@homarr/integrations/types";
+
+import { createIntegrationRequestHandler } from "./lib/integration-request-handler";
+
+const logger = createLogger({ module: "vpnRequestHandler" });
+
+export const vpnSummaryHandler = createIntegrationRequestHandler<
+ VpnSummary | null,
+ IntegrationKindByCategory<"vpn">,
+ Record
+>({
+ async requestAsync(integration, _input) {
+ try {
+ const integrationInstance = await createIntegrationAsync(integration);
+ return await integrationInstance.getSummaryAsync();
+ } catch (error) {
+ logger.warn("Failed to fetch VPN summary, marking as unavailable", {
+ integrationId: integration.id,
+ integrationKind: integration.kind,
+ error: error instanceof Error ? error.message : String(error),
+ });
+ return null;
+ }
+ },
+});
diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json
index affa84121..e99c72e69 100644
--- a/packages/translation/src/lang/en.json
+++ b/packages/translation/src/lang/en.json
@@ -1021,6 +1021,7 @@
"mediaMonitoring": "Monitoring",
"speedtest": "Speed Test",
"analytics": "Analytics",
+ "vpn": "VPN",
"mediaLibrary": "Media Library",
"uptimeMonitoring": "Uptime Monitoring"
},
@@ -1467,6 +1468,7 @@
"switchToDarkMode": "Switch to dark mode",
"switchToLightMode": "Switch to light mode",
"management": "Management",
+ "docker": "Docker",
"preferences": "Your preferences",
"logout": "Logout",
"login": "Login",
@@ -2408,6 +2410,12 @@
"title": "Instances"
}
},
+ "vpn": {
+ "name": "VPN",
+ "description": "Overview of your VPN connections",
+ "serviceUnavailable": "VPN unavailable",
+ "option": {}
+ },
"ups": {
"name": "UPS",
"description": "Monitor the status of your UPS devices through a NUT server",
@@ -4142,6 +4150,11 @@
"title": "Not Found",
"text": "Could not find requested resource"
},
+ "error": {
+ "title": "This page couldn't load",
+ "text": "An unexpected error occurred while loading this page. Your navigation is still available — try reloading, or use another section from the sidebar.",
+ "digest": "Error reference: {digest}"
+ },
"navbar": {
"items": {
"home": "Home",
@@ -4295,7 +4308,7 @@
"notStarted": "Not started"
},
"name": {
- "management": "Management",
+"management": "Management",
"dashboard": "Dashboard"
}
}
diff --git a/packages/widgets/src/downloads/component.tsx b/packages/widgets/src/downloads/component.tsx
index e020daa62..0edfb9ba5 100644
--- a/packages/widgets/src/downloads/component.tsx
+++ b/packages/widgets/src/downloads/component.tsx
@@ -1,6 +1,7 @@
"use client";
import "../widgets-common.css";
+import "./styles.css";
import { useCallback, useMemo, useState } from "react";
import type { MantineStyleProp } from "@mantine/core";
@@ -541,7 +542,7 @@ export default function DownloadClientsWidget({
enableColumnOrdering: isEditMode,
enableRowVirtualization: true,
rowVirtualizerOptions: { overscan: 5 },
- mantinePaperProps: { flex: 1, withBorder: false, shadow: undefined },
+ mantinePaperProps: { flex: 1, withBorder: false, shadow: undefined, bg: "transparent" },
mantineTableContainerProps: { style: { height: "100%" } },
mantineTableProps: {
className: "downloads-widget-table",
diff --git a/packages/widgets/src/downloads/styles.css b/packages/widgets/src/downloads/styles.css
new file mode 100644
index 000000000..ae9bee8c1
--- /dev/null
+++ b/packages/widgets/src/downloads/styles.css
@@ -0,0 +1,12 @@
+/* Make the MantineReactTable transparent so the board card's opacity-controlled
+ background shows through. MRT defaults to var(--mantine-color-body) which is opaque. */
+.downloads-widget-table,
+.downloads-widget-table .mantine-Table-table,
+.downloads-widget-table .mantine-Table-thead,
+.downloads-widget-table .mantine-Table-tbody,
+.downloads-widget-table .mantine-Table-tfoot,
+.downloads-widget-table .mantine-Table-tr,
+.downloads-widget-table th,
+.downloads-widget-table td {
+ background-color: transparent !important;
+}
diff --git a/packages/widgets/src/index.tsx b/packages/widgets/src/index.tsx
index 628c67e32..17e9b8eed 100644
--- a/packages/widgets/src/index.tsx
+++ b/packages/widgets/src/index.tsx
@@ -52,6 +52,7 @@ import * as timetable from "./timetable";
import * as tracearr from "./tracearr";
import * as umami from "./umami";
import * as ups from "./ups";
+import * as vpn from "./vpn";
import * as beszelSystemTable from "./beszel-system-table";
import * as beszelSystemGrid from "./beszel-system-grid";
import * as beszelAlerts from "./beszel-alerts";
@@ -108,6 +109,7 @@ export const widgetImports = {
uptimeKuma,
audioStats,
umami,
+ vpn,
ups,
beszelSystemTable,
beszelSystemGrid,
diff --git a/packages/widgets/src/vpn/component.tsx b/packages/widgets/src/vpn/component.tsx
new file mode 100644
index 000000000..e88463a85
--- /dev/null
+++ b/packages/widgets/src/vpn/component.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import React from "react";
+import { Flex, ScrollArea, Stack } from "@mantine/core";
+
+import { clientApi } from "@homarr/api/client";
+
+import type { WidgetComponentProps } from "../definition";
+import { VpnIntegrationCard } from "./vpn-card";
+
+export default function VpnWidget({ options, integrationIds }: WidgetComponentProps<"vpn">) {
+ const { data: integrations = [] } = clientApi.widget.vpn.getSummaries.useQuery({
+ ...options,
+ integrationIds,
+ });
+
+ if (integrations.length === 1) {
+ const vpn = integrations[0]!;
+
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+ {integrations.map((vpn) => (
+
+ ))}
+
+
+ );
+}
diff --git a/packages/widgets/src/vpn/helpers.ts b/packages/widgets/src/vpn/helpers.ts
new file mode 100644
index 000000000..9bc801429
--- /dev/null
+++ b/packages/widgets/src/vpn/helpers.ts
@@ -0,0 +1,5 @@
+export const RUNNING_STATUS = "running";
+
+export function getStatusColor(status: string) {
+ return status === RUNNING_STATUS ? "green" : "red";
+}
diff --git a/packages/widgets/src/vpn/index.ts b/packages/widgets/src/vpn/index.ts
new file mode 100644
index 000000000..426366a66
--- /dev/null
+++ b/packages/widgets/src/vpn/index.ts
@@ -0,0 +1,14 @@
+import { IconShieldLock } from "@tabler/icons-react";
+
+import { getIntegrationKindsByCategory } from "@homarr/definitions";
+
+import { createWidgetDefinition } from "../definition";
+import { optionsBuilder } from "../options";
+
+export const { componentLoader, definition } = createWidgetDefinition("vpn", {
+ icon: IconShieldLock,
+ createOptions() {
+ return optionsBuilder.from(() => ({}));
+ },
+ supportedIntegrations: getIntegrationKindsByCategory("vpn"),
+}).withDynamicImport(() => import("./component"));
diff --git a/packages/widgets/src/vpn/vpn-card.tsx b/packages/widgets/src/vpn/vpn-card.tsx
new file mode 100644
index 000000000..3cb26bdd5
--- /dev/null
+++ b/packages/widgets/src/vpn/vpn-card.tsx
@@ -0,0 +1,164 @@
+"use client";
+
+import React from "react";
+import { Badge, Box, Card, Flex, Group, Text } from "@mantine/core";
+import { IconArrowsExchange, IconMapPin, IconShieldCheck, IconShieldX } from "@tabler/icons-react";
+
+import type { RouterOutputs } from "@homarr/api";
+import { useRequiredBoard } from "@homarr/boards/context";
+import { useScopedI18n } from "@homarr/translation/client";
+
+import { getStatusColor, RUNNING_STATUS } from "./helpers";
+
+export type VpnInfo = RouterOutputs["widget"]["vpn"]["getSummaries"][number]["summary"];
+
+export function VpnIntegrationCard({
+ vpn,
+ integrationName,
+ variant = "single",
+}: {
+ vpn: VpnInfo;
+ integrationName?: string;
+ variant?: "single" | "list";
+}) {
+ const board = useRequiredBoard();
+ const compact = variant === "list";
+
+ const content = vpn ? (
+
+
+
+
+ ) : (
+
+ );
+
+ if (compact) {
+ return (
+
+ {content}
+
+ );
+ }
+
+ return {content} ;
+}
+
+function VpnUnavailableContent({ compact, integrationName }: { compact: boolean; integrationName?: string }) {
+ const t = useScopedI18n("widget.vpn");
+
+ return (
+
+ {/* Empty statuses render the shield and DNS badge in red (see getStatusColor). */}
+
+
+ {integrationName ? (
+
+ {integrationName}
+
+ ) : null}
+
+ {t("serviceUnavailable")}
+
+
+
+ );
+}
+
+function VpnStatusColumn({
+ vpnStatus,
+ dnsStatus,
+ compact,
+}: {
+ vpnStatus: string;
+ dnsStatus: string;
+ compact: boolean;
+}) {
+ // Connected shows a shield with a check; anything else (stopped or unavailable) shows a shield with a cross.
+ const ShieldIcon = vpnStatus === RUNNING_STATUS ? IconShieldCheck : IconShieldX;
+
+ return (
+
+
+
+
+ );
+}
+
+function VpnInfoColumn({
+ integrationName,
+ publicIp,
+ city,
+ country,
+ provider,
+ protocol,
+ compact,
+}: {
+ integrationName?: string;
+ publicIp: string;
+ city: string;
+ country: string;
+ provider: string;
+ protocol: string;
+ compact: boolean;
+}) {
+ return (
+
+ {integrationName ? (
+
+ {integrationName}
+
+ ) : null}
+
+ {publicIp}
+
+
+
+
+ {city}, {country}
+
+
+
+
+ );
+}
+
+function VpnProviderDetails({ provider, protocol }: { provider: string; protocol: string }) {
+ return (
+
+
+ {provider}
+
+
+
+ {protocol}
+
+
+ );
+}
+
+function StatusDot({ status, size = 10 }: { status: string; size?: number }) {
+ return ;
+}
+
+function DnsStatusBadge({ status }: { status: string }) {
+ return (
+ }
+ >
+ DNS
+
+ );
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bcba28f14..e834897eb 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -505,8 +505,8 @@ catalogs:
specifier: ^12.0.3
version: 12.0.3
timezones-ical-library:
- specifier: ^2.2.0
- version: 2.2.0
+ specifier: ^2.2.1
+ version: 2.2.1
trpc-to-mcp:
specifier: ^1.3.2
version: 1.3.2
@@ -532,8 +532,8 @@ catalogs:
specifier: ^1.8.1
version: 1.8.1
video.js:
- specifier: ^8.23.8
- version: 8.23.8
+ specifier: ^8.23.9
+ version: 8.23.9
vite:
specifier: ^8.0.16
version: 8.0.16
@@ -575,7 +575,7 @@ overrides:
form-data@>=4.0.0 <4.0.4: '>=4.0.6'
hono@<4.6.5: '>=4.12.26'
linkifyjs@<4.3.2: '>=4.3.3'
- nanoid@>=4.0.0 <5.0.9: '>=5.1.14'
+ nanoid@>=4.0.0 <5.0.9: '>=5.1.15'
prismjs@<1.30.0: '>=1.30.0'
proxmox-api>undici: 7.27.1
react-is: ^19.2.7
@@ -1965,7 +1965,7 @@ importers:
version: 1.1.1
timezones-ical-library:
specifier: 'catalog:'
- version: 2.2.0
+ version: 2.2.1
tsdav:
specifier: 'catalog:'
version: 2.2.2
@@ -2697,7 +2697,7 @@ importers:
version: 3.8.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)
video.js:
specifier: 'catalog:'
- version: 8.23.8
+ version: 8.23.9
zod:
specifier: 'catalog:'
version: 4.4.3
@@ -7882,8 +7882,8 @@ packages:
resolution: {integrity: sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==}
engines: {node: '>= 20'}
- '@videojs/http-streaming@3.17.4':
- resolution: {integrity: sha512-XAvdG2dolBuV2Fx8bu1kjmQ2D4TonGzZH68Pgv/O9xMSFWdZtITSMFismeQLEAtMmGwze8qNJp3RgV+jStrJqg==}
+ '@videojs/http-streaming@3.17.5':
+ resolution: {integrity: sha512-PRRQVu0hYW5h0XvRWpIyIpEatpMZzZtf7L1avV89Hnu+69f+0c7oLsSL9jwpg2nbvzGkdX5rQxQkz5Vc7tFR9w==}
engines: {node: '>=8', npm: '>=5'}
peerDependencies:
video.js: ^8.19.0
@@ -7892,6 +7892,10 @@ packages:
resolution: {integrity: sha512-5iLX6sR2ownbv4Mtejw6Ax+naosGvoT9kY+gcuHzANyUZZ+4NpeNdKMUhb6ag0acYej1Y7cmr/F2+4PrggMiVA==}
engines: {node: '>=8', npm: '>=5'}
+ '@videojs/vhs-utils@4.1.2':
+ resolution: {integrity: sha512-1Dsv5dtWOq0LP1G37O46FQFA2tKAZON5UMe6dFPxT7JvPhEx8QIXcFQxan8+hHI3fX6aomFRFrUBJNgRYQDSXA==}
+ engines: {node: '>=8', npm: '>=5'}
+
'@videojs/xhr@2.7.0':
resolution: {integrity: sha512-giab+EVRanChIupZK7gXjHy90y3nncA2phIOyG3Ne5fvpiMJzvqYwiTOnEVW2S4CoYcuKJkomat7bMXA/UoUZQ==}
@@ -11802,8 +11806,8 @@ packages:
engines: {node: '>=10'}
hasBin: true
- mpd-parser@1.3.1:
- resolution: {integrity: sha512-1FuyEWI5k2HcmhS1HkKnUAQV7yFPfXPht2DnRRGtoiiAAW+ESTbtEXIDpRkwdU+XyrQuwrIym7UkoPKsZ0SyFw==}
+ mpd-parser@1.4.0:
+ resolution: {integrity: sha512-blbA7XpAaOdC/PR0Tu8GiUxlx6CpBuRowQPYV7lhi9Yr9G9+dkPXIUjqjzc/NCn/uZZPopluaJlIYntIEI/VLw==}
hasBin: true
mrmime@2.0.0:
@@ -14259,9 +14263,8 @@ packages:
resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==}
engines: {node: '>=12'}
- timezones-ical-library@2.2.0:
- resolution: {integrity: sha512-D8pKEBzYnCAGZSEyinMZqh2yTc7ZJqonXtNkpTzhPuJlkJjPEUJOXBs7ExeHcHGBaGpaJRTye5t0mLR20BeklA==}
- engines: {node: '>=22.18.0', npm: '>=10.9.3'}
+ timezones-ical-library@2.2.1:
+ resolution: {integrity: sha512-VfDloy50AHsckqwuqc2ea+uyqxY7RjKXOqFMBd4T1zLkq31aVQNKhASvIF4+hSE2tuqaUGzGi4UQk5VzVNn94A==}
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -14721,8 +14724,8 @@ packages:
victory-vendor@37.3.6:
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
- video.js@8.23.8:
- resolution: {integrity: sha512-plZuWxN4clhDESSpi3QLYVs6tgnnf9qT9SA4iahHwZEmdVSuUh4+O6vQPcA5XqiEDvjeBDJvLafH1bhkAXaXKQ==}
+ video.js@8.23.9:
+ resolution: {integrity: sha512-3UCqyFW4wIusQu4TuhgRUxqtbzbj0WPT9hps0XIEXST43QBD11zb+bG7tBPrinrZztwVYdXCiOBo2pcGj9L05w==}
videojs-contrib-quality-levels@4.1.0:
resolution: {integrity: sha512-TfrXJJg1Bv4t6TOCMEVMwF/CoS8iENYsWNKip8zfhB5kTcegiFYezEA0eHAJPU64ZC8NQbxQgOwAsYU8VXbOWA==}
@@ -21476,22 +21479,27 @@ snapshots:
'@vercel/oidc@3.1.0': {}
- '@videojs/http-streaming@3.17.4(video.js@8.23.8)':
+ '@videojs/http-streaming@3.17.5(video.js@8.23.9)':
dependencies:
'@babel/runtime': 7.29.7
- '@videojs/vhs-utils': 4.1.1
+ '@videojs/vhs-utils': 4.1.2
aes-decrypter: 4.0.2
global: 4.4.0
m3u8-parser: 7.2.0
- mpd-parser: 1.3.1
+ mpd-parser: 1.4.0
mux.js: 7.1.0
- video.js: 8.23.8
+ video.js: 8.23.9
'@videojs/vhs-utils@4.1.1':
dependencies:
'@babel/runtime': 7.29.7
global: 4.4.0
+ '@videojs/vhs-utils@4.1.2':
+ dependencies:
+ '@babel/runtime': 7.29.7
+ global: 4.4.0
+
'@videojs/xhr@2.7.0':
dependencies:
'@babel/runtime': 7.29.7
@@ -25988,10 +25996,10 @@ snapshots:
mkdirp@3.0.1: {}
- mpd-parser@1.3.1:
+ mpd-parser@1.4.0:
dependencies:
'@babel/runtime': 7.29.7
- '@videojs/vhs-utils': 4.1.1
+ '@videojs/vhs-utils': 4.1.2
'@xmldom/xmldom': 0.8.10
global: 4.4.0
@@ -28801,7 +28809,7 @@ snapshots:
dependencies:
convert-hrtime: 5.0.0
- timezones-ical-library@2.2.0: {}
+ timezones-ical-library@2.2.1: {}
tiny-invariant@1.3.3: {}
@@ -29250,25 +29258,25 @@ snapshots:
d3-time: 3.1.0
d3-timer: 3.0.1
- video.js@8.23.8:
+ video.js@8.23.9:
dependencies:
'@babel/runtime': 7.29.7
- '@videojs/http-streaming': 3.17.4(video.js@8.23.8)
- '@videojs/vhs-utils': 4.1.1
+ '@videojs/http-streaming': 3.17.5(video.js@8.23.9)
+ '@videojs/vhs-utils': 4.1.2
'@videojs/xhr': 2.7.0
aes-decrypter: 4.0.2
global: 4.4.0
m3u8-parser: 7.2.0
- mpd-parser: 1.3.1
+ mpd-parser: 1.4.0
mux.js: 7.1.0
- videojs-contrib-quality-levels: 4.1.0(video.js@8.23.8)
+ videojs-contrib-quality-levels: 4.1.0(video.js@8.23.9)
videojs-font: 4.2.0
videojs-vtt.js: 0.15.5
- videojs-contrib-quality-levels@4.1.0(video.js@8.23.8):
+ videojs-contrib-quality-levels@4.1.0(video.js@8.23.9):
dependencies:
global: 4.4.0
- video.js: 8.23.8
+ video.js: 8.23.9
videojs-font@4.2.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 0518304f7..392c92129 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -171,7 +171,7 @@ catalog:
superjson: 2.2.6
svgson: ^5.3.1
testcontainers: ^12.0.3
- timezones-ical-library: ^2.2.0
+ timezones-ical-library: ^2.2.1
trpc-to-mcp: ^1.3.2
trpc-to-openapi: ^3.3.0
tsdav: ^2.2.2
@@ -181,7 +181,7 @@ catalog:
vite: ^8.0.16
undici: 7.27.2
use-deep-compare-effect: ^1.8.1
- video.js: ^8.23.8
+ video.js: ^8.23.9
vite-tsconfig-paths: ^6.1.1
vitest: ^4.1.9
winston: 3.19.0
@@ -214,7 +214,7 @@ overrides:
"form-data@>=4.0.0 <4.0.4": ">=4.0.6"
"hono@<4.6.5": ">=4.12.26"
"linkifyjs@<4.3.2": ">=4.3.3"
- "nanoid@>=4.0.0 <5.0.9": ">=5.1.14"
+ "nanoid@>=4.0.0 <5.0.9": ">=5.1.15"
"prismjs@<1.30.0": ">=1.30.0"
"proxmox-api>undici": "7.27.1"
"react-is": "^19.2.7"
diff --git a/static-data/contributors.json b/static-data/contributors.json
index 2137df790..07d22132c 100644
--- a/static-data/contributors.json
+++ b/static-data/contributors.json
@@ -1 +1 @@
-[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47495914?v=4"},{"login":"bebsworthy","avatar_url":"https://avatars.githubusercontent.com/u/5266759?v=4"},{"login":"catrielmuller","avatar_url":"https://avatars.githubusercontent.com/u/2272323?v=4"},{"login":"Vysp3r","avatar_url":"https://avatars.githubusercontent.com/u/62019735?v=4"},{"login":"DR-Blogs","avatar_url":"https://avatars.githubusercontent.com/u/96393186?v=4"},{"login":"singular0","avatar_url":"https://avatars.githubusercontent.com/u/1925883?v=4"},{"login":"diogovalentte","avatar_url":"https://avatars.githubusercontent.com/u/49578155?v=4"},{"login":"codezninja","avatar_url":"https://avatars.githubusercontent.com/u/4400773?v=4"},{"login":"fabricionaweb","avatar_url":"https://avatars.githubusercontent.com/u/15933?v=4"},{"login":"nyok1912","avatar_url":"https://avatars.githubusercontent.com/u/32163949?v=4"},{"login":"bo3bdo","avatar_url":"https://avatars.githubusercontent.com/u/5271380?v=4"},{"login":"hskrtich","avatar_url":"https://avatars.githubusercontent.com/u/1214484?v=4"},{"login":"ishults","avatar_url":"https://avatars.githubusercontent.com/u/7275442?v=4"},{"login":"jelliuk","avatar_url":"https://avatars.githubusercontent.com/u/4790959?v=4"},{"login":"Moohan","avatar_url":"https://avatars.githubusercontent.com/u/5982260?v=4"},{"login":"tesar-tech","avatar_url":"https://avatars.githubusercontent.com/u/33880579?v=4"},{"login":"jbruell","avatar_url":"https://avatars.githubusercontent.com/u/31685172?v=4"},{"login":"JasonLeeB06","avatar_url":"https://avatars.githubusercontent.com/u/82266978?v=4"},{"login":"jeffersonraimon","avatar_url":"https://avatars.githubusercontent.com/u/80064475?v=4"},{"login":"jo-chemla","avatar_url":"https://avatars.githubusercontent.com/u/16822841?v=4"},{"login":"joser93","avatar_url":"https://avatars.githubusercontent.com/u/6912089?v=4"},{"login":"justijndepover","avatar_url":"https://avatars.githubusercontent.com/u/9008623?v=4"},{"login":"Aandree5","avatar_url":"https://avatars.githubusercontent.com/u/32734153?v=4"},{"login":"mvanhorn","avatar_url":"https://avatars.githubusercontent.com/u/455140?v=4"},{"login":"jackwellerreal","avatar_url":"https://avatars.githubusercontent.com/u/97435840?v=4"},{"login":"afonsojramos","avatar_url":"https://avatars.githubusercontent.com/u/19473034?v=4"},{"login":"Copilot","avatar_url":"https://avatars.githubusercontent.com/in/1143301?v=4"},{"login":"SergioChan","avatar_url":"https://avatars.githubusercontent.com/u/10103766?v=4"},{"login":"toineenzo","avatar_url":"https://avatars.githubusercontent.com/u/4921397?v=4"},{"login":"oben01","avatar_url":"https://avatars.githubusercontent.com/u/22369027?v=4"},{"login":"rezstje","avatar_url":"https://avatars.githubusercontent.com/u/45420315?v=4"},{"login":"castielwaverly","avatar_url":"https://avatars.githubusercontent.com/u/137005689?v=4"},{"login":"xoxyuxu","avatar_url":"https://avatars.githubusercontent.com/u/3543416?v=4"},{"login":"StrandedTurtle","avatar_url":"https://avatars.githubusercontent.com/u/132835255?v=4"},{"login":"MaienM","avatar_url":"https://avatars.githubusercontent.com/u/583129?v=4"},{"login":"kylegrieder","avatar_url":"https://avatars.githubusercontent.com/u/25424229?v=4"},{"login":"HeapReaper","avatar_url":"https://avatars.githubusercontent.com/u/31496522?v=4"},{"login":"lopsch","avatar_url":"https://avatars.githubusercontent.com/u/4073114?v=4"},{"login":"Gilmoursa","avatar_url":"https://avatars.githubusercontent.com/u/10321421?v=4"},{"login":"Bricklou","avatar_url":"https://avatars.githubusercontent.com/u/15181236?v=4"},{"login":"mairas","avatar_url":"https://avatars.githubusercontent.com/u/407010?v=4"},{"login":"itzTheMeow","avatar_url":"https://avatars.githubusercontent.com/u/50887230?v=4"},{"login":"Nerdy-Technician","avatar_url":"https://avatars.githubusercontent.com/u/45691205?v=4"},{"login":"NicolasNewman","avatar_url":"https://avatars.githubusercontent.com/u/16135852?v=4"},{"login":"NikeLaosClericus","avatar_url":"https://avatars.githubusercontent.com/u/39810627?v=4"},{"login":"noahcroghan","avatar_url":"https://avatars.githubusercontent.com/u/27422774?v=4"},{"login":"ohmzi","avatar_url":"https://avatars.githubusercontent.com/u/6551272?v=4"},{"login":"Rhiz3K","avatar_url":"https://avatars.githubusercontent.com/u/33246262?v=4"},{"login":"saschabuehrle","avatar_url":"https://avatars.githubusercontent.com/u/47737812?v=4"},{"login":"CrazyWolf13","avatar_url":"https://avatars.githubusercontent.com/u/96661824?v=4"},{"login":"VIVAAN-DHAWAN","avatar_url":"https://avatars.githubusercontent.com/u/150339722?v=4"},{"login":"generalmat82","avatar_url":"https://avatars.githubusercontent.com/u/110256073?v=4"},{"login":"lthomsen","avatar_url":"https://avatars.githubusercontent.com/u/28560325?v=4"},{"login":"pitschi","avatar_url":"https://avatars.githubusercontent.com/u/6841565?v=4"},{"login":"sharpsalat","avatar_url":"https://avatars.githubusercontent.com/u/15666932?v=4"},{"login":"tech62","avatar_url":"https://avatars.githubusercontent.com/u/16237642?v=4"},{"login":"AartSchinkel","avatar_url":"https://avatars.githubusercontent.com/u/189675191?v=4"},{"login":"m0s4ik","avatar_url":"https://avatars.githubusercontent.com/u/26578395?v=4"},{"login":"ashtonbatty","avatar_url":"https://avatars.githubusercontent.com/u/230635?v=4"},{"login":"Oupsman","avatar_url":"https://avatars.githubusercontent.com/u/11260343?v=4"},{"login":"cethien","avatar_url":"https://avatars.githubusercontent.com/u/60463918?v=4"},{"login":"DZ945brH","avatar_url":"https://avatars.githubusercontent.com/u/42635243?v=4"},{"login":"dgshue","avatar_url":"https://avatars.githubusercontent.com/u/12952156?v=4"},{"login":"d-lindahl","avatar_url":"https://avatars.githubusercontent.com/u/690410?v=4"},{"login":"KartulUdus","avatar_url":"https://avatars.githubusercontent.com/u/23346150?v=4"},{"login":"ieugen","avatar_url":"https://avatars.githubusercontent.com/u/1050920?v=4"},{"login":"faugusto-oliveira","avatar_url":"https://avatars.githubusercontent.com/u/37384074?v=4"},{"login":"frankvHoof93","avatar_url":"https://avatars.githubusercontent.com/u/7346516?v=4"},{"login":"darbyjack","avatar_url":"https://avatars.githubusercontent.com/u/21014720?v=4"},{"login":"febrile42","avatar_url":"https://avatars.githubusercontent.com/u/14597122?v=4"},{"login":"Kaotic3","avatar_url":"https://avatars.githubusercontent.com/u/80522225?v=4"},{"login":"KelianB","avatar_url":"https://avatars.githubusercontent.com/u/11216155?v=4"},{"login":"hotrungnhan","avatar_url":"https://avatars.githubusercontent.com/u/40029631?v=4"},{"login":"leandroscardua","avatar_url":"https://avatars.githubusercontent.com/u/24698541?v=4"}]
\ No newline at end of file
+[{"login":"ajnart","avatar_url":"https://avatars.githubusercontent.com/u/49837342?v=4"},{"login":"manuel-rw","avatar_url":"https://avatars.githubusercontent.com/u/30572287?v=4"},{"login":"Meierschlumpf","avatar_url":"https://avatars.githubusercontent.com/u/63781622?v=4"},{"login":"SeDemal","avatar_url":"https://avatars.githubusercontent.com/u/26098587?v=4"},{"login":"lammersbjorn","avatar_url":"https://avatars.githubusercontent.com/u/71191962?v=4"},{"login":"LarveyOfficial","avatar_url":"https://avatars.githubusercontent.com/u/39219859?v=4"},{"login":"c00ldude1oo","avatar_url":"https://avatars.githubusercontent.com/u/15677803?v=4"},{"login":"InterN0te","avatar_url":"https://avatars.githubusercontent.com/u/10882916?v=4"},{"login":"Aimsucks","avatar_url":"https://avatars.githubusercontent.com/u/1828486?v=4"},{"login":"JannesV","avatar_url":"https://avatars.githubusercontent.com/u/5984244?v=4"},{"login":"MauriceNino","avatar_url":"https://avatars.githubusercontent.com/u/18658092?v=4"},{"login":"hillaliy","avatar_url":"https://avatars.githubusercontent.com/u/950010?v=4"},{"login":"Skeeww","avatar_url":"https://avatars.githubusercontent.com/u/9639992?v=4"},{"login":"RichyHBM","avatar_url":"https://avatars.githubusercontent.com/u/5472275?v=4"},{"login":"Bonfire","avatar_url":"https://avatars.githubusercontent.com/u/5704760?v=4"},{"login":"wieli99","avatar_url":"https://avatars.githubusercontent.com/u/46794825?v=4"},{"login":"ishaanparlikar","avatar_url":"https://avatars.githubusercontent.com/u/43013132?v=4"},{"login":"Zareix","avatar_url":"https://avatars.githubusercontent.com/u/29818713?v=4"},{"login":"tuggan","avatar_url":"https://avatars.githubusercontent.com/u/4325608?v=4"},{"login":"Spillebulle","avatar_url":"https://avatars.githubusercontent.com/u/46653946?v=4"},{"login":"marektancak0106","avatar_url":"https://avatars.githubusercontent.com/u/25422667?v=4"},{"login":"spkesDE","avatar_url":"https://avatars.githubusercontent.com/u/1682314?v=4"},{"login":"j3lte","avatar_url":"https://avatars.githubusercontent.com/u/2557568?v=4"},{"login":"dslatt","avatar_url":"https://avatars.githubusercontent.com/u/5211001?v=4"},{"login":"Malong11-007","avatar_url":"https://avatars.githubusercontent.com/u/40298510?v=4"},{"login":"StefanB7","avatar_url":"https://avatars.githubusercontent.com/u/22056509?v=4"},{"login":"souravghosh01","avatar_url":"https://avatars.githubusercontent.com/u/37548139?v=4"},{"login":"SR3u","avatar_url":"https://avatars.githubusercontent.com/u/2380522?v=4"},{"login":"Rikpat","avatar_url":"https://avatars.githubusercontent.com/u/33869814?v=4"},{"login":"NormanJS","avatar_url":"https://avatars.githubusercontent.com/u/83409549?v=4"},{"login":"No-Maines-Land","avatar_url":"https://avatars.githubusercontent.com/u/20687725?v=4"},{"login":"stark1tty","avatar_url":"https://avatars.githubusercontent.com/u/69326376?v=4"},{"login":"Momcilo42","avatar_url":"https://avatars.githubusercontent.com/u/10118137?v=4"},{"login":"marius-arch","avatar_url":"https://avatars.githubusercontent.com/u/72494359?v=4"},{"login":"Lumilias","avatar_url":"https://avatars.githubusercontent.com/u/10852161?v=4"},{"login":"Havock94","avatar_url":"https://avatars.githubusercontent.com/u/7635248?v=4"},{"login":"Lollyde","avatar_url":"https://avatars.githubusercontent.com/u/11347524?v=4"},{"login":"krishnamuppaneni","avatar_url":"https://avatars.githubusercontent.com/u/4703366?v=4"},{"login":"devtobi","avatar_url":"https://avatars.githubusercontent.com/u/28538704?v=4"},{"login":"TyxTang","avatar_url":"https://avatars.githubusercontent.com/u/90897955?v=4"},{"login":"beanbag420","avatar_url":"https://avatars.githubusercontent.com/u/19630511?v=4"},{"login":"WillyJL","avatar_url":"https://avatars.githubusercontent.com/u/49810075?v=4"},{"login":"anonysoul","avatar_url":"https://avatars.githubusercontent.com/u/105715150?v=4"},{"login":"cospeedster","avatar_url":"https://avatars.githubusercontent.com/u/52749645?v=4"},{"login":"diederbert","avatar_url":"https://avatars.githubusercontent.com/u/162878798?v=4"},{"login":"digdilem","avatar_url":"https://avatars.githubusercontent.com/u/4052275?v=4"},{"login":"dinghino","avatar_url":"https://avatars.githubusercontent.com/u/14729459?v=4"},{"login":"gfomichev","avatar_url":"https://avatars.githubusercontent.com/u/1596146?v=4"},{"login":"gnattu","avatar_url":"https://avatars.githubusercontent.com/u/12995396?v=4"},{"login":"tuxpizza","avatar_url":"https://avatars.githubusercontent.com/u/84710786?v=4"},{"login":"xmarkurion","avatar_url":"https://avatars.githubusercontent.com/u/46654687?v=4"},{"login":"huanga","avatar_url":"https://avatars.githubusercontent.com/u/732970?v=4"},{"login":"taos15","avatar_url":"https://avatars.githubusercontent.com/u/122130728?v=4"},{"login":"arghyadipchak","avatar_url":"https://avatars.githubusercontent.com/u/32430374?v=4"},{"login":"AuthorShin","avatar_url":"https://avatars.githubusercontent.com/u/4959043?v=4"},{"login":"benniblot","avatar_url":"https://avatars.githubusercontent.com/u/47495914?v=4"},{"login":"bebsworthy","avatar_url":"https://avatars.githubusercontent.com/u/5266759?v=4"},{"login":"catrielmuller","avatar_url":"https://avatars.githubusercontent.com/u/2272323?v=4"},{"login":"Vysp3r","avatar_url":"https://avatars.githubusercontent.com/u/62019735?v=4"},{"login":"DR-Blogs","avatar_url":"https://avatars.githubusercontent.com/u/96393186?v=4"},{"login":"singular0","avatar_url":"https://avatars.githubusercontent.com/u/1925883?v=4"},{"login":"diogovalentte","avatar_url":"https://avatars.githubusercontent.com/u/49578155?v=4"},{"login":"codezninja","avatar_url":"https://avatars.githubusercontent.com/u/4400773?v=4"},{"login":"fabricionaweb","avatar_url":"https://avatars.githubusercontent.com/u/15933?v=4"},{"login":"nyok1912","avatar_url":"https://avatars.githubusercontent.com/u/32163949?v=4"},{"login":"bo3bdo","avatar_url":"https://avatars.githubusercontent.com/u/5271380?v=4"},{"login":"hskrtich","avatar_url":"https://avatars.githubusercontent.com/u/1214484?v=4"},{"login":"ishults","avatar_url":"https://avatars.githubusercontent.com/u/7275442?v=4"},{"login":"jelliuk","avatar_url":"https://avatars.githubusercontent.com/u/4790959?v=4"},{"login":"Moohan","avatar_url":"https://avatars.githubusercontent.com/u/5982260?v=4"},{"login":"tesar-tech","avatar_url":"https://avatars.githubusercontent.com/u/33880579?v=4"},{"login":"jbruell","avatar_url":"https://avatars.githubusercontent.com/u/31685172?v=4"},{"login":"JasonLeeB06","avatar_url":"https://avatars.githubusercontent.com/u/82266978?v=4"},{"login":"jeffersonraimon","avatar_url":"https://avatars.githubusercontent.com/u/80064475?v=4"},{"login":"jo-chemla","avatar_url":"https://avatars.githubusercontent.com/u/16822841?v=4"},{"login":"joser93","avatar_url":"https://avatars.githubusercontent.com/u/6912089?v=4"},{"login":"justijndepover","avatar_url":"https://avatars.githubusercontent.com/u/9008623?v=4"},{"login":"Aandree5","avatar_url":"https://avatars.githubusercontent.com/u/32734153?v=4"},{"login":"afonsojramos","avatar_url":"https://avatars.githubusercontent.com/u/19473034?v=4"},{"login":"mvanhorn","avatar_url":"https://avatars.githubusercontent.com/u/455140?v=4"},{"login":"jackwellerreal","avatar_url":"https://avatars.githubusercontent.com/u/97435840?v=4"},{"login":"Copilot","avatar_url":"https://avatars.githubusercontent.com/in/1143301?v=4"},{"login":"SergioChan","avatar_url":"https://avatars.githubusercontent.com/u/10103766?v=4"},{"login":"toineenzo","avatar_url":"https://avatars.githubusercontent.com/u/4921397?v=4"},{"login":"oben01","avatar_url":"https://avatars.githubusercontent.com/u/22369027?v=4"},{"login":"rezstje","avatar_url":"https://avatars.githubusercontent.com/u/45420315?v=4"},{"login":"castielwaverly","avatar_url":"https://avatars.githubusercontent.com/u/137005689?v=4"},{"login":"xoxyuxu","avatar_url":"https://avatars.githubusercontent.com/u/3543416?v=4"},{"login":"StrandedTurtle","avatar_url":"https://avatars.githubusercontent.com/u/132835255?v=4"},{"login":"MaienM","avatar_url":"https://avatars.githubusercontent.com/u/583129?v=4"},{"login":"kylegrieder","avatar_url":"https://avatars.githubusercontent.com/u/25424229?v=4"},{"login":"HeapReaper","avatar_url":"https://avatars.githubusercontent.com/u/31496522?v=4"},{"login":"lopsch","avatar_url":"https://avatars.githubusercontent.com/u/4073114?v=4"},{"login":"Gilmoursa","avatar_url":"https://avatars.githubusercontent.com/u/10321421?v=4"},{"login":"Lok3sh-kumar","avatar_url":"https://avatars.githubusercontent.com/u/110459168?v=4"},{"login":"Bricklou","avatar_url":"https://avatars.githubusercontent.com/u/15181236?v=4"},{"login":"mairas","avatar_url":"https://avatars.githubusercontent.com/u/407010?v=4"},{"login":"itzTheMeow","avatar_url":"https://avatars.githubusercontent.com/u/50887230?v=4"},{"login":"Nerdy-Technician","avatar_url":"https://avatars.githubusercontent.com/u/45691205?v=4"},{"login":"NicolasNewman","avatar_url":"https://avatars.githubusercontent.com/u/16135852?v=4"},{"login":"NikeLaosClericus","avatar_url":"https://avatars.githubusercontent.com/u/39810627?v=4"},{"login":"noahcroghan","avatar_url":"https://avatars.githubusercontent.com/u/27422774?v=4"},{"login":"ohmzi","avatar_url":"https://avatars.githubusercontent.com/u/6551272?v=4"},{"login":"Rhiz3K","avatar_url":"https://avatars.githubusercontent.com/u/33246262?v=4"},{"login":"saschabuehrle","avatar_url":"https://avatars.githubusercontent.com/u/47737812?v=4"},{"login":"CrazyWolf13","avatar_url":"https://avatars.githubusercontent.com/u/96661824?v=4"},{"login":"VIVAAN-DHAWAN","avatar_url":"https://avatars.githubusercontent.com/u/150339722?v=4"},{"login":"generalmat82","avatar_url":"https://avatars.githubusercontent.com/u/110256073?v=4"},{"login":"lthomsen","avatar_url":"https://avatars.githubusercontent.com/u/28560325?v=4"},{"login":"pitschi","avatar_url":"https://avatars.githubusercontent.com/u/6841565?v=4"},{"login":"sharpsalat","avatar_url":"https://avatars.githubusercontent.com/u/15666932?v=4"},{"login":"tech62","avatar_url":"https://avatars.githubusercontent.com/u/16237642?v=4"},{"login":"AartSchinkel","avatar_url":"https://avatars.githubusercontent.com/u/189675191?v=4"},{"login":"m0s4ik","avatar_url":"https://avatars.githubusercontent.com/u/26578395?v=4"},{"login":"ashtonbatty","avatar_url":"https://avatars.githubusercontent.com/u/230635?v=4"},{"login":"Oupsman","avatar_url":"https://avatars.githubusercontent.com/u/11260343?v=4"},{"login":"Bergasha","avatar_url":"https://avatars.githubusercontent.com/u/25044200?v=4"},{"login":"cethien","avatar_url":"https://avatars.githubusercontent.com/u/60463918?v=4"},{"login":"DZ945brH","avatar_url":"https://avatars.githubusercontent.com/u/42635243?v=4"},{"login":"dgshue","avatar_url":"https://avatars.githubusercontent.com/u/12952156?v=4"},{"login":"d-lindahl","avatar_url":"https://avatars.githubusercontent.com/u/690410?v=4"},{"login":"KartulUdus","avatar_url":"https://avatars.githubusercontent.com/u/23346150?v=4"},{"login":"ieugen","avatar_url":"https://avatars.githubusercontent.com/u/1050920?v=4"},{"login":"faugusto-oliveira","avatar_url":"https://avatars.githubusercontent.com/u/37384074?v=4"},{"login":"frankvHoof93","avatar_url":"https://avatars.githubusercontent.com/u/7346516?v=4"},{"login":"darbyjack","avatar_url":"https://avatars.githubusercontent.com/u/21014720?v=4"},{"login":"febrile42","avatar_url":"https://avatars.githubusercontent.com/u/14597122?v=4"},{"login":"Kaotic3","avatar_url":"https://avatars.githubusercontent.com/u/80522225?v=4"},{"login":"KelianB","avatar_url":"https://avatars.githubusercontent.com/u/11216155?v=4"},{"login":"hotrungnhan","avatar_url":"https://avatars.githubusercontent.com/u/40029631?v=4"},{"login":"leandroscardua","avatar_url":"https://avatars.githubusercontent.com/u/24698541?v=4"}]
\ No newline at end of file
diff --git a/static-data/translators.json b/static-data/translators.json
index 221b28e9d..4d0b1456d 100644
--- a/static-data/translators.json
+++ b/static-data/translators.json
@@ -1 +1 @@
-[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/aaa1f083e04c572f953fde4563171e4a.jpeg"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14934947/medium/f5a8570713c34ab0f7d5405d105e2a9a.jpeg"},{"username":"f1refa11","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14949159/medium/fd2ae63b8eb4462200ba96abf943c1b9.png"},{"username":"jeffersonraimon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057621/medium/6dff469563860e2618bc9d45646d0ab4.png"},{"username":"giop98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057987/medium/b8a4825d3fc39fc662f35ee258db4b2d.jpeg"},{"username":"tomislav.kraljevic","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15118857/medium/e133f1061cc92850b854d05d8faaeafd.png"},{"username":"Walkx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15202182/medium/5c37361ae45aeed487b34582c1f7ca37.png"},{"username":"andrea.rosso","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15246318/medium/11f3f5ef44ec7f55b6f143090e208704_default.png"},{"username":"ajnart","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15270132/medium/a0f107a463c8910ee96bc2fa843a17e3.jpeg"},{"username":"Witchling","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15282238/medium/9e196b6783415b10ca571b7673c2ef33.jpg"},{"username":"guineuu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15292058/medium/22fbb758bda3b7805d50bf21d38f2c20.jpeg"},{"username":"Bulgus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15304568/medium/0e8787e5ceb02ed5c96a514d0068ae87.jpg"},{"username":"Narno","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15314996/medium/a6ba093f24884250a1bf6c6f2ca50f1b_default.png"},{"username":"BySempron","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15315986/medium/e3d22d7b1423c6823a9f36d595ed4bdb.png"},{"username":"jonathan.berglin.work","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15359236/medium/95930b2093db13b76179782f7322c5d5.png"},{"username":"icdmkg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405504/medium/f334d16cde1ac4ffb935b440d03465af.gif"},{"username":"irithys","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405614/medium/3086461c47cce0a0c031925e5f943412.png"},{"username":"JannesV","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419912/medium/a7809eb4b817d7c49b62cf10ae86b950.png"},{"username":"benniblot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419914/medium/8086b1573d31f152ba41949b3a172445.jpeg"},{"username":"pacjo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419916/medium/3cbeddbd7bc01faafb5a3bf47bba915b_default.png"},{"username":"Payou6994","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419934/medium/37c9b9b17dfb578404c1c1ddb73ba7a8.png"},{"username":"fzibi21","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419976/medium/0da688450358e0290a7b7359cc1f7328.png"},{"username":"WowMurdock","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420118/medium/de19576fccb495b6dfe4c4c04a56b834.png"},{"username":"hbooo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420120/medium/6c7c7f8db785061356ebb03d044d3329.jpeg"},{"username":"Manicraft1001","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420178/medium/50ec94563a06a9f74f33bd09f01eed4d.jpg"},{"username":"Void123","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420354/medium/86929d44df92a00f9fe900a985c196df_default.png"},{"username":"R4cc","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15422606/medium/a390979662b84694f59de30bdb732141.jpeg"},{"username":"fabricionaweb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15425808/medium/a9354142e7ae5152c144409d55fda551_default.png"},{"username":"JokeOfDead","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15426890/medium/a383eac9365e9de64fd4ab1d6fd0cb95.jpeg"},{"username":"hkz","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15427174/medium/c88acefb0d7306e1f7470e872029fb39_default.png"},{"username":"Steken","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428516/medium/d5aea5653c769c3a523182bdb60d1664.png"},{"username":"flar.anton","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428592/medium/b01fdb365d892e9f811f77fcb50a80a2.jpeg"},{"username":"Bon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15433542/medium/5397da4dfc821f20b6ac14fe0c514e9a.jpeg"},{"username":"bfkadan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434162/medium/4f9a0b43cfe3acaea60124c14ba7f44a.png"},{"username":"BunnySweety","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434662/medium/f0ef200a6a0dcf0e1d0e9ecd4148f560_default.png"},{"username":"HRKings","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15436168/medium/69a31e461d38549f01864e7ef10d642e.png"},{"username":"wiston81","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15439078/medium/10f292c8d12a7c21a44b54495fa4a3d8.jpeg"},{"username":"qaz0911","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15440860/medium/3d3e50ee388c72dc4bf7a771761f2d89_default.png"},{"username":"qqyule","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15441462/medium/7a3cdf82710ffb5d8f388bc0bd010665.png"},{"username":"Bims0n","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15445560/medium/603220b603eeb3367e0f0d3fa675247c.jpg"},{"username":"TariqDaCoder","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15446228/medium/50b0f4040112bbd67690b769477398e5_default.png"},{"username":"Anarchon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15449644/medium/e925e1f3e3ffbf0f982391ce263a1a28.jpeg"},{"username":"Ashun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15453020/medium/ccdcf51c73d6aae40751bb30beee1915_default.png"},{"username":"sebekmartin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15454038/medium/bcfb44598cdfd1d7cd4eb35812538962.jpeg"},{"username":"RagnarGraves","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15459882/medium/143d5af850c1154070a218bea124e9cb_default.png"},{"username":"PrtmPhlp","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15462414/medium/b80db55e9de301432dcd1f8c8b24fd49_default.png"},{"username":"DooYoo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15470768/medium/2a18cf4be67094724b508c9e1e698a21_default.png"},{"username":"espentruls","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486092/medium/8e38afc3a4ff669226a0cfd3e420ff3a.jpeg"},{"username":"frisco82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486922/medium/4e1c5d4189b42508e660daa3c1c25b2a.jpeg"},{"username":"lupineDK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15491798/medium/461bd501e8371c062bf29ea171aedd36_default.png"},{"username":"hillaliy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15492732/medium/0bae17b421604892d888e3fc70cf0587.jpeg"},{"username":"MarcOrfilaCarreras","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15501072/medium/ea52b26c3c6f21e4931e38e3ce3f3d6e.png"},{"username":"robertbridda","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15507822/medium/a368c2e30411bb2da9b49290084191f3.png"},{"username":"MKoniuszko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518090/medium/87605434fcc839f6763ab07c50f6d232.jpeg"},{"username":"HeroSizy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518710/medium/4e79c0e98cbeb536dd961e656331b509.png"},{"username":"dwt136","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15520022/medium/554422503f2baea43ace85facb4546fb_default.png"},{"username":"asifthewebguy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15526719/medium/b18931dd0c800d725048bd440646198b_default.png"},{"username":"eiloogs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15545537/medium/f290a2f1190983530a9b76b2e858a609.gif"},{"username":"_vytdv","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15547289/medium/60d8644cc8ad6f11a92ccea4a14cd098_default.png"},{"username":"crendasien","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15554645/medium/598ab1d4aaf6b8dccd5ba16be92da7b9.jpeg"},{"username":"edxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15573823/medium/8a565b732a75a77f840dd123cdb30bf4.png"},{"username":"somerlev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15617065/medium/f4b13513e311ec902d90b2f718412c55.jpg"},{"username":"kid1412621","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15643771/medium/dd455e32de652fa88e6fd97598bdffa7.png"},{"username":"suming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15644717/medium/244159dfe10fa03436205506f80c9e25.png"},{"username":"nick.gher","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15647517/medium/5374a2c6fef60a3fbf0edf86b997c351_default.png"},{"username":"DataCat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15650315/medium/ce7c1365adf35c5d490d77500a4607fb_default.png"},{"username":"dizo89","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15658375/medium/0ca745e5017d491fe1b22b0239904de8.jpeg"},{"username":"tee_noodle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674577/medium/804d9cf06f5196026acb6436b809d0da_default.png"},{"username":"Marty88","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674593/medium/492b1509d52bd2809dea768121217125.jpeg"},{"username":"Spillebulle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15677023/medium/20b98bb85062e07afe0e63618f8a5feb.PNG"},{"username":"petitmewen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15685239/medium/15de9b62d2e0bc25013435f1784bbcc1.jpeg"},{"username":"NoProsNoNoobs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15687709/medium/ae8f65fafeb8bcf74dcb8871bbe46461.png"},{"username":"y.gybson","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15690777/medium/b5cb5d2d5768904ff6586c727e3a6c77.jpeg"},{"username":"inside90","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15704947/medium/c1355fcb30dd76f8e39d98d1d49f1c52.png"},{"username":"RJSkudra","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15709853/medium/c3abf2774913dc4e81fb261d36d7668c.png"},{"username":"binge203","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15713937/medium/77c1cfa4314673db80e6881fd9f64668.gif"},{"username":"Mailootje","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15714337/medium/743c3bc4ab1989966a375eeeec83d8b8.jpeg"},{"username":"tim-wiegers","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15719805/medium/08a96f064813350661cd6b20bf3d7d99.png"},{"username":"GkhnG","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15722911/medium/71a027caec489ef6ce82bcf1888329d0_default.png"},{"username":"Zoen-Millo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15760967/medium/9e956f11adc5b34f5636268b5c485dbf.jpg"},{"username":"HooinKyoma","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15792897/medium/9489f0a9b368e0e827ae758b740a2eed.jpeg"},{"username":"wathergeen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15794495/medium/a83b7fc80d180669ebfd6deb3544cfd0.png"},{"username":"MoeToo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15818233/medium/d35cd6953717706eaf20f6c143c62947.png"},{"username":"itodouble","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15836233/medium/e984caea18fb0673bf319fcf28cef649.png"},{"username":"loslocitos","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15847901/medium/fe30d12fd2cf38212f929e13b169f9ec.jpeg"},{"username":"Oversleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15849065/medium/06141f13a6d541d753f3c2f2947b8068_default.png"},{"username":"Beardy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15865139/medium/fca6b9d2b3f52e286d1568f52b83b6a0_default.png"},{"username":"raelyan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15875457/medium/2f4fda1d1aaa5dcc79b328baf3f03151.jpeg"},{"username":"dolphin738","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15913763/medium/ee6fede7b8528ca642329ada80d1cc18.png"},{"username":"brunotco","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15916719/medium/09db45880fc05abc18adb8d932a5ecf9_default.png"},{"username":"LukeIsHereToDevelop","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15924739/medium/3e6cd3940297bb4ba7bb039c93e6f918.png"},{"username":"kennit","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15925879/medium/6b0733ad3c5949b91c55e4d8b03db8a5_default.png"},{"username":"kuunpire","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15950309/medium/8192a4f08f07086828ac9f74ed29a169.jpeg"},{"username":"Sandor-dev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15951759/medium/0216c2da4eb028164ebbecf1c72f6271_default.png"},{"username":"Meierschlumpf","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15953187/medium/23c744faef1ab84fbdc9351a7850aab6.jpeg"},{"username":"harmlesscat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15970733/medium/dd8d0214a0250c932bb518b1b55e45a2_default.png"},{"username":"OrzWTF","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15976121/medium/4c4557cbff7ff7b0503455bc59c020e0.jpeg"},{"username":"tagaishi","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15977271/medium/eade504c83a5a1ff831c80a538fbdb44_default.png"},{"username":"azurite928","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15981895/medium/547ccc459ee123e78b5401c499f1022d.png"},{"username":"Ronner231","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16021342/medium/7734d550df2de5a2fec2ffff33e7024c.jpeg"},{"username":"ugyes","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16034148/medium/ed001e3f470a2dea9a8ce955b18e7bd5.png"},{"username":"rpieja","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16045554/medium/bd55baca2ef8b92502a760cc9ee7c505_default.png"},{"username":"flambyisyou","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16051620/medium/3a3bc0c90f6b95ab4ef74396a0a17beb.png"},{"username":"Topbcy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16077170/medium/d3aed33ea56330338756cfcd89477cfe.jpeg"},{"username":"ai5d02sb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16084674/medium/7c8119fe2a5ca71bb15f636916a42b95_default.png"},{"username":"explosiveparrot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16097722/medium/7762f80fc1da63f5b2eb87de9d640324_default.png"},{"username":"DestroyCom","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16111544/medium/83e3d3ac5c8bfed634cd1a074854db67.png"},{"username":"neotraxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16116966/medium/b30df957873329019a8a90a69f6efe3a.png"},{"username":"gzxpa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16123314/medium/487b05577310a946b0b98bc72c11213b_default.png"},{"username":"TORIK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16124732/medium/de770890e6411860ac06e8cc2dc0abd0_default.png"},{"username":"sergio.pibot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16138114/medium/80db802b2fcd6bbf19ea4c7413367dca.jpeg"},{"username":"karam-ajaj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16139838/medium/7539ab958d3c85d5f67e7849483ea8f7.jpeg"},{"username":"SimonHenz97","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16151520/medium/9b94a85b536688e2d94538c384fd0a76.png"},{"username":"olsson82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16198988/medium/eeaab4cba210c537424d4204754b604e.png"},{"username":"bo3bdo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16222806/medium/8652f3beacb6098dde5a4cd0a29fe80d.jpeg"},{"username":"AngryPlayer04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245192/medium/342715274f4dc31fd4132cdae9126188.jpeg"},{"username":"ehatamm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245884/medium/979d5e5526fe545be9bf46db481f7182.jpeg"},{"username":"Balionelis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16246058/medium/986d62ba363594c517d966fe9178dcbb.png"},{"username":"kwargs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16248816/medium/19ce11f04bf1ae2637e0483399c675f9_default.png"},{"username":"lucicu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16273892/medium/3b09e8f3bead221ef98d6a45a16851dc_default.png"},{"username":"Michael5564445","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16357688/medium/9d9026a8bf3eaccb0417da240932ebf7.png"},{"username":"Netscream","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16359198/medium/694e0c8572dc8a47e6d63da31ec5b992.png"},{"username":"belicstefan04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16368142/medium/4742dcfd7bf7ad7e33e37ac44fba7e0f.png"},{"username":"Hamad2066","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16371478/medium/f37bbc7685b6757defdbd1494236af61.jpeg"},{"username":"3ct0pl4sm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16399616/medium/ce796d1c40163975624c0a483e56374f.png"},{"username":"sanchez.marcell626","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16433865/medium/3ddde1314764cc2205ffdcd1ae719a91.png"},{"username":"Minoctis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16490079/medium/91fd0849303a4eca033d660d0a7b6854.jpeg"},{"username":"homarr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16657003/medium/3afabfd3de949a54ff5d40ad04552aba.png"},{"username":"mwgg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16658379/medium/763dd09f5c238cc5324d4d5357985292.png"},{"username":"kiwinho","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16671855/medium/bfe3a2d497dc4748a04d02e4ae2139cb.jpeg"},{"username":"tjvg4m34r13","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16679135/medium/7a483b9b0abe0d925e54808e1cb38fa0.jpeg"},{"username":"Diegoem","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16705921/medium/29ce68ebc191fc3f65499511b12de98f_default.png"},{"username":"bouks69","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16746091/medium/b58b1fc08520de416198be9df5d093eb.jpeg"},{"username":"futhgar","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16748067/medium/b8c526b097f9aa9d687250cac3df16dc.jpeg"},{"username":"GrimSQL","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16764343/medium/fff729a76d1f8fbcdaee9bb0c0f5f3fd.png"},{"username":"chnikel","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767047/medium/3dda2a37e03049dafefe6697bb00f75c.png"},{"username":"yuki19870616","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767951/medium/f59c848e38382cda6c8464b0a7b9e642_default.png"},{"username":"noamdal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16782245/medium/b4a72a61f9caf138f0322c0cb987e6d0.png"},{"username":"MalithRukshan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16809057/medium/084d6a5b35fbbeacc70e7021197d222f.png"},{"username":"minirope19","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16930929/medium/e8cbbc26747e31c5a8959215e1dd067b.jpeg"},{"username":"prubin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17503492/medium/96ac87644691e49c1f983f2f094e541c_default.png"},{"username":"sam-tam-sam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17578052/medium/1628dc2954ce3ae95e0caaa2bd60ab0c.png"},{"username":"toineenzo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12371852/medium/9f1b2bedb4794c59479cd289bdba9fab.png"},{"username":"ArcaneWater","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12421105/medium/1ac82517a6c658f6b62c48aebcb17e86_default.png"},{"username":"Ebedami","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12974801/medium/b596dea5c09454c4c828eac558389755.jpg"},{"username":"perrygamerptyt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13117177/medium/1406c949507ecd438480473c8a1f6124.jpg"},{"username":"SergiSvK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13320543/medium/3df3c79cc5f0c53716c559cd3fd2fc20.png"},{"username":"jeongeon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13362856/medium/eb572f72cde5e5526c517ca3a6489850_default.png"},{"username":"MrTranCFCVN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13439475/medium/a1789d69c85d90c667194b5d6a518c08_default.png"},{"username":"Tobost06","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13467449/medium/9227dc07f6366bc060d28304f5cb8ce7.jpeg"},{"username":"Kf637","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13530622/medium/118b00bb72d01d3229c97409637e25d1_default.png"},{"username":"Cruellest","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13610615/medium/2a74faa0947ee08d18f7946fb8602c1e.jpg"},{"username":"olavinto","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14424644/medium/37c01732bd2b7a272b421807a05ccfeb.png"},{"username":"Thario","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14491372/medium/dd5e142070802a672900121aa3f332a6_default.png"},{"username":"Wjouter","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14585620/medium/b91a51513332bdc0a0a9ecbed99ecbc3.png"},{"username":"albanobattistella","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14686194/medium/564afcfd76f5cd24751bd669cf4943aa.jpg"},{"username":"Kachelkaiser","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14763406/medium/203a781797da4a18fe0f4e7db8fd547c.png"},{"username":"regi4","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14842936/medium/f7e1d672f562a7db61ba6b4b8f4de41d.png"},{"username":"baton5129","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14891064/medium/8d438f557b82977dd02f0ead3187265f.PNG"},{"username":"meviper","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15958903/medium/209f521ce33a05831fa8d7d1b7babc20_default.png"},{"username":"GerOriki","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16268376/medium/da00e54b08cd40348231d92873a0cd0a_default.png"},{"username":"TheRedCyclops","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16486399/medium/7f39226180120d5a9efcb8e13d8aec8e_default.png"},{"username":"dymek37","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16659221/medium/d1dc6d11d7e7fc370efc5070e837ae03.png"},{"username":"B3CKDOOR","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16744975/medium/e6c7b2d45e76465d54e6efb67c83da7d.png"},{"username":"on9686","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16768173/medium/6282fed9aafe0262a173a5ec0887e5d9_default.png"},{"username":"Larsluph","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16779933/medium/ed7d36f8b44013bad3a16ac0491bd0f7.png"},{"username":"FOEDI","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16781001/medium/6b4552fb72e359aa016f7a054078fac8.png"},{"username":"xmarkurion","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16805443/medium/34a20428427de5aaef99c7d140646803.png"},{"username":"gregid","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16806705/medium/f3c8f4a20436e8538929130142bbc104.png"},{"username":"Flameberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16820453/medium/d47be866148474c9d05fec6fbe406d88.png"},{"username":"Playfust","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16872865/medium/5f4609cc711e5fb9fa8a0b178635769c.jpeg"},{"username":"Cotignac","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16879297/medium/81ff353edf030699a159bb79cda26e84.jpg"},{"username":"LeaHun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16916375/medium/618520f0543b1e212ebc83d66b683d4b_default.png"},{"username":"jackwellerreal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16919717/medium/37bbfdc03893f141133ccc619593266d.png"},{"username":"renan.costaalencar","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16938427/medium/6d25b25320c5bde734118dec4af9cd5b.png"},{"username":"andrius.cim","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16967845/medium/46028fc1a0ddfaaeebcb37b39739625d_default.png"},{"username":"xjxexrxrxyx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17041412/medium/d34d664166db6fb25579bca9ff3b50b4.jpeg"},{"username":"bilhal.fellah","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17044572/medium/d1ec6f17e4aa013e9eededc83778d2c6.png"},{"username":"the_octo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17058060/medium/7423ab3a4cabb67f5decb2e2dfa2b675_default.png"},{"username":"BamesJond","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17075846/medium/128d7ffd40254c8e376f45678e2fc620_default.png"},{"username":"Kornikus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17134240/medium/d7a42b2c30db46ff152699ca01f9e11a_default.png"},{"username":"xoxyuxu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17176526/medium/108055aa24343043de852e412bec74be.png"},{"username":"cryptodntsleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17249408/medium/b9e15aa2b24a0dbc1e4f194aa8ed5109.png"},{"username":"yaniorg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17261842/medium/7dbfc77f21c826845961243c44540d26.jpg"},{"username":"Vidariondr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17262438/medium/45fbc71be02ef27296b6f423e97b6a03_default.png"},{"username":"Fastery","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17263066/medium/701b0edc59676174863ad651528bbb37.jpeg"},{"username":"stratself","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17290388/medium/57d9d8161b3fd0abf4dc5558b4a1fa05_default.png"},{"username":"wuzeqin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17292606/medium/b9e0308410b31337e2d7aa6ed346d718_default.png"},{"username":"ChristopherBull","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17304338/medium/21a175ce596bfae81430767467908b90.jpeg"},{"username":"darkbcnlord","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17311436/medium/b271bc0b419ec3a26ddb7b63ff8ffc4b.png"},{"username":"zhoran1997","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17344028/medium/c8f450b5a8f1e9494a45dc60af7ce63e.png"},{"username":"Pakura","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17396930/medium/18602738018348bf2ffa83a78bf165e2_default.png"},{"username":"gilalves1703","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17447768/medium/3f1de562a5c2391c88bbf8d5cf40a266.png"},{"username":"iServeCloud","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17470960/medium/11649b99d609438d66455a4d2fdc70f7.png"},{"username":"guibolzantvbox","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17474136/medium/59a576e6da538664ceb972253916fa02.jpeg"},{"username":"ivann136","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17506062/medium/67d09ada36fac5f444a1f7517e3a97bd_default.png"},{"username":"Maheshivara","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17516082/medium/576e1e6581cf969615b265de6aeff8b7.jpeg"},{"username":"brunogui81","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17517542/medium/7c0d174e810435af518fe8e0c05e151f.jpeg"},{"username":"michelwilhelm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17542190/medium/4c6b33fd8993ff565fa75add2ce8355e.jpeg"},{"username":"dias-gxstavo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17547376/medium/8c6b109aae8aeb651192346a39ae9e5b.jpeg"},{"username":"ToxykAuBleu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17614030/medium/8d21e0608560172b121bd46126d827c7.jpeg"},{"username":"e.erdemir","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17617994/medium/7259a29db9941c6171a8cbbd643b92bd.png"},{"username":"sjonkreutz","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17618306/medium/7601c45d015189a863656e21348d223b.png"},{"username":"volovich0377","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17620388/medium/c929a8fbd24f96d17620fa497ade7309.png"},{"username":"Hamstio1","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17624898/medium/406e0bf40094c24a01c3064f8f5b2a82.png"},{"username":"i3laa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17627462/medium/613a9d283ed8b69819ee181903851660.jpeg"},{"username":"bastiengrignon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17629884/medium/ddfc3339285bd65195fcd834e512e9b7.jpeg"},{"username":"kauhee","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17644600/medium/def1b9066cf9275b4273a5a5dca7f325.png"},{"username":"hongminzheng","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17683860/medium/a764f4963912777d27a7cd98e136a2b2.png"},{"username":"d-lindahl","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17726729/medium/cbde348582fbabcfadd89a56cd5a0b40.png"},{"username":"Enoal-Fauchille-Bolle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17734649/medium/6e6fb29198782dc7856ea21811db669c.jpeg"},{"username":"manos-grigorakis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17737973/medium/b37f685587c920c81fdaf9a357962434.jpeg"},{"username":"SertraFurr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17738703/medium/5f1e9f270e315ee3185cf2ccb3f5a833.png"},{"username":"Kle.Ma","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17739207/medium/b767d6093e6608f251ca152f53d359b8_default.png"},{"username":"WalkerLukas","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17744485/medium/87508ce83a9b6e5391f55883a66a054b.png"},{"username":"mtya","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17748447/medium/f574d5a770d74814f8c49ee68e00f5b7.jpeg"},{"username":"Elaktrato","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17750977/medium/f4109d379f29dc8ea2e7799ac9e208c8.png"},{"username":"marcelmeis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17753627/medium/4ba32116c27e427d3073e45713f6564e.png"}]
\ No newline at end of file
+[{"username":"carlchina","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/6697/medium/d22bbe7797bbeb30dbdc73a5648d329a_default.png"},{"username":"zielmann","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/7795/medium/ad22b8b8d5eb33e4154d53a454c862fd_default.png"},{"username":"Thalyn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12494028/medium/5faf5acc945a874ed116c4567edaa09f_default.png"},{"username":"magnushj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12503488/medium/68f32e33ea7b891960055c9f831f7344_default.png"},{"username":"almontegil","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12580457/medium/f4136cacbdfdb4c28ae7f85dc5f840db_default.png"},{"username":"andibing","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12664938/medium/b8be63e4dcb2e791ced1ffc9e3a049a5.jpg"},{"username":"gronare","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12680911/medium/c6eb14d1504156f34a158e507f73aa3d_default.png"},{"username":"SmartPhoneLover","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12701640/medium/ec95486662ec875cda080e778c3ff702.jpg"},{"username":"marcelotk_15","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12773227/medium/ecf6bea16ab0c873c131df2154ee9718_default.png"},{"username":"S3OD177","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13051544/medium/11dd990837f61f7e8f7d36b041a7fddb_default.png"},{"username":"marcg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13153301/medium/ebcf60fbf27deb148ec40a659566c6ff_default.png"},{"username":"BeersTeddy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13185230/medium/2f1f4e1effe74a23422b195cbefb2a95_default.png"},{"username":"jamesmcmahon0","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13245578/medium/586aa873b4abddbd9abc6f3de99ab70e.jpeg"},{"username":"b1nar10","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13286752/medium/aaa1f083e04c572f953fde4563171e4a.jpeg"},{"username":"vannCN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13330448/medium/9b8a9ee3611e51e951e22d5fd4eb7d8d.jpg"},{"username":"binswm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13343482/medium/45dde0e6097b9b72705d2eba9dbbc276_default.png"},{"username":"SkewRam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13496556/medium/188f5c2deb7938eda51eb786cc4539ca.jpeg"},{"username":"raphcatarino","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13547726/medium/b003511e67df13a4b4b5689488fa8099.jpg"},{"username":"shillos5","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13600557/medium/b17650b2b84f829d54fa548c44c1ab27.jpeg"},{"username":"wolong98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13641407/medium/f4634edc58c7857a357e5293543c15cf.jpg"},{"username":"spair0039","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14012333/medium/72430e96027c09c19141cac38eae4617.png"},{"username":"MajorMarcin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14072183/medium/0a197e64bb92b6ea94cecbb607512c45.jpg"},{"username":"KosmoMoustache","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14103535/medium/51c438cdd9f86e8811c75f4869de479c.png"},{"username":"Soochaehwa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14118689/medium/496a1ce63111547bf455a1e0a7ac75f1_default.png"},{"username":"garryfield","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14187035/medium/520c964706676045f6eeec1b08edc880.jpg"},{"username":"droidenko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14444264/medium/4aa3a8e824f72bc9e5ec0d8de307095e.jpeg"},{"username":"maathis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14489950/medium/6eeddf13a757ee35f78f1763f94d95e1.jpeg"},{"username":"bukvam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14546502/medium/a819dc40a13c57395a17dc46e847d3ce.jpg"},{"username":"gm.cinalli","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14670666/medium/2d466a0fdbda40764526be86c97c0ab4.jpeg"},{"username":"antoine2tt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14722148/medium/f88d926900862dd59007ea4b3419cb9d.png"},{"username":"cretzen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14799754/medium/3ba5c1805ec20c000d5810c07985afe9.jpg"},{"username":"ktKongTong","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14817246/medium/97cfc3c028dbdaf85ebd1102da71e58c.jpeg"},{"username":"marinkaberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14861042/medium/2d5c4e62613f03082f3e645fa92efd59.jpeg"},{"username":"djismgaming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14934947/medium/f5a8570713c34ab0f7d5405d105e2a9a.jpeg"},{"username":"f1refa11","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14949159/medium/fd2ae63b8eb4462200ba96abf943c1b9.png"},{"username":"jeffersonraimon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057621/medium/6dff469563860e2618bc9d45646d0ab4.png"},{"username":"giop98","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15057987/medium/b8a4825d3fc39fc662f35ee258db4b2d.jpeg"},{"username":"tomislav.kraljevic","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15118857/medium/e133f1061cc92850b854d05d8faaeafd.png"},{"username":"Walkx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15202182/medium/5c37361ae45aeed487b34582c1f7ca37.png"},{"username":"andrea.rosso","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15246318/medium/11f3f5ef44ec7f55b6f143090e208704_default.png"},{"username":"ajnart","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15270132/medium/a0f107a463c8910ee96bc2fa843a17e3.jpeg"},{"username":"Witchling","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15282238/medium/9e196b6783415b10ca571b7673c2ef33.jpg"},{"username":"guineuu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15292058/medium/22fbb758bda3b7805d50bf21d38f2c20.jpeg"},{"username":"Bulgus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15304568/medium/0e8787e5ceb02ed5c96a514d0068ae87.jpg"},{"username":"Narno","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15314996/medium/a6ba093f24884250a1bf6c6f2ca50f1b_default.png"},{"username":"BySempron","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15315986/medium/e3d22d7b1423c6823a9f36d595ed4bdb.png"},{"username":"jonathan.berglin.work","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15359236/medium/95930b2093db13b76179782f7322c5d5.png"},{"username":"icdmkg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405504/medium/f334d16cde1ac4ffb935b440d03465af.gif"},{"username":"irithys","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15405614/medium/3086461c47cce0a0c031925e5f943412.png"},{"username":"JannesV","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419912/medium/a7809eb4b817d7c49b62cf10ae86b950.png"},{"username":"benniblot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419914/medium/8086b1573d31f152ba41949b3a172445.jpeg"},{"username":"pacjo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419916/medium/3cbeddbd7bc01faafb5a3bf47bba915b_default.png"},{"username":"Payou6994","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419934/medium/37c9b9b17dfb578404c1c1ddb73ba7a8.png"},{"username":"fzibi21","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15419976/medium/0da688450358e0290a7b7359cc1f7328.png"},{"username":"WowMurdock","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420118/medium/de19576fccb495b6dfe4c4c04a56b834.png"},{"username":"hbooo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420120/medium/6c7c7f8db785061356ebb03d044d3329.jpeg"},{"username":"Manicraft1001","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420178/medium/50ec94563a06a9f74f33bd09f01eed4d.jpg"},{"username":"Void123","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15420354/medium/86929d44df92a00f9fe900a985c196df_default.png"},{"username":"R4cc","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15422606/medium/a390979662b84694f59de30bdb732141.jpeg"},{"username":"fabricionaweb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15425808/medium/a9354142e7ae5152c144409d55fda551_default.png"},{"username":"JokeOfDead","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15426890/medium/a383eac9365e9de64fd4ab1d6fd0cb95.jpeg"},{"username":"hkz","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15427174/medium/c88acefb0d7306e1f7470e872029fb39_default.png"},{"username":"Steken","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428516/medium/d5aea5653c769c3a523182bdb60d1664.png"},{"username":"flar.anton","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15428592/medium/b01fdb365d892e9f811f77fcb50a80a2.jpeg"},{"username":"Bon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15433542/medium/5397da4dfc821f20b6ac14fe0c514e9a.jpeg"},{"username":"bfkadan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434162/medium/4f9a0b43cfe3acaea60124c14ba7f44a.png"},{"username":"BunnySweety","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15434662/medium/f0ef200a6a0dcf0e1d0e9ecd4148f560_default.png"},{"username":"HRKings","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15436168/medium/69a31e461d38549f01864e7ef10d642e.png"},{"username":"wiston81","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15439078/medium/10f292c8d12a7c21a44b54495fa4a3d8.jpeg"},{"username":"qaz0911","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15440860/medium/3d3e50ee388c72dc4bf7a771761f2d89_default.png"},{"username":"qqyule","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15441462/medium/7a3cdf82710ffb5d8f388bc0bd010665.png"},{"username":"Bims0n","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15445560/medium/603220b603eeb3367e0f0d3fa675247c.jpg"},{"username":"TariqDaCoder","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15446228/medium/50b0f4040112bbd67690b769477398e5_default.png"},{"username":"Anarchon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15449644/medium/e925e1f3e3ffbf0f982391ce263a1a28.jpeg"},{"username":"Ashun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15453020/medium/ccdcf51c73d6aae40751bb30beee1915_default.png"},{"username":"sebekmartin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15454038/medium/bcfb44598cdfd1d7cd4eb35812538962.jpeg"},{"username":"RagnarGraves","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15459882/medium/143d5af850c1154070a218bea124e9cb_default.png"},{"username":"PrtmPhlp","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15462414/medium/b80db55e9de301432dcd1f8c8b24fd49_default.png"},{"username":"DooYoo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15470768/medium/2a18cf4be67094724b508c9e1e698a21_default.png"},{"username":"espentruls","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486092/medium/8e38afc3a4ff669226a0cfd3e420ff3a.jpeg"},{"username":"frisco82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15486922/medium/4e1c5d4189b42508e660daa3c1c25b2a.jpeg"},{"username":"lupineDK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15491798/medium/461bd501e8371c062bf29ea171aedd36_default.png"},{"username":"hillaliy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15492732/medium/0bae17b421604892d888e3fc70cf0587.jpeg"},{"username":"MarcOrfilaCarreras","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15501072/medium/ea52b26c3c6f21e4931e38e3ce3f3d6e.png"},{"username":"robertbridda","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15507822/medium/a368c2e30411bb2da9b49290084191f3.png"},{"username":"MKoniuszko","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518090/medium/87605434fcc839f6763ab07c50f6d232.jpeg"},{"username":"HeroSizy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15518710/medium/4e79c0e98cbeb536dd961e656331b509.png"},{"username":"dwt136","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15520022/medium/554422503f2baea43ace85facb4546fb_default.png"},{"username":"asifthewebguy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15526719/medium/b18931dd0c800d725048bd440646198b_default.png"},{"username":"eiloogs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15545537/medium/f290a2f1190983530a9b76b2e858a609.gif"},{"username":"_vytdv","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15547289/medium/60d8644cc8ad6f11a92ccea4a14cd098_default.png"},{"username":"crendasien","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15554645/medium/598ab1d4aaf6b8dccd5ba16be92da7b9.jpeg"},{"username":"edxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15573823/medium/8a565b732a75a77f840dd123cdb30bf4.png"},{"username":"somerlev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15617065/medium/f4b13513e311ec902d90b2f718412c55.jpg"},{"username":"kid1412621","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15643771/medium/dd455e32de652fa88e6fd97598bdffa7.png"},{"username":"suming","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15644717/medium/244159dfe10fa03436205506f80c9e25.png"},{"username":"nick.gher","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15647517/medium/5374a2c6fef60a3fbf0edf86b997c351_default.png"},{"username":"DataCat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15650315/medium/ce7c1365adf35c5d490d77500a4607fb_default.png"},{"username":"dizo89","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15658375/medium/0ca745e5017d491fe1b22b0239904de8.jpeg"},{"username":"tee_noodle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674577/medium/804d9cf06f5196026acb6436b809d0da_default.png"},{"username":"Marty88","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15674593/medium/492b1509d52bd2809dea768121217125.jpeg"},{"username":"Spillebulle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15677023/medium/20b98bb85062e07afe0e63618f8a5feb.PNG"},{"username":"petitmewen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15685239/medium/15de9b62d2e0bc25013435f1784bbcc1.jpeg"},{"username":"NoProsNoNoobs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15687709/medium/ae8f65fafeb8bcf74dcb8871bbe46461.png"},{"username":"y.gybson","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15690777/medium/b5cb5d2d5768904ff6586c727e3a6c77.jpeg"},{"username":"inside90","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15704947/medium/c1355fcb30dd76f8e39d98d1d49f1c52.png"},{"username":"RJSkudra","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15709853/medium/c3abf2774913dc4e81fb261d36d7668c.png"},{"username":"binge203","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15713937/medium/77c1cfa4314673db80e6881fd9f64668.gif"},{"username":"Mailootje","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15714337/medium/743c3bc4ab1989966a375eeeec83d8b8.jpeg"},{"username":"tim-wiegers","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15719805/medium/08a96f064813350661cd6b20bf3d7d99.png"},{"username":"GkhnG","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15722911/medium/71a027caec489ef6ce82bcf1888329d0_default.png"},{"username":"Zoen-Millo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15760967/medium/9e956f11adc5b34f5636268b5c485dbf.jpg"},{"username":"HooinKyoma","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15792897/medium/9489f0a9b368e0e827ae758b740a2eed.jpeg"},{"username":"wathergeen","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15794495/medium/a83b7fc80d180669ebfd6deb3544cfd0.png"},{"username":"MoeToo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15818233/medium/d35cd6953717706eaf20f6c143c62947.png"},{"username":"itodouble","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15836233/medium/e984caea18fb0673bf319fcf28cef649.png"},{"username":"loslocitos","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15847901/medium/fe30d12fd2cf38212f929e13b169f9ec.jpeg"},{"username":"Oversleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15849065/medium/06141f13a6d541d753f3c2f2947b8068_default.png"},{"username":"Beardy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15865139/medium/fca6b9d2b3f52e286d1568f52b83b6a0_default.png"},{"username":"raelyan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15875457/medium/2f4fda1d1aaa5dcc79b328baf3f03151.jpeg"},{"username":"dolphin738","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15913763/medium/ee6fede7b8528ca642329ada80d1cc18.png"},{"username":"brunotco","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15916719/medium/09db45880fc05abc18adb8d932a5ecf9_default.png"},{"username":"LukeIsHereToDevelop","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15924739/medium/3e6cd3940297bb4ba7bb039c93e6f918.png"},{"username":"kennit","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15925879/medium/6b0733ad3c5949b91c55e4d8b03db8a5_default.png"},{"username":"kuunpire","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15950309/medium/8192a4f08f07086828ac9f74ed29a169.jpeg"},{"username":"Sandor-dev","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15951759/medium/0216c2da4eb028164ebbecf1c72f6271_default.png"},{"username":"Meierschlumpf","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15953187/medium/23c744faef1ab84fbdc9351a7850aab6.jpeg"},{"username":"harmlesscat","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15970733/medium/dd8d0214a0250c932bb518b1b55e45a2_default.png"},{"username":"OrzWTF","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15976121/medium/4c4557cbff7ff7b0503455bc59c020e0.jpeg"},{"username":"tagaishi","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15977271/medium/eade504c83a5a1ff831c80a538fbdb44_default.png"},{"username":"azurite928","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15981895/medium/547ccc459ee123e78b5401c499f1022d.png"},{"username":"Ronner231","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16021342/medium/7734d550df2de5a2fec2ffff33e7024c.jpeg"},{"username":"ugyes","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16034148/medium/ed001e3f470a2dea9a8ce955b18e7bd5.png"},{"username":"rpieja","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16045554/medium/bd55baca2ef8b92502a760cc9ee7c505_default.png"},{"username":"flambyisyou","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16051620/medium/3a3bc0c90f6b95ab4ef74396a0a17beb.png"},{"username":"Topbcy","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16077170/medium/d3aed33ea56330338756cfcd89477cfe.jpeg"},{"username":"ai5d02sb","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16084674/medium/7c8119fe2a5ca71bb15f636916a42b95_default.png"},{"username":"explosiveparrot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16097722/medium/7762f80fc1da63f5b2eb87de9d640324_default.png"},{"username":"DestroyCom","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16111544/medium/83e3d3ac5c8bfed634cd1a074854db67.png"},{"username":"neotraxo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16116966/medium/b30df957873329019a8a90a69f6efe3a.png"},{"username":"gzxpa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16123314/medium/487b05577310a946b0b98bc72c11213b_default.png"},{"username":"TORIK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16124732/medium/de770890e6411860ac06e8cc2dc0abd0_default.png"},{"username":"sergio.pibot","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16138114/medium/80db802b2fcd6bbf19ea4c7413367dca.jpeg"},{"username":"karam-ajaj","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16139838/medium/7539ab958d3c85d5f67e7849483ea8f7.jpeg"},{"username":"SimonHenz97","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16151520/medium/9b94a85b536688e2d94538c384fd0a76.png"},{"username":"olsson82","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16198988/medium/eeaab4cba210c537424d4204754b604e.png"},{"username":"bo3bdo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16222806/medium/8652f3beacb6098dde5a4cd0a29fe80d.jpeg"},{"username":"AngryPlayer04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245192/medium/342715274f4dc31fd4132cdae9126188.jpeg"},{"username":"ehatamm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16245884/medium/979d5e5526fe545be9bf46db481f7182.jpeg"},{"username":"Balionelis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16246058/medium/986d62ba363594c517d966fe9178dcbb.png"},{"username":"kwargs","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16248816/medium/19ce11f04bf1ae2637e0483399c675f9_default.png"},{"username":"lucicu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16273892/medium/3b09e8f3bead221ef98d6a45a16851dc_default.png"},{"username":"Michael5564445","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16357688/medium/9d9026a8bf3eaccb0417da240932ebf7.png"},{"username":"Netscream","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16359198/medium/694e0c8572dc8a47e6d63da31ec5b992.png"},{"username":"belicstefan04","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16368142/medium/4742dcfd7bf7ad7e33e37ac44fba7e0f.png"},{"username":"Hamad2066","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16371478/medium/f37bbc7685b6757defdbd1494236af61.jpeg"},{"username":"3ct0pl4sm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16399616/medium/ce796d1c40163975624c0a483e56374f.png"},{"username":"sanchez.marcell626","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16433865/medium/3ddde1314764cc2205ffdcd1ae719a91.png"},{"username":"Minoctis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16490079/medium/91fd0849303a4eca033d660d0a7b6854.jpeg"},{"username":"homarr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16657003/medium/3afabfd3de949a54ff5d40ad04552aba.png"},{"username":"mwgg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16658379/medium/763dd09f5c238cc5324d4d5357985292.png"},{"username":"kiwinho","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16671855/medium/bfe3a2d497dc4748a04d02e4ae2139cb.jpeg"},{"username":"tjvg4m34r13","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16679135/medium/7a483b9b0abe0d925e54808e1cb38fa0.jpeg"},{"username":"Diegoem","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16705921/medium/29ce68ebc191fc3f65499511b12de98f_default.png"},{"username":"bouks69","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16746091/medium/b58b1fc08520de416198be9df5d093eb.jpeg"},{"username":"futhgar","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16748067/medium/b8c526b097f9aa9d687250cac3df16dc.jpeg"},{"username":"GrimSQL","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16764343/medium/fff729a76d1f8fbcdaee9bb0c0f5f3fd.png"},{"username":"chnikel","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767047/medium/3dda2a37e03049dafefe6697bb00f75c.png"},{"username":"yuki19870616","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16767951/medium/f59c848e38382cda6c8464b0a7b9e642_default.png"},{"username":"noamdal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16782245/medium/b4a72a61f9caf138f0322c0cb987e6d0.png"},{"username":"MalithRukshan","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16809057/medium/084d6a5b35fbbeacc70e7021197d222f.png"},{"username":"minirope19","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16930929/medium/e8cbbc26747e31c5a8959215e1dd067b.jpeg"},{"username":"prubin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17503492/medium/96ac87644691e49c1f983f2f094e541c_default.png"},{"username":"sam-tam-sam","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17578052/medium/1628dc2954ce3ae95e0caaa2bd60ab0c.png"},{"username":"KydoxOwn","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17764383/medium/0bc150a09d77f5fda43988e6342777dc.png"},{"username":"toineenzo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12371852/medium/9f1b2bedb4794c59479cd289bdba9fab.png"},{"username":"ArcaneWater","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12421105/medium/1ac82517a6c658f6b62c48aebcb17e86_default.png"},{"username":"Ebedami","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/12974801/medium/b596dea5c09454c4c828eac558389755.jpg"},{"username":"perrygamerptyt","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13117177/medium/1406c949507ecd438480473c8a1f6124.jpg"},{"username":"SergiSvK","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13320543/medium/3df3c79cc5f0c53716c559cd3fd2fc20.png"},{"username":"jeongeon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13362856/medium/eb572f72cde5e5526c517ca3a6489850_default.png"},{"username":"MrTranCFCVN","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13439475/medium/a1789d69c85d90c667194b5d6a518c08_default.png"},{"username":"Tobost06","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13467449/medium/9227dc07f6366bc060d28304f5cb8ce7.jpeg"},{"username":"Kf637","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13530622/medium/118b00bb72d01d3229c97409637e25d1_default.png"},{"username":"Cruellest","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/13610615/medium/2a74faa0947ee08d18f7946fb8602c1e.jpg"},{"username":"olavinto","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14424644/medium/37c01732bd2b7a272b421807a05ccfeb.png"},{"username":"Thario","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14491372/medium/dd5e142070802a672900121aa3f332a6_default.png"},{"username":"Wjouter","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14585620/medium/b91a51513332bdc0a0a9ecbed99ecbc3.png"},{"username":"albanobattistella","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14686194/medium/564afcfd76f5cd24751bd669cf4943aa.jpg"},{"username":"Kachelkaiser","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14763406/medium/203a781797da4a18fe0f4e7db8fd547c.png"},{"username":"regi4","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14842936/medium/f7e1d672f562a7db61ba6b4b8f4de41d.png"},{"username":"baton5129","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/14891064/medium/8d438f557b82977dd02f0ead3187265f.PNG"},{"username":"meviper","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/15958903/medium/209f521ce33a05831fa8d7d1b7babc20_default.png"},{"username":"GerOriki","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16268376/medium/da00e54b08cd40348231d92873a0cd0a_default.png"},{"username":"TheRedCyclops","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16486399/medium/7f39226180120d5a9efcb8e13d8aec8e_default.png"},{"username":"dymek37","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16659221/medium/d1dc6d11d7e7fc370efc5070e837ae03.png"},{"username":"B3CKDOOR","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16744975/medium/e6c7b2d45e76465d54e6efb67c83da7d.png"},{"username":"on9686","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16768173/medium/6282fed9aafe0262a173a5ec0887e5d9_default.png"},{"username":"Larsluph","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16779933/medium/ed7d36f8b44013bad3a16ac0491bd0f7.png"},{"username":"FOEDI","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16781001/medium/6b4552fb72e359aa016f7a054078fac8.png"},{"username":"xmarkurion","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16805443/medium/34a20428427de5aaef99c7d140646803.png"},{"username":"gregid","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16806705/medium/f3c8f4a20436e8538929130142bbc104.png"},{"username":"Flameberg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16820453/medium/d47be866148474c9d05fec6fbe406d88.png"},{"username":"Playfust","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16872865/medium/5f4609cc711e5fb9fa8a0b178635769c.jpeg"},{"username":"Cotignac","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16879297/medium/81ff353edf030699a159bb79cda26e84.jpg"},{"username":"LeaHun","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16916375/medium/618520f0543b1e212ebc83d66b683d4b_default.png"},{"username":"jackwellerreal","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16919717/medium/37bbfdc03893f141133ccc619593266d.png"},{"username":"renan.costaalencar","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16938427/medium/6d25b25320c5bde734118dec4af9cd5b.png"},{"username":"andrius.cim","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/16967845/medium/46028fc1a0ddfaaeebcb37b39739625d_default.png"},{"username":"xjxexrxrxyx","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17041412/medium/d34d664166db6fb25579bca9ff3b50b4.jpeg"},{"username":"bilhal.fellah","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17044572/medium/d1ec6f17e4aa013e9eededc83778d2c6.png"},{"username":"the_octo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17058060/medium/7423ab3a4cabb67f5decb2e2dfa2b675_default.png"},{"username":"BamesJond","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17075846/medium/128d7ffd40254c8e376f45678e2fc620_default.png"},{"username":"Kornikus","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17134240/medium/d7a42b2c30db46ff152699ca01f9e11a_default.png"},{"username":"Master3307","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17161852/medium/94ec577dd53e16b434f2aac9c34f64c6.png"},{"username":"xoxyuxu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17176526/medium/108055aa24343043de852e412bec74be.png"},{"username":"cryptodntsleep","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17249408/medium/b9e15aa2b24a0dbc1e4f194aa8ed5109.png"},{"username":"yaniorg","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17261842/medium/7dbfc77f21c826845961243c44540d26.jpg"},{"username":"Vidariondr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17262438/medium/45fbc71be02ef27296b6f423e97b6a03_default.png"},{"username":"Fastery","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17263066/medium/701b0edc59676174863ad651528bbb37.jpeg"},{"username":"stratself","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17290388/medium/57d9d8161b3fd0abf4dc5558b4a1fa05_default.png"},{"username":"wuzeqin","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17292606/medium/b9e0308410b31337e2d7aa6ed346d718_default.png"},{"username":"ChristopherBull","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17304338/medium/21a175ce596bfae81430767467908b90.jpeg"},{"username":"darkbcnlord","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17311436/medium/b271bc0b419ec3a26ddb7b63ff8ffc4b.png"},{"username":"zhoran1997","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17344028/medium/c8f450b5a8f1e9494a45dc60af7ce63e.png"},{"username":"Pakura","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17396930/medium/18602738018348bf2ffa83a78bf165e2_default.png"},{"username":"gilalves1703","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17447768/medium/3f1de562a5c2391c88bbf8d5cf40a266.png"},{"username":"iServeCloud","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17470960/medium/11649b99d609438d66455a4d2fdc70f7.png"},{"username":"guibolzantvbox","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17474136/medium/59a576e6da538664ceb972253916fa02.jpeg"},{"username":"ivann136","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17506062/medium/67d09ada36fac5f444a1f7517e3a97bd_default.png"},{"username":"Maheshivara","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17516082/medium/576e1e6581cf969615b265de6aeff8b7.jpeg"},{"username":"brunogui81","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17517542/medium/7c0d174e810435af518fe8e0c05e151f.jpeg"},{"username":"michelwilhelm","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17542190/medium/4c6b33fd8993ff565fa75add2ce8355e.jpeg"},{"username":"dias-gxstavo","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17547376/medium/8c6b109aae8aeb651192346a39ae9e5b.jpeg"},{"username":"ToxykAuBleu","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17614030/medium/8d21e0608560172b121bd46126d827c7.jpeg"},{"username":"e.erdemir","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17617994/medium/7259a29db9941c6171a8cbbd643b92bd.png"},{"username":"sjonkreutz","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17618306/medium/7601c45d015189a863656e21348d223b.png"},{"username":"volovich0377","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17620388/medium/c929a8fbd24f96d17620fa497ade7309.png"},{"username":"Hamstio1","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17624898/medium/406e0bf40094c24a01c3064f8f5b2a82.png"},{"username":"i3laa","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17627462/medium/613a9d283ed8b69819ee181903851660.jpeg"},{"username":"bastiengrignon","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17629884/medium/ddfc3339285bd65195fcd834e512e9b7.jpeg"},{"username":"kauhee","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17644600/medium/def1b9066cf9275b4273a5a5dca7f325.png"},{"username":"hongminzheng","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17683860/medium/a764f4963912777d27a7cd98e136a2b2.png"},{"username":"d-lindahl","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17726729/medium/cbde348582fbabcfadd89a56cd5a0b40.png"},{"username":"Enoal-Fauchille-Bolle","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17734649/medium/6e6fb29198782dc7856ea21811db669c.jpeg"},{"username":"manos-grigorakis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17737973/medium/b37f685587c920c81fdaf9a357962434.jpeg"},{"username":"SertraFurr","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17738703/medium/5f1e9f270e315ee3185cf2ccb3f5a833.png"},{"username":"Kle.Ma","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17739207/medium/b767d6093e6608f251ca152f53d359b8_default.png"},{"username":"WalkerLukas","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17744485/medium/87508ce83a9b6e5391f55883a66a054b.png"},{"username":"mtya","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17748447/medium/f574d5a770d74814f8c49ee68e00f5b7.jpeg"},{"username":"Elaktrato","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17750977/medium/f4109d379f29dc8ea2e7799ac9e208c8.png"},{"username":"marcelmeis","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17753627/medium/4ba32116c27e427d3073e45713f6564e.png"},{"username":"ProKafelek12e","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17760377/medium/5199af152f1ea42cee718e7729bd41ff.png"},{"username":"zalaghi","avatarUrl":"https://crowdin-static.cf-downloads.crowdin.com/avatar/17763367/medium/a06f7b15222663517b58b8dc8902f8a1.png"}]
\ No newline at end of file