diff --git a/hosts/koi/configuration.nix b/hosts/koi/configuration.nix index fd7d8b2..b7db95f 100755 --- a/hosts/koi/configuration.nix +++ b/hosts/koi/configuration.nix @@ -24,10 +24,10 @@ ./services/geesefs.nix ./containers/torrent.nix + ./containers/soulseek ./containers/vaultwarden.nix ./containers/sftpgo ./containers/verdaccio - ./containers/sharkey ./containers/pds ./containers/navidrome ./containers/conduwuit diff --git a/hosts/koi/containers/sharkey/.config/default.yml b/hosts/koi/containers/sharkey/.config/default.yml deleted file mode 100644 index 07db093..0000000 --- a/hosts/koi/containers/sharkey/.config/default.yml +++ /dev/null @@ -1,74 +0,0 @@ -url: https://very.stupid.fish -port: 80 - -db: - host: 172.17.0.1 - port: 5432 - db: misskey - user: misskey - pass: misskey - -dbReplications: false - -redis: - host: sharkey-redis.docker - port: 6379 - -# ┌───────────────────────────┐ -#───┘ MeiliSearch configuration └───────────────────────────── - -meilisearch: - host: sharkey-meili.docker - port: 7700 - apiKey: misskeymeilisearch - index: '' - scope: global - -id: 'aidx' - -# Number of worker processes -clusterLimit: 2 - -maxNoteLength: 30000 - -proxy: 'http://172.17.0.1:7890' -proxyBypassHosts: - - api.deepl.com - - api-free.deepl.com - - www.recaptcha.net - - hcaptcha.com - - challenges.cloudflare.com - - tei.su - -# Media Proxy -# Reference Implementation: https://github.com/misskey-dev/media-proxy -# * Deliver a common cache between instances -# * Perform image compression (on a different server resource than the main process) -#mediaProxy: https://example.com/proxy - -# Proxy remote files (default: true) -# Proxy remote files by this instance or mediaProxy to prevent remote files from running in remote domains. -proxyRemoteFiles: true - -# Movie Thumbnail Generation URL -# There is no reference implementation. -# For example, Misskey will point to the following URL: -# https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4 -#videoThumbnailGenerator: https://example.com - -# Sign to ActivityPub GET request (default: true) -signToActivityPubGet: true -# check that inbound ActivityPub GET requests are signed ("authorized fetch") -checkActivityPubGetSignature: false - -# For security reasons, uploading attachments from the intranet is prohibited, -# but exceptions can be made from the following settings. Default value is "undefined". -# Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)). -#allowedPrivateNetworks: [ -# '127.0.0.1/32' -#] - -customMOTD: ['meow'] - -# Upload or download file size limits (bytes) -maxFileSize: 262144000 diff --git a/hosts/koi/containers/sharkey/Dockerfile b/hosts/koi/containers/sharkey/Dockerfile deleted file mode 100644 index 772ac4c..0000000 --- a/hosts/koi/containers/sharkey/Dockerfile +++ /dev/null @@ -1,108 +0,0 @@ -# based on https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/Dockerfile -ARG NODE_VERSION=20.12.2-alpine3.19 - -FROM node:${NODE_VERSION} as build - - -RUN apk add git linux-headers build-base patch - -ENV PYTHONUNBUFFERED=1 -RUN apk add --update python3 && ln -sf python3 /usr/bin/python -RUN apk add py3-pip py3-setuptools - -RUN corepack enable - -# begin fetch -ARG COMMIT=c344705d6708fdc725d6122d2b321cb2d01dad4b -RUN git clone https://activitypub.software/TransFem-org/Sharkey.git /sharkey --depth=1 && \ - cd /sharkey && \ - git fetch --depth=1 origin ${COMMIT} && \ - git checkout ${COMMIT} && \ - git submodule update --init --recursive -# end fetch - -WORKDIR /sharkey - -RUN echo a 1 && git rev-parse HEAD - -RUN pnpm config set fetch-retries 5 -RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ - pnpm i --frozen-lockfile --aggregate-output - -# begin patch -COPY patches /patches - -RUN git apply /patches/zond.patch -RUN git apply /patches/software.patch -RUN git apply /patches/stats.patch -RUN git apply /patches/limits.patch -RUN git apply /patches/unfollow-notification.patch -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 - -RUN pnpm build -RUN node scripts/trim-deps.mjs -RUN mv packages/frontend/assets sharkey-assets -RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ - pnpm prune -RUN rm -r node_modules packages/frontend packages/sw -RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ - pnpm i --prod --frozen-lockfile --aggregate-output -RUN rm -rf .git - -# begin post-build patch -RUN node /patches/patch-locale.js -# end post-build patch - -FROM node:${NODE_VERSION} - -ARG UID="1104" -ARG GID="1104" - -RUN apk add ffmpeg tini jemalloc \ - && corepack enable \ - && addgroup -g "${GID}" sharkey \ - && adduser -D -u "${UID}" -G sharkey -h /sharkey sharkey \ - && find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /u+s -exec chmod u-s {} \; \ - && find / -type d -path /sys -prune -o -type d -path /proc -prune -o -type f -perm /g+s -exec chmod g-s {} \; - -USER sharkey -WORKDIR /sharkey - -COPY --chown=sharkey:sharkey --from=build /sharkey/node_modules ./node_modules -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/node_modules ./packages/backend/node_modules -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-reversi/node_modules ./packages/misskey-reversi/node_modules -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-bubble-game/node_modules ./packages/misskey-bubble-game/node_modules -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/megalodon/node_modules ./packages/megalodon/node_modules -COPY --chown=sharkey:sharkey --from=build /sharkey/built ./built -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-js/built ./packages/misskey-js/built -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-reversi/built ./packages/misskey-reversi/built -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/built ./packages/backend/built -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/megalodon/lib ./packages/megalodon/lib -COPY --chown=sharkey:sharkey --from=build /sharkey/fluent-emojis ./fluent-emojis -COPY --chown=sharkey:sharkey --from=build /sharkey/tossface-emojis/dist ./tossface-emojis/dist -COPY --chown=sharkey:sharkey --from=build /sharkey/sharkey-assets ./packages/frontend/assets - -COPY --chown=sharkey:sharkey --from=build /sharkey/package.json ./package.json -COPY --chown=sharkey:sharkey --from=build /sharkey/pnpm-workspace.yaml ./pnpm-workspace.yaml -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/package.json ./packages/backend/package.json -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/scripts/check_connect.js ./packages/backend/scripts/check_connect.js -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/ormconfig.js ./packages/backend/ormconfig.js -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/migration ./packages/backend/migration -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/backend/assets ./packages/backend/assets -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/megalodon/package.json ./packages/megalodon/package.json -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-js/package.json ./packages/misskey-js/package.json -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-reversi/package.json ./packages/misskey-reversi/package.json -COPY --chown=sharkey:sharkey --from=build /sharkey/packages/misskey-bubble-game/package.json ./packages/misskey-bubble-game/package.json - -ENV LD_PRELOAD=/usr/lib/libjemalloc.so.2 -ENV NODE_ENV=production -ENTRYPOINT ["/sbin/tini", "--"] -CMD ["pnpm", "run", "migrateandstart"] diff --git a/hosts/koi/containers/sharkey/default.nix b/hosts/koi/containers/sharkey/default.nix deleted file mode 100644 index d815dec..0000000 --- a/hosts/koi/containers/sharkey/default.nix +++ /dev/null @@ -1,78 +0,0 @@ -{ pkgs, ... }: - -let - UID = 1104; - context = pkgs.copyPathToStore ./.; -in { - users.users.misskey = { - isNormalUser = true; - uid = UID; - }; - - systemd.tmpfiles.rules = [ - "d /srv/Sharkey 0777 root root -" - ]; - - services.postgresql.ensureUsers = [ - { name = "misskey"; ensureDBOwnership = true; } - ]; - services.postgresql.ensureDatabases = [ "misskey" ]; - desu.postgresql.ensurePasswords.misskey = "misskey"; - - virtualisation.oci-containers.containers.sharkey-redis = { - image = "docker.io/redis:7.0-alpine"; - volumes = [ - "/srv/Sharkey/redis:/data" - ]; - user = builtins.toString UID; - }; - - virtualisation.oci-containers.containers.sharkey-meili = { - image = "getmeili/meilisearch:v1.3.4"; - volumes = [ - "/srv/Sharkey/meili_data:/meili_data" - ]; - environment = { - MEILI_NO_ANALYTICS = "true"; - MEILI_ENV = "production"; - MEILI_MASTER_KEY = "misskeymeilisearch"; - }; - user = builtins.toString UID; - }; - - # not really reproducible but fuck it i figured it's the best way lol. - # im **not** rewriting that 100 lines dockerfile - systemd.services.docker-sharkey.serviceConfig.ExecStartPre = [ - (pkgs.writeShellScript "build-sharkey" '' - docker build -t local/sharkey ${context} - '') - ]; - systemd.services.docker-sharkey.after = [ "postgresql.service" ]; - virtualisation.oci-containers.containers.sharkey = { - dependsOn = [ "sharkey-redis" "sharkey-meili" ]; - image = "local/sharkey"; - volumes = [ - "/srv/Sharkey/files:/sharkey/files" - "${context}/.config:/sharkey/.config:ro" - ]; - environment = { - NODE_ENV = "production"; - }; - user = builtins.toString UID; - }; - - services.nginx.virtualHosts."very.stupid.fish" = { - forceSSL = true; - useACMEHost = "stupid.fish"; - http2 = true; - - extraConfig = '' - client_max_body_size 250M; - ''; - - locations."/" = { - proxyPass = "http://sharkey.docker$request_uri"; - proxyWebsockets = true; - }; - }; -} diff --git a/hosts/koi/containers/sharkey/patches/index-everything.patch b/hosts/koi/containers/sharkey/patches/index-everything.patch deleted file mode 100644 index e35f5ab..0000000 --- a/hosts/koi/containers/sharkey/patches/index-everything.patch +++ /dev/null @@ -1,47 +0,0 @@ -diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts -index 631d707..cf537fd 100644 ---- a/packages/backend/src/core/NoteCreateService.ts -+++ b/packages/backend/src/core/NoteCreateService.ts -@@ -952,7 +952,7 @@ export class NoteCreateService implements OnApplicationShutdown { - } - - // Register to search database -- if (!user.noindex) this.index(note); -+ this.index(note); - } - - @bindThis -@@ -1051,7 +1051,7 @@ export class NoteCreateService implements OnApplicationShutdown { - } - - // Register to search database -- if (!user.noindex) this.index(note); -+ this.index(note); - } - - @bindThis -diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts -index a01dfec..8fd3138 100644 ---- a/packages/backend/src/core/NoteEditService.ts -+++ b/packages/backend/src/core/NoteEditService.ts -@@ -728,7 +728,7 @@ export class NoteEditService implements OnApplicationShutdown { - } - - // Register to search database -- if (!user.noindex) this.index(note); -+ this.index(note); - } - - @bindThis -diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts -index 6dc3e85..087bd36 100644 ---- a/packages/backend/src/core/SearchService.ts -+++ b/packages/backend/src/core/SearchService.ts -@@ -115,7 +115,6 @@ export class SearchService { - @bindThis - public async indexNote(note: MiNote): Promise { - if (note.text == null && note.cw == null) return; -- if (!['home', 'public'].includes(note.visibility)) return; - - if (this.meilisearch) { - switch (this.meilisearchIndexScope) { diff --git a/hosts/koi/containers/sharkey/patches/limits.patch b/hosts/koi/containers/sharkey/patches/limits.patch deleted file mode 100644 index 03fa356..0000000 --- a/hosts/koi/containers/sharkey/patches/limits.patch +++ /dev/null @@ -1,20 +0,0 @@ -diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts -index 02c2777..cd46330 100644 ---- a/packages/backend/src/const.ts -+++ b/packages/backend/src/const.ts -@@ -15,13 +15,13 @@ export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days - * Maximum note text length that can be stored in DB. - * Surrogate pairs count as one - */ --export const DB_MAX_NOTE_TEXT_LENGTH = 8192; -+export const DB_MAX_NOTE_TEXT_LENGTH = 32768; - - /** - * Maximum image description length that can be stored in DB. - * Surrogate pairs count as one - */ --export const DB_MAX_IMAGE_COMMENT_LENGTH = 8192; -+export const DB_MAX_IMAGE_COMMENT_LENGTH = 32768; - //#endregion - - // ブラウザで直接表示することを許可するファイルの種類のリスト diff --git a/hosts/koi/containers/sharkey/patches/no-remote-users.patch b/hosts/koi/containers/sharkey/patches/no-remote-users.patch deleted file mode 100644 index 64ed15d..0000000 --- a/hosts/koi/containers/sharkey/patches/no-remote-users.patch +++ /dev/null @@ -1,71 +0,0 @@ -diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts -index bd81989..8aaf8ca 100644 ---- a/packages/backend/src/server/api/endpoints/users/show.ts -+++ b/packages/backend/src/server/api/endpoints/users/show.ts -@@ -97,6 +97,8 @@ export default class extends Endpoint { // eslint- - const isModerator = await this.roleService.isModerator(me); - ps.username = ps.username?.trim(); - -+ const authed = me !== null -+ - if (ps.userIds) { - if (ps.userIds.length === 0) { - return []; -@@ -112,7 +114,9 @@ export default class extends Endpoint { // eslint- - const _users: MiUser[] = []; - for (const id of ps.userIds) { -- const user = users.find(x => x.id === id); -- if (user != null) _users.push(user); -+ const user = users.find(x => x.id === id) -+ if (user && (authed || user.host === null)) { -+ _users.push(user); -+ } - } - - const _userMap = await this.userEntityService.packMany(_users, me, { schema: 'UserDetailed' }) -@@ -137,6 +142,14 @@ export default class extends Endpoint { // eslint- - throw new ApiError(meta.errors.noSuchUser); - } - -+ if (!authed && user.host !== null) { -+ throw new ApiError({ -+ code: 'X_REMOTE_REDIRECT', -+ message: 'Redirect to remote user', -+ id: 'X_REMOTE_REDIRECT', -+ }, user.uri); -+ } -+ - if (user.host == null) { - if (me == null && ip != null) { - this.perUserPvChart.commitByVisitor(user, ip); -diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts -index cb41c4f..8199e3c 100644 ---- a/packages/backend/src/server/web/ClientServerService.ts -+++ b/packages/backend/src/server/web/ClientServerService.ts -@@ -526,6 +526,11 @@ export class ClientServerService { - - if (user != null) { - const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); -+ if (user.host !== null) { -+ reply.redirect(301, profile.url); -+ return; -+ } -+ - const meta = await this.metaService.fetch(); - const me = profile.fields - ? profile.fields -diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue -index ebe4176..0cd39f3 100644 ---- a/packages/frontend/src/pages/user/index.vue -+++ b/packages/frontend/src/pages/user/index.vue -@@ -70,6 +70,10 @@ function fetchUser(): void { - misskeyApi('users/show', Misskey.acct.parse(props.acct)).then(u => { - user.value = u; - }).catch(err => { -+ if (err.code === 'X_REMOTE_REDIRECT') { -+ location.replace(err.info); -+ return; -+ } - error.value = err; - }); - } diff --git a/hosts/koi/containers/sharkey/patches/no-xpost-extension.patch b/hosts/koi/containers/sharkey/patches/no-xpost-extension.patch deleted file mode 100644 index 8741226..0000000 --- a/hosts/koi/containers/sharkey/patches/no-xpost-extension.patch +++ /dev/null @@ -1,242 +0,0 @@ -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); -+} - - -