Merge remote-tracking branch 'misskey-dev/develop' into prismisskey
# Conflicts: # package.json
This commit is contained in:
commit
6e0b64bebe
35 changed files with 864 additions and 363 deletions
|
|
@ -160,6 +160,7 @@ import * as ep___federation_users from './endpoints/federation/users.js';
|
|||
import * as ep___federation_stats from './endpoints/federation/stats.js';
|
||||
import * as ep___following_create from './endpoints/following/create.js';
|
||||
import * as ep___following_delete from './endpoints/following/delete.js';
|
||||
import * as ep___following_update from './endpoints/following/update.js';
|
||||
import * as ep___following_invalidate from './endpoints/following/invalidate.js';
|
||||
import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
|
||||
import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
|
||||
|
|
@ -508,6 +509,7 @@ const $federation_users: Provider = { provide: 'ep:federation/users', useClass:
|
|||
const $federation_stats: Provider = { provide: 'ep:federation/stats', useClass: ep___federation_stats.default };
|
||||
const $following_create: Provider = { provide: 'ep:following/create', useClass: ep___following_create.default };
|
||||
const $following_delete: Provider = { provide: 'ep:following/delete', useClass: ep___following_delete.default };
|
||||
const $following_update: Provider = { provide: 'ep:following/update', useClass: ep___following_update.default };
|
||||
const $following_invalidate: Provider = { provide: 'ep:following/invalidate', useClass: ep___following_invalidate.default };
|
||||
const $following_requests_accept: Provider = { provide: 'ep:following/requests/accept', useClass: ep___following_requests_accept.default };
|
||||
const $following_requests_cancel: Provider = { provide: 'ep:following/requests/cancel', useClass: ep___following_requests_cancel.default };
|
||||
|
|
@ -860,6 +862,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$federation_stats,
|
||||
$following_create,
|
||||
$following_delete,
|
||||
$following_update,
|
||||
$following_invalidate,
|
||||
$following_requests_accept,
|
||||
$following_requests_cancel,
|
||||
|
|
@ -1206,6 +1209,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
|||
$federation_stats,
|
||||
$following_create,
|
||||
$following_delete,
|
||||
$following_update,
|
||||
$following_invalidate,
|
||||
$following_requests_accept,
|
||||
$following_requests_cancel,
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ import * as ep___federation_users from './endpoints/federation/users.js';
|
|||
import * as ep___federation_stats from './endpoints/federation/stats.js';
|
||||
import * as ep___following_create from './endpoints/following/create.js';
|
||||
import * as ep___following_delete from './endpoints/following/delete.js';
|
||||
import * as ep___following_update from './endpoints/following/update.js';
|
||||
import * as ep___following_invalidate from './endpoints/following/invalidate.js';
|
||||
import * as ep___following_requests_accept from './endpoints/following/requests/accept.js';
|
||||
import * as ep___following_requests_cancel from './endpoints/following/requests/cancel.js';
|
||||
|
|
@ -506,6 +507,7 @@ const eps = [
|
|||
['federation/stats', ep___federation_stats],
|
||||
['following/create', ep___following_create],
|
||||
['following/delete', ep___following_delete],
|
||||
['following/update', ep___following_update],
|
||||
['following/invalidate', ep___following_invalidate],
|
||||
['following/requests/accept', ep___following_requests_accept],
|
||||
['following/requests/cancel', ep___following_requests_cancel],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const meta = {
|
|||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 50,
|
||||
max: 100,
|
||||
},
|
||||
|
||||
requireCredential: true,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export const meta = {
|
|||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '5b12c78d-2b28-4dca-99d2-f56139b42ff8',
|
||||
id: 'b77e6ae6-a3e5-40da-9cc8-c240115479cc',
|
||||
},
|
||||
|
||||
followerIsYourself: {
|
||||
|
|
@ -41,7 +41,7 @@ export const meta = {
|
|||
notFollowing: {
|
||||
message: 'The other use is not following you.',
|
||||
code: 'NOT_FOLLOWING',
|
||||
id: '5dbf82f5-c92b-40b1-87d1-6c8c0741fd09',
|
||||
id: '918faac3-074f-41ae-9c43-ed5d2946770d',
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
|||
107
packages/backend/src/server/api/endpoints/following/update.ts
Normal file
107
packages/backend/src/server/api/endpoints/following/update.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { FollowingsRepository } from '@/models/_.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['following', 'users'],
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 100,
|
||||
},
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:following',
|
||||
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '14318698-f67e-492a-99da-5353a5ac52be',
|
||||
},
|
||||
|
||||
followeeIsYourself: {
|
||||
message: 'Followee is yourself.',
|
||||
code: 'FOLLOWEE_IS_YOURSELF',
|
||||
id: '4c4cbaf9-962a-463b-8418-a5e365dbf2eb',
|
||||
},
|
||||
|
||||
notFollowing: {
|
||||
message: 'You are not following that user.',
|
||||
code: 'NOT_FOLLOWING',
|
||||
id: 'b8dc75cf-1cb5-46c9-b14b-5f1ffbd782c9',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserLite',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
notify: { type: 'string', enum: ['normal', 'none'] },
|
||||
},
|
||||
required: ['userId', 'notify'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
private getterService: GetterService,
|
||||
private userFollowingService: UserFollowingService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const follower = me;
|
||||
|
||||
// Check if the follower is yourself
|
||||
if (me.id === ps.userId) {
|
||||
throw new ApiError(meta.errors.followeeIsYourself);
|
||||
}
|
||||
|
||||
// Get followee
|
||||
const followee = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
// Check not following
|
||||
const exist = await this.followingsRepository.findOneBy({
|
||||
followerId: follower.id,
|
||||
followeeId: followee.id,
|
||||
});
|
||||
|
||||
if (exist == null) {
|
||||
throw new ApiError(meta.errors.notFollowing);
|
||||
}
|
||||
|
||||
await this.followingsRepository.update({
|
||||
id: exist.id,
|
||||
}, {
|
||||
notify: ps.notify === 'none' ? null : ps.notify,
|
||||
});
|
||||
|
||||
return await this.userEntityService.pack(follower.id, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -6,11 +6,13 @@
|
|||
import RE2 from 're2';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
|
||||
import { extractHashtags } from '@/misc/extract-hashtags.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiLocalUser, MiUser } from '@/models/User.js';
|
||||
import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/User.js';
|
||||
import type { MiUserProfile } from '@/models/UserProfile.js';
|
||||
import { notificationTypes } from '@/types.js';
|
||||
|
|
@ -27,6 +29,9 @@ import { RoleService } from '@/core/RoleService.js';
|
|||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { safeForSql } from '@/misc/safe-for-sql.js';
|
||||
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
|
|
@ -37,6 +42,11 @@ export const meta = {
|
|||
|
||||
kind: 'write:account',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchAvatar: {
|
||||
message: 'No such avatar file.',
|
||||
|
|
@ -173,6 +183,9 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
|
|
@ -195,9 +208,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private hashtagService: HashtagService,
|
||||
private roleService: RoleService,
|
||||
private cacheService: CacheService,
|
||||
private httpRequestService: HttpRequestService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, _user, token) => {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser;
|
||||
const isSecure = token == null;
|
||||
|
||||
const updates = {} as Partial<MiUser>;
|
||||
|
|
@ -296,9 +310,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (ps.fields) {
|
||||
profileUpdates.fields = ps.fields
|
||||
.filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '')
|
||||
.filter(x => typeof x.name === 'string' && x.name.trim() !== '' && typeof x.value === 'string' && x.value.trim() !== '')
|
||||
.map(x => {
|
||||
return { name: x.name, value: x.value };
|
||||
return { name: x.name.trim(), value: x.value.trim() };
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -364,7 +378,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (Object.keys(updates).includes('alsoKnownAs')) {
|
||||
this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates });
|
||||
}
|
||||
if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates);
|
||||
|
||||
await this.userProfilesRepository.update(user.id, {
|
||||
...profileUpdates,
|
||||
verifiedLinks: [],
|
||||
});
|
||||
|
||||
const iObj = await this.userEntityService.pack<true, true>(user.id, user, {
|
||||
detail: true,
|
||||
|
|
@ -386,7 +404,34 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
// フォロワーにUpdateを配信
|
||||
this.accountUpdateService.publishToFollowers(user.id);
|
||||
|
||||
const urls = updatedProfile.fields.filter(x => x.value.startsWith('https://'));
|
||||
for (const url of urls) {
|
||||
this.verifyLink(url.value, user);
|
||||
}
|
||||
|
||||
return iObj;
|
||||
});
|
||||
}
|
||||
|
||||
private async verifyLink(url: string, user: MiLocalUser) {
|
||||
if (!safeForSql(url)) return;
|
||||
|
||||
const html = await this.httpRequestService.getHtml(url);
|
||||
|
||||
const { window } = new JSDOM(html);
|
||||
const doc = window.document;
|
||||
|
||||
const myLink = `${this.config.url}/@${user.username}`;
|
||||
|
||||
const includesMyLink = Array.from(doc.getElementsByTagName('a')).some(a => a.href === myLink);
|
||||
|
||||
if (includesMyLink) {
|
||||
await this.userProfilesRepository.createQueryBuilder('profile').update()
|
||||
.where('userId = :userId', { userId: user.id })
|
||||
.set({
|
||||
verifiedLinks: () => `array_append("verifiedLinks", '${url}')`, // ここでSQLインジェクションされそうなのでとりあえず safeForSql で弾いている
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue