summaryrefslogtreecommitdiff
path: root/packages/backend/src/misc
diff options
context:
space:
mode:
authorJulia <julia@insertdomain.name>2025-03-02 19:54:32 +0000
committerJulia <julia@insertdomain.name>2025-03-02 19:54:32 +0000
commit9e13c375c5ef4103ad5ee87fea583b154e9e16f3 (patch)
treefe9e7b1a474e22fb0c37bd68cfd260f7ba39be74 /packages/backend/src/misc
parentmerge: pin corepack version (!885) (diff)
parentbump version (diff)
downloadsharkey-9e13c375c5ef4103ad5ee87fea583b154e9e16f3.tar.gz
sharkey-9e13c375c5ef4103ad5ee87fea583b154e9e16f3.tar.bz2
sharkey-9e13c375c5ef4103ad5ee87fea583b154e9e16f3.zip
merge: 2025.2.2 (!927)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/927 Approved-by: Marie <github@yuugi.dev> Approved-by: Julia <julia@insertdomain.name>
Diffstat (limited to 'packages/backend/src/misc')
-rw-r--r--packages/backend/src/misc/append-content-warning.ts62
-rw-r--r--packages/backend/src/misc/gen-identicon.ts4
-rw-r--r--packages/backend/src/misc/get-note-summary.ts11
-rw-r--r--packages/backend/src/misc/identifiable-error.ts8
-rw-r--r--packages/backend/src/misc/is-retryable-error.ts22
-rw-r--r--packages/backend/src/misc/json-schema.ts7
6 files changed, 108 insertions, 6 deletions
diff --git a/packages/backend/src/misc/append-content-warning.ts b/packages/backend/src/misc/append-content-warning.ts
new file mode 100644
index 0000000000..152cd6760e
--- /dev/null
+++ b/packages/backend/src/misc/append-content-warning.ts
@@ -0,0 +1,62 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/*
+ * Important Note: this file must be kept in sync with packages/frontend-shared/js/append-content-warning.ts
+ */
+
+/**
+ * Appends an additional content warning onto an existing one.
+ * The additional value will not be added if it already exists within the original input.
+ * @param original Existing content warning
+ * @param additional Content warning to append
+ * @param reverse If true, then the additional CW will be prepended instead of appended.
+ */
+export function appendContentWarning(original: string | null | undefined, additional: string, reverse = false): string {
+ // Easy case - if original is empty, then additional replaces it.
+ if (!original) {
+ return additional;
+ }
+
+ // Easy case - if the additional CW is empty, then don't append it.
+ if (!additional) {
+ return original;
+ }
+
+ // If the additional CW already exists in the input, then we *don't* append another copy!
+ if (includesWholeWord(original, additional)) {
+ return original;
+ }
+
+ return reverse
+ ? `${additional}, ${original}`
+ : `${original}, ${additional}`;
+}
+
+/**
+ * Emulates a regular expression like /\b(pattern)\b/, but with a raw non-regex pattern.
+ * We're checking to see whether the default CW appears inside the existing CW, but *only* if there's word boundaries on either side.
+ * @param input Input string to search
+ * @param target Target word / phrase to search for
+ */
+function includesWholeWord(input: string, target: string): boolean {
+ const parts = input.split(target);
+
+ // The additional string could appear multiple times within the original input.
+ // We need to check each occurrence, since any of them could potentially match.
+ for (let i = 0; i + 1 < parts.length; i++) {
+ const before = parts[i];
+ const after = parts[i + 1];
+
+ // If either the preceding or following tokens are a "word", then this "match" is actually just part of a longer word.
+ // Likewise, if *neither* token is a word, then this is a real match and the CW already exists in the input.
+ if (!/\w$/.test(before) && !/^\w/.test(after)) {
+ return true;
+ }
+ }
+
+ // If we don't match, then there is no existing CW.
+ return false;
+}
diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts
index 342e0f8602..f3c08cc76e 100644
--- a/packages/backend/src/misc/gen-identicon.ts
+++ b/packages/backend/src/misc/gen-identicon.ts
@@ -8,7 +8,7 @@
* https://en.wikipedia.org/wiki/Identicon
*/
-import { createCanvas } from '@napi-rs/canvas';
+import { createCanvas } from 'canvas';
import gen from 'random-seed';
const size = 128; // px
@@ -100,5 +100,5 @@ export async function genIdenticon(seed: string): Promise<Buffer> {
}
}
- return await canvas.encode('png');
+ return await canvas.toBuffer('image/png');
}
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 60dddee9a2..be2d3ea98d 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
+import { appendContentWarning } from './append-content-warning.js';
import type { Packed } from './json-schema.js';
/**
@@ -20,9 +21,15 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
let summary = '';
+ // Append mandatory CW, if applicable
+ let cw = note.cw;
+ if (note.user.mandatoryCW) {
+ cw = appendContentWarning(cw, note.user.mandatoryCW);
+ }
+
// 本文
- if (note.cw != null) {
- summary += `CW: ${note.cw}`;
+ if (cw != null) {
+ summary += `CW: ${cw}`;
} else if (note.text) {
summary += note.text;
}
diff --git a/packages/backend/src/misc/identifiable-error.ts b/packages/backend/src/misc/identifiable-error.ts
index 13c41f1e3b..f5c3fcd6cb 100644
--- a/packages/backend/src/misc/identifiable-error.ts
+++ b/packages/backend/src/misc/identifiable-error.ts
@@ -10,9 +10,15 @@ export class IdentifiableError extends Error {
public message: string;
public id: string;
- constructor(id: string, message?: string) {
+ /**
+ * Indicates that this is a temporary error that may be cleared by retrying
+ */
+ public readonly isRetryable: boolean;
+
+ constructor(id: string, message?: string, isRetryable = false) {
super(message);
this.message = message ?? '';
this.id = id;
+ this.isRetryable = isRetryable;
}
}
diff --git a/packages/backend/src/misc/is-retryable-error.ts b/packages/backend/src/misc/is-retryable-error.ts
new file mode 100644
index 0000000000..9bb8700c7a
--- /dev/null
+++ b/packages/backend/src/misc/is-retryable-error.ts
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { AbortError } from 'node-fetch';
+import { UnrecoverableError } from 'bullmq';
+import { StatusError } from '@/misc/status-error.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+
+/**
+ * Returns false if the provided value represents a "permanent" error that cannot be retried.
+ * Returns true if the error is retryable, unknown (as all errors are retryable by default), or not an error object.
+ */
+export function isRetryableError(e: unknown): boolean {
+ if (e instanceof StatusError) return e.isRetryable;
+ if (e instanceof IdentifiableError) return e.isRetryable;
+ if (e instanceof UnrecoverableError) return false;
+ if (e instanceof AbortError) return true;
+ if (e instanceof Error) return e.name === 'AbortError';
+ return true;
+}
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 040e36228c..f612591eda 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -33,7 +33,11 @@ import { packedClipSchema } from '@/models/json-schema/clip.js';
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
-import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/json-schema/emoji.js';
+import {
+ packedEmojiDetailedAdminSchema,
+ packedEmojiDetailedSchema,
+ packedEmojiSimpleSchema,
+} from '@/models/json-schema/emoji.js';
import { packedFlashSchema } from '@/models/json-schema/flash.js';
import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
import { packedSigninSchema } from '@/models/json-schema/signin.js';
@@ -95,6 +99,7 @@ export const refs = {
GalleryPost: packedGalleryPostSchema,
EmojiSimple: packedEmojiSimpleSchema,
EmojiDetailed: packedEmojiDetailedSchema,
+ EmojiDetailedAdmin: packedEmojiDetailedAdminSchema,
Flash: packedFlashSchema,
Signin: packedSigninSchema,
RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,