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

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>
<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"
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."
deeplFreeMode: "Use DeepLX-JS (No Auth Key)"
deeplFreeModeDescription: "Need Help? Check our documentation to know how to setup DeepLX-JS."
serverLogs: "Server logs"
deleteAll: "Delete all"
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などの使用時に有効にする必要がある場合があります
*/
"s3ForcePathStyleDesc": string;
/**
* DeepLX-JS 使 ()
*/
"deeplFreeMode": string;
/**
* ? DeepLX-JSのセットアップ方法については
*/
"deeplFreeModeDescription": string;
/**
*
*/

View file

@ -556,6 +556,8 @@ objectStorageUseProxy: "Proxyを利用する"
objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
objectStorageSetPublicRead: "アップロード時に'public-read'を設定する"
s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。"
deeplFreeMode: "DeepLX-JS を使用する (認証キーなし)"
deeplFreeModeDescription: "ヘルプが必要ですか? DeepLX-JSのセットアップ方法については、ドキュメントを参照してください。"
serverLogs: "サーバーログ"
deleteAll: "全て削除"
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/testing": "10.2.10",
"@peertube/http-signature": "1.7.0",
"@sharkey/sfm-js": "0.24.4",
"@transfem-org/sfm-js": "0.24.4",
"@simplewebauthn/server": "9.0.0",
"@sinonjs/fake-timers": "11.2.2",
"@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 { bindThis } from '@/decorators.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;
@ -476,173 +476,173 @@ export class MfmService {
const handlers: {
[K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
} = {
async bold(node) {
const el = doc.createElement('span');
el.textContent = '**';
await appendChildren(node.children, el);
el.textContent += '**';
return el;
},
async bold(node) {
const el = doc.createElement('span');
el.textContent = '**';
await appendChildren(node.children, el);
el.textContent += '**';
return el;
},
async small(node) {
const el = doc.createElement('small');
await appendChildren(node.children, el);
return el;
},
async small(node) {
const el = doc.createElement('small');
await appendChildren(node.children, el);
return el;
},
async strike(node) {
const el = doc.createElement('span');
el.textContent = '~~';
await appendChildren(node.children, el);
el.textContent += '~~';
return el;
},
async strike(node) {
const el = doc.createElement('span');
el.textContent = '~~';
await appendChildren(node.children, el);
el.textContent += '~~';
return el;
},
async italic(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},
async italic(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},
async fn(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},
async fn(node) {
const el = doc.createElement('span');
el.textContent = '*';
await appendChildren(node.children, el);
el.textContent += '*';
return el;
},
blockCode(node) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');
blockCode(node) {
const pre = doc.createElement('pre');
const inner = doc.createElement('code');
const nodes = node.props.code
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));
const nodes = node.props.code
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));
for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
inner.appendChild(x === 'br' ? doc.createElement('br') : x);
}
for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
inner.appendChild(x === 'br' ? doc.createElement('br') : x);
}
pre.appendChild(inner);
return pre;
},
pre.appendChild(inner);
return pre;
},
async center(node) {
const el = doc.createElement('div');
await appendChildren(node.children, el);
return el;
},
async center(node) {
const el = doc.createElement('div');
await appendChildren(node.children, el);
return el;
},
emojiCode(node) {
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
},
emojiCode(node) {
return doc.createTextNode(`\u200B:${node.props.name}:\u200B`);
},
unicodeEmoji(node) {
return doc.createTextNode(node.props.emoji);
},
unicodeEmoji(node) {
return doc.createTextNode(node.props.emoji);
},
hashtag: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
a.textContent = `#${node.props.hashtag}`;
a.setAttribute('rel', 'tag');
a.setAttribute('class', 'hashtag');
return a;
},
hashtag: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
a.textContent = `#${node.props.hashtag}`;
a.setAttribute('rel', 'tag');
a.setAttribute('class', 'hashtag');
return a;
},
inlineCode(node) {
const el = doc.createElement('code');
el.textContent = node.props.code;
return el;
},
inlineCode(node) {
const el = doc.createElement('code');
el.textContent = node.props.code;
return el;
},
mathInline(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},
mathInline(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},
mathBlock(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},
mathBlock(node) {
const el = doc.createElement('code');
el.textContent = node.props.formula;
return el;
},
async link(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
await appendChildren(node.children, a);
return a;
},
async link(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
await appendChildren(node.children, a);
return a;
},
async mention(node) {
const { username, host, acct } = node.props;
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
async mention(node) {
const { username, host, acct } = node.props;
const resolved = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
const el = doc.createElement('span');
if (!resolved) {
el.textContent = acct;
} else {
el.setAttribute('class', 'h-card');
el.setAttribute('translate', 'no');
const a = doc.createElement('a');
a.setAttribute('href', resolved.url ? resolved.url : resolved.uri);
a.className = 'u-url mention';
const span = doc.createElement('span');
span.textContent = resolved.username || username;
a.textContent = '@';
a.appendChild(span);
el.appendChild(a);
}
const el = doc.createElement('span');
if (!resolved) {
el.textContent = acct;
} else {
el.setAttribute('class', 'h-card');
el.setAttribute('translate', 'no');
const a = doc.createElement('a');
a.setAttribute('href', resolved.url ? resolved.url : resolved.uri);
a.className = 'u-url mention';
const span = doc.createElement('span');
span.textContent = resolved.username || username;
a.textContent = '@';
a.appendChild(span);
el.appendChild(a);
}
return el;
},
return el;
},
async quote(node) {
const el = doc.createElement('blockquote');
await appendChildren(node.children, el);
return el;
},
async quote(node) {
const el = doc.createElement('blockquote');
await appendChildren(node.children, el);
return el;
},
text(node) {
const el = doc.createElement('span');
const nodes = node.props.text
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));
text(node) {
const el = doc.createElement('span');
const nodes = node.props.text
.split(/\r\n|\r|\n/)
.map((x) => doc.createTextNode(x));
for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
el.appendChild(x === 'br' ? doc.createElement('br') : x);
}
for (const x of intersperse<FIXME | 'br'>('br', nodes)) {
el.appendChild(x === 'br' ? doc.createElement('br') : x);
}
return el;
},
return el;
},
url(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
a.textContent = node.props.url.replace(/^https?:\/\//, '');
return a;
},
url(node) {
const a = doc.createElement('a');
a.setAttribute('rel', 'nofollow noopener noreferrer');
a.setAttribute('target', '_blank');
a.setAttribute('href', node.props.url);
a.textContent = node.props.url.replace(/^https?:\/\//, '');
return a;
},
search: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `https"google.com/${node.props.query}`);
a.textContent = node.props.content;
return a;
},
search: (node) => {
const a = doc.createElement('a');
a.setAttribute('href', `https"google.com/${node.props.query}`);
a.textContent = node.props.content;
return a;
},
async plain(node) {
const el = doc.createElement('span');
await appendChildren(node.children, el);
return el;
},
async plain(node) {
const el = doc.createElement('span');
await appendChildren(node.children, el);
return el;
},
};
await appendChildren(nodes, doc.body);

