350 lines
12 KiB
JavaScript
350 lines
12 KiB
JavaScript
import RE2 from "re2";
|
|
import { updateUserProfileData } from "../../../../services/i/update.js";
|
|
import { Users, DriveFiles, UserProfiles, Pages } from "../../../../models/index.js";
|
|
import { notificationTypes } from "../../../../types.js";
|
|
import { langmap } from "../../../../misc/langmap.js";
|
|
import { verifyLink } from "../../../../services/fetch-rel-me.js";
|
|
import { ApiError } from "../../error.js";
|
|
import define from "../../define.js";
|
|
export const meta = {
|
|
tags: [
|
|
"account"
|
|
],
|
|
requireCredential: true,
|
|
kind: "write:account",
|
|
errors: {
|
|
noSuchAvatar: {
|
|
message: "No such avatar file.",
|
|
code: "NO_SUCH_AVATAR",
|
|
id: "539f3a45-f215-4f81-a9a8-31293640207f"
|
|
},
|
|
noSuchBanner: {
|
|
message: "No such banner file.",
|
|
code: "NO_SUCH_BANNER",
|
|
id: "0d8f5629-f210-41c2-9433-735831a58595"
|
|
},
|
|
avatarNotAnImage: {
|
|
message: "The file specified as an avatar is not an image.",
|
|
code: "AVATAR_NOT_AN_IMAGE",
|
|
id: "f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191"
|
|
},
|
|
bannerNotAnImage: {
|
|
message: "The file specified as a banner is not an image.",
|
|
code: "BANNER_NOT_AN_IMAGE",
|
|
id: "75aedb19-2afd-4e6d-87fc-67941256fa60"
|
|
},
|
|
noSuchPage: {
|
|
message: "No such page.",
|
|
code: "NO_SUCH_PAGE",
|
|
id: "8e01b590-7eb9-431b-a239-860e086c408e"
|
|
},
|
|
invalidRegexp: {
|
|
message: "Invalid Regular Expression.",
|
|
code: "INVALID_REGEXP",
|
|
id: "0d786918-10df-41cd-8f33-8dec7d9a89a5"
|
|
},
|
|
invalidFieldName: {
|
|
message: "Invalid field name.",
|
|
code: "INVALID_FIELD_NAME",
|
|
id: "8f81972e-8b53-4d30-b0d2-efb026dda673"
|
|
},
|
|
invalidFieldValue: {
|
|
message: "Invalid field value.",
|
|
code: "INVALID_FIELD_VALUE",
|
|
id: "aede7444-244b-11ee-be56-0242ac120002"
|
|
}
|
|
},
|
|
res: {
|
|
type: "object",
|
|
optional: false,
|
|
nullable: false,
|
|
ref: "MeDetailed"
|
|
}
|
|
};
|
|
export const paramDef = {
|
|
type: "object",
|
|
properties: {
|
|
name: {
|
|
...Users.nameSchema,
|
|
nullable: true
|
|
},
|
|
description: {
|
|
...Users.descriptionSchema,
|
|
nullable: true
|
|
},
|
|
location: {
|
|
...Users.locationSchema,
|
|
nullable: true
|
|
},
|
|
birthday: {
|
|
...Users.birthdaySchema,
|
|
nullable: true
|
|
},
|
|
lang: {
|
|
type: "string",
|
|
enum: [
|
|
null,
|
|
...Object.keys(langmap)
|
|
],
|
|
nullable: true
|
|
},
|
|
avatarId: {
|
|
type: "string",
|
|
format: "misskey:id",
|
|
nullable: true
|
|
},
|
|
bannerId: {
|
|
type: "string",
|
|
format: "misskey:id",
|
|
nullable: true
|
|
},
|
|
fields: {
|
|
type: "array",
|
|
minItems: 0,
|
|
maxItems: 16,
|
|
items: {
|
|
type: "object",
|
|
properties: {
|
|
name: {
|
|
type: "string"
|
|
},
|
|
value: {
|
|
type: "string"
|
|
}
|
|
},
|
|
required: [
|
|
"name",
|
|
"value"
|
|
]
|
|
}
|
|
},
|
|
isLocked: {
|
|
type: "boolean"
|
|
},
|
|
isExplorable: {
|
|
type: "boolean"
|
|
},
|
|
hideOnlineStatus: {
|
|
type: "boolean"
|
|
},
|
|
publicReactions: {
|
|
type: "boolean"
|
|
},
|
|
allowCalls: {
|
|
type: "boolean"
|
|
},
|
|
symbolFileId: {
|
|
type: "string",
|
|
format: "misskey:id",
|
|
nullable: true
|
|
},
|
|
carefulBot: {
|
|
type: "boolean"
|
|
},
|
|
autoAcceptFollowed: {
|
|
type: "boolean"
|
|
},
|
|
noCrawle: {
|
|
type: "boolean"
|
|
},
|
|
preventAiLearning: {
|
|
type: "boolean"
|
|
},
|
|
isBot: {
|
|
type: "boolean"
|
|
},
|
|
isCat: {
|
|
type: "boolean"
|
|
},
|
|
speakAsCat: {
|
|
type: "boolean"
|
|
},
|
|
minorBadges: {
|
|
type: "array",
|
|
uniqueItems: true,
|
|
maxItems: 1,
|
|
items: {
|
|
type: "string",
|
|
enum: [
|
|
"K",
|
|
"T",
|
|
"E"
|
|
]
|
|
}
|
|
},
|
|
injectFeaturedNote: {
|
|
type: "boolean"
|
|
},
|
|
receiveAnnouncementEmail: {
|
|
type: "boolean"
|
|
},
|
|
alwaysMarkNsfw: {
|
|
type: "boolean"
|
|
},
|
|
ffVisibility: {
|
|
type: "string",
|
|
enum: [
|
|
"public",
|
|
"followers",
|
|
"private"
|
|
]
|
|
},
|
|
pinnedPageId: {
|
|
type: "string",
|
|
format: "misskey:id",
|
|
nullable: true
|
|
},
|
|
mutedWords: {
|
|
type: "array"
|
|
},
|
|
mutedInstances: {
|
|
type: "array",
|
|
items: {
|
|
type: "string"
|
|
}
|
|
},
|
|
mutingNotificationTypes: {
|
|
type: "array",
|
|
items: {
|
|
type: "string",
|
|
enum: notificationTypes
|
|
}
|
|
},
|
|
emailNotificationTypes: {
|
|
type: "array",
|
|
items: {
|
|
type: "string"
|
|
}
|
|
},
|
|
pronouns: {
|
|
type: "object",
|
|
nullable: true
|
|
},
|
|
canBite: {
|
|
type: "string",
|
|
enum: [
|
|
"anyone",
|
|
"followers",
|
|
"nobody"
|
|
],
|
|
nullable: true
|
|
}
|
|
}
|
|
};
|
|
export default define(meta, paramDef, async (ps, _user, token)=>{
|
|
const user = await Users.findOneByOrFail({
|
|
id: _user.id
|
|
});
|
|
const isSecure = token == null;
|
|
const updates = {};
|
|
const profileUpdates = {};
|
|
const profile = await UserProfiles.findOneByOrFail({
|
|
userId: user.id
|
|
});
|
|
if (ps.name !== undefined) updates.name = ps.name;
|
|
if (ps.description !== undefined) profileUpdates.description = ps.description;
|
|
if (ps.lang !== undefined) profileUpdates.lang = ps.lang;
|
|
if (ps.location !== undefined) profileUpdates.location = ps.location;
|
|
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
|
|
if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility;
|
|
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
|
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
|
if (ps.mutedWords !== undefined) {
|
|
// validate regular expression syntax
|
|
ps.mutedWords.filter((x)=>!Array.isArray(x)).forEach((x)=>{
|
|
const regexp = x.match(/^\/(.+)\/(.*)$/);
|
|
if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
|
|
try {
|
|
new RE2(regexp[1], regexp[2]);
|
|
} catch (err) {
|
|
throw new ApiError(meta.errors.invalidRegexp);
|
|
}
|
|
});
|
|
profileUpdates.mutedWords = ps.mutedWords;
|
|
profileUpdates.enableWordMute = ps.mutedWords.length > 0;
|
|
}
|
|
if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
|
|
if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes;
|
|
if (typeof ps.isLocked === "boolean") updates.isLocked = ps.isLocked;
|
|
if (typeof ps.isExplorable === "boolean") updates.isExplorable = ps.isExplorable;
|
|
if (typeof ps.hideOnlineStatus === "boolean") updates.hideOnlineStatus = ps.hideOnlineStatus;
|
|
if (typeof ps.publicReactions === "boolean") profileUpdates.publicReactions = ps.publicReactions;
|
|
if (typeof ps.allowCalls === "boolean") profileUpdates.allowCalls = ps.allowCalls;
|
|
if (ps.symbolFileId !== undefined) profileUpdates.symbolFileId = ps.symbolFileId;
|
|
if (typeof ps.isBot === "boolean") updates.isBot = ps.isBot;
|
|
if (ps.minorBadges !== undefined) updates.minorBadges = ps.minorBadges.slice(0, 1);
|
|
if (typeof ps.carefulBot === "boolean") profileUpdates.carefulBot = ps.carefulBot;
|
|
if (typeof ps.autoAcceptFollowed === "boolean") profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
|
if (typeof ps.noCrawle === "boolean") profileUpdates.noCrawle = ps.noCrawle;
|
|
if (typeof ps.preventAiLearning === "boolean") profileUpdates.preventAiLearning = ps.preventAiLearning;
|
|
if (typeof ps.isCat === "boolean") updates.isCat = ps.isCat;
|
|
if (typeof ps.speakAsCat === "boolean") updates.speakAsCat = ps.speakAsCat;
|
|
if (typeof ps.injectFeaturedNote === "boolean") profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
|
if (typeof ps.receiveAnnouncementEmail === "boolean") profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
|
|
if (typeof ps.alwaysMarkNsfw === "boolean") profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
|
if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
|
|
if (typeof ps.pronouns === "object") {
|
|
if (ps.pronouns === null) ps.pronouns = {};
|
|
for (const key of Object.keys(ps.pronouns)){
|
|
if (key.length !== 2 || typeof ps.pronouns[key] !== "string" || ps.pronouns[key].length === 0) {
|
|
delete ps.pronouns[key];
|
|
}
|
|
}
|
|
profileUpdates.pronouns = ps.pronouns;
|
|
}
|
|
const avatar = ps.avatarId ? await DriveFiles.findOneBy({
|
|
id: ps.avatarId
|
|
}) : null;
|
|
const banner = ps.bannerId ? await DriveFiles.findOneBy({
|
|
id: ps.bannerId
|
|
}) : null;
|
|
if (ps.avatarId) {
|
|
if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
|
|
if (!avatar.type.startsWith("image/")) throw new ApiError(meta.errors.avatarNotAnImage);
|
|
updates.avatarUrl = DriveFiles.getDatabasePrefetchUrl(avatar, true);
|
|
updates.avatarBlurhash = avatar.blurhash;
|
|
}
|
|
if (ps.bannerId) {
|
|
if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
|
|
if (!banner.type.startsWith("image/")) throw new ApiError(meta.errors.bannerNotAnImage);
|
|
updates.bannerUrl = DriveFiles.getDatabasePrefetchUrl(banner, false);
|
|
updates.bannerBlurhash = banner.blurhash;
|
|
}
|
|
if (ps.pinnedPageId) {
|
|
const page = await Pages.findOneBy({
|
|
id: ps.pinnedPageId
|
|
});
|
|
if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
|
|
profileUpdates.pinnedPageId = page.id;
|
|
} else if (ps.pinnedPageId === null) {
|
|
profileUpdates.pinnedPageId = null;
|
|
}
|
|
if (ps.fields) {
|
|
for (const field of ps.fields){
|
|
if (!field || field.name === "" || field.value === "") {
|
|
continue;
|
|
}
|
|
if (typeof field.name !== "string" || field.name === "") {
|
|
throw new ApiError(meta.errors.invalidFieldName);
|
|
}
|
|
if (typeof field.value !== "string" || field.value === "") {
|
|
throw new ApiError(meta.errors.invalidFieldValue);
|
|
}
|
|
if (field.value.startsWith("http")) {
|
|
field.verified = await verifyLink(field.value, user.username);
|
|
}
|
|
}
|
|
profileUpdates.fields = ps.fields.filter((x)=>Object.keys(x).length !== 0).map((x)=>{
|
|
return {
|
|
name: x.name,
|
|
value: x.value,
|
|
verified: x.verified
|
|
};
|
|
});
|
|
}
|
|
if (ps.canBite) {
|
|
updates.canBite = ps.canBite;
|
|
}
|
|
return updateUserProfileData(user, profile, updates, profileUpdates, isSecure);
|
|
});
|