Compare commits

...

24 commits

Author SHA1 Message Date
Marie
f93a8e2d38 merge: Fix sfm-js linkage (!399)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/399

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: dakkar <dakkar@thenautilus.net>
2024-02-03 18:28:47 +00:00
Marie
d3d0e510d8 merge: DeepLX-JS support (!396)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/396

Closes #324

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: dakkar <dakkar@thenautilus.net>
2024-02-03 17:54:59 +00:00
Marie
65a1bc2199
upd: add i18n 2024-02-03 18:49:41 +01:00
Marie
469c3f3f1a
upd: remove https 2024-02-03 18:41:47 +01:00
Marie
75bce2228f merge: some fixes to NoteEdit and muting (!376)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/376

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: Luna <her@mint.lgbt>
Approved-by: Marie <marie@kaifa.ch>
2024-02-03 17:25:17 +00:00
Marie
65a7623944 merge: follow-up to !390 (!397)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/397

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: Marie <marie@kaifa.ch>
2024-02-03 17:05:21 +00:00
Amelia Yukii
b2d520369f merge: Temp redirect until Join Sharkey dot Org is up. (!400)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/400

Closes #392

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: dakkar <dakkar@thenautilus.net>
2024-02-03 16:32:47 +00:00
Leah
441523b6d4 merge: Added lines and line numbers to syntax errors (!395)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/395

Approved-by: dakkar <dakkar@thenautilus.net>
2024-02-03 16:05:33 +00:00
Adam Howard
fc06494908 Temp redirect until Join Sharkey dot Org is up. 2024-02-03 16:03:09 +00:00
Marie
f091b84c6e
chore: change sfm registry and name 2024-02-03 15:01:09 +01:00
dakkar
4bc517ca89 import fs/promises the right way
thanks Marie
2024-02-03 12:55:56 +00:00
dakkar
bb3694bfed lint 2024-02-03 12:55:46 +00:00
dakkar
1bb5021c54 decode entity references from tweets
apparently *some* tweets have those ☹
2024-02-03 12:05:08 +00:00
dakkar
a981bca7a3 simpler logic
thanks Alina
2024-02-03 11:37:20 +00:00
dakkar
3a3a051bb5 make almost all fs ops async
there's no `fs.promises.exists`
2024-02-03 11:33:42 +00:00
dakkar
7684f45a5e simpler mapping
thanks Alina
2024-02-03 11:30:39 +00:00
dakkar
25948c9232 simpler json-isation
thanks Alina for the suggestion
2024-02-03 11:29:46 +00:00
Marie
83f1c596b0
upd: add caption to deeplx url input 2024-02-03 09:19:00 +01:00
KevinWh0
93bd4dc8fe added lines and lines to errors 2024-02-02 21:32:08 +01:00
Marie
074c47fdf8
upd: misskey-js api definitions 2024-01-26 21:31:14 +01:00
Marie
c6e3ec07d1
add: DeepLX-JS support
Closes #324
2024-01-26 21:29:38 +01:00
dakkar
e93e73673a probably more correct muting logic
we want to notify if neither the thread nor the user are muted;
otherwise, for example, notes in a muted thread from a non-muted user
would get notified
2024-01-26 18:09:25 +00:00
dakkar
b0fcc11d9e null-ify text after trimming
this comes from upstream, it's already present in the "import"
section, I think it was missed in a merge
2024-01-26 18:07:49 +00:00
dakkar
83e9057b27 apply to NoteEditService all recent changes from NoteCreateService 2024-01-26 18:07:34 +00:00
49 changed files with 655 additions and 298 deletions

2
.npmrc
View file

@ -1,2 +1,2 @@
@sharkey:registry=https://git.joinsharkey.org/api/packages/Sharkey/npm/ @transfem-org:registry=https://activitypub.software/api/v4/packages/npm/
engine-strict = true engine-strict = true

View file

@ -7,7 +7,7 @@
--- ---
<a href="https://joinsharkey.org"> <a href="https://fedidb.org/software/sharkey">
<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=sharkey&labelColor=363B40" alt="find an instance"/></a> <img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=sharkey&labelColor=363B40" alt="find an instance"/></a>
<a href="https://docs.joinsharkey.org/docs/install/fresh/"> <a href="https://docs.joinsharkey.org/docs/install/fresh/">

View file

@ -553,6 +553,8 @@ objectStorageUseProxy: "Connect over Proxy"
objectStorageUseProxyDesc: "Turn this off if you are not going to use a Proxy for API connections" objectStorageUseProxyDesc: "Turn this off if you are not going to use a Proxy for API connections"
objectStorageSetPublicRead: "Set \"public-read\" on upload" objectStorageSetPublicRead: "Set \"public-read\" on upload"
s3ForcePathStyleDesc: "If s3ForcePathStyle is enabled, the bucket name has to included in the path of the URL as opposed to the hostname of the URL. You may need to enable this setting when using services such as a self-hosted Minio instance." s3ForcePathStyleDesc: "If s3ForcePathStyle is enabled, the bucket name has to included in the path of the URL as opposed to the hostname of the URL. You may need to enable this setting when using services such as a self-hosted Minio instance."
deeplFreeMode: "Use DeepLX-JS (No Auth Key)"
deeplFreeModeDescription: "Need Help? Check our documentation to know how to setup DeepLX-JS."
serverLogs: "Server logs" serverLogs: "Server logs"
deleteAll: "Delete all" deleteAll: "Delete all"
showFixedPostForm: "Display the posting form at the top of the timeline" showFixedPostForm: "Display the posting form at the top of the timeline"

8
locales/index.d.ts vendored
View file

@ -2240,6 +2240,14 @@ export interface Locale extends ILocale {
* s3ForcePathStyleを有効にするとURLのホスト名ではなくパスの一部として指定することを強制しますMinioなどの使用時に有効にする必要がある場合があります * s3ForcePathStyleを有効にするとURLのホスト名ではなくパスの一部として指定することを強制しますMinioなどの使用時に有効にする必要がある場合があります
*/ */
"s3ForcePathStyleDesc": string; "s3ForcePathStyleDesc": string;
/**
* DeepLX-JS 使 ()
*/
"deeplFreeMode": string;
/**
* ? DeepLX-JSのセットアップ方法については
*/
"deeplFreeModeDescription": string;
/** /**
* *
*/ */

View file

@ -556,6 +556,8 @@ objectStorageUseProxy: "Proxyを利用する"
objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください" objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
objectStorageSetPublicRead: "アップロード時に'public-read'を設定する" objectStorageSetPublicRead: "アップロード時に'public-read'を設定する"
s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。" s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。"
deeplFreeMode: "DeepLX-JS を使用する (認証キーなし)"
deeplFreeModeDescription: "ヘルプが必要ですか? DeepLX-JSのセットアップ方法については、ドキュメントを参照してください。"
serverLogs: "サーバーログ" serverLogs: "サーバーログ"
deleteAll: "全て削除" deleteAll: "全て削除"
showFixedPostForm: "タイムライン上部に投稿フォームを表示する" showFixedPostForm: "タイムライン上部に投稿フォームを表示する"

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class Deeplx1706232992000 {
name = 'Deeplx1706232992000';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "deeplFreeMode" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "deeplFreeInstance" character varying(1024)`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplFreeMode"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplFreeInstance"`);
}
}

View file

@ -83,7 +83,7 @@
"@nestjs/core": "10.2.10", "@nestjs/core": "10.2.10",
"@nestjs/testing": "10.2.10", "@nestjs/testing": "10.2.10",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sharkey/sfm-js": "0.24.4", "@transfem-org/sfm-js": "0.24.4",
"@simplewebauthn/server": "9.0.0", "@simplewebauthn/server": "9.0.0",
"@sinonjs/fake-timers": "11.2.2", "@sinonjs/fake-timers": "11.2.2",
"@smithy/node-http-handler": "2.1.10", "@smithy/node-http-handler": "2.1.10",

View file