View file

@ -4,7 +4,7 @@
*/
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 * as Redis from 'ioredis';
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.trim();
if (data.text === '') {
data.text = null;
}
} else {
data.text = null;
}
@ -807,7 +810,7 @@ export class NoteCreateService implements OnApplicationShutdown {
const muted = isUserRelated(note, userIdsWhoMeMuting);
if (!isThreadMuted || !muted) {
if (!isThreadMuted && !muted) {
nm.push(data.reply.userId, 'reply');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
@ -842,7 +845,7 @@ export class NoteCreateService implements OnApplicationShutdown {
const muted = isUserRelated(note, userIdsWhoMeMuting);
if (!isThreadMuted || !muted) {
if (!isThreadMuted && !muted) {
nm.push(data.renote.userId, type);
}
}

View file

@ -4,7 +4,7 @@
*/
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 * as Redis from 'ioredis';
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
@ -46,6 +46,11 @@ import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { FanoutTimelineService } from '@/core/FanoutTimelineService.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';
@ -206,6 +211,8 @@ export class NoteEditService implements OnApplicationShutdown {
private activeUsersChart: ActiveUsersChart,
private instanceChart: InstanceChart,
private utilityService: UtilityService,
private userBlockingService: UserBlockingService,
private cacheService: CacheService,
) { }
@bindThis
@ -222,7 +229,7 @@ export class NoteEditService implements OnApplicationShutdown {
const oldnote = await this.notesRepository.findOneBy({
id: editid,
});
});
if (oldnote == null) {
throw new Error('no such note');
@ -255,16 +262,18 @@ export class NoteEditService implements OnApplicationShutdown {
if (data.channel != null) data.localOnly = true;
if (data.updatedAt == null) data.updatedAt = new Date();
const meta = await this.metaService.fetch();
if (data.visibility === 'public' && data.channel == null) {
const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
if (this.isSensitive(data, sensitiveWords)) {
const sensitiveWords = meta.sensitiveWords;
if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
data.visibility = 'home';
} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
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) {
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にする
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
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.trim();
if (data.text === '') {
data.text = null;
}
} else {
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> = {};
if (data.text !== oldnote.text) {
update.text = data.text;
@ -557,7 +589,7 @@ export class NoteEditService implements OnApplicationShutdown {
} else {
this.globalEventService.publishNoteStream(note.id, 'updated', {
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');
this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
@ -603,11 +643,28 @@ export class NoteEditService implements OnApplicationShutdown {
// If it is renote
if (data.renote) {
const type = data.text ? 'quote' : 'renote';
const type = this.isQuote(data) ? 'quote' : 'renote';
// Notify
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
@ -657,7 +714,7 @@ export class NoteEditService implements OnApplicationShutdown {
this.relayService.deliverToRelays(user, noteActivity);
}
dm.execute();
trackPromise(dm.execute());
})();
}
//#endregion
@ -686,28 +743,9 @@ export class NoteEditService implements OnApplicationShutdown {
}
@bindThis
private isSensitive(note: Option, sensitiveWord: string[]): boolean {
if (sensitiveWord.length > 0) {
const text = note.cw ?? note.text ?? '';
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;
private isQuote(note: Option): note is Option & { renote: MiNote } {
// sync with misc/is-quote.ts
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
}
@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;
}
@ -748,7 +794,7 @@ export class NoteEditService implements OnApplicationShutdown {
const user = await this.usersRepository.findOneBy({ id: note.userId });
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.renderUpdate(await this.apRendererService.renderUpNote(note, false), user);
@ -782,6 +828,7 @@ export class NoteEditService implements OnApplicationShutdown {
@bindThis
private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
const meta = await this.metaService.fetch();
if (!meta.enableFanoutTimeline) return;
const r = this.redisForTimelines.pipeline();
@ -825,7 +872,7 @@ export class NoteEditService implements OnApplicationShutdown {
if (note.visibility === 'followers') {
// 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万件程度を目安に分割して実行するようにする
@ -834,7 +881,7 @@ export class NoteEditService implements OnApplicationShutdown {
if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
if (isReply(note, following.followerId)) {
if (!following.withReplies) continue;
}
@ -848,11 +895,12 @@ export class NoteEditService implements OnApplicationShutdown {
// ダイレクトのとき、そのリストが対象外のユーザーの場合
if (
note.visibility === 'specified' &&
note.userId !== userListMembership.userListUserId &&
!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
) continue;
// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
if (isReply(note, userListMembership.userListUserId)) {
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);
if (note.visibility === 'public' && note.userHost == null) {
this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
if (note.replyUserHost == null) {
this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r);
}
}
} else {
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 * as mfm from '@sharkey/sfm-js';
import * as mfm from '@transfem-org/sfm-js';
import { MfmService } from '@/core/MfmService.js';
import type { MiNote } from '@/models/Note.js';
import { bindThis } from '@/decorators.js';

View file

@ -6,7 +6,7 @@
import { createPublicKey, randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
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 type { Config } from '@/config.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 { isNotNull } from '@/misc/is-not-null.js';
import { IdService } from '@/core/IdService.js';
import { MetaService } from '../MetaService.js';
import { LdSignatureService } from './LdSignatureService.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 { MetaService } from '../MetaService.js';
@Injectable()
export class ApRendererService {
@ -821,12 +821,12 @@ export class ApRendererService {
'_misskey_summary': 'misskey:_misskey_summary',
'isCat': 'misskey:isCat',
// Firefish
firefish: "https://joinfirefish.org/ns#",
speakAsCat: "firefish:speakAsCat",
firefish: 'https://joinfirefish.org/ns#',
speakAsCat: 'firefish:speakAsCat',
// Sharkey
sharkey: "https://joinsharkey.org/ns#",
backgroundUrl: "sharkey:backgroundUrl",
listenbrainz: "sharkey:listenbrainz",
sharkey: 'https://joinsharkey.org/ns#',
backgroundUrl: 'sharkey:backgroundUrl',
listenbrainz: 'sharkey:listenbrainz',
// vcard
vcard: 'http://www.w3.org/2006/vcard/ns#',
},

View file

@ -3,7 +3,7 @@
* 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';
export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] {

View file

@ -3,7 +3,7 @@
* 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';
export function extractHashtags(nodes: mfm.MfmNode[]): string[] {

View file

@ -5,7 +5,7 @@
// 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'][] {
// TODO: 重複を削除

View file

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

View file

@ -1,4 +1,5 @@
import * as fs from 'node:fs';
import * as fsp from 'node:fs/promises';
import * as vm from 'node:vm';
import * as crypto from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
@ -51,7 +52,7 @@ export class ImportNotesProcessorService {
@bindThis
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) {
const name = `${dir}/${file}`;
if (fs.statSync(name).isDirectory()) {
@ -130,14 +131,16 @@ export class ImportNotesProcessorService {
return typeof obj[Symbol.iterator] === 'function';
}
private parseTwitterFile(str : string) : null | [{ tweet: any }] {
const removed = str.replace(new RegExp('window\\.YTD\\.tweets\\.part0 = ', 'g'), '');
@bindThis
private parseTwitterFile(str : string) : { tweet: object }[] {
const jsonStr = str.replace(/^\s*window\.YTD\.tweets\.part0\s*=\s*/, '');
try {
return JSON.parse(removed);
return JSON.parse(jsonStr);
} catch (error) {
//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';
try {
fs.writeFileSync(destPath, '', 'binary');
await fsp.writeFile(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') {
@ -185,21 +188,13 @@ export class ImportNotesProcessorService {
const outputPath = path + '/twitter';
try {
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)
if (unprocessedTweetJson) {
const tweets = Object.keys(unprocessedTweetJson).reduce((m, key, i, obj) => {
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');
}
const tweets = unprocessedTweets.map(e => e.tweet);
const processedTweets = await this.recreateChain(['id_str'], ['in_reply_to_status_id_str'], tweets, false);
this.queueService.createImportTweetsToDbJob(job.data.user, processedTweets, null);
} finally {
cleanup();
}
@ -211,7 +206,7 @@ export class ImportNotesProcessorService {
const destPath = path + '/facebook.zip';
try {
fs.writeFileSync(destPath, '', 'binary');
await fsp.writeFile(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') {
@ -223,8 +218,8 @@ export class ImportNotesProcessorService {
const outputPath = path + '/facebook';
try {
this.logger.succ(`Unzipping to ${outputPath}`);
ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath));
const postsJson = fs.readFileSync(outputPath + '/your_activity_across_facebook/posts/your_posts__check_ins__photos_and_videos_1.json', 'utf-8');
ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
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 facebookFolder = await this.driveFoldersRepository.findOneBy({ name: 'Facebook', userId: job.data.user.id, parentId: folder?.id });
if (facebookFolder == null && folder) {
@ -244,7 +239,7 @@ export class ImportNotesProcessorService {
const destPath = path + '/unknown.zip';
try {
fs.writeFileSync(destPath, '', 'binary');
await fsp.writeFile(destPath, '', 'binary');
await this.downloadService.downloadUrl(file.url, destPath);
} catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') {
@ -256,11 +251,11 @@ export class ImportNotesProcessorService {
const outputPath = path + '/unknown';
try {
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 isOutbox = type === 'Mastodon' || fs.existsSync(outputPath + '/outbox.json');
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 igFolder = await this.driveFoldersRepository.findOneBy({ name: 'Instagram', userId: job.data.user.id, parentId: folder?.id });
if (igFolder == null && folder) {
@ -270,16 +265,16 @@ export class ImportNotesProcessorService {
}
this.queueService.createImportIGToDbJob(job.data.user, posts);
} 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 isPleroma = actor['@context'].some((v: any) => typeof v === 'string' && v.match(/litepub(.*)/));
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 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);
} 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);
let mastoFolder = await this.driveFoldersRepository.findOneBy({ name: 'Mastodon', userId: job.data.user.id, parentId: folder?.id });
if (mastoFolder == null && folder) {
@ -302,7 +297,7 @@ export class ImportNotesProcessorService {
this.logger.info(`Temp dir is ${path}`);
try {
fs.writeFileSync(path, '', 'utf-8');
await fsp.writeFile(path, '', 'utf-8');
await this.downloadService.downloadUrl(file.url, path);
} catch (e) { // TODO: 何度か再試行
if (e instanceof Error || typeof e === 'string') {
@ -311,7 +306,7 @@ export class ImportNotesProcessorService {
throw e;
}
const notesJson = fs.readFileSync(path, 'utf-8');
const notesJson = await fsp.readFile(path, 'utf-8');
const notes = JSON.parse(notesJson);
const processedNotes = await this.recreateChain(['id'], ['replyId'], notes, false);
this.queueService.createImportKeyNotesToDbJob(job.data.user, processedNotes, null);
@ -590,7 +585,8 @@ export class ImportNotesProcessorService {
try {
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 files: MiDriveFile[] = [];

View file

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

View file

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

View file

@ -4,7 +4,7 @@
*/
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 ms from 'ms';
import { JSDOM } from 'jsdom';

View file

@ -411,7 +411,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
enableEmail: instance.enableEmail,
enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null,
translatorAvailable: instance.deeplAuthKey != null || instance.deeplFreeMode && instance.deeplFreeInstance,
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();
if (instance.deeplAuthKey == null) {
if (instance.deeplAuthKey == null && !instance.deeplFreeMode) {
return 204; // TODO: 良い感じのエラー返す
}
if (instance.deeplFreeMode && !instance.deeplFreeInstance) {
return 204;
}
let targetLang = ps.targetLang;
if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
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('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, {
method: 'POST',
@ -103,18 +107,37 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
},
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 {
translations: {
detected_source_language: string;
text: string;
}[];
};
return {
sourceLang: json.translations[0].detected_source_language,
text: json.translations[0].text,
};
} else {
const json = (await res.json()) as {
code: number,
message: string,
data: string,
source_lang: string,
target_lang: string,
alternatives: string[],
};
return {
sourceLang: json.translations[0].detected_source_language,
text: json.translations[0].text,
};
const languageNames = new Intl.DisplayNames(['en'], {
type: 'language',
});
return {
sourceLang: languageNames.of(json.source_lang),
text: json.data,
};
}
});
}
}

View file

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Entity } from 'megalodon';
import mfm from '@sharkey/sfm-js';
import mfm from '@transfem-org/sfm-js';
import { DI } from '@/di-symbols.js';
import { MfmService } from '@/core/MfmService.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 { awaitAll } from '@/misc/prelude/await-all.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { GetterService } from '../GetterService.js';
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
import { IdService } from '@/core/IdService.js';
import { GetterService } from '../GetterService.js';
export enum IdConvertType {
MastodonId,
@ -94,10 +94,10 @@ export class MastoConverters {
text_url: f.url,
meta: {
width: f.properties.width,
height: f.properties.height
height: f.properties.height,
},
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),
spoiler_text: edit.cw ?? '',
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;
history.push(awaitAll(item));

View file

@ -4,7 +4,7 @@
*/
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 { CoreModule } from '@/core/CoreModule.js';

View file

@ -5,7 +5,7 @@
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';
describe('Extract mentions', () => {

View file

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

View file

@ -58,6 +58,21 @@ watch(() => props.lang, (to) => {
</script>
<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) {
padding: 1em;
margin: .5em 0;

View file

@ -204,6 +204,8 @@ watch(v, newValue => {
min-width: calc(100% - 24px);
height: 100%;
padding: 12px;
// the +2.5 rem is because of the line numbers
padding-left: calc(12px + 2.5rem);
line-height: 1.5em;
font-size: 1em;
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>
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 MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
@ -477,7 +477,7 @@ function renote(visibility: Visibility | 'local') {
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;
if (el) {
const rect = el.getBoundingClientRect();

View file

@ -170,7 +170,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="!repliesLoaded" style="padding: 16px">
<MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton>
</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 v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
<MkPagination :pagination="renotesPagination" :disableAutoLoad="true">
@ -221,7 +221,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
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 MkNoteSub from '@/components/MkNoteSub.vue';
import MkNoteSimple from '@/components/MkNoteSimple.vue';
@ -501,7 +501,7 @@ function renote(visibility: Visibility | 'local') {
os.toast(i18n.ts.renoted);
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;
if (el) {
const rect = el.getBoundingClientRect();

View file

@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
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 insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode/';

View file

@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed } from 'vue';
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 MkPoll from '@/components/MkPoll.vue';
import MkButton from '@/components/MkButton.vue';

View file

@ -180,7 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
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 SkNoteSub from '@/components/SkNoteSub.vue';
import SkNoteHeader from '@/components/SkNoteHeader.vue';
@ -478,7 +478,7 @@ function renote(visibility: Visibility | 'local') {
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;
if (el) {
const rect = el.getBoundingClientRect();

View file

@ -229,7 +229,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
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 SkNoteSub from '@/components/SkNoteSub.vue';
import SkNoteSimple from '@/components/SkNoteSimple.vue';
@ -510,7 +510,7 @@ function renote(visibility: Visibility | 'local') {
os.toast(i18n.ts.renoted);
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;
if (el) {
const rect = el.getBoundingClientRect();

View file

@ -77,7 +77,7 @@
<script lang="ts" setup>
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 MkNoteSimple from '@/components/MkNoteSimple.vue';
import MkMediaList from '@/components/MkMediaList.vue';

View file

@ -4,7 +4,7 @@
*/
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 MkUrl from '@/components/global/MkUrl.vue';
import MkTime from '@/components/global/MkTime.vue';
@ -52,7 +52,7 @@ type MfmEvents = {
// eslint-disable-next-line import/no-default-export
export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
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
if (props.text == null || props.text === '') return;

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
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 { TextBlock } from './block.type.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">
<template #label>Pro account</template>
</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>
</FormSection>
</FormSuspense>
@ -49,17 +57,23 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
const deeplAuthKey = ref<string>('');
const deeplIsPro = ref<boolean>(false);
const deeplFreeMode = ref<boolean>(false);
const deeplFreeInstance = ref<string>('');
async function init() {
const meta = await misskeyApi('admin/meta');
deeplAuthKey.value = meta.deeplAuthKey;
deeplIsPro.value = meta.deeplIsPro;
deeplFreeMode.value = meta.deeplFreeMode;
deeplFreeInstance.value = meta.deeplFreeInstance;
}
function save() {
os.apiWithDialog('admin/update-meta', {
deeplAuthKey: deeplAuthKey.value,
deeplIsPro: deeplIsPro.value,
deeplFreeMode: deeplFreeMode.value,
deeplFreeInstance: deeplFreeInstance.value,
}).then(() => {
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 {
const animatedNodes = mfm.extract(nodes, (node) => {

View file

@ -5,7 +5,7 @@
// 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'][] {
// TODO: 重複を削除

View file

@ -3,7 +3,7 @@
* 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';
// unique without hash

View file

@ -64,7 +64,11 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
try {
ast = parser.parse(code);
} 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);

View file

@ -85,6 +85,9 @@ type AdminAnnouncementsListResponse = operations['admin/announcements/list']['re
// @public (undocumented)
type AdminAnnouncementsUpdateRequest = operations['admin/announcements/update']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminApproveUserRequest = operations['admin/approve-user']['requestBody']['content']['application/json'];
// @public (undocumented)
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)
type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json'];
// @public (undocumented)
type AdminNsfwUserRequest = operations['admin/nsfw-user']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json'];
@ -304,15 +310,24 @@ type AdminShowUsersRequest = operations['admin/show-users']['requestBody']['cont
// @public (undocumented)
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)
type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminUnnsfwUserRequest = operations['admin/unnsfw-user']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json'];
// @public (undocumented)
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)
type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json'];
@ -594,6 +609,13 @@ export type Channels = {
};
receives: null;
};
bubbleTimeline: {
params: null;
events: {
note: (payload: Note) => void;
};
receives: null;
};
userList: {
params: {
listId: string;
@ -1165,7 +1187,12 @@ declare namespace entities {
AdminShowUserResponse,
AdminShowUsersRequest,
AdminShowUsersResponse,
AdminNsfwUserRequest,
AdminUnnsfwUserRequest,
AdminSilenceUserRequest,
AdminUnsilenceUserRequest,
AdminSuspendUserRequest,
AdminApproveUserRequest,
AdminUnsuspendUserRequest,
AdminUpdateMetaRequest,
AdminDeleteAccountRequest,
@ -1393,6 +1420,7 @@ declare namespace entities {
IGalleryPostsResponse,
IImportBlockingRequest,
IImportFollowingRequest,
IImportNotesRequest,
IImportMutingRequest,
IImportUserListsRequest,
IImportAntennasRequest,
@ -1410,6 +1438,7 @@ declare namespace entities {
IRegenerateTokenRequest,
IRegistryGetAllRequest,
IRegistryGetAllResponse,
IRegistryGetUnsecureRequest,
IRegistryGetDetailRequest,
IRegistryGetDetailResponse,
IRegistryGetRequest,
@ -1477,6 +1506,8 @@ declare namespace entities {
NotesFeaturedResponse,
NotesGlobalTimelineRequest,
NotesGlobalTimelineResponse,
NotesBubbleTimelineRequest,
NotesBubbleTimelineResponse,
NotesHybridTimelineRequest,
NotesHybridTimelineResponse,
NotesLocalTimelineRequest,
@ -1490,6 +1521,7 @@ declare namespace entities {
NotesReactionsResponse,
NotesReactionsCreateRequest,
NotesReactionsDeleteRequest,
NotesLikeRequest,
NotesRenotesRequest,
NotesRenotesResponse,
NotesRepliesRequest,
@ -1511,6 +1543,10 @@ declare namespace entities {
NotesUnrenoteRequest,
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
NotesEditRequest,
NotesEditResponse,
NotesVersionsRequest,
NotesVersionsResponse,
NotificationsCreateRequest,
PagePushRequest,
PagesCreateRequest,
@ -1619,6 +1655,7 @@ declare namespace entities {
FetchExternalResourcesRequest,
FetchExternalResourcesResponse,
RetentionResponse,
SponsorsRequest,
BubbleGameRegisterRequest,
BubbleGameRegisterResponse,
BubbleGameRankingRequest,
@ -2016,6 +2053,9 @@ type IImportFollowingRequest = operations['i/import-following']['requestBody']['
// @public (undocumented)
type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
// @public (undocumented)
type IImportNotesRequest = operations['i/import-notes']['requestBody']['content']['application/json'];
// @public (undocumented)
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)
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)
type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json'];
@ -2196,6 +2239,9 @@ type ModerationLog = {
} & ({
type: 'updateServerSettings';
info: ModerationLogPayloads['updateServerSettings'];
} | {
type: 'approve';
info: ModerationLogPayloads['approve'];
} | {
type: 'suspend';
info: ModerationLogPayloads['suspend'];
@ -2307,7 +2353,7 @@ type ModerationLog = {
});
// @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)
type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json'];
@ -2342,6 +2388,12 @@ type NoteFavorite = components['schemas']['NoteFavorite'];
// @public (undocumented)
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)
type NotesChildrenRequest = operations['notes/children']['requestBody']['content']['application/json'];
@ -2369,6 +2421,12 @@ type NotesCreateResponse = operations['notes/create']['responses']['200']['conte
// @public (undocumented)
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)
type NotesFavoritesCreateRequest = operations['notes/favorites/create']['requestBody']['content']['application/json'];
@ -2393,6 +2451,9 @@ type NotesHybridTimelineRequest = operations['notes/hybrid-timeline']['requestBo
// @public (undocumented)
type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json'];
// @public (undocumented)
type NotesLikeRequest = operations['notes/like']['requestBody']['content']['application/json'];
// @public (undocumented)
type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json'];
@ -2495,6 +2556,12 @@ type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requ
// @public (undocumented)
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)
export const noteVisibilities: readonly ["public", "home", "followers", "specified"];
@ -2553,7 +2620,7 @@ type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['
function parse(acct: string): Acct;
// @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)
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
@ -2746,6 +2813,9 @@ type SignupResponse = MeDetailed & {
token: string;
};
// @public (undocumented)
type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
// @public (undocumented)
type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];

View file

@ -1,6 +1,6 @@
/*
* version: 2024.2.0-beta.6
* generatedAt: 2024-01-24T07:32:10.455Z
* version: 2024.2.0-beta2
* generatedAt: 2024-01-26T20:30:18.423Z
*/
import type { SwitchCaseResponseType } from '../api.js';
@ -691,6 +691,50 @@ declare module '../api.js' {
credential?: string | null,
): 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.
*
@ -702,6 +746,17 @@ declare module '../api.js' {
credential?: string | null,
): 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.
*
@ -2514,6 +2569,17 @@ declare module '../api.js' {
credential?: string | null,
): 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.
*
@ -2993,6 +3059,17 @@ declare module '../api.js' {
credential?: string | null,
): 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.
*
@ -4053,8 +4130,9 @@ declare module '../api.js' {
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/** No description provided.
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:account*
*/

View file

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

View file

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

View file

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

View file

@ -2,8 +2,8 @@
/* eslint @typescript-eslint/no-explicit-any: 0 */
/*
* version: 2024.2.0-beta.6
* generatedAt: 2024-01-24T07:32:10.370Z
* version: 2024.2.0-beta2
* generatedAt: 2024-01-26T20:30:18.319Z
*/
/**
@ -4784,6 +4784,8 @@ export type operations = {
backgroundImageUrl: string | null;
deeplAuthKey: string | null;
deeplIsPro: boolean;
deeplFreeMode: boolean;
deeplFreeInstance: string | null;
defaultDarkTheme: string | null;
defaultLightTheme: string | null;
description: string | null;
@ -8795,6 +8797,8 @@ export type operations = {
summalyProxy?: string | null;
deeplAuthKey?: string | null;
deeplIsPro?: boolean;
deeplFreeMode?: boolean;
deeplFreeInstance?: string | null;
enableEmail?: boolean;
email?: string | null;
smtpSecure?: boolean;
@ -14101,6 +14105,7 @@ export type operations = {
subscribing?: boolean | null;
publishing?: boolean | null;
nsfw?: boolean | null;
bubble?: boolean | null;
/** @default 30 */
limit?: number;
/** @default 0 */
@ -26440,9 +26445,40 @@ export type operations = {
204: {
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.
*
* **Credential required**: *Yes* / **Permission**: *write:account*

View file

@ -115,9 +115,6 @@ importers:
'@peertube/http-signature':
specifier: 1.7.0
version: 1.7.0
'@sharkey/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@simplewebauthn/server':
specifier: 9.0.0
version: 9.0.0
@ -133,6 +130,9 @@ importers:
'@swc/core':
specifier: 1.3.105
version: 1.3.105
'@transfem-org/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@twemoji/parser':
specifier: 15.0.0
version: 15.0.0
@ -696,12 +696,12 @@ importers:
'@rollup/pluginutils':
specifier: 5.1.0
version: 5.1.0(rollup@4.9.6)
'@sharkey/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@syuilo/aiscript':
specifier: 0.17.0
version: 0.17.0
'@transfem-org/sfm-js':
specifier: 0.24.4
version: 0.24.4
'@twemoji/parser':
specifier: 15.0.0
version: 15.0.0
@ -5782,12 +5782,6 @@ packages:
string-argv: 0.3.1
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:
resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==}
dependencies:
@ -7513,6 +7507,12 @@ packages:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
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:
resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
engines: {node: '>=10.13.0'}