Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/db/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ export interface ServerConfigRecord {
/** On unless the owner turns it off. */
spam_filter_enabled: boolean;
spam_filter_sensitivity: SpamSensitivity;
/** Whether members may use a video avatar or banner, where the worker can transcode one. */
video_profiles_enabled: boolean;
is_configured: boolean;
created_at: Date;
updated_at: Date;
Expand Down
5 changes: 5 additions & 0 deletions src/db/sqlite/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ function createSchema(d: DatabaseSync): void {
allow_dms INTEGER NOT NULL DEFAULT 1,
spam_filter INTEGER NOT NULL DEFAULT 1,
spam_sensitivity TEXT NOT NULL DEFAULT 'normal',
video_profiles INTEGER NOT NULL DEFAULT 1,
is_configured INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
Expand Down Expand Up @@ -941,6 +942,10 @@ function runMigrations(d: DatabaseSync): void {
if (!hasColumn(d, "server_config", "spam_sensitivity")) {
d.exec("ALTER TABLE server_config ADD COLUMN spam_sensitivity TEXT NOT NULL DEFAULT 'normal'");
}
// On by default: it only takes effect where the worker can transcode video (GRYT-1664).
if (!hasColumn(d, "server_config", "video_profiles")) {
d.exec("ALTER TABLE server_config ADD COLUMN video_profiles INTEGER NOT NULL DEFAULT 1");
}

// Stored and handed back whole, and never read here. One column rather than a
// key beside a signature, so the two cannot be mixed between members.
Expand Down
3 changes: 3 additions & 0 deletions src/db/sqlite/servers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ function rowToConfig(r: Record<string, unknown>): ServerConfigRecord {
allow_dms: (r.allow_dms as number) !== 0,
spam_filter_enabled: (r.spam_filter as number) !== 0,
spam_filter_sensitivity: normalizeSpamSensitivity(r.spam_sensitivity),
video_profiles_enabled: (r.video_profiles as number) !== 0,
is_configured: (r.is_configured as number) === 1,
created_at: fromIso(r.created_at as string),
updated_at: fromIso(r.updated_at as string),
Expand Down Expand Up @@ -241,6 +242,7 @@ export async function updateServerConfig(patch: {
allowDms?: boolean;
spamFilter?: boolean;
spamSensitivity?: SpamSensitivity;
videoProfiles?: boolean;
isConfigured?: boolean;
}): Promise<ServerConfigRecord> {
const db = getSqliteDb();
Expand Down Expand Up @@ -270,6 +272,7 @@ export async function updateServerConfig(patch: {
allowDms: { col: "allow_dms", transform: (v) => v ? 1 : 0 },
spamFilter: { col: "spam_filter", transform: (v) => v ? 1 : 0 },
spamSensitivity: { col: "spam_sensitivity", transform: (v) => normalizeSpamSensitivity(v) },
videoProfiles: { col: "video_profiles", transform: (v) => v ? 1 : 0 },
isConfigured: { col: "is_configured", transform: (v) => v ? 1 : 0 },
};

Expand Down
8 changes: 7 additions & 1 deletion src/routes/uploadQuarantine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import sharp from "sharp";

import { getSqliteDb, initSqlite } from "../db/sqlite/connection";
import { getFile } from "../db/sqlite/messages";
import { createServerConfigIfNotExists, setServerRole } from "../db/sqlite/servers";
import { createServerConfigIfNotExists, setServerRole, updateServerConfig } from "../db/sqlite/servers";
import { getUserByServerId, upsertUser } from "../db/sqlite/users";
import { refreshWorkerCapabilities } from "../services/workerCapabilities";
import { buildMemberList } from "../socket/utils/clients";
Expand Down Expand Up @@ -189,6 +189,12 @@ describe("uploads with a worker that clears quarantine", () => {
assert.equal(file?.mime, "video/mp4");
workerWritesOut(videoId, `banners/${videoId}.mp4`, "video/mp4");
await until(async () => (await getUserByServerId(dave.serverUserId))?.banner_file_id === videoId);
await updateServerConfig({ videoProfiles: false });
const off = await sendVideo();
assert.equal(off.status, 415, "the owner turned video off");
assert.equal(off.body.error, "video_disabled");
await updateServerConfig({ videoProfiles: true });

const row = (await buildMemberList({})).find((m) => m.serverUserId === dave.serverUserId);
assert.equal(row?.bannerVideo, true, "the member list says it plays");
assert.equal(row?.avatarVideo, false);
Expand Down
4 changes: 4 additions & 0 deletions src/routes/uploads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ const storeAvatarImage = (purpose: "avatar" | "group" | "webhook" | "banner") =>
Promise.resolve()
.then(async () => {
const cfg = await getServerConfig().catch(() => null);
if (isVideo && cfg?.video_profiles_enabled === false) {
res.status(415).json({ error: "video_disabled", message: "This server's owner has turned off video avatars and banners. Use a picture instead." });
return;
}
const maxBytes = isVideo
? (typeof cfg?.upload_max_bytes === "number" ? cfg.upload_max_bytes : DEFAULT_UPLOAD_MAX_BYTES)
: (typeof cfg?.avatar_max_bytes === "number" ? cfg.avatar_max_bytes : DEFAULT_AVATAR_MAX_BYTES);
Expand Down
8 changes: 8 additions & 0 deletions src/settings/serverSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { broadcastServerUiUpdate } from "../socket/utils/server";
import { syncMdnsAdvertising } from "../mdns";
import { VALID_CENSOR_STYLES, type CensorStyle } from "../utils/profanityFilter";
import { SPAM_SENSITIVITIES, type SpamSensitivity } from "../moderation/spamFilter";
import { workerClearsQuarantine, workerTranscodesVideo } from "../services/workerCapabilities";

/**
* One place, for the side effects rather than the validation: a caller that only
Expand All @@ -34,6 +35,7 @@ export interface SettingsPatch {
discoverable?: boolean;
spamFilter?: boolean;
spamSensitivity?: string;
videoProfiles?: boolean;
}

/** Who asked for the change, for the audit trail. */
Expand Down Expand Up @@ -103,6 +105,8 @@ export async function applyServerSettings(
? patch.spamSensitivity as SpamSensitivity
: undefined;

const videoProfiles: boolean | undefined = typeof patch.videoProfiles === "boolean" ? patch.videoProfiles : undefined;

const updated = await updateServerConfig({
displayName: displayName === undefined ? undefined : (displayName.length > 0 ? displayName : null),
description: description === undefined ? undefined : (description.length > 0 ? description : null),
Expand All @@ -119,6 +123,7 @@ export async function applyServerSettings(
discoverable,
spamFilter,
spamSensitivity,
videoProfiles,
});

if (systemChannelId !== undefined) invalidateSystemChannelCache();
Expand Down Expand Up @@ -169,5 +174,8 @@ export function settingsView(cfg: ServerConfigRecord, serverId: string, isOwner:
discoverable: cfg.discoverable !== false,
spamFilter: cfg.spam_filter_enabled !== false,
spamSensitivity: cfg.spam_filter_sensitivity ?? "normal",
videoProfiles: cfg.video_profiles_enabled !== false,
// What this host can do, so settings can say why a switch is off rather than hide it.
media: { checked: workerClearsQuarantine(), video: workerTranscodesVideo() },
};
}
1 change: 1 addition & 0 deletions src/socket/handlers/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ export function registerAdminHandlers(ctx: HandlerContext): EventHandlerMap {
discoverable?: boolean;
spamFilter?: boolean;
spamSensitivity?: string;
videoProfiles?: boolean;
}) => {
try {
const rl = rlCheck("server:settings:update", ctx, RL_SETTINGS);
Expand Down
6 changes: 6 additions & 0 deletions src/socket/utils/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from "../../db";

import { pushVoiceCapabilities } from "./voiceCapabilities";
import { workerClearsQuarantine, workerTranscodesVideo } from "../../services/workerCapabilities";
import { MLS_CIPHERSUITE_ID } from "../../services/mlsWire";
import { mlsRetentionDays } from "../../jobs/mlsRetention";

Expand Down Expand Up @@ -366,6 +367,7 @@ export async function sendServerDetails(socket: Socket, clientsInfo: Clients, in
let cfgIconUrl: string | null = null;
let cfgAvatarMaxBytes: number = DEFAULT_AVATAR_MAX_BYTES;
let cfgUploadMaxBytes: number = DEFAULT_UPLOAD_MAX_BYTES;
let cfgVideoProfiles = true;
let isOwner = false;
let role = FALLBACK_ROLE_ID;
let roleIds: string[] = [];
Expand All @@ -380,6 +382,7 @@ export async function sendServerDetails(socket: Socket, clientsInfo: Clients, in
if (cfg?.icon_url) cfgIconUrl = cfg.icon_url;
if (typeof cfg?.avatar_max_bytes === "number") cfgAvatarMaxBytes = cfg.avatar_max_bytes;
if (typeof cfg?.upload_max_bytes === "number") cfgUploadMaxBytes = cfg.upload_max_bytes;
cfgVideoProfiles = cfg?.video_profiles_enabled !== false;
isOwner = !!(cfg?.owner_gryt_user_id && cfg.owner_gryt_user_id === client.grytUserId);
if (client.serverUserId && !client.serverUserId.startsWith("temp_")) {
const standing = await getEffectiveStanding(client.serverUserId, client.grytUserId);
Expand Down Expand Up @@ -431,6 +434,9 @@ export async function sendServerDetails(socket: Socket, clientsInfo: Clients, in
voice_enabled: !!sfuHost,
avatar_max_bytes: cfgAvatarMaxBytes,
upload_max_bytes: cfgUploadMaxBytes,
/** Uploads go through the worker's sandbox first, and whether a video avatar or banner is taken. */
uploads_checked: workerClearsQuarantine(),
video_profiles: workerTranscodesVideo() && cfgVideoProfiles,
version: process.env.SERVER_VERSION || "1.0.0",
/** What code sits between a member and the people they talk to, so it is
not configurable. No version: that names which known problem applies. */
Expand Down
Loading