mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-25 03:17:38 -07:00
Merge branch 'develop' into pr/ThatOneCalculator/8764
This commit is contained in:
commit
2665322b23
21 changed files with 285 additions and 114 deletions
|
@ -1 +1 @@
|
|||
v18.2.0
|
||||
v16.0.0
|
||||
|
|
46
CHANGELOG.md
46
CHANGELOG.md
|
@ -10,18 +10,17 @@ You should also include the user name that made the change.
|
|||
-->
|
||||
|
||||
## 12.x.x (unreleased)
|
||||
### NOTE
|
||||
- From this version, Node 18.0.0 or later is required.
|
||||
|
||||
### Improvements
|
||||
- enhance: ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
|
||||
- enhance: API: notifications/readは配列でも受け付けるように #7667 @tamaina
|
||||
- enhance: プッシュ通知を複数アカウント対応に #7667 @tamaina
|
||||
- enhance: プッシュ通知にクリックやactionを設定 #7667 @tamaina
|
||||
- replaced webpack with Vite @tamaina
|
||||
- update dependencies @syuilo
|
||||
- enhance: display URL of QR code for TOTP registration @syuilo
|
||||
- enhance: Supports Unicode Emoji 14.0 @mei23
|
||||
- Supports Unicode Emoji 14.0 @mei23
|
||||
- プッシュ通知を複数アカウント対応に #7667 @tamaina
|
||||
- プッシュ通知にクリックやactionを設定 #7667 @tamaina
|
||||
- ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina
|
||||
- Server: always remove completed tasks of job queue @Johann150
|
||||
- Client: make emoji stand out more on reaction button @Johann150
|
||||
- Client: display URL of QR code for TOTP registration @tamaina
|
||||
- API: notifications/readは配列でも受け付けるように #7667 @tamaina
|
||||
- API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina
|
||||
- MFM: Allow speed changes in all animated MFMs @Johann150
|
||||
- The theme color is now better validated. @Johann150
|
||||
Your own theme color may be unset if it was in an invalid format.
|
||||
Admins should check their instance settings if in doubt.
|
||||
|
@ -31,20 +30,31 @@ You should also include the user name that made the change.
|
|||
- Migrate to Yarn v3.2.0 @ThatOneCalculator
|
||||
|
||||
### Bugfixes
|
||||
- Client: fix settings page @tamaina
|
||||
- Client: fix profile tabs @futchitwo
|
||||
- Server: keep file order of note attachement @Johann150
|
||||
- Server: fix caching @Johann150
|
||||
- Server: await promises when following or unfollowing users @Johann150
|
||||
- Client: fix abuse reports page to be able to show all reports @Johann150
|
||||
- Federation: Add rel attribute to host-meta @mei23
|
||||
- Client: fix profile picture height in mentions @tamaina
|
||||
- MFM: more animated functions support `speed` parameter @futchitwo
|
||||
- Federation: Fix quote renotes containing no text being federated correctly @Johann150
|
||||
- Server: fix missing foreign key for reports leading to reports page being unusable @Johann150
|
||||
- Server: fix internal in-memory caching @Johann150
|
||||
- Server: use correct order of attachments on notes @Johann150
|
||||
- Server: prevent crash when processing certain PNGs @syuilo
|
||||
- Server: Fix unable to generate video thumbnails @mei23
|
||||
- Server: Fix `Cannot find module` issue @mei23
|
||||
- Federation: Add rel attribute to host-meta @mei23
|
||||
- Federation: add id for activitypub follows @Johann150
|
||||
- Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150
|
||||
- Federation: correctly render empty note text @Johann150
|
||||
- Federation: Fix quote renotes containing no text being federated correctly @Johann150
|
||||
- Federation: remove duplicate br tag/newline @Johann150
|
||||
- Federation: add missing authorization checks @Johann150
|
||||
- Client: fix profile picture height in mentions @tamaina
|
||||
- Client: fix abuse reports page to be able to show all reports @Johann150
|
||||
- Client: fix settings page @tamaina
|
||||
- Client: fix profile tabs @futchitwo
|
||||
- Client: fix popout URL @futchitwo
|
||||
- Client: correctly handle MiAuth URLs with query string @sn0w
|
||||
- Client: ノート詳細ページの新しいノートを表示する機能の動作が正しくなるように修正する @xianonn
|
||||
- MFM: more animated functions support `speed` parameter @futchitwo
|
||||
- MFM: limit large MFM @Johann150
|
||||
|
||||
## 12.110.1 (2022/04/23)
|
||||
|
||||
|
|
|
@ -71,15 +71,17 @@ For now, basically only @syuilo has the authority to merge PRs into develop beca
|
|||
However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor.
|
||||
|
||||
## Release
|
||||
For now, basically only @syuilo has the authority to release Misskey.
|
||||
However, in case of emergency, a release can be made at the discretion of a contributor.
|
||||
|
||||
### Release Instructions
|
||||
1. commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
|
||||
2. follow the `master` branch to the `develop` branch.
|
||||
3. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
|
||||
- The target branch must be `master`
|
||||
- The tag name must be the version
|
||||
1. Commit version changes in the `develop` branch ([package.json](https://github.com/misskey-dev/misskey/blob/develop/package.json))
|
||||
2. Create a release PR.
|
||||
- Into `master` from `develop` branch.
|
||||
- The title must be in the format `Release: x.y.z`.
|
||||
- `x.y.z` is the new version you are trying to release.
|
||||
- Assign about 2~3 reviewers.
|
||||
3. The release PR is approved, merge it.
|
||||
4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases)
|
||||
- The target branch must be `master`
|
||||
- The tag name must be the version
|
||||
|
||||
## Localization (l10n)
|
||||
Misskey uses [Crowdin](https://crowdin.com/project/misskey) for localization management.
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"style-loader": "3.3.1",
|
||||
"summaly": "2.5.0",
|
||||
"summaly": "2.5.1",
|
||||
"syslog-pro": "1.0.0",
|
||||
"systeminformation": "5.11.16",
|
||||
"tinycolor2": "1.4.2",
|
||||
|
|
|
@ -73,6 +73,7 @@ import { entities as charts } from '@/services/chart/entities.js';
|
|||
import { Webhook } from '@/models/entities/webhook.js';
|
||||
import { envOption } from '../env.js';
|
||||
import { dbLogger } from './logger.js';
|
||||
import { redisClient } from './redis.js';
|
||||
|
||||
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
|
||||
|
||||
|
@ -217,6 +218,7 @@ export async function initDb() {
|
|||
|
||||
export async function resetDb() {
|
||||
const reset = async () => {
|
||||
await redisClient.FLUSHDB();
|
||||
const tables = await db.query(`SELECT relname AS "table"
|
||||
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
|
||||
WHERE nspname NOT IN ('pg_catalog', 'information_schema')
|
||||
|
|
|
@ -29,7 +29,9 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
|||
|
||||
getPublicProperties(file: DriveFile): DriveFile['properties'] {
|
||||
if (file.properties.orientation != null) {
|
||||
const properties = structuredClone(file.properties);
|
||||
// TODO
|
||||
//const properties = structuredClone(file.properties);
|
||||
const properties = JSON.parse(JSON.stringify(file.properties));
|
||||
if (file.properties.orientation >= 5) {
|
||||
[properties.width, properties.height] = [properties.height, properties.width];
|
||||
}
|
||||
|
|
|
@ -5,14 +5,52 @@ import { User, IRemoteUser, CacheableRemoteUser, CacheableUser } from '@/models/
|
|||
import { UserPublickey } from '@/models/entities/user-publickey.js';
|
||||
import { MessagingMessage } from '@/models/entities/messaging-message.js';
|
||||
import { Notes, Users, UserPublickeys, MessagingMessages } from '@/models/index.js';
|
||||
import { IObject, getApId } from './type.js';
|
||||
import { resolvePerson } from './models/person.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { uriPersonCache, userByIdCache } from '@/services/user-cache.js';
|
||||
import { IObject, getApId } from './type.js';
|
||||
import { resolvePerson } from './models/person.js';
|
||||
|
||||
const publicKeyCache = new Cache<UserPublickey | null>(Infinity);
|
||||
const publicKeyByUserIdCache = new Cache<UserPublickey | null>(Infinity);
|
||||
|
||||
export type UriParseResult = {
|
||||
/** wether the URI was generated by us */
|
||||
local: true;
|
||||
/** id in DB */
|
||||
id: string;
|
||||
/** hint of type, e.g. "notes", "users" */
|
||||
type: string;
|
||||
/** any remaining text after type and id, not including the slash after id. undefined if empty */
|
||||
rest?: string;
|
||||
} | {
|
||||
/** wether the URI was generated by us */
|
||||
local: false;
|
||||
/** uri in DB */
|
||||
uri: string;
|
||||
};
|
||||
|
||||
export function parseUri(value: string | IObject): UriParseResult {
|
||||
const uri = getApId(value);
|
||||
|
||||
// the host part of a URL is case insensitive, so use the 'i' flag.
|
||||
const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/(\\w+)/(\\w+)(?:\/(.+))?', 'i');
|
||||
const matchLocal = uri.match(localRegex);
|
||||
|
||||
if (matchLocal) {
|
||||
return {
|
||||
local: true,
|
||||
type: matchLocal[1],
|
||||
id: matchLocal[2],
|
||||
rest: matchLocal[3],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
local: false,
|
||||
uri,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default class DbResolver {
|
||||
constructor() {
|
||||
}
|
||||
|
@ -21,60 +59,54 @@ export default class DbResolver {
|
|||
* AP Note => Misskey Note in DB
|
||||
*/
|
||||
public async getNoteFromApId(value: string | IObject): Promise<Note | null> {
|
||||
const parsed = this.parseUri(value);
|
||||
const parsed = parseUri(value);
|
||||
|
||||
if (parsed.local) {
|
||||
if (parsed.type !== 'notes') return null;
|
||||
|
||||
if (parsed.id) {
|
||||
return await Notes.findOneBy({
|
||||
id: parsed.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (parsed.uri) {
|
||||
} else {
|
||||
return await Notes.findOneBy({
|
||||
uri: parsed.uri,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> {
|
||||
const parsed = this.parseUri(value);
|
||||
const parsed = parseUri(value);
|
||||
|
||||
if (parsed.local) {
|
||||
if (parsed.type !== 'notes') return null;
|
||||
|
||||
if (parsed.id) {
|
||||
return await MessagingMessages.findOneBy({
|
||||
id: parsed.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (parsed.uri) {
|
||||
} else {
|
||||
return await MessagingMessages.findOneBy({
|
||||
uri: parsed.uri,
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* AP Person => Misskey User in DB
|
||||
*/
|
||||
public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
|
||||
const parsed = this.parseUri(value);
|
||||
const parsed = parseUri(value);
|
||||
|
||||
if (parsed.local) {
|
||||
if (parsed.type !== 'users') return null;
|
||||
|
||||
if (parsed.id) {
|
||||
return await userByIdCache.fetchMaybe(parsed.id, () => Users.findOneBy({
|
||||
id: parsed.id,
|
||||
}).then(x => x ?? undefined)) ?? null;
|
||||
}
|
||||
|
||||
if (parsed.uri) {
|
||||
} else {
|
||||
return await uriPersonCache.fetch(parsed.uri, () => Users.findOneBy({
|
||||
uri: parsed.uri,
|
||||
}));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,31 +152,4 @@ export default class DbResolver {
|
|||
key,
|
||||
};
|
||||
}
|
||||
|
||||
public parseUri(value: string | IObject): UriParseResult {
|
||||
const uri = getApId(value);
|
||||
|
||||
const localRegex = new RegExp('^' + escapeRegexp(config.url) + '/' + '(\\w+)' + '/' + '(\\w+)');
|
||||
const matchLocal = uri.match(localRegex);
|
||||
|
||||
if (matchLocal) {
|
||||
return {
|
||||
type: matchLocal[1],
|
||||
id: matchLocal[2],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
uri,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UriParseResult = {
|
||||
/** id in DB (local object only) */
|
||||
id?: string;
|
||||
/** uri in DB (remote object only) */
|
||||
uri?: string;
|
||||
/** hint of type (local object only, ex: notes, users) */
|
||||
type?: string
|
||||
};
|
||||
|
|
|
@ -3,8 +3,6 @@ import { Note } from '@/models/entities/note.js';
|
|||
import { toHtml } from '../../../mfm/to-html.js';
|
||||
|
||||
export default function(note: Note) {
|
||||
let html = note.text ? toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers)) : null;
|
||||
if (html == null) html = '<p>.</p>';
|
||||
|
||||
return html;
|
||||
if (!note.text) return '';
|
||||
return toHtml(mfm.parse(note.text), JSON.parse(note.mentionedRemoteUsers));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,20 @@
|
|||
import config from '@/config/index.js';
|
||||
import { ILocalUser, IRemoteUser } from '@/models/entities/user.js';
|
||||
import { Blocking } from '@/models/entities/blocking.js';
|
||||
|
||||
export default (blocker: ILocalUser, blockee: IRemoteUser) => ({
|
||||
type: 'Block',
|
||||
actor: `${config.url}/users/${blocker.id}`,
|
||||
object: blockee.uri,
|
||||
});
|
||||
/**
|
||||
* Renders a block into its ActivityPub representation.
|
||||
*
|
||||
* @param block The block to be rendered. The blockee relation must be loaded.
|
||||
*/
|
||||
export function renderBlock(block: Blocking) {
|
||||
if (block.blockee?.url == null) {
|
||||
throw new Error('renderBlock: missing blockee uri');
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'Block',
|
||||
id: `${config.url}/blocks/${block.id}`,
|
||||
actor: `${config.url}/users/${block.blockerId}`,
|
||||
object: block.blockee.uri,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@ import { Users } from '@/models/index.js';
|
|||
|
||||
export default (follower: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] }, requestId?: string) => {
|
||||
const follow = {
|
||||
id: requestId ?? `${config.url}/follows/${follower.id}/${followee.id}`,
|
||||
type: 'Follow',
|
||||
actor: Users.isLocalUser(follower) ? `${config.url}/users/${follower.id}` : follower.uri,
|
||||
object: Users.isLocalUser(followee) ? `${config.url}/users/${followee.id}` : followee.uri,
|
||||
} as any;
|
||||
|
||||
if (requestId) follow.id = requestId;
|
||||
|
||||
return follow;
|
||||
};
|
||||
|
|
|
@ -82,15 +82,15 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
|
|||
|
||||
const files = await getPromisedFiles(note.fileIds);
|
||||
|
||||
const text = note.text;
|
||||
// text should never be undefined
|
||||
const text = note.text ?? null;
|
||||
let poll: Poll | null = null;
|
||||
|
||||
if (note.hasPoll) {
|
||||
poll = await Polls.findOneBy({ noteId: note.id });
|
||||
}
|
||||
|
||||
let apText = text;
|
||||
if (apText == null) apText = '';
|
||||
let apText = text ?? '';
|
||||
|
||||
if (quote) {
|
||||
apText += `\n\nRE: ${quote}`;
|
||||
|
|
|
@ -3,9 +3,18 @@ import { getJson } from '@/misc/fetch.js';
|
|||
import { ILocalUser } from '@/models/entities/user.js';
|
||||
import { getInstanceActor } from '@/services/instance-actor.js';
|
||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||
import { extractDbHost } from '@/misc/convert-host.js';
|
||||
import { extractDbHost, isSelfHost } from '@/misc/convert-host.js';
|
||||
import { signedGet } from './request.js';
|
||||
import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type.js';
|
||||
import { FollowRequests, Notes, NoteReactions, Polls, Users } from '@/models/index.js';
|
||||
import { parseUri } from './db-resolver.js';
|
||||
import renderNote from '@/remote/activitypub/renderer/note.js';
|
||||
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
||||
import { renderPerson } from '@/remote/activitypub/renderer/person.js';
|
||||
import renderQuestion from '@/remote/activitypub/renderer/question.js';
|
||||
import renderCreate from '@/remote/activitypub/renderer/create.js';
|
||||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||
|
||||
export default class Resolver {
|
||||
private history: Set<string>;
|
||||
|
@ -40,14 +49,25 @@ export default class Resolver {
|
|||
return value;
|
||||
}
|
||||
|
||||
if (value.includes('#')) {
|
||||
// URLs with fragment parts cannot be resolved correctly because
|
||||
// the fragment part does not get transmitted over HTTP(S).
|
||||
// Avoid strange behaviour by not trying to resolve these at all.
|
||||
throw new Error(`cannot resolve URL with fragment: ${value}`);
|
||||
}
|
||||
|
||||
if (this.history.has(value)) {
|
||||
throw new Error('cannot resolve already resolved one');
|
||||
}
|
||||
|
||||
this.history.add(value);
|
||||
|
||||
const meta = await fetchMeta();
|
||||
const host = extractDbHost(value);
|
||||
if (isSelfHost(host)) {
|
||||
return await this.resolveLocal(value);
|
||||
}
|
||||
|
||||
const meta = await fetchMeta();
|
||||
if (meta.blockedHosts.includes(host)) {
|
||||
throw new Error('Instance is blocked');
|
||||
}
|
||||
|
@ -70,4 +90,44 @@ export default class Resolver {
|
|||
|
||||
return object;
|
||||
}
|
||||
|
||||
private resolveLocal(url: string): Promise<IObject> {
|
||||
const parsed = parseUri(url);
|
||||
if (!parsed.local) throw new Error('resolveLocal: not local');
|
||||
|
||||
switch (parsed.type) {
|
||||
case 'notes':
|
||||
return Notes.findOneByOrFail({ id: parsed.id })
|
||||
.then(note => {
|
||||
if (parsed.rest === 'activity') {
|
||||
// this refers to the create activity and not the note itself
|
||||
return renderActivity(renderCreate(renderNote(note)));
|
||||
} else {
|
||||
return renderNote(note);
|
||||
}
|
||||
});
|
||||
case 'users':
|
||||
return Users.findOneByOrFail({ id: parsed.id })
|
||||
.then(user => renderPerson(user as ILocalUser));
|
||||
case 'questions':
|
||||
// Polls are indexed by the note they are attached to.
|
||||
return Promise.all([
|
||||
Notes.findOneByOrFail({ id: parsed.id }),
|
||||
Polls.findOneByOrFail({ noteId: parsed.id }),
|
||||
])
|
||||
.then(([note, poll]) => renderQuestion({ id: note.userId }, note, poll));
|
||||
case 'likes':
|
||||
return NoteReactions.findOneByOrFail({ id: parsed.id }).then(reaction => renderActivity(renderLike(reaction, { uri: null })));
|
||||
case 'follows':
|
||||
// rest should be <followee id>
|
||||
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
|
||||
|
||||
return Promise.all(
|
||||
[parsed.id, parsed.rest].map(id => Users.findOneByOrFail({ id }))
|
||||
)
|
||||
.then(([follower, followee]) => renderActivity(renderFollow(follower, followee, url)));
|
||||
default:
|
||||
throw new Error(`resolveLocal: type ${type} unhandled`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,9 +15,10 @@ import { inbox as processInbox } from '@/queue/index.js';
|
|||
import { isSelfHost } from '@/misc/convert-host.js';
|
||||
import { Notes, Users, Emojis, NoteReactions } from '@/models/index.js';
|
||||
import { ILocalUser, User } from '@/models/entities/user.js';
|
||||
import { In, IsNull } from 'typeorm';
|
||||
import { In, IsNull, Not } from 'typeorm';
|
||||
import { renderLike } from '@/remote/activitypub/renderer/like.js';
|
||||
import { getUserKeypair } from '@/misc/keypair-store.js';
|
||||
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||
|
||||
// Init router
|
||||
const router = new Router();
|
||||
|
@ -224,4 +225,30 @@ router.get('/likes/:like', async ctx => {
|
|||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
// follow
|
||||
router.get('/follows/:follower/:followee', async ctx => {
|
||||
// This may be used before the follow is completed, so we do not
|
||||
// check if the following exists.
|
||||
|
||||
const [follower, followee] = await Promise.all([
|
||||
Users.findOneBy({
|
||||
id: ctx.params.follower,
|
||||
host: IsNull(),
|
||||
}),
|
||||
Users.findOneBy({
|
||||
id: ctx.params.followee,
|
||||
host: Not(IsNull()),
|
||||
}),
|
||||
]);
|
||||
|
||||
if (follower == null || followee == null) {
|
||||
ctx.status = 404;
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.body = renderActivity(renderFollow(follower, followee));
|
||||
ctx.set('Cache-Control', 'public, max-age=180');
|
||||
setResponseType(ctx);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Signins, UserProfiles, Users } from '@/models/index.js';
|
||||
import define from '../../define.js';
|
||||
import { Users } from '@/models/index.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
@ -23,9 +23,12 @@ export const paramDef = {
|
|||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps, me) => {
|
||||
const user = await Users.findOneBy({ id: ps.userId });
|
||||
const [user, profile] = await Promise.all([
|
||||
Users.findOneBy({ id: ps.userId }),
|
||||
UserProfiles.findOneBy({ userId: ps.userId })
|
||||
]);
|
||||
|
||||
if (user == null) {
|
||||
if (user == null || profile == null) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
|
@ -34,8 +37,37 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
throw new Error('cannot show info of admin');
|
||||
}
|
||||
|
||||
if (!_me.isAdmin) {
|
||||
return {
|
||||
isModerator: user.isModerator,
|
||||
isSilenced: user.isSilenced,
|
||||
isSuspended: user.isSuspended,
|
||||
};
|
||||
}
|
||||
|
||||
const maskedKeys = ['accessToken', 'accessTokenSecret', 'refreshToken'];
|
||||
Object.keys(profile.integrations).forEach(integration => {
|
||||
maskedKeys.forEach(key => profile.integrations[integration][key] = '<MASKED>');
|
||||
});
|
||||
|
||||
const signins = await Signins.findBy({ userId: user.id });
|
||||
|
||||
return {
|
||||
...user,
|
||||
token: user.token != null ? '<MASKED>' : user.token,
|
||||
email: profile.email,
|
||||
emailVerified: profile.emailVerified,
|
||||
autoAcceptFollowed: profile.autoAcceptFollowed,
|
||||
noCrawle: profile.noCrawle,
|
||||
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||
carefulBot: profile.carefulBot,
|
||||
injectFeaturedNote: profile.injectFeaturedNote,
|
||||
receiveAnnouncementEmail: profile.receiveAnnouncementEmail,
|
||||
integrations: profile.integrations,
|
||||
mutedWords: profile.mutedWords,
|
||||
mutedInstances: profile.mutedInstances,
|
||||
mutingNotificationTypes: profile.mutingNotificationTypes,
|
||||
isModerator: user.isModerator,
|
||||
isSilenced: user.isSilenced,
|
||||
isSuspended: user.isSuspended,
|
||||
signins,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -3,7 +3,9 @@ import { fetchMeta } from '@/misc/fetch-meta.js';
|
|||
import manifest from './manifest.json' assert { type: 'json' };
|
||||
|
||||
export const manifestHandler = async (ctx: Koa.Context) => {
|
||||
const res = structuredClone(manifest);
|
||||
// TODO
|
||||
//const res = structuredClone(manifest);
|
||||
const res = JSON.parse(JSON.stringify(manifest));
|
||||
|
||||
const instance = await fetchMeta(true);
|
||||
|
||||
|
|
|
@ -2,9 +2,10 @@ import { publishMainStream, publishUserEvent } from '@/services/stream.js';
|
|||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderFollow from '@/remote/activitypub/renderer/follow.js';
|
||||
import renderUndo from '@/remote/activitypub/renderer/undo.js';
|
||||
import renderBlock from '@/remote/activitypub/renderer/block.js';
|
||||
import { renderBlock } from '@/remote/activitypub/renderer/block.js';
|
||||
import { deliver } from '@/queue/index.js';
|
||||
import renderReject from '@/remote/activitypub/renderer/reject.js';
|
||||
import { Blocking } from '@/models/entities/blocking.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Blockings, Users, FollowRequests, Followings, UserListJoinings, UserLists } from '@/models/index.js';
|
||||
import { perUserFollowingChart } from '@/services/chart/index.js';
|
||||
|
@ -22,15 +23,19 @@ export default async function(blocker: User, blockee: User) {
|
|||
removeFromList(blockee, blocker),
|
||||
]);
|
||||
|
||||
await Blockings.insert({
|
||||
const blocking = {
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
blocker,
|
||||
blockerId: blocker.id,
|
||||
blockee,
|
||||
blockeeId: blockee.id,
|
||||
});
|
||||
} as Blocking;
|
||||
|
||||
await Blockings.insert(blocking);
|
||||
|
||||
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
|
||||
const content = renderActivity(renderBlock(blocker, blockee));
|
||||
const content = renderActivity(renderBlock(blocking));
|
||||
deliver(blocker, content, blockee.inbox);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { renderActivity } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderBlock from '@/remote/activitypub/renderer/block.js';
|
||||
import { renderBlock } from '@/remote/activitypub/renderer/block.js';
|
||||
import renderUndo from '@/remote/activitypub/renderer/undo.js';
|
||||
import { deliver } from '@/queue/index.js';
|
||||
import Logger from '../logger.js';
|
||||
|
@ -19,11 +19,16 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Since we already have the blocker and blockee, we do not need to fetch
|
||||
// them in the query above and can just manually insert them here.
|
||||
blocking.blocker = blocker;
|
||||
blocking.blockee = blockee;
|
||||
|
||||
Blockings.delete(blocking.id);
|
||||
|
||||
// deliver if remote bloking
|
||||
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
|
||||
const content = renderActivity(renderUndo(renderBlock(blocker, blockee), blocker));
|
||||
const content = renderActivity(renderUndo(renderBlock(blocking), blocker));
|
||||
deliver(blocker, content, blockee.inbox);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createSystemUser } from './create-system-user.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { renderFollowRelay } from '@/remote/activitypub/renderer/follow-relay.js';
|
||||
import { renderActivity, attachLdSignature } from '@/remote/activitypub/renderer/index.js';
|
||||
import renderUndo from '@/remote/activitypub/renderer/undo.js';
|
||||
|
@ -8,7 +8,7 @@ import { Users, Relays } from '@/models/index.js';
|
|||
import { genId } from '@/misc/gen-id.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import { Relay } from '@/models/entities/relay.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { createSystemUser } from './create-system-user.js';
|
||||
|
||||
const ACTOR_USERNAME = 'relay.actor' as const;
|
||||
|
||||
|
@ -88,7 +88,9 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
|
|||
}));
|
||||
if (relays.length === 0) return;
|
||||
|
||||
const copy = structuredClone(activity);
|
||||
// TODO
|
||||
//const copy = structuredClone(activity);
|
||||
const copy = JSON.parse(JSON.stringify(activity));
|
||||
if (!copy.to) copy.to = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||
|
||||
const signed = await attachLdSignature(copy, user);
|
||||
|
|
|
@ -2,11 +2,13 @@ process.env.NODE_ENV = 'test';
|
|||
|
||||
import * as assert from 'assert';
|
||||
import rndstr from 'rndstr';
|
||||
import { initDb } from '../src/db/postgre.js';
|
||||
import { initTestDb } from './utils.js';
|
||||
|
||||
describe('ActivityPub', () => {
|
||||
before(async () => {
|
||||
await initTestDb();
|
||||
//await initTestDb();
|
||||
await initDb();
|
||||
});
|
||||
|
||||
describe('Parse minimum object', () => {
|
||||
|
|
|
@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue';
|
|||
import MkButton from '@/components/ui/button.vue';
|
||||
import * as os from '@/os';
|
||||
import { login } from '@/account';
|
||||
import { appendQuery, query } from '@/scripts/url';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
|
@ -82,7 +83,9 @@ export default defineComponent({
|
|||
|
||||
this.state = 'accepted';
|
||||
if (this.callback) {
|
||||
location.href = `${this.callback}?session=${this.session}`;
|
||||
location.href = appendQuery(this.callback, query({
|
||||
session: this.session
|
||||
}));
|
||||
}
|
||||
},
|
||||
deny() {
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
<FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
|
||||
</FormSection>
|
||||
|
||||
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
||||
</MkObjectView>
|
||||
|
||||
<MkObjectView tall :value="user">
|
||||
</MkObjectView>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue