chore(koi): sharkey is no more

This commit is contained in:
alina 🌸 2024-12-02 05:53:58 +03:00
parent 9bc0f4c841
commit 3e5ff3efb3
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
16 changed files with 1 additions and 970 deletions

View file

@ -24,10 +24,10 @@
./services/geesefs.nix ./services/geesefs.nix
./containers/torrent.nix ./containers/torrent.nix
./containers/soulseek
./containers/vaultwarden.nix ./containers/vaultwarden.nix
./containers/sftpgo ./containers/sftpgo
./containers/verdaccio ./containers/verdaccio
./containers/sharkey
./containers/pds ./containers/pds
./containers/navidrome ./containers/navidrome
./containers/conduwuit ./containers/conduwuit

View file

@ -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

View file

@ -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"]

View file

@ -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;
};
};
}

View file

@ -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<void> {
if (note.text == null && note.cw == null) return;
- if (!['home', 'public'].includes(note.visibility)) return;
if (this.meilisearch) {
switch (this.meilisearchIndexScope) {

View file

@ -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
// ブラウザで直接表示することを許可するファイルの種類のリスト

View file

@ -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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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;
});
}

View file

@ -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<MiNote>) {
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<typeof meta, typeof paramDef> { // 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<typeof meta, typeof paramDef> { // 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
<span :class="$style.itemDescription">{{ i18n.ts._visibility.specifiedDescription }}</span>
</div>
</button>
+ <MkSwitch :modelValue="noXpost" @update:modelValue="noXpostChanged" :disabled="localOnly">
+ do not cross-post
+ </MkSwitch>
</div>
</MkModal>
</template>
@@ -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<InstanceType<typeof MkModal>>();
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);
+}
</script>
<style lang="scss" module>

View file

@ -1,72 +0,0 @@
const fs = require('fs')
const path = require('path')
const LOCALES_DIR = '/sharkey/built/_frontend_dist_/locales'
const locales = fs.readdirSync(LOCALES_DIR)
const enLocale = locales.find(locale => locale.startsWith('en-US.') && locale.endsWith('.json'))
if (!enLocale) {
throw new Error('en-US locale not found')
}
function parseInterpolations(str) {
const regex = /{([^}]+)}/g
const matches = str.match(regex)
if (!matches) {
return { parts: [str], variables: [] }
}
const parts = []
const variables = []
let lastIndex = 0
for (const match of matches) {
const index = str.indexOf(match)
const part = str.slice(lastIndex, index)
parts.push(part)
const variable = match.slice(1, -1)
variables.push(variable)
lastIndex = index + match.length
}
const lastPart = str.slice(lastIndex)
parts.push(lastPart)
return { parts, variables }
}
function unparseInterpolations({ parts, variables }) {
let str = parts[0]
for (let i = 0; i < variables.length; i++) {
str += `{${variables[i]}}${parts[i + 1]}`
}
return str
}
const enLocaleFull = path.join(LOCALES_DIR, enLocale)
const json = JSON.parse(fs.readFileSync(enLocaleFull, 'utf8'))
// recursively make every string lowercase
function patchObject(obj) {
for (const [key, value] of Object.entries(obj)) {
if (typeof value === 'object') {
patchObject(value)
continue
}
if (typeof value !== 'string') {
continue
}
const { parts, variables } = parseInterpolations(value)
const lowercasedParts = parts.map(part => part.toLowerCase())
const newValue = unparseInterpolations({ parts: lowercasedParts, variables })
obj[key] = newValue
}
}
patchObject(json)
fs.writeFileSync(enLocaleFull, JSON.stringify(json, null))

View file

