diff --git a/hosts/koi/containers/sharkey/Dockerfile b/hosts/koi/containers/sharkey/Dockerfile index 7440998..d7b22b7 100644 --- a/hosts/koi/containers/sharkey/Dockerfile +++ b/hosts/koi/containers/sharkey/Dockerfile @@ -1,6 +1,6 @@ # based on https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/Dockerfile ARG NODE_VERSION=20.12.2-alpine3.19 -ARG COMMIT=717696c4728d2e507ddfbd0e4890189758ab1087 +ARG COMMIT=c344705d6708fdc725d6122d2b321cb2d01dad4b FROM node:${NODE_VERSION} as build @@ -38,6 +38,7 @@ RUN git apply /patches/webhook-notification.patch # motivation: https://very.stupid.fish/notes/9shhrn2qncid008s RUN git apply /patches/index-everything.patch RUN git apply /patches/no-remote-users.patch +RUN git apply /patches/no-xpost-extension.patch RUN cp -f /patches/robots.txt packages/backend/assets/robots.txt # end patch diff --git a/hosts/koi/containers/sharkey/patches/no-xpost-extension.patch b/hosts/koi/containers/sharkey/patches/no-xpost-extension.patch new file mode 100644 index 0000000..8741226 --- /dev/null +++ b/hosts/koi/containers/sharkey/patches/no-xpost-extension.patch @@ -0,0 +1,242 @@ +diff --git a/packages/backend/migration/1722103475000-no-xpost.js b/packages/backend/migration/1722103475000-no-xpost.js +new file mode 100644 +index 0000000..8818c37 +--- /dev/null ++++ b/packages/backend/migration/1722103475000-no-xpost.js +@@ -0,0 +1,16 @@ ++/* ++ * SPDX-FileCopyrightText: syuilo and misskey-project ++ * SPDX-License-Identifier: AGPL-3.0-only ++ */ ++ ++export class NoXpost1722103475000 { ++ name = 'NoXpost1722103475000' ++ ++ async up(queryRunner) { ++ await queryRunner.query(`ALTER TABLE "note" ADD "noXpost" boolean NOT NULL DEFAULT false`); ++ } ++ ++ async down(queryRunner) { ++ await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "noXpost"`); ++ } ++} +diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts +index 41efa76..87a388e 100644 +--- a/packages/backend/src/core/NoteCreateService.ts ++++ b/packages/backend/src/core/NoteCreateService.ts +@@ -149,6 +149,7 @@ type Option = { + uri?: string | null; + url?: string | null; + app?: MiApp | null; ++ noXpost?: boolean; + }; + + @Injectable() +@@ -625,6 +626,7 @@ export class NoteCreateService implements OnApplicationShutdown { + renoteUserId: data.renote ? data.renote.userId : null, + renoteUserHost: data.renote ? data.renote.userHost : null, + userHost: user.host, ++ noXpost: data.noXpost, + }); + + // should really not happen, but better safe than sorry +diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts +index 0cb58d0..00b4be3 100644 +--- a/packages/backend/src/core/NoteEditService.ts ++++ b/packages/backend/src/core/NoteEditService.ts +@@ -141,6 +141,7 @@ type Option = { + app?: MiApp | null; + updatedAt?: Date | null; + editcount?: boolean | null; ++ noXpost?: boolean; + }; + + @Injectable() +@@ -494,6 +495,7 @@ export class NoteEditService implements OnApplicationShutdown { + renoteUserId: data.renote ? data.renote.userId : null, + renoteUserHost: data.renote ? data.renote.userHost : null, + userHost: user.host, ++ noXpost: data.noXpost, + }); + + if (data.uri != null) note.uri = data.uri; +diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts +index 90784fd..0f9769f 100644 +--- a/packages/backend/src/core/activitypub/ApRendererService.ts ++++ b/packages/backend/src/core/activitypub/ApRendererService.ts +@@ -467,6 +467,7 @@ export class ApRendererService { + attachment: files.map(x => this.renderDocument(x)), + sensitive: note.cw != null || files.some(file => file.isSensitive), + tag, ++ 'desu:no-xpost': note.noXpost, + ...asPoll, + }; + } +@@ -759,6 +760,7 @@ export class ApRendererService { + attachment: files.map(x => this.renderDocument(x)), + sensitive: note.cw != null || files.some(file => file.isSensitive), + tag, ++ 'desu:no-xpost': note.noXpost, + ...asPoll, + }; + } +diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts +index 8edd8a1..4518b27 100644 +--- a/packages/backend/src/core/activitypub/type.ts ++++ b/packages/backend/src/core/activitypub/type.ts +@@ -122,6 +122,7 @@ export interface IPost extends IObject { + quoteUrl?: string; + quoteUri?: string; + updated?: string; ++ 'desu:no-xpost'?: boolean; + } + + export interface IQuestion extends IObject { +diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts +index b11e2ec..8cf6787 100644 +--- a/packages/backend/src/models/Note.ts ++++ b/packages/backend/src/models/Note.ts +@@ -235,6 +235,11 @@ export class MiNote { + comment: '[Denormalized]', + }) + public renoteUserHost: string | null; ++ ++ @Column('boolean', { ++ default: false, ++ }) ++ public noXpost: boolean; + //#endregion + + constructor(data: Partial) { +diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts +index 626f03b..451f2d8 100644 +--- a/packages/backend/src/server/api/endpoints/notes/create.ts ++++ b/packages/backend/src/server/api/endpoints/notes/create.ts +@@ -155,6 +155,7 @@ export const paramDef = { + noExtractMentions: { type: 'boolean', default: false }, + noExtractHashtags: { type: 'boolean', default: false }, + noExtractEmojis: { type: 'boolean', default: false }, ++ noXpost: { type: 'boolean', default: false }, + replyId: { type: 'string', format: 'misskey:id', nullable: true }, + renoteId: { type: 'string', format: 'misskey:id', nullable: true }, + channelId: { type: 'string', format: 'misskey:id', nullable: true }, +@@ -396,6 +397,7 @@ export default class extends Endpoint { // eslint- + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, ++ noXpost: ps.noXpost, + }); + + return { +diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts +index 835cbc1..ee2c787 100644 +--- a/packages/backend/src/server/api/endpoints/notes/edit.ts ++++ b/packages/backend/src/server/api/endpoints/notes/edit.ts +@@ -203,6 +203,7 @@ export const paramDef = { + noExtractMentions: { type: 'boolean', default: false }, + noExtractHashtags: { type: 'boolean', default: false }, + noExtractEmojis: { type: 'boolean', default: false }, ++ noXpost: { type: 'boolean', default: false }, + replyId: { type: 'string', format: 'misskey:id', nullable: true }, + renoteId: { type: 'string', format: 'misskey:id', nullable: true }, + channelId: { type: 'string', format: 'misskey:id', nullable: true }, +@@ -447,6 +448,7 @@ export default class extends Endpoint { // eslint- + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, ++ noXpost: ps.noXpost, + }); + + return { +diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue +index 57ed045..0d71611 100644 +--- a/packages/frontend/src/components/MkPostForm.vue ++++ b/packages/frontend/src/components/MkPostForm.vue +@@ -203,6 +203,7 @@ const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]' + const imeText = ref(''); + const showingOptions = ref(false); + const textAreaReadOnly = ref(false); ++const noXpost = ref(false); + + const draftKey = computed((): string => { + let key = props.channel ? `channel:${props.channel.id}` : ''; +@@ -471,6 +472,7 @@ function setVisibility() { + + os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), { + currentVisibility: visibility.value, ++ currentNoXpost: noXpost.value, + isSilenced: $i.isSilenced, + localOnly: localOnly.value, + src: visibilityButton.value, +@@ -482,6 +484,9 @@ function setVisibility() { + defaultStore.set('visibility', visibility.value); + } + }, ++ changeNoXpost: v => { ++ noXpost.value = v; ++ }, + }, 'closed'); + } + +@@ -810,6 +815,7 @@ async function post(ev?: MouseEvent) { + visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined, + reactionAcceptance: reactionAcceptance.value, + editId: props.editId ? props.editId : undefined, ++ noXpost: noXpost.value, + }; + + if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') { +diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue +index e0aec8b..db25745 100644 +--- a/packages/frontend/src/components/MkVisibilityPicker.vue ++++ b/packages/frontend/src/components/MkVisibilityPicker.vue +@@ -37,6 +37,9 @@ SPDX-License-Identifier: AGPL-3.0-only + {{ i18n.ts._visibility.specifiedDescription }} + + ++ ++ do not cross-post ++ + + + +@@ -45,12 +48,14 @@ SPDX-License-Identifier: AGPL-3.0-only + import { nextTick, shallowRef, ref } from 'vue'; + import * as Misskey from 'misskey-js'; + import MkModal from '@/components/MkModal.vue'; ++import MkSwitch from '@/components/MkSwitch.vue'; + import { i18n } from '@/i18n.js'; + + const modal = shallowRef>(); + + const props = withDefaults(defineProps<{ + currentVisibility: typeof Misskey.noteVisibilities[number]; ++ currentNoXpost: boolean; + isSilenced: boolean; + localOnly: boolean; + src?: HTMLElement; +@@ -60,10 +65,12 @@ const props = withDefaults(defineProps<{ + + const emit = defineEmits<{ + (ev: 'changeVisibility', v: typeof Misskey.noteVisibilities[number]): void; ++ (ev: 'changeNoXpost', v: boolean): void; + (ev: 'closed'): void; + }>(); + + const v = ref(props.currentVisibility); ++const noXpost = ref(props.currentNoXpost) + + function choose(visibility: typeof Misskey.noteVisibilities[number]): void { + v.value = visibility; +@@ -72,6 +79,11 @@ function choose(visibility: typeof Misskey.noteVisibilities[number]): void { + if (modal.value) modal.value.close(); + }); + } ++ ++function noXpostChanged(v: boolean): void { ++ noXpost.value = v; ++ emit('changeNoXpost', v); ++} + + +