mirror of
https://github.com/MathMan05/Fermi.git
synced 2026-05-13 17:13:15 +00:00
1750 lines
45 KiB
TypeScript
1750 lines
45 KiB
TypeScript
import {Member} from "./member.js";
|
|
import {MarkDown} from "./markdown.js";
|
|
import {Contextmenu} from "./contextmenu.js";
|
|
import {Localuser} from "./localuser.js";
|
|
import {Guild} from "./guild.js";
|
|
import {SnowFlake} from "./snowflake.js";
|
|
import {
|
|
ConnectionJson,
|
|
highMemberJSON,
|
|
presencejson,
|
|
relationJson,
|
|
userjson,
|
|
webhookInfo,
|
|
} from "./jsontypes.js";
|
|
import {Role} from "./role.js";
|
|
import {Search} from "./search.js";
|
|
import {I18n} from "./i18n.js";
|
|
import {Hover} from "./hover.js";
|
|
import {Dialog, Float, Options} from "./settings.js";
|
|
import {createImg, removeAni, safeImg} from "./utils/utils.js";
|
|
import {Direct} from "./direct.js";
|
|
import {Permissions} from "./permissions.js";
|
|
import {Channel} from "./channel.js";
|
|
import {getDeveloperSettings} from "./utils/storage/devSettings";
|
|
import {ReportMenu} from "./reporting/report.js";
|
|
import {CDNParams} from "./utils/cdnParams.js";
|
|
export const userVolMenu = new Contextmenu<Localuser, string>("user vol stacked", true);
|
|
userVolMenu.addSlider(
|
|
() => I18n.Voice.userVol(),
|
|
(local, id, e) => {
|
|
local.setUserAudio(id, e);
|
|
},
|
|
undefined,
|
|
"default",
|
|
{
|
|
startVal: (local, id) => {
|
|
return local.getUserAudio(id);
|
|
},
|
|
},
|
|
);
|
|
class User extends SnowFlake {
|
|
owner: Localuser;
|
|
hypotheticalpfp!: boolean;
|
|
avatar!: string | null;
|
|
uid: string;
|
|
username!: string;
|
|
nickname: string | null = null;
|
|
relationshipType: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
|
|
bio!: MarkDown;
|
|
discriminator!: string;
|
|
pronouns?: string;
|
|
bot!: boolean;
|
|
public_flags!: number;
|
|
webhook?: webhookInfo;
|
|
accent_color!: number;
|
|
banner: string | undefined;
|
|
hypotheticalbanner!: boolean;
|
|
premium_since!: string;
|
|
premium_type!: number;
|
|
theme_colors: [number, number] | null = null;
|
|
badge_ids!: string[];
|
|
members: WeakMap<Guild, Member | undefined | Promise<Member | undefined>> = new WeakMap();
|
|
status!: string;
|
|
avatar_decoration_data?: {
|
|
asset: string;
|
|
sku_id: string;
|
|
} | null;
|
|
|
|
resolving: false | Promise<any> = false;
|
|
get headers() {
|
|
return this.localuser.headers;
|
|
}
|
|
|
|
constructor(userjson: userjson, owner: Localuser, dontclone: boolean = false) {
|
|
super(userjson.id);
|
|
this.owner = owner;
|
|
if (getDeveloperSettings().logBannedFields && owner.user && owner.user.id !== userjson.id) {
|
|
this.checkfortmi(userjson);
|
|
}
|
|
if (!owner) {
|
|
console.error("missing localuser");
|
|
}
|
|
this.uid = userjson.id;
|
|
if (userjson.webhook) {
|
|
this.uid += ":::" + userjson.username;
|
|
console.log(this.uid);
|
|
}
|
|
userjson.uid = this.uid;
|
|
if (dontclone) {
|
|
this.userupdate(userjson);
|
|
this.hypotheticalpfp = false;
|
|
} else {
|
|
return User.checkuser(userjson, owner);
|
|
}
|
|
}
|
|
static makeSelector(
|
|
opt: Options,
|
|
doneText: string,
|
|
options: User[],
|
|
{single = false, addText = I18n.add()} = {},
|
|
): Promise<Set<User> | void> {
|
|
return new Promise<Set<User> | void>((res) => {
|
|
const div = document.createElement("div");
|
|
div.classList.add("flexttb", "friendGroupSelect");
|
|
|
|
const invited = new Set<User>();
|
|
|
|
const makeList = (search: string) => {
|
|
const list = options
|
|
.map((user) => [user, user.compare(search)] as const)
|
|
.filter((_) => _[1] !== 0)
|
|
.sort((a, b) => a[1] - b[1])
|
|
.map((_) => _[0]);
|
|
div.innerHTML = "";
|
|
div.append(
|
|
...list.map((user) => {
|
|
const div = document.createElement("div");
|
|
div.classList.add("flexltr");
|
|
|
|
//TODO implement status stuff here once spacebar really supports it
|
|
div.append(user.buildpfp(), user.name);
|
|
if (single) {
|
|
const button = document.createElement("button");
|
|
button.textContent = addText;
|
|
div.append(button);
|
|
button.onclick = () => res(new Set([user]));
|
|
} else {
|
|
const check = document.createElement("input");
|
|
check.type = "checkbox";
|
|
check.checked = invited.has(user);
|
|
check.onchange = () => {
|
|
if (check.checked) {
|
|
invited.add(user);
|
|
} else {
|
|
invited.delete(user);
|
|
}
|
|
};
|
|
div.append(check);
|
|
}
|
|
return div;
|
|
}),
|
|
);
|
|
};
|
|
opt.addTextInput("", () => {}).onchange = makeList;
|
|
opt.addHTMLArea(div);
|
|
const buttons = opt.addOptions("", {ltr: true});
|
|
buttons.addButtonInput("", I18n.cancel(), () => {
|
|
res();
|
|
});
|
|
buttons.addButtonInput("", doneText, async () => {
|
|
res(invited);
|
|
});
|
|
|
|
makeList("");
|
|
buttons.container.deref()?.classList.add("expandButtons");
|
|
});
|
|
}
|
|
compare(str: string) {
|
|
function similar(str2: string | null | undefined) {
|
|
if (!str2) return 0;
|
|
const strl = Math.max(str.length, 1);
|
|
if (str2.includes(str)) {
|
|
return strl / str2.length;
|
|
} else if (str2.toLowerCase().includes(str.toLowerCase())) {
|
|
return strl / str2.length / 1.2;
|
|
}
|
|
return 0;
|
|
}
|
|
return Math.max(
|
|
similar(this.name),
|
|
similar(this.nickname),
|
|
similar(this.username),
|
|
similar(this.id) / 1.5,
|
|
);
|
|
}
|
|
/**
|
|
* function is meant to check if userjson contains too much information IE non-public stuff
|
|
*
|
|
*
|
|
*/
|
|
checkfortmi(json: any) {
|
|
if (json.data) {
|
|
console.error("Server sent *way* too much info, this is really bad, it sent data");
|
|
}
|
|
const bad = new Set([
|
|
"fingerprints",
|
|
"extended_settings",
|
|
"mfa_enabled",
|
|
"nsfw_allowed",
|
|
"premium_usage_flags",
|
|
"totp_last_ticket",
|
|
"totp_secret",
|
|
"webauthn_enabled",
|
|
]);
|
|
if (!this.localuser.rights.getPermission("OPERATOR")) {
|
|
//Unless the user is an operator, we really shouldn't ever see this
|
|
bad.add("rights");
|
|
}
|
|
for (const thing of bad) {
|
|
if (json.hasOwnProperty(thing)) {
|
|
console.error(thing + " should not be exposed to the client");
|
|
}
|
|
}
|
|
}
|
|
tojson(): userjson {
|
|
return {
|
|
username: this.username,
|
|
id: this.id,
|
|
public_flags: this.public_flags,
|
|
discriminator: this.discriminator,
|
|
avatar: this.avatar,
|
|
accent_color: this.accent_color,
|
|
banner: this.banner,
|
|
bio: this.bio.rawString,
|
|
premium_since: this.premium_since,
|
|
premium_type: this.premium_type,
|
|
bot: this.bot,
|
|
theme_colors: this.theme_colors,
|
|
pronouns: this.pronouns,
|
|
badge_ids: this.badge_ids,
|
|
};
|
|
}
|
|
|
|
clone(): User {
|
|
const json = this.tojson();
|
|
json.id += "#clone";
|
|
return new User(json, this.owner);
|
|
}
|
|
|
|
public getPresence(presence: presencejson | undefined): void {
|
|
if (presence) {
|
|
this.setstatus(presence.status);
|
|
} else {
|
|
this.setstatus("offline");
|
|
}
|
|
}
|
|
get online() {
|
|
return this.status && this.status != "offline";
|
|
}
|
|
setstatus(status: string): void {
|
|
this.status = status;
|
|
const has = this.statusChange();
|
|
if (has) this.localstatusUpdate();
|
|
}
|
|
|
|
getStatus(): string {
|
|
return this.status || "offline";
|
|
}
|
|
|
|
static contextmenu = new Contextmenu<User, Member | undefined>("User Menu");
|
|
async opendm(message?: string) {
|
|
for (const dm of (this.localuser.guildids.get("@me") as Direct).channels) {
|
|
if ((dm.type === 1 || dm.type === undefined) && dm.users[0].id === this.id) {
|
|
await this.localuser.goToChannel(dm.id);
|
|
if (message) {
|
|
await dm.sendMessage(message, {
|
|
attachments: [],
|
|
embeds: [],
|
|
replyingto: null,
|
|
sticker_ids: [],
|
|
});
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
await fetch(this.info.api + "/users/@me/channels", {
|
|
method: "POST",
|
|
body: JSON.stringify({recipients: [this.id]}),
|
|
headers: this.localuser.headers,
|
|
})
|
|
.then((res) => res.json())
|
|
.then((json) => {
|
|
return this.localuser.goToChannel(json.id);
|
|
});
|
|
if (message) {
|
|
for (const dm of (this.localuser.guildids.get("@me") as Direct).channels) {
|
|
if ((dm.type === 1 || dm.type === undefined) && dm.users[0].id === this.id) {
|
|
dm.sendMessage(message, {
|
|
attachments: [],
|
|
embeds: [],
|
|
replyingto: null,
|
|
sticker_ids: [],
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
async changeRelationship(type: 0 | 1 | 2 | 3 | 4 | 5) {
|
|
const relChange = this.localuser.relationChange(this.id);
|
|
if (type !== 0) {
|
|
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
|
|
method: "PUT",
|
|
headers: this.owner.headers,
|
|
body: JSON.stringify({
|
|
type,
|
|
}),
|
|
});
|
|
} else {
|
|
await fetch(`${this.info.api}/users/@me/relationships/${this.id}`, {
|
|
method: "DELETE",
|
|
headers: this.owner.headers,
|
|
});
|
|
}
|
|
await relChange;
|
|
}
|
|
static setUpContextMenu(): void {
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.message(),
|
|
function (this: User) {
|
|
this.opendm();
|
|
},
|
|
{
|
|
icon: {
|
|
css: "svg-frmessage",
|
|
},
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addSeperator();
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.block(),
|
|
function (this: User) {
|
|
this.block();
|
|
},
|
|
{
|
|
visible: function () {
|
|
return this.relationshipType !== 2 && this.id !== this.localuser.user.id;
|
|
},
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.unblock(),
|
|
function (this: User) {
|
|
this.unblock();
|
|
},
|
|
{
|
|
visible: function () {
|
|
return this.relationshipType === 2 && this.id !== this.localuser.user.id;
|
|
},
|
|
},
|
|
);
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.friendReq(),
|
|
function (this: User) {
|
|
this.changeRelationship(1);
|
|
},
|
|
{
|
|
visible: function () {
|
|
return (
|
|
(this.relationshipType === 0 || this.relationshipType === 3) &&
|
|
this.id !== this.localuser.user.id &&
|
|
!this.bot
|
|
);
|
|
},
|
|
icon: {
|
|
css: "svg-addfriend",
|
|
},
|
|
},
|
|
);
|
|
this.contextmenu.addButton(
|
|
() => I18n.friends.removeFriend(),
|
|
function (this: User) {
|
|
this.changeRelationship(0);
|
|
},
|
|
{
|
|
visible: function () {
|
|
return this.relationshipType === 1 && this.id !== this.localuser.user.id;
|
|
},
|
|
},
|
|
);
|
|
this.contextmenu.addButton(
|
|
function () {
|
|
switch (this.relationshipType) {
|
|
case 1:
|
|
return I18n.user.nick.friend();
|
|
case 2:
|
|
return I18n.user.nick.foe();
|
|
case 3:
|
|
return I18n.user.nick.stalker();
|
|
case 4:
|
|
return I18n.user.nick.stalking();
|
|
default:
|
|
return "You shouldn't see this";
|
|
}
|
|
},
|
|
function (this: User) {
|
|
this.setFriendNick();
|
|
},
|
|
{
|
|
visible: function () {
|
|
return new Set([1, 2, 3, 4]).has(this.relationshipType);
|
|
},
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addSeperator();
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.editServerProfile(),
|
|
function (this: User, member: Member | undefined) {
|
|
if (!member) return;
|
|
member.showEditProfile();
|
|
},
|
|
{
|
|
visible: function (member) {
|
|
return member?.id === this.localuser.user.id;
|
|
},
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.editNick(),
|
|
function (this: User, member: Member | undefined) {
|
|
if (!member) return;
|
|
member.showEditNick();
|
|
},
|
|
{
|
|
visible: function (member) {
|
|
return (
|
|
!!member &&
|
|
member?.id !== this.localuser.user.id &&
|
|
member.guild.member.hasPermission("MANAGE_NICKNAMES")
|
|
);
|
|
},
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.timeout(),
|
|
function (this: User, member: Member | undefined) {
|
|
member?.timeout();
|
|
},
|
|
{
|
|
visible: function (member) {
|
|
if (!member) return false;
|
|
if (member.hasPermission("MODERATE_MEMBERS")) return false;
|
|
|
|
return (
|
|
!member.commuicationDisabledLeft() &&
|
|
member.guild.member.hasPermission("MODERATE_MEMBERS")
|
|
);
|
|
},
|
|
color: "red",
|
|
icon: {
|
|
css: "svg-timeout",
|
|
},
|
|
},
|
|
);
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.unTimeout(),
|
|
function (memb) {
|
|
memb?.removeTimeout();
|
|
},
|
|
{
|
|
visible: function (member) {
|
|
if (!member) return false;
|
|
|
|
return (
|
|
!!member.commuicationDisabledLeft() &&
|
|
member.guild.member.hasPermission("MODERATE_MEMBERS")
|
|
);
|
|
},
|
|
color: "red",
|
|
},
|
|
);
|
|
|
|
//TODO kick icon
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.kick(),
|
|
function (this: User, member: Member | undefined) {
|
|
member?.kick();
|
|
},
|
|
{
|
|
visible: function (member) {
|
|
if (!member) return false;
|
|
const us = member.guild.member;
|
|
if (member.id === us.id) {
|
|
return false;
|
|
}
|
|
if (member.id === member.guild.properties.owner_id) {
|
|
return false;
|
|
}
|
|
return us.hasPermission("KICK_MEMBERS") && this.id !== this.localuser.user.id;
|
|
},
|
|
color: "red",
|
|
},
|
|
);
|
|
|
|
//TODO ban icon
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.ban(),
|
|
function (this: User, member: Member | undefined) {
|
|
member?.ban();
|
|
},
|
|
{
|
|
visible: function (member) {
|
|
if (!member) return false;
|
|
const us = member.guild.member;
|
|
if (member.id === us.id) {
|
|
return false;
|
|
}
|
|
if (member.id === member.guild.properties.owner_id) {
|
|
return false;
|
|
}
|
|
return us.hasPermission("BAN_MEMBERS") && this.id !== this.localuser.user.id;
|
|
},
|
|
color: "red",
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addSeperator();
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.addRole(),
|
|
async function (this: User, member: Member | undefined, e) {
|
|
if (member) {
|
|
e.stopPropagation();
|
|
const roles: [Role, string[]][] = [];
|
|
for (const role of member.guild.roles) {
|
|
if (!role.canManage() || member.roles.indexOf(role) !== -1) {
|
|
continue;
|
|
}
|
|
roles.push([role, [role.name]]);
|
|
}
|
|
const search = new Search(roles);
|
|
const result = await search.find(e.x, e.y);
|
|
if (!result) return;
|
|
member.addRole(result);
|
|
}
|
|
},
|
|
{
|
|
visible: (member) => {
|
|
if (!member) return false;
|
|
const us = member.guild.member;
|
|
console.log(us.hasPermission("MANAGE_ROLES"));
|
|
return us.hasPermission("MANAGE_ROLES") || false;
|
|
},
|
|
},
|
|
);
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.removeRole(),
|
|
async function (this: User, member: Member | undefined, e) {
|
|
if (member) {
|
|
e.stopPropagation();
|
|
const roles: [Role, string[]][] = [];
|
|
for (const role of member.roles) {
|
|
if (!role.canManage()) {
|
|
continue;
|
|
}
|
|
roles.push([role, [role.name]]);
|
|
}
|
|
const search = new Search(roles);
|
|
const result = await search.find(e.x, e.y);
|
|
if (!result) return;
|
|
member.removeRole(result);
|
|
}
|
|
},
|
|
{
|
|
visible: (member) => {
|
|
if (!member) return false;
|
|
const us = member.guild.member;
|
|
console.log(us.hasPermission("MANAGE_ROLES"));
|
|
return us.hasPermission("MANAGE_ROLES") || false;
|
|
},
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addSeperator();
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.copyId(),
|
|
function (this: User) {
|
|
navigator.clipboard.writeText(this.id);
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addSeperator();
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.report(),
|
|
async function (member) {
|
|
const menu = await ReportMenu.makeReport("user", this.localuser, {user: this, member});
|
|
menu?.spawnMenu();
|
|
},
|
|
{
|
|
visible: function () {
|
|
const settings = getDeveloperSettings();
|
|
return this.id !== this.localuser.user.id && settings.reportSystem;
|
|
},
|
|
color: "red",
|
|
},
|
|
);
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.reportApp(),
|
|
async function () {
|
|
const menu = await ReportMenu.makeReport("application", this.localuser, {
|
|
application_id: this.id,
|
|
});
|
|
menu?.spawnMenu();
|
|
},
|
|
{
|
|
visible: function () {
|
|
const settings = getDeveloperSettings();
|
|
return this.bot && settings.reportSystem;
|
|
},
|
|
color: "red",
|
|
},
|
|
);
|
|
|
|
this.contextmenu.addButton(
|
|
() => I18n.user.instanceBan(),
|
|
function (this: User) {
|
|
const params = {
|
|
reason: "",
|
|
persistInstanceBan: true,
|
|
};
|
|
const menu = new Dialog("");
|
|
const options = menu.float.options;
|
|
options.addTitle(I18n.user.confirmInstBan(this.name));
|
|
options.addTextInput(I18n.member["reason:"](), () => {}, {}).onchange = (txt) => {
|
|
params.reason = txt;
|
|
};
|
|
options.addCheckboxInput(I18n.member.persist(), () => {}, {
|
|
initState: false,
|
|
}).onchange = (checked) => {
|
|
params.persistInstanceBan = !checked;
|
|
};
|
|
const opt = options.addOptions("", {ltr: true});
|
|
opt.addButtonInput("", I18n.yes(), () => {
|
|
fetch(this.info.api + "/users/" + this.id + "/delete", {
|
|
headers: this.localuser.headers,
|
|
method: "POST",
|
|
body: JSON.stringify(params),
|
|
});
|
|
menu.hide();
|
|
});
|
|
opt.addButtonInput("", I18n.no(), () => {
|
|
menu.hide();
|
|
});
|
|
menu.show();
|
|
},
|
|
{
|
|
visible: function () {
|
|
return this.localuser.rights.hasPermission("MANAGE_USERS");
|
|
},
|
|
color: "red",
|
|
},
|
|
);
|
|
console.warn("this ran");
|
|
}
|
|
setFriendNick() {
|
|
const dio = new Dialog("");
|
|
const form = dio.options.addForm(
|
|
"",
|
|
() => {
|
|
dio.hide();
|
|
},
|
|
{
|
|
fetchURL: this.info.api + `/users/@me/relationships/${this.id}`,
|
|
method: "PATCH",
|
|
headers: this.headers,
|
|
},
|
|
);
|
|
form.addTextInput(I18n.member["nick:"](), "nickname", {
|
|
initText: this.nickname || "",
|
|
});
|
|
dio.show();
|
|
}
|
|
getMembersSync() {
|
|
return this.localuser.guilds
|
|
.map((guild) => {
|
|
const m = this.members.get(guild);
|
|
return m instanceof Member ? m : undefined;
|
|
})
|
|
.filter((m) => m !== undefined);
|
|
}
|
|
|
|
elms = new Set<WeakRef<HTMLElement>>();
|
|
subName(elm: HTMLElement) {
|
|
this.elms.add(new WeakRef(elm));
|
|
}
|
|
nameChange() {
|
|
this.getMembersSync().forEach((memb) => {
|
|
memb.nameChange();
|
|
});
|
|
|
|
for (const ref of this.elms) {
|
|
const elm = ref.deref();
|
|
if (!elm || !document.contains(elm)) {
|
|
this.elms.delete(ref);
|
|
continue;
|
|
}
|
|
elm.textContent = this.name;
|
|
}
|
|
}
|
|
|
|
static checkuser(user: User | userjson, owner: Localuser): User {
|
|
const tempUser = owner.userMap.get(user.uid || user.id);
|
|
if (tempUser) {
|
|
if (!(user instanceof User)) {
|
|
tempUser.userupdate(user);
|
|
}
|
|
return tempUser;
|
|
} else {
|
|
const tempuser = new User(user as userjson, owner, true);
|
|
owner.userMap.set(user.uid || user.id, tempuser);
|
|
return tempuser;
|
|
}
|
|
}
|
|
|
|
get info() {
|
|
return this.owner.info;
|
|
}
|
|
|
|
get localuser() {
|
|
return this.owner;
|
|
}
|
|
|
|
get name() {
|
|
return this.nickname || (this.relationshipType === 2 ? I18n.friends.bu() : this.username);
|
|
}
|
|
|
|
async resolvemember(guild: Guild): Promise<Member | undefined> {
|
|
return await Member.resolveMember(this, guild);
|
|
}
|
|
|
|
async getUserProfile(): Promise<any> {
|
|
return await fetch(
|
|
`${this.info.api}/users/${this.id.replace(
|
|
"#clone",
|
|
"",
|
|
)}/profile?with_mutual_guilds=true&with_mutual_friends=true`,
|
|
{
|
|
headers: this.localuser.headers,
|
|
},
|
|
).then((res) => res.json());
|
|
}
|
|
|
|
async getBadge(id: string) {
|
|
if (this.localuser.badges.has(id)) {
|
|
return this.localuser.badges.get(id);
|
|
} else {
|
|
if (this.resolving) {
|
|
await this.resolving;
|
|
return this.localuser.badges.get(id);
|
|
}
|
|
|
|
const prom = await this.getUserProfile();
|
|
this.resolving = prom;
|
|
const badges = prom.badges;
|
|
this.resolving = false;
|
|
for (const badge of badges) {
|
|
this.localuser.badges.set(badge.id, badge);
|
|
}
|
|
return this.localuser.badges.get(id);
|
|
}
|
|
}
|
|
|
|
buildpfp(guild: Guild | void | Member | null, hoverElm: void | HTMLElement): HTMLDivElement {
|
|
const div = document.createElement("div");
|
|
div.classList.add("pfpDiv");
|
|
hoverElm ??= div;
|
|
const pfp = createImg(this.getpfpsrc(), undefined, hoverElm);
|
|
pfp.loading = "lazy";
|
|
pfp.classList.add("pfp");
|
|
if (!this.webhook) pfp.classList.add("userid:" + this.id);
|
|
if (guild) {
|
|
(async () => {
|
|
if (guild instanceof Guild) {
|
|
const memb = await Member.resolveMember(this, guild);
|
|
if (!memb) return;
|
|
pfp.setSrcs(memb.getpfpsrc());
|
|
} else {
|
|
pfp.setSrcs(guild.getpfpsrc());
|
|
}
|
|
})();
|
|
}
|
|
if (this.avatar_decoration_data && this.localuser.perminfo.user.decorations) {
|
|
const dec = createImg(
|
|
this.info.cdn +
|
|
`/avatar-decoration-presets/${this.avatar_decoration_data.asset}.png` +
|
|
new CDNParams({expectedSize: 96}),
|
|
void 0,
|
|
hoverElm,
|
|
);
|
|
dec.classList.add("avDec");
|
|
div.append(dec);
|
|
}
|
|
div.append(pfp);
|
|
return div;
|
|
}
|
|
createWidget(guild?: Guild) {
|
|
guild = this.localuser.guildids.get("@me") as Guild;
|
|
const div = document.createElement("div");
|
|
div.classList.add("flexltr", "createdWebhook");
|
|
//TODO make sure this is something I can actually do here
|
|
const name = document.createElement("b");
|
|
name.textContent = this.name;
|
|
|
|
const nameBox = document.createElement("div");
|
|
nameBox.classList.add("flexttb");
|
|
nameBox.append(name);
|
|
const pfp = this.buildpfp(undefined, div);
|
|
div.append(pfp, nameBox);
|
|
Member.resolveMember(this, guild).then((_) => {
|
|
if (_) {
|
|
_.subName(name);
|
|
name.textContent = _.name;
|
|
(pfp.children[0] as HTMLImageElement).src = _.getpfpsrc();
|
|
} else if (guild.id !== "@me") {
|
|
this.subName(name);
|
|
const notFound = document.createElement("span");
|
|
notFound.textContent = I18n.webhooks.notFound();
|
|
nameBox.append(notFound);
|
|
}
|
|
});
|
|
this.bind(div, guild, undefined);
|
|
return div;
|
|
}
|
|
updateStatusSet = new Set<WeakRef<HTMLDivElement>>();
|
|
contextMap = new WeakMap<HTMLDivElement, Guild | Channel>();
|
|
localstatusUpdate = () => {};
|
|
registerStatus(status: HTMLDivElement, thing: Guild | void | Member | null | Channel) {
|
|
if (thing) {
|
|
if (thing instanceof Member) {
|
|
this.contextMap.set(status, thing.guild);
|
|
} else {
|
|
this.contextMap.set(status, thing);
|
|
}
|
|
}
|
|
this.updateStatusSet.add(new WeakRef(status));
|
|
this.updateStatus(status);
|
|
}
|
|
updateStatus(status: HTMLDivElement) {
|
|
status.classList.remove("offlinestatus", "dndstatus", "onlinestatus", "typingstatus");
|
|
switch (this.getStatus()) {
|
|
case "offline":
|
|
case "invisible":
|
|
status.classList.add("offlinestatus");
|
|
break;
|
|
case "dnd":
|
|
status.classList.add("dndstatus");
|
|
break;
|
|
case "online":
|
|
default:
|
|
status.classList.add("onlinestatus");
|
|
break;
|
|
}
|
|
const m = this.contextMap.get(status);
|
|
if (m) {
|
|
let guild: Guild;
|
|
let channel: Channel | void = undefined;
|
|
if ("guild" in m) {
|
|
channel = m;
|
|
guild = m.guild;
|
|
} else {
|
|
guild = m;
|
|
}
|
|
const memb = this.members.get(guild);
|
|
if (memb && !(memb instanceof Promise) && channel) {
|
|
const typing = channel.typingmap.get(memb);
|
|
|
|
if (typing) {
|
|
status.classList.add("typingstatus");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
statusChange() {
|
|
let has = false;
|
|
for (const ref of this.updateStatusSet) {
|
|
const elm = ref.deref();
|
|
if (!elm || !document.body.contains(elm)) {
|
|
this.updateStatusSet.delete(ref);
|
|
continue;
|
|
}
|
|
has = true;
|
|
this.updateStatus(elm);
|
|
}
|
|
return has;
|
|
}
|
|
buildstatuspfp(guild: Guild | void | Member | null | Channel): HTMLDivElement {
|
|
const isChannel = !!(guild && "guild" in guild);
|
|
const div = this.buildpfp(isChannel ? guild.guild : guild);
|
|
|
|
const status = document.createElement("div");
|
|
this.registerStatus(status, guild);
|
|
status.classList.add("statusDiv");
|
|
status.append(document.createElement("div"));
|
|
|
|
div.append(status);
|
|
return div;
|
|
}
|
|
|
|
userupdate(json: userjson): void {
|
|
const up = json.username !== this.username;
|
|
if (json.avatar !== this.avatar) {
|
|
Array.from(document.getElementsByClassName("userid:" + this.id)).forEach((element) => {
|
|
const img = element as safeImg;
|
|
if ("setSrcs" in element) {
|
|
img.setSrcs(this.getpfpsrc());
|
|
} else {
|
|
console.warn("element didn't have setSrcs property");
|
|
}
|
|
});
|
|
}
|
|
for (const key of Object.keys(json)) {
|
|
if (key === "bio") {
|
|
this.bio = new MarkDown(json[key], this.localuser);
|
|
continue;
|
|
}
|
|
if (key === "id") {
|
|
continue;
|
|
}
|
|
(this as any)[key] = (json as any)[key];
|
|
}
|
|
if ("rights" in this) {
|
|
if (
|
|
this === this.localuser.user &&
|
|
(typeof this.rights == "string" || typeof this.rights == "number")
|
|
) {
|
|
this.localuser.updateRights(this.rights);
|
|
}
|
|
}
|
|
if (up) {
|
|
this.nameChange();
|
|
}
|
|
}
|
|
|
|
bind(
|
|
html: HTMLElement,
|
|
guild: Guild | null = null,
|
|
error = true,
|
|
button: "right" | "left" | "none" = "right",
|
|
): void {
|
|
if (guild && guild.id !== "@me") {
|
|
Member.resolveMember(this, guild)
|
|
.then((member) => {
|
|
User.contextmenu.bindContextmenu(html, this, member);
|
|
if (member === undefined && error) {
|
|
if (this.webhook) return;
|
|
const errorSpan = document.createElement("span");
|
|
errorSpan.textContent = "!";
|
|
errorSpan.classList.add("membererror");
|
|
html.after(errorSpan);
|
|
return;
|
|
}
|
|
if (member) {
|
|
member.bind(html);
|
|
} else {
|
|
if (button !== "none")
|
|
User.contextmenu.bindContextmenu(html, this, undefined, undefined, undefined, button);
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
console.log(err);
|
|
});
|
|
} else {
|
|
if (button !== "none")
|
|
User.contextmenu.bindContextmenu(html, this, undefined, undefined, undefined, button);
|
|
}
|
|
if (button !== "none")
|
|
if (guild) {
|
|
this.profileclick(html, guild);
|
|
} else {
|
|
this.profileclick(html);
|
|
}
|
|
}
|
|
|
|
static async resolve(id: string, localuser: Localuser): Promise<User> {
|
|
const time = SnowFlake.stringToUnixTime(id);
|
|
|
|
if (time < 1420070400000 + 100)
|
|
return new User(
|
|
{
|
|
id: "0",
|
|
public_flags: 0,
|
|
username: I18n.friends.notfound(),
|
|
avatar: null,
|
|
discriminator: "0000",
|
|
bio: "",
|
|
bot: false,
|
|
premium_type: 0,
|
|
premium_since: "",
|
|
accent_color: 0,
|
|
theme_colors: null,
|
|
badge_ids: [],
|
|
},
|
|
localuser,
|
|
);
|
|
let user: User | undefined;
|
|
if ((user = localuser.userMap.get(id))) return user;
|
|
const json = await fetch(localuser.info.api.toString() + "/users/" + id + "/profile", {
|
|
headers: localuser.headers,
|
|
}).then((res) => res.json());
|
|
if (json.code === 404) {
|
|
return new User(
|
|
{
|
|
id: "0",
|
|
public_flags: 0,
|
|
username: I18n.friends.notfound(),
|
|
avatar: null,
|
|
discriminator: "0000",
|
|
bio: "",
|
|
bot: false,
|
|
premium_type: 0,
|
|
premium_since: "",
|
|
accent_color: 0,
|
|
theme_colors: null,
|
|
badge_ids: [],
|
|
},
|
|
localuser,
|
|
);
|
|
}
|
|
return new User(json.user, localuser);
|
|
}
|
|
|
|
changepfp(update: string | null): void {
|
|
this.avatar = update;
|
|
this.hypotheticalpfp = false;
|
|
}
|
|
|
|
async block() {
|
|
await this.changeRelationship(2);
|
|
const channel = this.localuser.channelfocus;
|
|
if (channel) {
|
|
for (const message of channel.messages) {
|
|
message[1].generateMessage();
|
|
}
|
|
}
|
|
}
|
|
|
|
async unblock() {
|
|
await this.changeRelationship(0);
|
|
const channel = this.localuser.channelfocus;
|
|
if (channel) {
|
|
for (const message of channel.messages) {
|
|
message[1].generateMessage();
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @param guild this is an optional thing that'll get the src of the member if it exists, otherwise ignores it, this is meant to be fast, not accurate
|
|
*/
|
|
getpfpsrc(guild: Guild | void): string {
|
|
if (this.hypotheticalpfp && this.avatar) {
|
|
return this.avatar;
|
|
}
|
|
if (guild) {
|
|
const member = this.members.get(guild);
|
|
if (member instanceof Member) {
|
|
return member.getpfpsrc();
|
|
}
|
|
}
|
|
if (this.avatar !== null) {
|
|
return (
|
|
`${this.info.cdn}/avatars/${this.id.replace("#clone", "")}/${this.avatar}.png` +
|
|
new CDNParams({expectedSize: 96})
|
|
);
|
|
} else {
|
|
const int = Number((BigInt(this.id.replace("#clone", "")) >> 22n) % 6n);
|
|
return `${this.info.cdn}/embed/avatars/${int}.png`;
|
|
}
|
|
}
|
|
async getBadges() {
|
|
let i = 0;
|
|
let flagbits = this.public_flags;
|
|
const ids = [
|
|
"staff",
|
|
"partner",
|
|
"certified_moderator",
|
|
"hypesquad",
|
|
"hypesquad_house_1",
|
|
"hypesquad_house_2",
|
|
"hypesquad_house_3",
|
|
"bug_hunter_level_1",
|
|
"bug_hunter_level_2",
|
|
"active_developer",
|
|
"verified_developer",
|
|
"early_supporter",
|
|
"premium",
|
|
"guild_booster_lvl1",
|
|
"guild_booster_lvl2",
|
|
"guild_booster_lvl3",
|
|
"guild_booster_lvl4",
|
|
"guild_booster_lvl5",
|
|
"guild_booster_lvl6",
|
|
"guild_booster_lvl7",
|
|
"guild_booster_lvl8",
|
|
"guild_booster_lvl9",
|
|
"bot_commands",
|
|
"automod",
|
|
"application_guild_subscription",
|
|
"legacy_username",
|
|
"quest_completed",
|
|
];
|
|
let badgeids: string[] = [];
|
|
while (flagbits !== 0) {
|
|
if (flagbits & 1) {
|
|
badgeids.push(ids[i]);
|
|
}
|
|
flagbits >>= 1;
|
|
i++;
|
|
}
|
|
if (this.badge_ids) {
|
|
badgeids = badgeids.concat(this.badge_ids);
|
|
}
|
|
|
|
let badges: {
|
|
id: string;
|
|
description: string;
|
|
icon: string;
|
|
link?: string;
|
|
translate?: boolean;
|
|
}[] = [];
|
|
|
|
const b = (await Promise.all(badgeids.map((_) => this.getBadge(_)))).filter(
|
|
(_) => _ !== undefined,
|
|
);
|
|
badges = b;
|
|
|
|
return badges;
|
|
}
|
|
async highInfo() {
|
|
return (await (
|
|
await fetch(
|
|
this.info.api +
|
|
"/users/" +
|
|
this.id +
|
|
"/profile?with_mutual_guilds=true&with_mutual_friends=true",
|
|
{headers: this.localuser.headers},
|
|
)
|
|
).json()) as highMemberJSON;
|
|
}
|
|
handleRelationship(relation: relationJson) {
|
|
const nickChange = this.nickname !== relation.nickname;
|
|
this.nickname = relation.nickname;
|
|
this.relationshipType = relation.type;
|
|
this.localuser.inrelation.add(this);
|
|
if (nickChange) {
|
|
this.nameChange();
|
|
}
|
|
}
|
|
removeRelation() {
|
|
const nickChange = this.nickname;
|
|
this.nickname = null;
|
|
this.relationshipType = 0;
|
|
this.localuser.inrelation.delete(this);
|
|
if (nickChange) {
|
|
this.nameChange();
|
|
}
|
|
}
|
|
static getLink(con: ConnectionJson): string | void {
|
|
switch (con.type) {
|
|
case "steam": {
|
|
return `https://steamcommunity.com/profiles/${con.external_id}`;
|
|
}
|
|
}
|
|
}
|
|
async fullProfile(guild: Guild | null | Member = null) {
|
|
console.log(guild);
|
|
const membres = (async () => {
|
|
if (!guild) return;
|
|
let member: Member | undefined;
|
|
if (guild instanceof Guild) {
|
|
member = await Member.resolveMember(this, guild);
|
|
} else {
|
|
member = guild;
|
|
}
|
|
return member;
|
|
})();
|
|
const background = document.createElement("div");
|
|
background.classList.add("background");
|
|
background.onclick = () => {
|
|
removeAni(background);
|
|
};
|
|
const div = document.createElement("div");
|
|
div.onclick = (e) => e.stopImmediatePropagation();
|
|
div.classList.add("centeritem", "profile");
|
|
|
|
if (this.accent_color) {
|
|
div.style.setProperty(
|
|
"--accent_color",
|
|
`#${this.accent_color.toString(16).padStart(6, "0")}`,
|
|
);
|
|
} else {
|
|
div.style.setProperty("--accent_color", "transparent");
|
|
}
|
|
const banner = this.getBanner(guild);
|
|
div.append(banner);
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
if (member.accent_color && member.accent_color !== 0) {
|
|
div.style.setProperty(
|
|
"--accent_color",
|
|
`#${member.accent_color.toString(16).padStart(6, "0")}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
const badgediv = document.createElement("div");
|
|
badgediv.classList.add("badges");
|
|
(async () => {
|
|
const badges = await this.getBadges();
|
|
for (const badgejson of badges) {
|
|
const badge = document.createElement(badgejson.link ? "a" : "div");
|
|
badge.classList.add("badge");
|
|
let src: string;
|
|
if (URL.canParse(badgejson.icon)) {
|
|
src = badgejson.icon;
|
|
} else {
|
|
src =
|
|
this.info.cdn +
|
|
"/badge-icons/" +
|
|
badgejson.icon +
|
|
".png" +
|
|
new CDNParams({expectedSize: 32});
|
|
}
|
|
const img = createImg(src, undefined, badgediv);
|
|
|
|
badge.append(img);
|
|
let hovertxt: string;
|
|
if (badgejson.translate) {
|
|
//@ts-ignore
|
|
hovertxt = I18n.badge[badgejson.description]();
|
|
} else {
|
|
hovertxt = badgejson.description;
|
|
}
|
|
const hover = new Hover(hovertxt);
|
|
hover.addEvent(badge);
|
|
if (badgejson.link && badge instanceof HTMLAnchorElement) {
|
|
badge.href = badgejson.link;
|
|
}
|
|
badgediv.append(badge);
|
|
}
|
|
})();
|
|
|
|
const pfp = this.buildstatuspfp(guild);
|
|
div.appendChild(pfp);
|
|
const userbody = document.createElement("div");
|
|
userbody.classList.add("flexttb", "infosection");
|
|
div.appendChild(userbody);
|
|
|
|
const usernamehtml = document.createElement("h2");
|
|
usernamehtml.textContent = this.name;
|
|
|
|
userbody.appendChild(usernamehtml);
|
|
|
|
if (this.bot) {
|
|
const username = document.createElement("span");
|
|
username.classList.add("bot");
|
|
username.textContent = this.webhook ? I18n.webhook() : I18n.bot();
|
|
usernamehtml.appendChild(username);
|
|
}
|
|
|
|
userbody.appendChild(badgediv);
|
|
const discrimatorhtml = document.createElement("h3");
|
|
discrimatorhtml.classList.add("tag");
|
|
discrimatorhtml.textContent = `${this.username}#${this.discriminator}`;
|
|
userbody.appendChild(discrimatorhtml);
|
|
|
|
const pronounshtml = document.createElement("p");
|
|
pronounshtml.textContent = this.pronouns || "";
|
|
pronounshtml.classList.add("pronouns");
|
|
userbody.appendChild(pronounshtml);
|
|
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
if (member.pronouns && member.pronouns !== "") {
|
|
pronounshtml.textContent = member.pronouns;
|
|
}
|
|
});
|
|
|
|
const rule = document.createElement("hr");
|
|
userbody.appendChild(rule);
|
|
const float = new Float("");
|
|
const infoDiv = document.createElement("div");
|
|
infoDiv.classList.add("flexttb");
|
|
const buttons = float.options.addButtons("", {top: true, titles: false});
|
|
{
|
|
const info = buttons.add(I18n.profile.userInfo());
|
|
|
|
infoDiv.append(I18n.profile.bio(), document.createElement("hr"));
|
|
const biohtml = this.bio.makeHTML();
|
|
infoDiv.appendChild(biohtml);
|
|
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
if (member.bio && member.bio !== "") {
|
|
//TODO make markdown take Guild
|
|
infoDiv.insertBefore(new MarkDown(member.bio, this.localuser).makeHTML(), biohtml);
|
|
biohtml.remove();
|
|
}
|
|
});
|
|
info.addHTMLArea(infoDiv);
|
|
|
|
const roles = document.createElement("div");
|
|
const joined = document.createElement("div");
|
|
joined.textContent = I18n.profile.joined(new Date(this.getUnixTime()).toLocaleString());
|
|
infoDiv.append(roles, document.createElement("hr"), joined);
|
|
|
|
if (guild) {
|
|
membres.then((member) => {
|
|
if (!member) {
|
|
this.subName(usernamehtml);
|
|
return;
|
|
}
|
|
const p = document.createElement("p");
|
|
p.textContent = I18n.profile.joinedMember(
|
|
member.guild.properties.name,
|
|
new Date(member.joined_at).toLocaleString(),
|
|
);
|
|
joined.append(p);
|
|
|
|
usernamehtml.textContent = member.name;
|
|
member.subName(usernamehtml);
|
|
if (this.bot) {
|
|
const username = document.createElement("span");
|
|
username.classList.add("bot");
|
|
username.textContent = this.webhook ? I18n.webhook() : I18n.bot();
|
|
usernamehtml.appendChild(username);
|
|
}
|
|
|
|
roles.classList.add("flexltr", "rolesbox");
|
|
for (const role of member.roles) {
|
|
if (role.id === member.guild.id) continue;
|
|
const roleDiv = document.createElement("div");
|
|
roleDiv.classList.add("rolediv");
|
|
const color = document.createElement("div");
|
|
roleDiv.append(color);
|
|
|
|
color.style.setProperty("--role-color", role.getColorStyle(true));
|
|
color.classList.add("colorrolediv");
|
|
const span = document.createElement("span");
|
|
roleDiv.append(span);
|
|
span.textContent = role.name;
|
|
roles.append(roleDiv);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
(async () => {
|
|
const memb = await membres;
|
|
if (!memb) return;
|
|
const perms = buttons.add(I18n.profile.permInfo());
|
|
const permDiv = document.createElement("div");
|
|
permDiv.classList.add("permbox");
|
|
const permsL = Permissions.info()
|
|
.filter((_) => memb.hasPermission(_.name, false))
|
|
.map((_) => _.readableName);
|
|
for (const perm of permsL) {
|
|
const span = document.createElement("span");
|
|
span.textContent = perm;
|
|
permDiv.append(span);
|
|
}
|
|
perms.addHTMLArea(permDiv);
|
|
})();
|
|
|
|
const fhtml = float.generateHTML();
|
|
fhtml.style.overflow = "auto";
|
|
userbody.append(fhtml);
|
|
|
|
document.body.append(background);
|
|
background.append(div);
|
|
console.log(background);
|
|
(async () => {
|
|
const high = await this.highInfo();
|
|
const mut = buttons.add(I18n.profile.mut());
|
|
const mutDiv = document.createElement("div");
|
|
|
|
mutDiv.append(
|
|
...high.mutual_guilds
|
|
.map((_) => [this.localuser.guildids.get(_.id), _.nick] as const)
|
|
.map(([guild, nick]) => {
|
|
if (!guild) return;
|
|
const icon = guild.generateGuildIcon(false);
|
|
|
|
const box = document.createElement("div");
|
|
box.classList.add("mutGuildBox", "flexltr");
|
|
|
|
const info = document.createElement("div");
|
|
info.classList.add("flexttb");
|
|
const gname = document.createElement("span");
|
|
gname.textContent = guild.properties.name;
|
|
info.append(gname);
|
|
box.append(icon, info);
|
|
if (nick) info.append(nick);
|
|
return box;
|
|
})
|
|
.filter((_) => _ !== undefined),
|
|
);
|
|
mut.addHTMLArea(mutDiv);
|
|
|
|
if (high.mutual_friends) {
|
|
const friends = buttons.add(I18n.profile.mutFriends());
|
|
const div = document.createElement("div");
|
|
div.classList.add("mutFriends");
|
|
div.append(
|
|
...high.mutual_friends
|
|
.map((_) => new User(_, this.localuser))
|
|
.map((user) => {
|
|
const html = user.createWidget(this.localuser.lookingguild);
|
|
html.onclick = (e) => {
|
|
e.stopImmediatePropagation();
|
|
e.preventDefault();
|
|
user.fullProfile(guild);
|
|
removeAni(background);
|
|
};
|
|
return html;
|
|
}),
|
|
);
|
|
friends.addHTMLArea(div);
|
|
}
|
|
if (high.connected_accounts.length) {
|
|
const cons = await this.localuser.getConnections();
|
|
|
|
for (const con of high.connected_accounts) {
|
|
const conic = cons[con.type];
|
|
if (!conic) continue;
|
|
const conDiv = document.createElement("div");
|
|
conDiv.classList.add("flexltr", "conProfDiv");
|
|
const url = User.getLink(con);
|
|
if (url) {
|
|
MarkDown.safeLink(conDiv, url);
|
|
conDiv.style.cursor = "pointer";
|
|
}
|
|
|
|
if (conic.icon_url) {
|
|
const span = document.createElement("span");
|
|
span.classList.add("conImg", "svgicon", "conProfImg");
|
|
span.style.setProperty("mask", `url("${conic.icon_url}")`);
|
|
//span.alt = key;
|
|
conDiv.append(span);
|
|
} else {
|
|
conDiv.textContent = con.type.charAt(0).toUpperCase() + con.type.slice(1);
|
|
}
|
|
|
|
const nameDateBox = document.createElement("div");
|
|
nameDateBox.classList.add("flexttb");
|
|
const username = document.createElement("span");
|
|
username.textContent = con.name;
|
|
nameDateBox.append(username);
|
|
if (con.metadata && con.metadata.created_at) {
|
|
const len = document.createElement("span");
|
|
len.textContent = I18n.connections.since(
|
|
new Date(con.metadata.created_at).toDateString(),
|
|
);
|
|
nameDateBox.append(len);
|
|
}
|
|
conDiv.append(nameDateBox);
|
|
infoDiv.append(conDiv);
|
|
}
|
|
}
|
|
})();
|
|
return background;
|
|
}
|
|
|
|
async buildprofile(
|
|
x: number,
|
|
y: number,
|
|
guild: Guild | null | Member = null,
|
|
zIndex = -1,
|
|
): Promise<HTMLDivElement> {
|
|
const membres = (async () => {
|
|
if (!guild) return;
|
|
let member: Member | undefined;
|
|
if (guild instanceof Guild) {
|
|
member = await Member.resolveMember(this, guild);
|
|
} else {
|
|
member = guild;
|
|
}
|
|
return member;
|
|
})();
|
|
const div = document.createElement("div");
|
|
if (zIndex !== -1) {
|
|
div.style.zIndex = zIndex + "";
|
|
}
|
|
if (this.accent_color) {
|
|
div.style.setProperty(
|
|
"--accent_color",
|
|
`#${this.accent_color.toString(16).padStart(6, "0")}`,
|
|
);
|
|
} else {
|
|
div.style.setProperty("--accent_color", "transparent");
|
|
}
|
|
const banner = this.getBanner(guild);
|
|
div.append(banner);
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
if (member.accent_color && member.accent_color !== 0) {
|
|
div.style.setProperty(
|
|
"--accent_color",
|
|
`#${member.accent_color.toString(16).padStart(6, "0")}`,
|
|
);
|
|
}
|
|
});
|
|
|
|
if (x !== -1) {
|
|
div.style.left = `${x}px`;
|
|
div.style.top = `${y}px`;
|
|
div.classList.add("profile", "flexttb");
|
|
} else {
|
|
this.setstatus("online");
|
|
div.classList.add("hypoprofile", "profile", "flexttb");
|
|
}
|
|
const badgediv = document.createElement("div");
|
|
badgediv.classList.add("badges");
|
|
(async () => {
|
|
const badges = await this.getBadges();
|
|
for (const badgejson of badges) {
|
|
const badge = document.createElement(badgejson.link ? "a" : "div");
|
|
badge.classList.add("badge");
|
|
let src: string;
|
|
if (URL.canParse(badgejson.icon)) {
|
|
src = badgejson.icon;
|
|
} else {
|
|
src =
|
|
this.info.cdn +
|
|
"/badge-icons/" +
|
|
badgejson.icon +
|
|
".png" +
|
|
new CDNParams({expectedSize: 32});
|
|
}
|
|
const img = createImg(src, undefined, badgediv);
|
|
|
|
badge.append(img);
|
|
let hovertxt: string;
|
|
if (badgejson.translate) {
|
|
//@ts-ignore
|
|
hovertxt = I18n.badge[badgejson.description]();
|
|
} else {
|
|
hovertxt = badgejson.description;
|
|
}
|
|
const hover = new Hover(hovertxt);
|
|
hover.addEvent(badge);
|
|
if (badgejson.link && badge instanceof HTMLAnchorElement) {
|
|
badge.href = badgejson.link;
|
|
}
|
|
badgediv.append(badge);
|
|
}
|
|
})();
|
|
const pfp = this.buildstatuspfp(guild);
|
|
pfp.onclick = (e) => {
|
|
if (this.id.includes("clone")) return;
|
|
this.fullProfile(guild);
|
|
div.remove();
|
|
e.stopImmediatePropagation();
|
|
e.preventDefault();
|
|
};
|
|
div.appendChild(pfp);
|
|
const userbody = document.createElement("div");
|
|
userbody.classList.add("flexttb", "infosection");
|
|
div.appendChild(userbody);
|
|
const usernamehtml = document.createElement("h2");
|
|
usernamehtml.textContent = this.username;
|
|
|
|
const friendDiv = document.createElement("div");
|
|
friendDiv.classList.add("friendProf");
|
|
|
|
const friendSpan = document.createElement("span");
|
|
friendSpan.classList.add("svgicon");
|
|
const updateIcon = () => {
|
|
friendSpan.classList.remove("svg-addfriend", "svg-waitfriend");
|
|
console.log(this.relationshipType);
|
|
if (this.relationshipType === 0 || this.relationshipType === 3) {
|
|
friendSpan.classList.add("svg-addfriend");
|
|
} else if (this.relationshipType === 4) {
|
|
friendSpan.classList.add("svg-waitfriend");
|
|
} else {
|
|
friendSpan.classList.add("svg-hasfriend");
|
|
}
|
|
};
|
|
if (this !== this.localuser.user && !this.bot) {
|
|
friendDiv.append(friendSpan);
|
|
div.append(friendDiv);
|
|
updateIcon();
|
|
const queForUpdates = async () => {
|
|
while (document.body.contains(friendDiv)) {
|
|
updateIcon();
|
|
await this.localuser.relationChange(this.id);
|
|
}
|
|
};
|
|
setTimeout(queForUpdates, 100);
|
|
friendDiv.onclick = async () => {
|
|
if (this.relationshipType === 0 || this.relationshipType === 3) {
|
|
await this.changeRelationship(1);
|
|
} else if (this.relationshipType === 4) {
|
|
//nothing
|
|
} else {
|
|
//also nothing
|
|
}
|
|
};
|
|
}
|
|
|
|
userbody.appendChild(usernamehtml);
|
|
if (this.bot) {
|
|
const username = document.createElement("span");
|
|
username.classList.add("bot");
|
|
username.textContent = this.webhook ? I18n.webhook() : I18n.bot();
|
|
usernamehtml.appendChild(username);
|
|
}
|
|
userbody.appendChild(badgediv);
|
|
const discrimatorhtml = document.createElement("h3");
|
|
discrimatorhtml.classList.add("tag");
|
|
discrimatorhtml.textContent = `${this.username}#${this.discriminator}`;
|
|
userbody.appendChild(discrimatorhtml);
|
|
|
|
const pronounshtml = document.createElement("p");
|
|
pronounshtml.textContent = this.pronouns || "";
|
|
pronounshtml.classList.add("pronouns");
|
|
userbody.appendChild(pronounshtml);
|
|
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
if (member.pronouns && member.pronouns !== "") {
|
|
pronounshtml.textContent = member.pronouns;
|
|
}
|
|
});
|
|
|
|
const rule = document.createElement("hr");
|
|
userbody.appendChild(rule);
|
|
const biohtml = this.bio.makeHTML();
|
|
userbody.appendChild(biohtml);
|
|
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
if (member.bio && member.bio !== "") {
|
|
//TODO make markdown take Guild
|
|
userbody.insertBefore(new MarkDown(member.bio, this.localuser).makeHTML(), biohtml);
|
|
biohtml.remove();
|
|
}
|
|
});
|
|
|
|
const send = document.createElement("input");
|
|
if (!this.id.includes("#clone")) div.append(send);
|
|
send.placeholder = I18n.user.sendMessage(this.name);
|
|
send.onkeyup = (e) => {
|
|
if (e.key === "Enter") {
|
|
this.opendm(send.value);
|
|
div.remove();
|
|
}
|
|
};
|
|
|
|
if (guild) {
|
|
membres.then((member) => {
|
|
if (!member) return;
|
|
send.placeholder = I18n.user.sendMessage(member.name);
|
|
usernamehtml.textContent = member.name;
|
|
if (this.bot) {
|
|
const username = document.createElement("span");
|
|
username.classList.add("bot");
|
|
username.textContent = this.webhook ? I18n.webhook() : I18n.bot();
|
|
usernamehtml.appendChild(username);
|
|
}
|
|
const roles = document.createElement("div");
|
|
roles.classList.add("flexltr", "rolesbox");
|
|
for (const role of member.roles) {
|
|
if (role.id === member.guild.id) continue;
|
|
const roleDiv = document.createElement("div");
|
|
roleDiv.classList.add("rolediv");
|
|
const color = document.createElement("div");
|
|
roleDiv.append(color);
|
|
color.style.setProperty("--role-color", role.getColorStyle(true));
|
|
color.classList.add("colorrolediv");
|
|
const span = document.createElement("span");
|
|
roleDiv.append(span);
|
|
span.textContent = role.name;
|
|
roles.append(roleDiv);
|
|
}
|
|
userbody.append(roles);
|
|
});
|
|
}
|
|
|
|
if (x !== -1) {
|
|
Contextmenu.declareMenu(div);
|
|
document.body.appendChild(div);
|
|
Contextmenu.keepOnScreen(div);
|
|
}
|
|
return div;
|
|
}
|
|
getBanner(guild: Guild | null | Member): HTMLImageElement {
|
|
const banner = createImg(undefined);
|
|
|
|
const bsrc = this.getBannerUrl();
|
|
if (bsrc) {
|
|
banner.setSrcs(bsrc);
|
|
banner.classList.add("banner");
|
|
}
|
|
|
|
if (guild) {
|
|
if (guild instanceof Member) {
|
|
const bsrc = guild.getBannerUrl();
|
|
if (bsrc) {
|
|
banner.setSrcs(bsrc);
|
|
banner.classList.add("banner");
|
|
}
|
|
} else {
|
|
Member.resolveMember(this, guild).then((memb) => {
|
|
if (!memb) return;
|
|
const bsrc = memb.getBannerUrl();
|
|
if (bsrc) {
|
|
banner.setSrcs(bsrc);
|
|
banner.classList.add("banner");
|
|
}
|
|
});
|
|
}
|
|
}
|
|
return banner;
|
|
}
|
|
getBannerUrl(): string | undefined {
|
|
if (this.banner) {
|
|
if (!this.hypotheticalbanner) {
|
|
return (
|
|
`${this.info.cdn}/banners/${this.id.replace("#clone", "")}/${this.banner}.png` +
|
|
new CDNParams({expectedSize: 160})
|
|
);
|
|
} else {
|
|
return this.banner;
|
|
}
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
profileclick(obj: HTMLElement, guild?: Guild): void {
|
|
const getIndex = (elm: HTMLElement) => {
|
|
const index = getComputedStyle(elm).zIndex;
|
|
if (index === "auto") {
|
|
if (elm.parentElement) {
|
|
return getIndex(elm.parentElement);
|
|
}
|
|
}
|
|
return +index;
|
|
};
|
|
obj.onclick = (e: MouseEvent) => {
|
|
const index = 1 + getIndex(obj);
|
|
this.buildprofile(e.clientX, e.clientY, guild, index);
|
|
e.stopPropagation();
|
|
};
|
|
}
|
|
}
|
|
|
|
User.setUpContextMenu();
|
|
export {User};
|