@ -1,37 +0,0 @@
user-agent: *
disallow: /
# explicit disallows because some bots are assholes that need that
User-Agent: Googlebot
Disallow: /
User-Agent: Storebot-Google
Disallow: /
User-Agent: GoogleOther
Disallow: /
User-Agent: Google-Extended
Disallow: /
User-agent: CCBot
Disallow: /
User-agent: ChatGPT-User
Disallow: /
User-agent: GPTBot
Disallow: /
User-agent: Google-Extended
Disallow: /
User-agent: Omgilibot
Disallow: /
User-Agent: FacebookBot
Disallow: /
User-agent: Amazonbot
Disallow: /

View file

@ -1,12 +0,0 @@
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -76,7 +76,7 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const document: any = {
software: {
- name: 'sharkey',
+ name: 'fishkey',
version: this.config.version,
homepage: nodeinfo_homepage,
repository: meta.repositoryUrl,
},

View file

@ -1,29 +0,0 @@
--- a/packages/backend/src/server/api/endpoints/stats.ts
+++ b/packages/backend/src/server/api/endpoints/stats.ts
@@ -75,9 +75,9 @@
const originalNotesCount = notesChart.local.total[0];
const usersChart = await this.usersChart.getChart('hour', 1, null);
- const usersCount = usersChart.local.total[0] + usersChart.remote.total[0];
- const originalUsersCount = usersChart.local.total[0];
+ const usersCount = 1 + usersChart.remote.total[0];
+ const originalUsersCount = 1;
const [
reactionsCount,
//originalReactionsCount,
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -50,10 +50,9 @@
const now = Date.now();
const notesChart = await this.notesChart.getChart('hour', 1, null);
const localPosts = notesChart.local.total[0];
- const usersChart = await this.usersChart.getChart('hour', 1, null);
- const total = usersChart.local.total[0];
+ const total = 1;
const [
meta,

View file

@ -1,64 +0,0 @@
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index deeecde..2770ecb 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -388,6 +388,7 @@ export class UserFollowingService implements OnModuleInit {
this.cacheService.userFollowingsCache.refresh(follower.id);
this.decrementFollowing(following.follower, following.followee);
+ this.notificationService.createNotification(followee.id, 'unfollow', {}, follower.id);
if (!silent && this.userEntityService.isLocalUser(follower)) {
// Publish unfollow event
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 4ed71a1..0bbd0ca 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -15,7 +15,7 @@ export type MiNotification = {
notifierId: MiUser['id'];
noteId: MiNote['id'];
} | {
- type: 'follow';
+ type: 'follow' | 'unfollow';
id: string;
createdAt: string;
notifierId: MiUser['id'];
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index f849e94..9ba8351 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<img v-else-if="notification.icon" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/>
<div
:class="[$style.subIcon, {
- [$style.t_follow]: notification.type === 'follow',
+ [$style.t_follow]: notification.type === 'follow' || notification.type === 'unfollow',
[$style.t_followRequestAccepted]: notification.type === 'followRequestAccepted',
[$style.t_receiveFollowRequest]: notification.type === 'receiveFollowRequest',
[$style.t_renote]: notification.type === 'renote',
@@ -30,6 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
}]"
> <!-- we re-use t_pollEnded for "edited" instead of making an identical style -->
<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
+ <i v-else-if="notification.type === 'unfollow'" class="ti ti-minus"></i>
<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
<i v-else-if="notification.type === 'renote'" class="ti ti-repeat"></i>
@@ -61,6 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
+ <MkA v-else-if="notification.type === 'unfollow'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
<span v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'">{{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }}</span>
<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
@@ -105,6 +107,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="notification.type === 'follow'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
</template>
+ <template v-else-if="notification.type === 'unfollow'">
+ <span :class="$style.text" style="opacity: 0.6;">unfollowed you</span>
+ </template>
<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
<template v-else-if="notification.type === 'receiveFollowRequest'">
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}</span>

View file

@ -1,105 +0,0 @@
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 68ad92f..69b04c1 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -21,6 +21,8 @@ import type { Config } from '@/config.js';
import { UserListService } from '@/core/UserListService.js';
import type { FilterUnionByProperty } from '@/types.js';
import { trackPromise } from '@/misc/promise-tracker.js';
+import { WebhookService } from './WebhookService.js';
+import { QueueService } from './QueueService.js';
@Injectable()
export class NotificationService implements OnApplicationShutdown {
@@ -42,6 +44,8 @@ export class NotificationService implements OnApplicationShutdown {
private pushNotificationService: PushNotificationService,
private cacheService: CacheService,
private userListService: UserListService,
+ private webhookService: WebhookService,
+ private queueService: QueueService,
) {
}
@@ -175,6 +179,13 @@ export class NotificationService implements OnApplicationShutdown {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return;
+ const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === notifieeId && x.on.includes('notification'));
+ for (const webhook of webhooks) {
+ this.queueService.webhookDeliver(webhook, 'notification', {
+ notification: packed
+ });
+ }
+
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts
index 2a727f8..c4d490e 100644
--- a/packages/backend/src/models/Webhook.ts
+++ b/packages/backend/src/models/Webhook.ts
@@ -7,7 +7,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
import { id } from './util/id.js';
import { MiUser } from './User.js';
-export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction', 'edited'] as const;
+export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction', 'edited', 'notification'] as const;
@Entity('webhook')
export class MiWebhook {
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 99326c8..fbfabc4 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -29,6 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
+ <MkSwitch v-model="event_notification">on notification</MkSwitch>
</div>
</FormSection>
@@ -75,6 +76,7 @@ const event_reply = ref(webhook.on.includes('reply'));
const event_renote = ref(webhook.on.includes('renote'));
const event_reaction = ref(webhook.on.includes('reaction'));
const event_mention = ref(webhook.on.includes('mention'));
+const event_notification = ref(webhook.on.includes('notification'));
async function save(): Promise<void> {
const events = [];
@@ -85,6 +87,7 @@ async function save(): Promise<void> {
if (event_renote.value) events.push('renote');
if (event_reaction.value) events.push('reaction');
if (event_mention.value) events.push('mention');
+ if (event_notification.value) events.push('notification');
os.apiWithDialog('i/webhooks/update', {
name: name.value,
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index 2993863..c2db510 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -29,6 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
+ <MkSwitch v-model="event_notification">on notification</MkSwitch>
</div>
</FormSection>
@@ -59,6 +60,7 @@ const event_reply = ref(true);
const event_renote = ref(true);
const event_reaction = ref(true);
const event_mention = ref(true);
+const event_notification = ref(true);
async function create(): Promise<void> {
const events = [];
@@ -69,6 +71,7 @@ async function create(): Promise<void> {
if (event_renote.value) events.push('renote');
if (event_reaction.value) events.push('reaction');
if (event_mention.value) events.push('mention');
+ if (event_notification.value) events.push('notification');
os.apiWithDialog('i/webhooks/create', {
name: name.value,

View file

@ -1,9 +0,0 @@
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -44,5 +44,6 @@
link(rel='stylesheet' href=`/static-assets/fonts/sharkey-icons/style.css?version=${version}`)
link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
+ script(src='https://zond.tei.su/script.js' data-website-id="9629ae3b-b086-4be1-acd2-82e2a4a58c2a")
if !config.clientManifestExists
script(type="module" src="/vite/@vite/client")

View file

@ -8,7 +8,6 @@ let
10.42.0.2 torrent.stupid.fish 10.42.0.2 torrent.stupid.fish
10.42.0.2 koi.stupid.fish 10.42.0.2 koi.stupid.fish
10.42.0.2 hass.stupid.fish 10.42.0.2 hass.stupid.fish
10.42.0.2 very.stupid.fish
10.42.0.8 bnuuy.stupid.fish 10.42.0.8 bnuuy.stupid.fish
10.42.0.2 puffer.stupid.fish 10.42.0.2 puffer.stupid.fish
10.42.0.2 puffer-webdav.stupid.fish 10.42.0.2 puffer-webdav.stupid.fish