@ -13,7 +13,7 @@ import { intersperse } from '@/misc/prelude/array.js';
import type { IMentionedRemoteUsers } from '@/models/Note.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js'; import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
import type * as mfm from '@sharkey/sfm-js'; import type * as mfm from '@transfem-org/sfm-js';
const treeAdapter = TreeAdapter.defaultTreeAdapter; const treeAdapter = TreeAdapter.defaultTreeAdapter;
@ -476,173 +476,173 @@ export class MfmService {
const handlers: { const handlers: {
[K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any; [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
} = { } = {
async bold(node) { async bold(node) {
const el = doc.createElement('span'); const el = doc.createElement('span');
el.textContent = '**'; el.textContent = '**';
await appendChildren(node.children, el); await appendChildren(node.children, el);
el.textContent += '**'; el.textContent += '**';
return el; return el;
}, },
async small(node) { async small(node) {
const el = doc.createElement('small'); const el = doc.createElement('small');
await appendChildren(node.children, el); await appendChildren(node.children, el);
return el; return el;
}, },
async strike(node) { async strike(node) {
const el = doc.createElement('span'); const el = doc.createElement('span');
el.textContent = '~~'; el.textContent = '~~';
await appendChildren(node.children, el); await appendChildren(node.children, el);
el.textContent += '~~'; el.textContent += '~~';
return el; return el;
}, },
async italic(node) { async italic(node) {
const el = doc.createElement('span'); const el = doc.createElement('span');
el.textContent = '*'; el.textContent = '*';
await appendChildren(node.children, el); await appendChildren(node.children, el);
el.textContent += '*'; el.textContent += '*';
return el; return el;
}, },
async fn(node) { async fn(node) {
const el = doc.createElement('span'); const el = doc.createElement('span');
el.textContent = '*'; el.textContent = '*';
await appendChildren(node.children, el); await appendChildren(node.children, el);
el.textContent += '*'; el.textContent += '*';
return el; return el;
}, },
blockCode(node) { blockCode(node) {
const pre = doc.createElement('pre'); const pre = doc.createElement('pre');
const inner = doc.createElement('code'); const inner = doc.createElement('code');
const nodes = node.props.code const nodes = node.props.code
.split(/\r\n|\r|\n/) .split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x)); .map((x) => doc.createTextNode(x));
for (const x of intersperse<FIXME | 'br'>('br', nodes)) { for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
inner.appendChild(x === 'br' ? doc.createElement('br') : x); inner.appendChild(x === 'br' ? doc.createElement('br') : x);
} }
pre.appendChild(inner); pre.appendChild(inner);
return pre; return pre;
}, },
async center(node) { async center(node) {
const el = doc.createElement('div'); const el = doc.createElement('div');
await appendChildren(node.children, el); await appendChildren(node.children, el);
return el; return el;
}, },
emojiCode(node) { emojiCode(node) {
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`); return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
}, },
unicodeEmoji(node) { unicodeEmoji(node) {
return doc.createTextNode(node.props.emoji); return doc.createTextNode(node.props.emoji);
}, },
hashtag: (node) => { hashtag: (node) => {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`); a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
a.textContent = `#${node.props.hashtag}`; a.textContent = `#${node.props.hashtag}`;
a.setAttribute('rel', 'tag'); a.setAttribute('rel', 'tag');
a.setAttribute('class', 'hashtag'); a.setAttribute('class', 'hashtag');
return a; return a;
}, },
inlineCode(node) { inlineCode(node) {
const el = doc.createElement('code'); const el = doc.createElement('code');
el.textContent = node.props.code; el.textContent = node.props.code;
return el; return el;
}, },
mathInline(node) { mathInline(node) {
const el = doc.createElement('code'); const el = doc.createElement('code');
el.textContent = node.props.formula; el.textContent = node.props.formula;
return el; return el;
}, },
mathBlock(node) { mathBlock(node) {
const el = doc.createElement('code'); const el = doc.createElement('code');
el.textContent = node.props.formula; el.textContent = node.props.formula;
return el; return el;
}, },
async link(node) { async link(node) {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer'); a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank'); a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url); a.setAttribute('href', node.props.url);
await appendChildren(node.children, a); await appendChildren(node.children, a);
return a; return a;
}, },
async mention(node) { async mention(node) {
const { username, host, acct } = node.props; const { username, host, acct } = node.props;
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
const el = doc.createElement('span'); const el = doc.createElement('span');
if (!resolved) { if (!resolved) {
el.textContent = acct; el.textContent = acct;
} else { } else {
el.setAttribute('class', 'h-card'); el.setAttribute('class', 'h-card');
el.setAttribute('translate', 'no'); el.setAttribute('translate', 'no');
const a = doc.createElement('a'); const a = doc.createElement('a');
a.setAttribute('href', resolved.url ? resolved.url : resolved.uri); a.setAttribute('href', resolved.url ? resolved.url : resolved.uri);
a.className = 'u-url mention'; a.className = 'u-url mention';
const span = doc.createElement('span'); const span = doc.createElement('span');
span.textContent = resolved.username || username; span.textContent = resolved.username || username;
a.textContent = '@'; a.textContent = '@';
a.appendChild(span); a.appendChild(span);
el.appendChild(a); el.appendChild(a);
} }
return el; return el;
}, },
async quote(node) { async quote(node) {
const el = doc.createElement('blockquote'); const el = doc.createElement('blockquote');
await appendChildren(node.children, el); await appendChildren(node.children, el);
return el; return el;
}, },
text(node) { text(node) {
const el = doc.createElement('span'); const el = doc.createElement('span');
const nodes = node.props.text const nodes = node.props.text
.split(/\r\n|\r|\n/) .split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x)); .map((x) => doc.createTextNode(x));
for (const x of intersperse<FIXME | 'br'>('br', nodes)) { for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
el.appendChild(x === 'br' ? doc.createElement('br') : x); el.appendChild(x === 'br' ? doc.createElement('br') : x);
} }
return el; return el;
}, },
url(node) { url(node) {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer'); a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank'); a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url); a.setAttribute('href', node.props.url);
a.textContent = node.props.url.replace(/^https?:\/\//, ''); a.textContent = node.props.url.replace(/^https?:\/\//, '');
return a; return a;
}, },
search: (node) => { search: (node) => {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.setAttribute('href', `https"google.com/${node.props.query}`); a.setAttribute('href', `https"google.com/${node.props.query}`);
a.textContent = node.props.content; a.textContent = node.props.content;
return a; return a;
}, },
async plain(node) { async plain(node) {
const el = doc.createElement('span'); const el = doc.createElement('span');
await appendChildren(node.children, el); await appendChildren(node.children, el);
return el; return el;
}, },
}; };
await appendChildren(nodes, doc.body); await appendChildren(nodes, doc.body);

View file

