mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-05-14 03:15:11 +00:00
Add Avatar Customisation Commands (#1108)
Tests / Build & Lint (push) Failing after 2m30s
Tests / Unit tests (push) Successful in 2m58s
Tests / Integration tests (push) Failing after 14s
Tests / Application Service Integration tests (push) Failing after 16s
GHCR - Development Branches / ghcr-publish (push) Failing after 13m10s
Docker Hub - Develop / docker-latest (push) Failing after 14m5s
Tests / Build & Lint (push) Failing after 2m30s
Tests / Unit tests (push) Successful in 2m58s
Tests / Integration tests (push) Failing after 14s
Tests / Application Service Integration tests (push) Failing after 16s
GHCR - Development Branches / ghcr-publish (push) Failing after 13m10s
Docker Hub - Develop / docker-latest (push) Failing after 14m5s
* Add Avatar Command to bot mode and AS mode * Add changesets * Move MXC URI validation to matrix basic types * Fix MediaID being mixed case when its exempt from usual case rules. * Update changeset * Integrate Gnuxie Review Feedback * Fix linting errors * Fix having forgotten to run prettier. * Integrate Gnuxie review feedback. * Fix version.txt.tmp files
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"draupnir": patch
|
||||
---
|
||||
|
||||
Add Avatar customisation command for Draupnir
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"draupnir": patch
|
||||
---
|
||||
|
||||
Add avatar customisation command for Appservice mode admin bot
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@the-draupnir-project/matrix-basic-types": patch
|
||||
---
|
||||
|
||||
Add MXC URI validation support.
|
||||
@@ -25,6 +25,9 @@ venv/
|
||||
|
||||
/db
|
||||
|
||||
# temporary version file generated from npm build
|
||||
# Only the tmp file for version is ignored because the branch variant doesnt get created by this part of the build failing on windows.
|
||||
version.txt.tmp
|
||||
# version file generated from npm build
|
||||
version.txt
|
||||
# branch file generated from npm build
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { AppserviceProvisionForUserCommand } from "./ProvisionCommand";
|
||||
import { AppserviceVersionCommand } from "./VersionCommand";
|
||||
import { AppserviceDisplaynameCommand } from "./DisplaynameCommand";
|
||||
import { AppserviceAvatarCommand } from "./AvatarCommand";
|
||||
|
||||
AppserviceBotCommands.internCommand(AppserviceBotHelpCommand, ["admin", "help"])
|
||||
.internCommand(AppserviceAllowCommand, ["admin", "allow"])
|
||||
@@ -28,6 +29,7 @@ AppserviceBotCommands.internCommand(AppserviceBotHelpCommand, ["admin", "help"])
|
||||
.internCommand(AppserviceProvisionForUserCommand, ["admin", "provision"])
|
||||
.internCommand(AppserviceVersionCommand, ["admin", "version"])
|
||||
.internCommand(AppserviceDisplaynameCommand, ["admin", "displayname"])
|
||||
.internCommand(AppserviceAvatarCommand, ["admin", "avatar"])
|
||||
.internCommand(AppserviceRestartDraupnirCommand, ["admin", "restart"])
|
||||
.internCommand(AppserviceListUnstartedCommand, [
|
||||
"admin",
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { AppserviceAdaptorContext } from "./AppserviceBotPrerequisite";
|
||||
import {
|
||||
ActionResult,
|
||||
isError,
|
||||
Ok,
|
||||
ActionError,
|
||||
} from "matrix-protection-suite";
|
||||
import {
|
||||
StringPresentationType,
|
||||
describeCommand,
|
||||
} from "@the-draupnir-project/interface-manager";
|
||||
import { isStringMediaURI } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { AppserviceBotInterfaceAdaptor } from "./AppserviceBotInterfaceAdaptor";
|
||||
import { resultifyBotSDKRequestError } from "matrix-protection-suite-for-matrix-bot-sdk";
|
||||
|
||||
export const AppserviceAvatarCommand = describeCommand({
|
||||
summary: "Sets the avatar of the main appservice admin bot.",
|
||||
parameters: [],
|
||||
rest: {
|
||||
name: "avatar url",
|
||||
acceptor: StringPresentationType,
|
||||
},
|
||||
async executor(
|
||||
context: AppserviceAdaptorContext,
|
||||
_info,
|
||||
_keywords,
|
||||
avatarParts
|
||||
): Promise<ActionResult<void>> {
|
||||
const avatarUrl = avatarParts.join(" ").trim();
|
||||
if (!avatarUrl) {
|
||||
return ActionError.Result("Avatar URL cannot be empty");
|
||||
}
|
||||
if (!isStringMediaURI(avatarUrl)) {
|
||||
return ActionError.Result(
|
||||
`Invalid MXC URI format. Expected format: mxc://server/media-id, got: ${avatarUrl}`
|
||||
);
|
||||
}
|
||||
const setAvatarResult = await context.client
|
||||
.setAvatarUrl(avatarUrl)
|
||||
.then((_) => Ok(undefined), resultifyBotSDKRequestError);
|
||||
if (isError(setAvatarResult)) {
|
||||
return setAvatarResult.elaborate(
|
||||
`Failed to set appservice bot avatar to ${avatarUrl}`
|
||||
);
|
||||
}
|
||||
return setAvatarResult;
|
||||
},
|
||||
});
|
||||
|
||||
AppserviceBotInterfaceAdaptor.describeRenderer(AppserviceAvatarCommand, {
|
||||
isAlwaysSupposedToUseDefaultRenderer: true,
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
// SPDX-FileCopyrightText: 2024 Gnuxie <Gnuxie@protonmail.com>
|
||||
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
//
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
DraupnirRulesMatchingMembersCommand,
|
||||
} from "./Rules";
|
||||
import { DraupnirDisplaynameCommand } from "./SetDisplayNameCommand";
|
||||
import { DraupnirAvatarCommand } from "./SetAvatarCommand";
|
||||
import { DraupnirSetPowerLevelCommand } from "./SetPowerLevelCommand";
|
||||
import { SynapseAdminShutdownRoomCommand } from "./server-admin/ShutdownRoomCommand";
|
||||
import { DraupnirStatusCommand } from "./StatusCommand";
|
||||
@@ -127,6 +129,7 @@ const DraupnirCommands = new StandardCommandTable("draupnir")
|
||||
])
|
||||
.internCommand(DraupnirSafeModeCommand, ["safe", "mode"])
|
||||
.internCommand(DraupnirDisplaynameCommand, ["displayname"])
|
||||
.internCommand(DraupnirAvatarCommand, ["avatar"])
|
||||
.internCommand(DraupnirSetPowerLevelCommand, ["powerlevel"])
|
||||
.internCommand(DraupnirStatusCommand, ["status"])
|
||||
.internCommand(DraupnirTakedownCommand, ["takedown"])
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { isError, Ok, Result } from "@gnuxie/typescript-result";
|
||||
import {
|
||||
StringPresentationType,
|
||||
describeCommand,
|
||||
} from "@the-draupnir-project/interface-manager";
|
||||
import { isStringMediaURI } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { Draupnir } from "../Draupnir";
|
||||
import { DraupnirInterfaceAdaptor } from "./DraupnirCommandPrerequisites";
|
||||
import { resultifyBotSDKRequestError } from "matrix-protection-suite-for-matrix-bot-sdk";
|
||||
import { ActionError } from "matrix-protection-suite";
|
||||
|
||||
export const DraupnirAvatarCommand = describeCommand({
|
||||
summary:
|
||||
"Sets the avatar of the draupnir instance to the specified MXC URI in all rooms.",
|
||||
parameters: [],
|
||||
rest: {
|
||||
name: "avatar url",
|
||||
acceptor: StringPresentationType,
|
||||
},
|
||||
async executor(
|
||||
draupnir: Draupnir,
|
||||
_info,
|
||||
_keywords,
|
||||
avatarParts
|
||||
): Promise<Result<void>> {
|
||||
const avatarUrl = avatarParts.join(" ").trim();
|
||||
if (!avatarUrl) {
|
||||
return ActionError.Result("Avatar URL cannot be empty");
|
||||
}
|
||||
if (!isStringMediaURI(avatarUrl)) {
|
||||
return ActionError.Result(
|
||||
`Invalid MXC URI format. Expected format: mxc://server/media-id, got: ${avatarUrl}`
|
||||
);
|
||||
}
|
||||
const setAvatarResult = await draupnir.client
|
||||
.setAvatarUrl(avatarUrl)
|
||||
.then((_) => Ok(undefined), resultifyBotSDKRequestError);
|
||||
if (isError(setAvatarResult)) {
|
||||
return setAvatarResult.elaborate(`Failed to set avatar to ${avatarUrl}`);
|
||||
}
|
||||
return setAvatarResult;
|
||||
},
|
||||
});
|
||||
|
||||
DraupnirInterfaceAdaptor.describeRenderer(DraupnirAvatarCommand, {
|
||||
isAlwaysSupposedToUseDefaultRenderer: true,
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2026 Catalan Lover <catalanlover@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
StringMediaURI,
|
||||
isStringMediaURI,
|
||||
MediaURIMediaID,
|
||||
MediaURIServerName,
|
||||
} from "./StringMediaURI";
|
||||
|
||||
test("isStringMediaURI accepts valid MXC URIs", function () {
|
||||
expect(isStringMediaURI("mxc://matrix.org/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://matrix.org:8888/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://1.2.3.4/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://1.2.3.4:1234/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://[1234:5678::abcd]/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://[1234:5678::abcd]:5678/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://[::1]/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://[::1]:8008/abc123")).toBe(true);
|
||||
expect(isStringMediaURI("mxc://matrix.org/a_b-c123")).toBe(true);
|
||||
});
|
||||
|
||||
test("isStringMediaURI rejects invalid MXC URIs", function () {
|
||||
expect(isStringMediaURI("invalid://matrix.org/abc123")).toBe(false);
|
||||
expect(isStringMediaURI("mxc://matrix.org")).toBe(false);
|
||||
expect(isStringMediaURI("mxc://matrix.org/abc 123")).toBe(false);
|
||||
expect(isStringMediaURI("mxc://example.com~invalid/abc123")).toBe(false);
|
||||
expect(isStringMediaURI("mxc://matrix.org/abc.def")).toBe(false);
|
||||
expect(isStringMediaURI("mxc://matrix.org/abc~def")).toBe(false);
|
||||
});
|
||||
|
||||
test("StringMediaURI accessors", function () {
|
||||
const uri = StringMediaURI("mxc://[1234:5678::abcd]:5678/abc123");
|
||||
|
||||
expect(MediaURIServerName(uri)).toBe("[1234:5678::abcd]:5678");
|
||||
expect(MediaURIMediaID(uri)).toBe("abc123");
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: 2026 Catalan Lover <catalanlover@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
StringServerName,
|
||||
StringServerNameRegexPart,
|
||||
} from "./StringServerName";
|
||||
|
||||
export const StringMediaURIRegex = new RegExp(
|
||||
`^mxc://(?<serverName>${StringServerNameRegexPart.source})/(?<mediaID>[A-Za-z0-9_-]+)$`
|
||||
);
|
||||
|
||||
export type StringMediaURIBrand = {
|
||||
readonly StringMediaURI: unique symbol;
|
||||
};
|
||||
|
||||
export type StringMediaURI = string & StringMediaURIBrand;
|
||||
|
||||
export function isStringMediaURI(string: string): string is StringMediaURI {
|
||||
return StringMediaURIRegex.test(string);
|
||||
}
|
||||
|
||||
export function StringMediaURI(string: unknown): StringMediaURI {
|
||||
if (typeof string === "string" && isStringMediaURI(string)) {
|
||||
return string;
|
||||
}
|
||||
throw new TypeError("Not a valid StringMediaURI");
|
||||
}
|
||||
|
||||
export function MediaURIServerName(uri: StringMediaURI): StringServerName {
|
||||
const match = StringMediaURIRegex.exec(uri)?.groups?.serverName;
|
||||
if (match === undefined) {
|
||||
throw new TypeError(
|
||||
"Somehow a StringMediaURI was created that is invalid."
|
||||
);
|
||||
}
|
||||
return match as StringServerName;
|
||||
}
|
||||
|
||||
export function MediaURIMediaID(uri: StringMediaURI): string {
|
||||
const match = StringMediaURIRegex.exec(uri)?.groups?.mediaID;
|
||||
if (match === undefined) {
|
||||
throw new TypeError(
|
||||
"Somehow a StringMediaURI was created that is invalid."
|
||||
);
|
||||
}
|
||||
return match;
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
// </text>
|
||||
|
||||
export * from "./StringEventID";
|
||||
export * from "./StringMediaURI";
|
||||
export * from "./StringRoomAlias";
|
||||
export * from "./StringRoomID";
|
||||
export * from "./StringServerName";
|
||||
|
||||
Reference in New Issue
Block a user