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