@ -4,7 +4,7 @@
*/ */
import { setImmediate } from 'node:timers/promises'; import { setImmediate } from 'node:timers/promises';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { In, DataSource, IsNull, LessThan } from 'typeorm'; import { In, DataSource, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
@ -328,6 +328,9 @@ export class NoteCreateService implements OnApplicationShutdown {
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH); data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
} }
data.text = data.text.trim(); data.text = data.text.trim();
if (data.text === '') {
data.text = null;
}
} else { } else {
data.text = null; data.text = null;
} }
@ -807,7 +810,7 @@ export class NoteCreateService implements OnApplicationShutdown {
const muted = isUserRelated(note, userIdsWhoMeMuting); const muted = isUserRelated(note, userIdsWhoMeMuting);
if (!isThreadMuted || !muted) { if (!isThreadMuted && !muted) {
nm.push(data.reply.userId, 'reply'); nm.push(data.reply.userId, 'reply');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj); this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
@ -842,7 +845,7 @@ export class NoteCreateService implements OnApplicationShutdown {
const muted = isUserRelated(note, userIdsWhoMeMuting); const muted = isUserRelated(note, userIdsWhoMeMuting);
if (!isThreadMuted || !muted) { if (!isThreadMuted && !muted) {
nm.push(data.renote.userId, type); nm.push(data.renote.userId, type);
} }
} }

View file

@ -4,7 +4,7 @@
*/ */
import { setImmediate } from 'node:timers/promises'; import { setImmediate } from 'node:timers/promises';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { DataSource, In, IsNull, LessThan } from 'typeorm'; import { DataSource, In, IsNull, LessThan } from 'typeorm';
import * as Redis from 'ioredis'; import * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
@ -46,6 +46,11 @@ import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js'; import { SearchService } from '@/core/SearchService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { CacheService } from '@/core/CacheService.js';
import { isReply } from '@/misc/is-reply.js';
import { trackPromise } from '@/misc/promise-tracker.js';
import { isUserRelated } from '@/misc/is-user-related.js';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -206,6 +211,8 @@ export class NoteEditService implements OnApplicationShutdown {
private activeUsersChart: ActiveUsersChart, private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart, private instanceChart: InstanceChart,
private utilityService: UtilityService, private utilityService: UtilityService,
private userBlockingService: UserBlockingService,
private cacheService: CacheService,
) { } ) { }
@bindThis @bindThis
@ -255,16 +262,18 @@ export class NoteEditService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true; if (data.channel != null) data.localOnly = true;
if (data.updatedAt == null) data.updatedAt = new Date(); if (data.updatedAt == null) data.updatedAt = new Date();
const meta = await this.metaService.fetch();
if (data.visibility === 'public' && data.channel == null) { if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = (await this.metaService.fetch()).sensitiveWords; const sensitiveWords = meta.sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) { if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home'; data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) { } else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
data.visibility = 'home'; data.visibility = 'home';
} }
} }
const inSilencedInstance = this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, user.host); const inSilencedInstance = this.utilityService.isSilencedHost((meta).silencedHosts, user.host);
if (data.visibility === 'public' && inSilencedInstance && user.host !== null) { if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
data.visibility = 'home'; data.visibility = 'home';
@ -296,6 +305,18 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
// Check blocking
if (data.renote && !this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
if (blocked) {
throw new Error('blocked');
}
}
}
}
// 返信対象がpublicではないならhomeにする // 返信対象がpublicではないならhomeにする
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') { if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
data.visibility = 'home'; data.visibility = 'home';
@ -316,6 +337,9 @@ export class NoteEditService implements OnApplicationShutdown {
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH); data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
} }
data.text = data.text.trim(); data.text = data.text.trim();
if (data.text === '') {
data.text = null;
}
} else { } else {
data.text = null; data.text = null;
} }
@ -361,6 +385,14 @@ export class NoteEditService implements OnApplicationShutdown {
} }
} }
if (user.host && !data.cw) {
await this.federatedInstanceService.fetch(user.host).then(async i => {
if (i.isNSFW) {
data.cw = 'Instance is marked as NSFW';
}
});
}
const update: Partial<MiNote> = {}; const update: Partial<MiNote> = {};
if (data.text !== oldnote.text) { if (data.text !== oldnote.text) {
update.text = data.text; update.text = data.text;
@ -557,7 +589,7 @@ export class NoteEditService implements OnApplicationShutdown {
} else { } else {
this.globalEventService.publishNoteStream(note.id, 'updated', { this.globalEventService.publishNoteStream(note.id, 'updated', {
cw: note.cw, cw: note.cw,
text: note.text! text: note.text!,
}); });
} }
@ -587,7 +619,15 @@ export class NoteEditService implements OnApplicationShutdown {
}, },
}); });
if (!isThreadMuted) { const [
userIdsWhoMeMuting,
] = data.reply.userId ? await Promise.all([
this.cacheService.userMutingsCache.fetch(data.reply.userId),
]) : [new Set<string>()];
const muted = isUserRelated(note, userIdsWhoMeMuting);
if (!isThreadMuted && !muted) {
nm.push(data.reply.userId, 'reply'); nm.push(data.reply.userId, 'reply');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj); this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
@ -603,11 +643,28 @@ export class NoteEditService implements OnApplicationShutdown {
// If it is renote // If it is renote
if (data.renote) { if (data.renote) {
const type = data.text ? 'quote' : 'renote'; const type = this.isQuote(data) ? 'quote' : 'renote';
// Notify // Notify
if (data.renote.userHost === null) { if (data.renote.userHost === null) {
nm.push(data.renote.userId, type); const isThreadMuted = await this.noteThreadMutingsRepository.exist({
where: {
userId: data.renote.userId,
threadId: data.renote.threadId ?? data.renote.id,
},
});
const [
userIdsWhoMeMuting,
] = data.renote.userId ? await Promise.all([
this.cacheService.userMutingsCache.fetch(data.renote.userId),
]) : [new Set<string>()];
const muted = isUserRelated(note, userIdsWhoMeMuting);
if (!isThreadMuted && !muted) {
nm.push(data.renote.userId, type);
}
} }
// Publish event // Publish event
@ -657,7 +714,7 @@ export class NoteEditService implements OnApplicationShutdown {
this.relayService.deliverToRelays(user, noteActivity); this.relayService.deliverToRelays(user, noteActivity);
} }
dm.execute(); trackPromise(dm.execute());
})(); })();
} }
//#endregion //#endregion
@ -686,28 +743,9 @@ export class NoteEditService implements OnApplicationShutdown {
} }
@bindThis @bindThis
private isSensitive(note: Option, sensitiveWord: string[]): boolean { private isQuote(note: Option): note is Option & { renote: MiNote } {
if (sensitiveWord.length > 0) { // sync with misc/is-quote.ts
const text = note.cw ?? note.text ?? ''; return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
if (text === '') return false;
const matched = sensitiveWord.some(filter => {
// represents RegExp
const regexp = filter.match(/^\/(.+)\/(.*)$/);
// This should never happen due to input sanitisation.
if (!regexp) {
const words = filter.split(' ');
return words.every(keyword => text.includes(keyword));
}
try {
return new RE2(regexp[1], regexp[2]).test(text);
} catch (err) {
// This should never happen due to input sanitisation.
return false;
}
});
if (matched) return true;
}
return false;
} }
@bindThis @bindThis
@ -720,7 +758,15 @@ export class NoteEditService implements OnApplicationShutdown {
}, },
}); });
if (isThreadMuted) { const [
userIdsWhoMeMuting,
] = u.id ? await Promise.all([
this.cacheService.userMutingsCache.fetch(u.id),
]) : [new Set<string>()];
const muted = isUserRelated(note, userIdsWhoMeMuting);
if (isThreadMuted || muted) {
continue; continue;
} }
@ -748,7 +794,7 @@ export class NoteEditService implements OnApplicationShutdown {
const user = await this.usersRepository.findOneBy({ id: note.userId }); const user = await this.usersRepository.findOneBy({ id: note.userId });
if (user == null) throw new Error('user not found'); if (user == null) throw new Error('user not found');
const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0) const content = data.renote && !this.isQuote(data)
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user); : this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user);
@ -782,6 +828,7 @@ export class NoteEditService implements OnApplicationShutdown {
@bindThis @bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) { private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
if (!meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline(); const r = this.redisForTimelines.pipeline();
@ -825,7 +872,7 @@ export class NoteEditService implements OnApplicationShutdown {
if (note.visibility === 'followers') { if (note.visibility === 'followers') {
// TODO: 重そうだから何とかしたい Set 使う? // TODO: 重そうだから何とかしたい Set 使う?
userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId)); userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId));
} }
// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする // TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
@ -834,7 +881,7 @@ export class NoteEditService implements OnApplicationShutdown {
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue; if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合 // 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) { if (isReply(note, following.followerId)) {
if (!following.withReplies) continue; if (!following.withReplies) continue;
} }
@ -848,11 +895,12 @@ export class NoteEditService implements OnApplicationShutdown {
// ダイレクトのとき、そのリストが対象外のユーザーの場合 // ダイレクトのとき、そのリストが対象外のユーザーの場合
if ( if (
note.visibility === 'specified' && note.visibility === 'specified' &&
note.userId !== userListMembership.userListUserId &&
!note.visibleUserIds.some(v => v === userListMembership.userListUserId) !note.visibleUserIds.some(v => v === userListMembership.userListUserId)
) continue; ) continue;
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合 // 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) { if (isReply(note, userListMembership.userListUserId)) {
if (!userListMembership.withReplies) continue; if (!userListMembership.withReplies) continue;
} }
@ -870,11 +918,14 @@ export class NoteEditService implements OnApplicationShutdown {
} }
// 自分自身以外への返信 // 自分自身以外への返信
if (note.replyId && note.replyUserId !== note.userId) { if (isReply(note)) {
this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
if (note.visibility === 'public' && note.userHost == null) { if (note.visibility === 'public' && note.userHost == null) {
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r); this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
if (note.replyUserHost == null) {
this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r);
}
} }
} else { } else {
this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r); this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);

View file

@ -4,7 +4,7 @@
*/ */
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { MfmService } from '@/core/MfmService.js'; import { MfmService } from '@/core/MfmService.js';
import type { MiNote } from '@/models/Note.js'; import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View file

@ -6,7 +6,7 @@
import { createPublicKey, randomUUID } from 'node:crypto'; import { createPublicKey, randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js'; import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
@ -28,10 +28,10 @@ import { bindThis } from '@/decorators.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { isNotNull } from '@/misc/is-not-null.js'; import { isNotNull } from '@/misc/is-not-null.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { MetaService } from '../MetaService.js';
import { LdSignatureService } from './LdSignatureService.js'; import { LdSignatureService } from './LdSignatureService.js';
import { ApMfmService } from './ApMfmService.js'; import { ApMfmService } from './ApMfmService.js';
import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js'; import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
import { MetaService } from '../MetaService.js';
@Injectable() @Injectable()
export class ApRendererService { export class ApRendererService {
@ -821,12 +821,12 @@ export class ApRendererService {
'_misskey_summary': 'misskey:_misskey_summary', '_misskey_summary': 'misskey:_misskey_summary',
'isCat': 'misskey:isCat', 'isCat': 'misskey:isCat',
// Firefish // Firefish
firefish: "https://joinfirefish.org/ns#", firefish: 'https://joinfirefish.org/ns#',
speakAsCat: "firefish:speakAsCat", speakAsCat: 'firefish:speakAsCat',
// Sharkey // Sharkey
sharkey: "https://joinsharkey.org/ns#", sharkey: 'https://joinsharkey.org/ns#',
backgroundUrl: "sharkey:backgroundUrl", backgroundUrl: 'sharkey:backgroundUrl',
listenbrainz: "sharkey:listenbrainz", listenbrainz: 'sharkey:listenbrainz',
// vcard // vcard
vcard: 'http://www.w3.org/2006/vcard/ns#', vcard: 'http://www.w3.org/2006/vcard/ns#',
}, },

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { unique } from '@/misc/prelude/array.js'; import { unique } from '@/misc/prelude/array.js';
export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] { export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] {

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { unique } from '@/misc/prelude/array.js'; import { unique } from '@/misc/prelude/array.js';
export function extractHashtags(nodes: mfm.MfmNode[]): string[] { export function extractHashtags(nodes: mfm.MfmNode[]): string[] {

View file

@ -5,7 +5,7 @@
// test is located in test/extract-mentions // test is located in test/extract-mentions
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] { export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
// TODO: 重複を削除 // TODO: 重複を削除

View file

@ -353,6 +353,17 @@ export class MiMeta {
}) })
public deeplIsPro: boolean; public deeplIsPro: boolean;
@Column('boolean', {
default: false,
})
public deeplFreeMode: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public deeplFreeInstance: string | null;
@Column('varchar', { @Column('varchar', {
length: 1024, length: 1024,
nullable: true, nullable: true,

View file

@ -1,4 +1,5 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as fsp from 'node:fs/promises';
import * as vm from 'node:vm'; import * as vm from 'node:vm';
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
@ -51,7 +52,7 @@ export class ImportNotesProcessorService {
@bindThis @bindThis
private async uploadFiles(dir: string, user: MiUser, folder?: MiDriveFolder['id']) { private async uploadFiles(dir: string, user: MiUser, folder?: MiDriveFolder['id']) {
const fileList = fs.readdirSync(dir); const fileList = await fsp.readdir(dir);
for await (const file of fileList) { for await (const file of fileList) {
const name = `${dir}/${file}`; const name = `${dir}/${file}`;
if (fs.statSync(name).isDirectory()) { if (fs.statSync(name).isDirectory()) {
@ -130,14 +131,16 @@ export class ImportNotesProcessorService {
return typeof obj[Symbol.iterator] === 'function'; return typeof obj[Symbol.iterator] === 'function';
} }
private parseTwitterFile(str : string) : null | [{ tweet: any }] { @bindThis
const removed = str.replace(new RegExp('window\\.YTD\\.tweets\\.part0 = ', 'g'), ''); private parseTwitterFile(str : string) : { tweet: object }[] {
const jsonStr = str.replace(/^\s*window\.YTD\.tweets\.part0\s*=\s*/, '');
try { try {
return JSON.parse(removed); return JSON.parse(jsonStr);
} catch (error) { } catch (error) {
//The format is not what we expected. Either this file was tampered with or twitters exports changed //The format is not what we expected. Either this file was tampered with or twitters exports changed
return null; this.logger.warn('Failed to import twitter notes due to malformed file');
throw error;
} }
} }
@ -173,7 +176,7 @@ export class ImportNotesProcessorService {
const destPath = path + '/twitter.zip'; const destPath = path + '/twitter.zip';
try { try {
fs.writeFileSync(destPath, '', 'binary'); await fsp.writeFile(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath); await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行 } catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') { if (e instanceof Error || typeof e === 'string') {
@ -185,21 +188,13 @@ export class ImportNotesProcessorService {
const outputPath = path + '/twitter'; const outputPath = path + '/twitter';
try { try {
this.logger.succ(`Unzipping to ${outputPath}`); this.logger.succ(`Unzipping to ${outputPath}`);
ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath)); ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
const unprocessedTweetJson = this.parseTwitterFile(fs.readFileSync(outputPath + '/data/tweets.js', 'utf-8')); const unprocessedTweets = this.parseTwitterFile(await fsp.readFile(outputPath + '/data/tweets.js', 'utf-8'));
//Make sure that it isnt null (because if something went wrong in parseTwitterFile it returns null) const tweets = unprocessedTweets.map(e => e.tweet);
if (unprocessedTweetJson) { const processedTweets = await this.recreateChain(['id_str'], ['in_reply_to_status_id_str'], tweets, false);
const tweets = Object.keys(unprocessedTweetJson).reduce((m, key, i, obj) => { this.queueService.createImportTweetsToDbJob(job.data.user, processedTweets, null);
return m.concat(unprocessedTweetJson[i].tweet);
}, []);
const processedTweets = await this.recreateChain(['id_str'], ['in_reply_to_status_id_str'], tweets, false);
this.queueService.createImportTweetsToDbJob(job.data.user, processedTweets, null);
} else {
this.logger.warn('Failed to import twitter notes due to malformed file');
}
} finally { } finally {
cleanup(); cleanup();
} }
@ -211,7 +206,7 @@ export class ImportNotesProcessorService {
const destPath = path + '/facebook.zip'; const destPath = path + '/facebook.zip';
try { try {
fs.writeFileSync(destPath, '', 'binary'); await fsp.writeFile(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath); await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行 } catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') { if (e instanceof Error || typeof e === 'string') {
@ -223,8 +218,8 @@ export class ImportNotesProcessorService {
const outputPath = path + '/facebook'; const outputPath = path + '/facebook';
try { try {
this.logger.succ(`Unzipping to ${outputPath}`); this.logger.succ(`Unzipping to ${outputPath}`);
ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath)); ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
const postsJson = fs.readFileSync(outputPath + '/your_activity_across_facebook/posts/your_posts__check_ins__photos_and_videos_1.json', 'utf-8'); const postsJson = await fsp.readFile(outputPath + '/your_activity_across_facebook/posts/your_posts__check_ins__photos_and_videos_1.json', 'utf-8');
const posts = JSON.parse(postsJson); const posts = JSON.parse(postsJson);
const facebookFolder = await this.driveFoldersRepository.findOneBy({ name: 'Facebook', userId: job.data.user.id, parentId: folder?.id }); const facebookFolder = await this.driveFoldersRepository.findOneBy({ name: 'Facebook', userId: job.data.user.id, parentId: folder?.id });
if (facebookFolder == null && folder) { if (facebookFolder == null && folder) {
@ -244,7 +239,7 @@ export class ImportNotesProcessorService {
const destPath = path + '/unknown.zip'; const destPath = path + '/unknown.zip';
try { try {
fs.writeFileSync(destPath, '', 'binary'); await fsp.writeFile(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath); await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行 } catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') { if (e instanceof Error || typeof e === 'string') {
@ -256,11 +251,11 @@ export class ImportNotesProcessorService {
const outputPath = path + '/unknown'; const outputPath = path + '/unknown';
try { try {
this.logger.succ(`Unzipping to ${outputPath}`); this.logger.succ(`Unzipping to ${outputPath}`);
ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath)); ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
const isInstagram = type === 'Instagram' || fs.existsSync(outputPath + '/instagram_live') || fs.existsSync(outputPath + '/instagram_ads_and_businesses'); const isInstagram = type === 'Instagram' || fs.existsSync(outputPath + '/instagram_live') || fs.existsSync(outputPath + '/instagram_ads_and_businesses');
const isOutbox = type === 'Mastodon' || fs.existsSync(outputPath + '/outbox.json'); const isOutbox = type === 'Mastodon' || fs.existsSync(outputPath + '/outbox.json');
if (isInstagram) { if (isInstagram) {
const postsJson = fs.readFileSync(outputPath + '/content/posts_1.json', 'utf-8'); const postsJson = await fsp.readFile(outputPath + '/content/posts_1.json', 'utf-8');
const posts = JSON.parse(postsJson); const posts = JSON.parse(postsJson);
const igFolder = await this.driveFoldersRepository.findOneBy({ name: 'Instagram', userId: job.data.user.id, parentId: folder?.id }); const igFolder = await this.driveFoldersRepository.findOneBy({ name: 'Instagram', userId: job.data.user.id, parentId: folder?.id });
if (igFolder == null && folder) { if (igFolder == null && folder) {
@ -270,16 +265,16 @@ export class ImportNotesProcessorService {
} }
this.queueService.createImportIGToDbJob(job.data.user, posts); this.queueService.createImportIGToDbJob(job.data.user, posts);
} else if (isOutbox) { } else if (isOutbox) {
const actorJson = fs.readFileSync(outputPath + '/actor.json', 'utf-8'); const actorJson = await fsp.readFile(outputPath + '/actor.json', 'utf-8');
const actor = JSON.parse(actorJson); const actor = JSON.parse(actorJson);
const isPleroma = actor['@context'].some((v: any) => typeof v === 'string' && v.match(/litepub(.*)/)); const isPleroma = actor['@context'].some((v: any) => typeof v === 'string' && v.match(/litepub(.*)/));
if (isPleroma) { if (isPleroma) {
const outboxJson = fs.readFileSync(outputPath + '/outbox.json', 'utf-8'); const outboxJson = await fsp.readFile(outputPath + '/outbox.json', 'utf-8');
const outbox = JSON.parse(outboxJson); const outbox = JSON.parse(outboxJson);
const processedToots = await this.recreateChain(['object', 'id'], ['object', 'inReplyTo'], outbox.orderedItems.filter((x: any) => x.type === 'Create' && x.object.type === 'Note'), true); const processedToots = await this.recreateChain(['object', 'id'], ['object', 'inReplyTo'], outbox.orderedItems.filter((x: any) => x.type === 'Create' && x.object.type === 'Note'), true);
this.queueService.createImportPleroToDbJob(job.data.user, processedToots, null); this.queueService.createImportPleroToDbJob(job.data.user, processedToots, null);
} else { } else {
const outboxJson = fs.readFileSync(outputPath + '/outbox.json', 'utf-8'); const outboxJson = await fsp.readFile(outputPath + '/outbox.json', 'utf-8');
const outbox = JSON.parse(outboxJson); const outbox = JSON.parse(outboxJson);
let mastoFolder = await this.driveFoldersRepository.findOneBy({ name: 'Mastodon', userId: job.data.user.id, parentId: folder?.id }); let mastoFolder = await this.driveFoldersRepository.findOneBy({ name: 'Mastodon', userId: job.data.user.id, parentId: folder?.id });
if (mastoFolder == null && folder) { if (mastoFolder == null && folder) {
@ -302,7 +297,7 @@ export class ImportNotesProcessorService {
this.logger.info(`Temp dir is ${path}`); this.logger.info(`Temp dir is ${path}`);
try { try {
fs.writeFileSync(path, '', 'utf-8'); await fsp.writeFile(path, '', 'utf-8');
await this.downloadService.downloadUrl(file.url, path); await this.downloadService.downloadUrl(file.url, path);
} catch (e) { // TODO: 何度か再試行 } catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') { if (e instanceof Error || typeof e === 'string') {
@ -311,7 +306,7 @@ export class ImportNotesProcessorService {
throw e; throw e;
} }
const notesJson = fs.readFileSync(path, 'utf-8'); const notesJson = await fsp.readFile(path, 'utf-8');
const notes = JSON.parse(notesJson); const notes = JSON.parse(notesJson);
const processedNotes = await this.recreateChain(['id'], ['replyId'], notes, false); const processedNotes = await this.recreateChain(['id'], ['replyId'], notes, false);
this.queueService.createImportKeyNotesToDbJob(job.data.user, processedNotes, null); this.queueService.createImportKeyNotesToDbJob(job.data.user, processedNotes, null);
@ -590,7 +585,8 @@ export class ImportNotesProcessorService {
try { try {
const date = new Date(tweet.created_at); const date = new Date(tweet.created_at);
const textReplaceURLs = tweet.entities.urls && tweet.entities.urls.length > 0 ? await replaceTwitterUrls(tweet.full_text, tweet.entities.urls) : tweet.full_text; const decodedText = tweet.full_text.replaceAll('&gt;', '>').replaceAll('&lt;', '<').replaceAll('&amp;', '&');
const textReplaceURLs = tweet.entities.urls && tweet.entities.urls.length > 0 ? await replaceTwitterUrls(decodedText, tweet.entities.urls) : decodedText;
const text = tweet.entities.user_mentions && tweet.entities.user_mentions.length > 0 ? await replaceTwitterMentions(textReplaceURLs, tweet.entities.user_mentions) : textReplaceURLs; const text = tweet.entities.user_mentions && tweet.entities.user_mentions.length > 0 ? await replaceTwitterMentions(textReplaceURLs, tweet.entities.user_mentions) : textReplaceURLs;
const files: MiDriveFile[] = []; const files: MiDriveFile[] = [];

View file

@ -395,6 +395,14 @@ export const meta = {
type: 'boolean', type: 'boolean',
optional: false, nullable: false, optional: false, nullable: false,
}, },
deeplFreeMode: {
type: 'boolean',
optional: false, nullable: false,
},
deeplFreeInstance: {
type: 'string',
optional: false, nullable: true,
},
defaultDarkTheme: { defaultDarkTheme: {
type: 'string', type: 'string',
optional: false, nullable: true, optional: false, nullable: true,
@ -576,6 +584,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
deeplAuthKey: instance.deeplAuthKey, deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro, deeplIsPro: instance.deeplIsPro,
deeplFreeMode: instance.deeplFreeMode,
deeplFreeInstance: instance.deeplFreeInstance,
enableIpLogging: instance.enableIpLogging, enableIpLogging: instance.enableIpLogging,
enableActiveEmailValidation: instance.enableActiveEmailValidation, enableActiveEmailValidation: instance.enableActiveEmailValidation,
enableVerifymailApi: instance.enableVerifymailApi, enableVerifymailApi: instance.enableVerifymailApi,

View file

@ -91,6 +91,8 @@ export const paramDef = {
summalyProxy: { type: 'string', nullable: true }, summalyProxy: { type: 'string', nullable: true },
deeplAuthKey: { type: 'string', nullable: true }, deeplAuthKey: { type: 'string', nullable: true },
deeplIsPro: { type: 'boolean' }, deeplIsPro: { type: 'boolean' },
deeplFreeMode: { type: 'boolean' },
deeplFreeInstance: { type: 'string', nullable: true },
enableEmail: { type: 'boolean' }, enableEmail: { type: 'boolean' },
email: { type: 'string', nullable: true }, email: { type: 'string', nullable: true },
smtpSecure: { type: 'boolean' }, smtpSecure: { type: 'boolean' },
@ -479,6 +481,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.deeplIsPro = ps.deeplIsPro; set.deeplIsPro = ps.deeplIsPro;
} }
if (ps.deeplFreeMode !== undefined) {
set.deeplFreeMode = ps.deeplFreeMode;
}
if (ps.deeplFreeInstance !== undefined) {
if (ps.deeplFreeInstance === '') {
set.deeplFreeInstance = null;
} else {
set.deeplFreeInstance = ps.deeplFreeInstance;
}
}
if (ps.enableIpLogging !== undefined) { if (ps.enableIpLogging !== undefined) {
set.enableIpLogging = ps.enableIpLogging; set.enableIpLogging = ps.enableIpLogging;
} }

View file

@ -4,7 +4,7 @@
*/ */
import RE2 from 're2'; import RE2 from 're2';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms'; import ms from 'ms';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';

View file

@ -411,7 +411,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
enableEmail: instance.enableEmail, enableEmail: instance.enableEmail,
enableServiceWorker: instance.enableServiceWorker, enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null, translatorAvailable: instance.deeplAuthKey != null || instance.deeplFreeMode && instance.deeplFreeInstance,
serverRules: instance.serverRules, serverRules: instance.serverRules,

View file

@ -81,19 +81,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const instance = await this.metaService.fetch(); const instance = await this.metaService.fetch();
if (instance.deeplAuthKey == null) { if (instance.deeplAuthKey == null && !instance.deeplFreeMode) {
return 204; // TODO: 良い感じのエラー返す return 204; // TODO: 良い感じのエラー返す
} }
if (instance.deeplFreeMode && !instance.deeplFreeInstance) {
return 204;
}
let targetLang = ps.targetLang; let targetLang = ps.targetLang;
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0]; if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append('auth_key', instance.deeplAuthKey); if (instance.deeplAuthKey) params.append('auth_key', instance.deeplAuthKey);
params.append('text', note.text); params.append('text', note.text);
params.append('target_lang', targetLang); params.append('target_lang', targetLang);
const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate'; const endpoint = instance.deeplFreeMode && instance.deeplFreeInstance ? instance.deeplFreeInstance : instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
const res = await this.httpRequestService.send(endpoint, { const res = await this.httpRequestService.send(endpoint, {
method: 'POST', method: 'POST',
@ -103,18 +107,37 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}, },
body: params.toString(), body: params.toString(),
}); });
if (instance.deeplAuthKey) {
const json = (await res.json()) as {
translations: {
detected_source_language: string;
text: string;
}[];
};
const json = (await res.json()) as { return {
translations: { sourceLang: json.translations[0].detected_source_language,
detected_source_language: string; text: json.translations[0].text,
text: string; };
}[]; } else {
}; const json = (await res.json()) as {
code: number,
message: string,
data: string,
source_lang: string,
target_lang: string,
alternatives: string[],
};
return { const languageNames = new Intl.DisplayNames(['en'], {
sourceLang: json.translations[0].detected_source_language, type: 'language',
text: json.translations[0].text, });
};
return {
sourceLang: languageNames.of(json.source_lang),
text: json.data,
};
}
}); });
} }
} }

View file

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Entity } from 'megalodon'; import { Entity } from 'megalodon';
import mfm from '@sharkey/sfm-js'; import mfm from '@transfem-org/sfm-js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { MfmService } from '@/core/MfmService.js'; import { MfmService } from '@/core/MfmService.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@ -9,9 +9,9 @@ import type { MiUser } from '@/models/User.js';
import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { GetterService } from '../GetterService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js'; import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GetterService } from '../GetterService.js';
export enum IdConvertType { export enum IdConvertType {
MastodonId, MastodonId,
@ -94,10 +94,10 @@ export class MastoConverters {
text_url: f.url, text_url: f.url,
meta: { meta: {
width: f.properties.width, width: f.properties.width,
height: f.properties.height height: f.properties.height,
}, },
description: f.comment ? f.comment : null, description: f.comment ? f.comment : null,
blurhash: f.blurhash ? f.blurhash : null blurhash: f.blurhash ? f.blurhash : null,
}; };
} }
@ -185,7 +185,7 @@ export class MastoConverters {
sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false), sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false),
spoiler_text: edit.cw ?? '', spoiler_text: edit.cw ?? '',
poll: null, poll: null,
media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : []) media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : []),
}; };
lastDate = edit.updatedAt; lastDate = edit.updatedAt;
history.push(awaitAll(item)); history.push(awaitAll(item));

View file

@ -4,7 +4,7 @@
*/ */
import * as assert from 'assert'; import * as assert from 'assert';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { Test } from '@nestjs/testing'; import { Test } from '@nestjs/testing';
import { CoreModule } from '@/core/CoreModule.js'; import { CoreModule } from '@/core/CoreModule.js';

View file

@ -5,7 +5,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import { parse } from '@sharkey/sfm-js'; import { parse } from '@transfem-org/sfm-js';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
describe('Extract mentions', () => { describe('Extract mentions', () => {

View file

@ -24,7 +24,7 @@
"@rollup/plugin-json": "6.1.0", "@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "5.0.5", "@rollup/plugin-replace": "5.0.5",
"@rollup/pluginutils": "5.1.0", "@rollup/pluginutils": "5.1.0",
"@sharkey/sfm-js": "0.24.4", "@transfem-org/sfm-js": "0.24.4",
"@syuilo/aiscript": "0.17.0", "@syuilo/aiscript": "0.17.0",
"@phosphor-icons/web": "^2.0.3", "@phosphor-icons/web": "^2.0.3",
"@twemoji/parser": "15.0.0", "@twemoji/parser": "15.0.0",

View file

@ -58,6 +58,21 @@ watch(() => props.lang, (to) => {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.codeBlockRoot :deep(.shiki) > code {
counter-reset: step;
counter-increment: step 0;
}
.codeBlockRoot :deep(.shiki) > code > .line::before {
content: counter(step);
counter-increment: step;
width: 1rem;
margin-right: 1.5rem;
display: inline-block;
text-align: right;
color: rgba(115,138,148,.4)
}
.codeBlockRoot :deep(.shiki) { .codeBlockRoot :deep(.shiki) {
padding: 1em; padding: 1em;
margin: .5em 0; margin: .5em 0;

View file

@ -204,6 +204,8 @@ watch(v, newValue => {
min-width: calc(100% - 24px); min-width: calc(100% - 24px);
height: 100%; height: 100%;
padding: 12px; padding: 12px;
// the +2.5 rem is because of the line numbers
padding-left: calc(12px + 2.5rem);
line-height: 1.5em; line-height: 1.5em;
font-size: 1em; font-size: 1em;
font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;

View file

@ -178,7 +178,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue'; import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkNoteHeader from '@/components/MkNoteHeader.vue';
@ -477,7 +477,7 @@ function renote(visibility: Visibility | 'local') {
renoted.value = true; renoted.value = true;
}); });
} }
} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) { } else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
const el = renoteButton.value as HTMLElement | null | undefined; const el = renoteButton.value as HTMLElement | null | undefined;
if (el) { if (el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();

View file

@ -170,7 +170,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="!repliesLoaded" style="padding: 16px"> <div v-if="!repliesLoaded" style="padding: 16px">
<MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton> <MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton>
</div> </div>
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" /> <MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply"/>
</div> </div>
<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes"> <div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
<MkPagination :pagination="renotesPagination" :disableAutoLoad="true"> <MkPagination :pagination="renotesPagination" :disableAutoLoad="true">
@ -221,7 +221,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue'; import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue';
@ -501,7 +501,7 @@ function renote(visibility: Visibility | 'local') {
os.toast(i18n.ts.renoted); os.toast(i18n.ts.renoted);
renoted.value = true; renoted.value = true;
}); });
} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) { } else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
const el = renoteButton.value as HTMLElement | null | undefined; const el = renoteButton.value as HTMLElement | null | undefined;
if (el) { if (el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();

View file

@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue'; import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode/'; import { toASCII } from 'punycode/';

View file

@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import MkMediaList from '@/components/MkMediaList.vue'; import MkMediaList from '@/components/MkMediaList.vue';
import MkPoll from '@/components/MkPoll.vue'; import MkPoll from '@/components/MkPoll.vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';

View file

@ -180,7 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue'; import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import SkNoteSub from '@/components/SkNoteSub.vue'; import SkNoteSub from '@/components/SkNoteSub.vue';
import SkNoteHeader from '@/components/SkNoteHeader.vue'; import SkNoteHeader from '@/components/SkNoteHeader.vue';
@ -478,7 +478,7 @@ function renote(visibility: Visibility | 'local') {
renoted.value = true; renoted.value = true;
}); });
} }
} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) { } else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
const el = renoteButton.value as HTMLElement | null | undefined; const el = renoteButton.value as HTMLElement | null | undefined;
if (el) { if (el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();

View file

@ -229,7 +229,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shallowRef, watch } from 'vue'; import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shallowRef, watch } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import SkNoteSub from '@/components/SkNoteSub.vue'; import SkNoteSub from '@/components/SkNoteSub.vue';
import SkNoteSimple from '@/components/SkNoteSimple.vue'; import SkNoteSimple from '@/components/SkNoteSimple.vue';
@ -510,7 +510,7 @@ function renote(visibility: Visibility | 'local') {
os.toast(i18n.ts.renoted); os.toast(i18n.ts.renoted);
renoted.value = true; renoted.value = true;
}); });
} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) { } else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
const el = renoteButton.value as HTMLElement | null | undefined; const el = renoteButton.value as HTMLElement | null | undefined;
if (el) { if (el) {
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();

View file

@ -77,7 +77,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onMounted, ref, shallowRef, computed } from 'vue'; import { inject, onMounted, ref, shallowRef, computed } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue';
import MkMediaList from '@/components/MkMediaList.vue'; import MkMediaList from '@/components/MkMediaList.vue';

View file

@ -4,7 +4,7 @@
*/ */
import { VNode, h, defineAsyncComponent, SetupContext } from 'vue'; import { VNode, h, defineAsyncComponent, SetupContext } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkUrl from '@/components/global/MkUrl.vue'; import MkUrl from '@/components/global/MkUrl.vue';
import MkTime from '@/components/global/MkTime.vue'; import MkTime from '@/components/global/MkTime.vue';
@ -52,7 +52,7 @@ type MfmEvents = {
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
export default function(props: MfmProps, context: SetupContext<MfmEvents>) { export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
const isNote = props.isNote ?? true; const isNote = props.isNote ?? true;
const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author?.speakAsCat : false : false : false; const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author.speakAsCat : false : false : false;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (props.text == null || props.text === '') return; if (props.text == null || props.text === '') return;

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { defineAsyncComponent } from 'vue'; import { defineAsyncComponent } from 'vue';
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { TextBlock } from './block.type.js'; import { TextBlock } from './block.type.js';
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';

View file

@ -19,6 +19,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="deeplIsPro"> <MkSwitch v-model="deeplIsPro">
<template #label>Pro account</template> <template #label>Pro account</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="deeplFreeMode">
<template #label>{{ i18n.ts.deeplFreeMode }}</template>
</MkSwitch>
<MkInput v-if="deeplFreeMode" v-model="deeplFreeInstance" :placeholder="'example.com/translate'">
<template #prefix><i class="ph-globe-simple ph-bold ph-lg"></i></template>
<template #label>DeepLX-JS URL</template>
<template #caption>{{ i18n.ts.deeplFreeModeDescription }}</template>
</MkInput>
</div> </div>
</FormSection> </FormSection>
</FormSuspense> </FormSuspense>
@ -49,17 +57,23 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
const deeplAuthKey = ref<string>(''); const deeplAuthKey = ref<string>('');
const deeplIsPro = ref<boolean>(false); const deeplIsPro = ref<boolean>(false);
const deeplFreeMode = ref<boolean>(false);
const deeplFreeInstance = ref<string>('');
async function init() { async function init() {
const meta = await misskeyApi('admin/meta'); const meta = await misskeyApi('admin/meta');
deeplAuthKey.value = meta.deeplAuthKey; deeplAuthKey.value = meta.deeplAuthKey;
deeplIsPro.value = meta.deeplIsPro; deeplIsPro.value = meta.deeplIsPro;
deeplFreeMode.value = meta.deeplFreeMode;
deeplFreeInstance.value = meta.deeplFreeInstance;
} }
function save() { function save() {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
deeplAuthKey: deeplAuthKey.value, deeplAuthKey: deeplAuthKey.value,
deeplIsPro: deeplIsPro.value, deeplIsPro: deeplIsPro.value,
deeplFreeMode: deeplFreeMode.value,
deeplFreeInstance: deeplFreeInstance.value,
}).then(() => { }).then(() => {
fetchInstance(); fetchInstance();
}); });

View file

@ -1,4 +1,4 @@
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
export function checkAnimationFromMfm(nodes: mfm.MfmNode[]): boolean { export function checkAnimationFromMfm(nodes: mfm.MfmNode[]): boolean {
const animatedNodes = mfm.extract(nodes, (node) => { const animatedNodes = mfm.extract(nodes, (node) => {

View file

@ -5,7 +5,7 @@
// test is located in test/extract-mentions // test is located in test/extract-mentions
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] { export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
// TODO: 重複を削除 // TODO: 重複を削除

View file

@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import * as mfm from '@sharkey/sfm-js'; import * as mfm from '@transfem-org/sfm-js';
import { unique } from '@/scripts/array.js'; import { unique } from '@/scripts/array.js';
// unique without hash // unique without hash

View file

@ -64,7 +64,11 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
try { try {
ast = parser.parse(code); ast = parser.parse(code);
} catch (err) { } catch (err) {
throw new Error('Aiscript syntax error'); if (err instanceof Error) {
throw new Error(`Aiscript syntax error\n${(err as Error).message}`);
} else {
throw new Error('Aiscript syntax error');
}
} }
const meta = Interpreter.collectMetadata(ast); const meta = Interpreter.collectMetadata(ast);

View file

@ -85,6 +85,9 @@ type AdminAnnouncementsListResponse = operations['admin/announcements/list']['re
// @public (undocumented) // @public (undocumented)
type AdminAnnouncementsUpdateRequest = operations['admin/announcements/update']['requestBody']['content']['application/json']; type AdminAnnouncementsUpdateRequest = operations['admin/announcements/update']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminApproveUserRequest = operations['admin/approve-user']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type AdminAvatarDecorationsCreateRequest = operations['admin/avatar-decorations/create']['requestBody']['content']['application/json']; type AdminAvatarDecorationsCreateRequest = operations['admin/avatar-decorations/create']['requestBody']['content']['application/json'];
@ -208,6 +211,9 @@ type AdminInviteListResponse = operations['admin/invite/list']['responses']['200
// @public (undocumented) // @public (undocumented)
type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json']; type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json'];
// @public (undocumented)
type AdminNsfwUserRequest = operations['admin/nsfw-user']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json']; type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json'];
@ -304,15 +310,24 @@ type AdminShowUsersRequest = operations['admin/show-users']['requestBody']['cont
// @public (undocumented) // @public (undocumented)
type AdminShowUsersResponse = operations['admin/show-users']['responses']['200']['content']['application/json']; type AdminShowUsersResponse = operations['admin/show-users']['responses']['200']['content']['application/json'];
// @public (undocumented)
type AdminSilenceUserRequest = operations['admin/silence-user']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json']; type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminUnnsfwUserRequest = operations['admin/unnsfw-user']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json']; type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json']; type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminUnsilenceUserRequest = operations['admin/unsilence-user']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json']; type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json'];
@ -594,6 +609,13 @@ export type Channels = {
}; };
receives: null; receives: null;
}; };
bubbleTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
userList: { userList: {
params: { params: {
listId: string; listId: string;
@ -1165,7 +1187,12 @@ declare namespace entities {
AdminShowUserResponse, AdminShowUserResponse,
AdminShowUsersRequest, AdminShowUsersRequest,
AdminShowUsersResponse, AdminShowUsersResponse,
AdminNsfwUserRequest,
AdminUnnsfwUserRequest,
AdminSilenceUserRequest,
AdminUnsilenceUserRequest,
AdminSuspendUserRequest, AdminSuspendUserRequest,
AdminApproveUserRequest,
AdminUnsuspendUserRequest, AdminUnsuspendUserRequest,
AdminUpdateMetaRequest, AdminUpdateMetaRequest,
AdminDeleteAccountRequest, AdminDeleteAccountRequest,
@ -1393,6 +1420,7 @@ declare namespace entities {
IGalleryPostsResponse, IGalleryPostsResponse,
IImportBlockingRequest, IImportBlockingRequest,
IImportFollowingRequest, IImportFollowingRequest,
IImportNotesRequest,
IImportMutingRequest, IImportMutingRequest,
IImportUserListsRequest, IImportUserListsRequest,
IImportAntennasRequest, IImportAntennasRequest,
@ -1410,6 +1438,7 @@ declare namespace entities {
IRegenerateTokenRequest, IRegenerateTokenRequest,
IRegistryGetAllRequest, IRegistryGetAllRequest,
IRegistryGetAllResponse, IRegistryGetAllResponse,
IRegistryGetUnsecureRequest,
IRegistryGetDetailRequest, IRegistryGetDetailRequest,
IRegistryGetDetailResponse, IRegistryGetDetailResponse,
IRegistryGetRequest, IRegistryGetRequest,
@ -1477,6 +1506,8 @@ declare namespace entities {
NotesFeaturedResponse, NotesFeaturedResponse,
NotesGlobalTimelineRequest, NotesGlobalTimelineRequest,
NotesGlobalTimelineResponse, NotesGlobalTimelineResponse,
NotesBubbleTimelineRequest,
NotesBubbleTimelineResponse,
NotesHybridTimelineRequest, NotesHybridTimelineRequest,
NotesHybridTimelineResponse, NotesHybridTimelineResponse,
NotesLocalTimelineRequest, NotesLocalTimelineRequest,
@ -1490,6 +1521,7 @@ declare namespace entities {
NotesReactionsResponse, NotesReactionsResponse,
NotesReactionsCreateRequest, NotesReactionsCreateRequest,
NotesReactionsDeleteRequest, NotesReactionsDeleteRequest,
NotesLikeRequest,
NotesRenotesRequest, NotesRenotesRequest,
NotesRenotesResponse, NotesRenotesResponse,
NotesRepliesRequest, NotesRepliesRequest,
@ -1511,6 +1543,10 @@ declare namespace entities {
NotesUnrenoteRequest, NotesUnrenoteRequest,
NotesUserListTimelineRequest, NotesUserListTimelineRequest,
NotesUserListTimelineResponse, NotesUserListTimelineResponse,
NotesEditRequest,
NotesEditResponse,
NotesVersionsRequest,
NotesVersionsResponse,
NotificationsCreateRequest, NotificationsCreateRequest,
PagePushRequest, PagePushRequest,
PagesCreateRequest, PagesCreateRequest,
@ -1619,6 +1655,7 @@ declare namespace entities {
FetchExternalResourcesRequest, FetchExternalResourcesRequest,
FetchExternalResourcesResponse, FetchExternalResourcesResponse,
RetentionResponse, RetentionResponse,
SponsorsRequest,
BubbleGameRegisterRequest, BubbleGameRegisterRequest,
BubbleGameRegisterResponse, BubbleGameRegisterResponse,
BubbleGameRankingRequest, BubbleGameRankingRequest,
@ -2016,6 +2053,9 @@ type IImportFollowingRequest = operations['i/import-following']['requestBody']['
// @public (undocumented) // @public (undocumented)
type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json']; type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
// @public (undocumented)
type IImportNotesRequest = operations['i/import-notes']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json']; type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json'];
@ -2097,6 +2137,9 @@ type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content'
// @public (undocumented) // @public (undocumented)
type IRegistryGetResponse = operations['i/registry/get']['responses']['200']['content']['application/json']; type IRegistryGetResponse = operations['i/registry/get']['responses']['200']['content']['application/json'];
// @public (undocumented)
type IRegistryGetUnsecureRequest = operations['i/registry/get-unsecure']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json']; type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json'];
@ -2196,6 +2239,9 @@ type ModerationLog = {
} & ({ } & ({
type: 'updateServerSettings'; type: 'updateServerSettings';
info: ModerationLogPayloads['updateServerSettings']; info: ModerationLogPayloads['updateServerSettings'];
} | {
type: 'approve';
info: ModerationLogPayloads['approve'];
} | { } | {
type: 'suspend'; type: 'suspend';
info: ModerationLogPayloads['suspend']; info: ModerationLogPayloads['suspend'];
@ -2307,7 +2353,7 @@ type ModerationLog = {
}); });
// @public (undocumented) // @public (undocumented)
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"]; export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
// @public (undocumented) // @public (undocumented)
type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json']; type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json'];
@ -2342,6 +2388,12 @@ type NoteFavorite = components['schemas']['NoteFavorite'];
// @public (undocumented) // @public (undocumented)
type NoteReaction = components['schemas']['NoteReaction']; type NoteReaction = components['schemas']['NoteReaction'];
// @public (undocumented)
type NotesBubbleTimelineRequest = operations['notes/bubble-timeline']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesBubbleTimelineResponse = operations['notes/bubble-timeline']['responses']['200']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type NotesChildrenRequest = operations['notes/children']['requestBody']['content']['application/json']; type NotesChildrenRequest = operations['notes/children']['requestBody']['content']['application/json'];
@ -2369,6 +2421,12 @@ type NotesCreateResponse = operations['notes/create']['responses']['200']['conte
// @public (undocumented) // @public (undocumented)
type NotesDeleteRequest = operations['notes/delete']['requestBody']['content']['application/json']; type NotesDeleteRequest = operations['notes/delete']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesEditRequest = operations['notes/edit']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesEditResponse = operations['notes/edit']['responses']['200']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type NotesFavoritesCreateRequest = operations['notes/favorites/create']['requestBody']['content']['application/json']; type NotesFavoritesCreateRequest = operations['notes/favorites/create']['requestBody']['content']['application/json'];
@ -2393,6 +2451,9 @@ type NotesHybridTimelineRequest = operations['notes/hybrid-timeline']['requestBo
// @public (undocumented) // @public (undocumented)
type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json']; type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json'];
// @public (undocumented)
type NotesLikeRequest = operations['notes/like']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json']; type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json'];
@ -2495,6 +2556,12 @@ type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requ
// @public (undocumented) // @public (undocumented)
type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json']; type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json'];
// @public (undocumented)
type NotesVersionsRequest = operations['notes/versions']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesVersionsResponse = operations['notes/versions']['responses']['200']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
export const noteVisibilities: readonly ["public", "home", "followers", "specified"]; export const noteVisibilities: readonly ["public", "home", "followers", "specified"];
@ -2553,7 +2620,7 @@ type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['
function parse(acct: string): Acct; function parse(acct: string): Acct;
// @public (undocumented) // @public (undocumented)
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"]; export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
// @public (undocumented) // @public (undocumented)
type PingResponse = operations['ping']['responses']['200']['content']['application/json']; type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
@ -2746,6 +2813,9 @@ type SignupResponse = MeDetailed & {
token: string; token: string;
}; };
// @public (undocumented)
type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
// @public (undocumented) // @public (undocumented)
type StatsResponse = operations['stats']['responses']['200']['content']['application/json']; type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];

View file

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.6 * version: 2024.2.0-beta2
* generatedAt: 2024-01-24T07:32:10.455Z * generatedAt: 2024-01-26T20:30:18.423Z
*/ */
import type { SwitchCaseResponseType } from '../api.js'; import type { SwitchCaseResponseType } from '../api.js';
@ -691,6 +691,50 @@ declare module '../api.js' {
credential?: string | null, credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>; ): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
*/
request<E extends 'admin/nsfw-user', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
*/
request<E extends 'admin/unnsfw-user', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
*/
request<E extends 'admin/silence-user', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
*/
request<E extends 'admin/unsilence-user', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/** /**
* No description provided. * No description provided.
* *
@ -702,6 +746,17 @@ declare module '../api.js' {
credential?: string | null, credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>; ): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
*/
request<E extends 'admin/approve-user', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/** /**
* No description provided. * No description provided.
* *
@ -2514,6 +2569,17 @@ declare module '../api.js' {
credential?: string | null, credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>; ): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *read:account*
*/
request<E extends 'i/registry/get-unsecure', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/** /**
* No description provided. * No description provided.
* *
@ -2993,6 +3059,17 @@ declare module '../api.js' {
credential?: string | null, credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>; ): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *No*
*/
request<E extends 'notes/bubble-timeline', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/** /**
* No description provided. * No description provided.
* *
@ -4054,7 +4131,8 @@ declare module '../api.js' {
credential?: string | null, credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>; ): Promise<SwitchCaseResponseType<E, P>>;
/** No description provided. /**
* No description provided.
* *
* **Credential required**: *Yes* / **Permission**: *write:account* * **Credential required**: *Yes* / **Permission**: *write:account*
*/ */

View file

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.6 * version: 2024.2.0-beta2
* generatedAt: 2024-01-24T07:32:10.453Z * generatedAt: 2024-01-26T20:30:18.421Z
*/ */
import type { import type {

View file

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.6 * version: 2024.2.0-beta2
* generatedAt: 2024-01-24T07:32:10.452Z * generatedAt: 2024-01-26T20:30:18.419Z
*/ */
import { operations } from './types.js'; import { operations } from './types.js';

View file

@ -1,6 +1,6 @@
/* /*
* version: 2024.2.0-beta.6 * version: 2024.2.0-beta2
* generatedAt: 2024-01-24T07:32:10.450Z * generatedAt: 2024-01-26T20:30:18.418Z
*/ */
import { components } from './types.js'; import { components } from './types.js';

View file

@ -2,8 +2,8 @@
/* eslint @typescript-eslint/no-explicit-any: 0 */ /* eslint @typescript-eslint/no-explicit-any: 0 */
/* /*
* version: 2024.2.0-beta.6 * version: 2024.2.0-beta2
* generatedAt: 2024-01-24T07:32:10.370Z * generatedAt: 2024-01-26T20:30:18.319Z
*/ */
/** /**
@ -4784,6 +4784,8 @@ export type operations = {
backgroundImageUrl: string | null; backgroundImageUrl: string | null;
deeplAuthKey: string | null; deeplAuthKey: string | null;
deeplIsPro: boolean; deeplIsPro: boolean;
deeplFreeMode: boolean;
deeplFreeInstance: string | null;
defaultDarkTheme: string | null; defaultDarkTheme: string | null;
defaultLightTheme: string | null; defaultLightTheme: string | null;
description: string | null; description: string | null;
@ -8795,6 +8797,8 @@ export type operations = {
summalyProxy?: string | null; summalyProxy?: string | null;
deeplAuthKey?: string | null; deeplAuthKey?: string | null;
deeplIsPro?: boolean; deeplIsPro?: boolean;
deeplFreeMode?: boolean;
deeplFreeInstance?: string | null;
enableEmail?: boolean; enableEmail?: boolean;
email?: string | null; email?: string | null;
smtpSecure?: boolean; smtpSecure?: boolean;
@ -14101,6 +14105,7 @@ export type operations = {
subscribing?: boolean | null; subscribing?: boolean | null;
publishing?: boolean | null; publishing?: boolean | null;
nsfw?: boolean | null; nsfw?: boolean | null;
bubble?: boolean | null;
/** @default 30 */ /** @default 30 */
limit?: number; limit?: number;
/** @default 0 */ /** @default 0 */
@ -26440,9 +26445,40 @@ export type operations = {
204: { 204: {
content: never; content: never;
}; };
/** @description Client error */
400: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Authentication error */
401: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Forbidden error */
403: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description I'm Ai */
418: {
content: {
'application/json': components['schemas']['Error'];
};
};
/** @description Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
}; };
}; };
/** bubble-game/register /**
* bubble-game/register
* @description No description provided. * @description No description provided.
* *
* **Credential required**: *Yes* / **Permission**: *write:account* * **Credential required**: *Yes* / **Permission**: *write:account*

View file

@ -115,9 +115,6 @@ importers:
'@peertube/http-signature': '@peertube/http-signature':
specifier: 1.7.0 specifier: 1.7.0
version: 1.7.0 version: 1.7.0
'@sharkey/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@simplewebauthn/server': '@simplewebauthn/server':
specifier: 9.0.0 specifier: 9.0.0
version: 9.0.0 version: 9.0.0
@ -133,6 +130,9 @@ importers:
'@swc/core': '@swc/core':
specifier: 1.3.105 specifier: 1.3.105
version: 1.3.105 version: 1.3.105
'@transfem-org/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@twemoji/parser': '@twemoji/parser':
specifier: 15.0.0 specifier: 15.0.0
version: 15.0.0 version: 15.0.0
@ -696,12 +696,12 @@ importers:
'@rollup/pluginutils': '@rollup/pluginutils':
specifier: 5.1.0 specifier: 5.1.0
version: 5.1.0(rollup@4.9.6) version: 5.1.0(rollup@4.9.6)
'@sharkey/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@syuilo/aiscript': '@syuilo/aiscript':
specifier: 0.17.0 specifier: 0.17.0
version: 0.17.0 version: 0.17.0
'@transfem-org/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@twemoji/parser': '@twemoji/parser':
specifier: 15.0.0 specifier: 15.0.0
version: 15.0.0 version: 15.0.0
@ -5782,12 +5782,6 @@ packages:
string-argv: 0.3.1 string-argv: 0.3.1
dev: true dev: true
/@sharkey/sfm-js@0.24.4:
resolution: {integrity: sha512-m9reKRceS8kmFEPFlK2nhbN6mP9kE1I895WAc9OlFISMdT5L+IdvBW1NHv8dzjSPSTFZLeCfMHbys1oz+5uLuA==, tarball: https://git.joinsharkey.org/api/packages/Sharkey/npm/%40sharkey%2Fsfm-js/-/0.24.4/sfm-js-0.24.4.tgz}
dependencies:
'@twemoji/parser': 15.0.0
dev: false
/@sideway/address@4.1.4: /@sideway/address@4.1.4:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
dependencies: dependencies:
@ -7513,6 +7507,12 @@ packages:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
dev: false dev: false
/@transfem-org/sfm-js@0.24.4:
resolution: {integrity: sha1-0wEXqL5UJseGFO4GGFRrES6NCDk=, tarball: https://activitypub.software/api/v4/projects/2/packages/npm/@transfem-org/sfm-js/-/@transfem-org/sfm-js-0.24.4.tgz}
dependencies:
'@twemoji/parser': 15.0.0
dev: false
/@trysound/sax@0.2.0: /@trysound/sax@0.2.0:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}