383 lines
9.8 KiB
TypeScript
383 lines
9.8 KiB
TypeScript
import {Contextmenu} from "./contextmenu.js";
|
|
import {Guild} from "./guild.js";
|
|
import {Hover} from "./hover.js";
|
|
import {emojijson} from "./jsontypes.js";
|
|
import {Localuser} from "./localuser.js";
|
|
import {BinRead} from "./utils/binaryUtils.js";
|
|
|
|
//I need to recompile the emoji format for translation
|
|
class Emoji {
|
|
static emojis: {
|
|
name: string;
|
|
emojis: {
|
|
name: string;
|
|
emoji: string;
|
|
}[];
|
|
}[];
|
|
name: string;
|
|
id?: string;
|
|
emoji?: string;
|
|
animated: boolean;
|
|
owner: Guild | Localuser;
|
|
get guild() {
|
|
if (this.owner instanceof Guild) {
|
|
return this.owner;
|
|
}
|
|
return null;
|
|
}
|
|
get localuser() {
|
|
if (this.owner instanceof Guild) {
|
|
return this.owner.localuser;
|
|
} else {
|
|
return this.owner;
|
|
}
|
|
}
|
|
get info() {
|
|
return this.owner.info;
|
|
}
|
|
constructor(json: emojijson, owner: Guild | Localuser) {
|
|
this.name = json.name;
|
|
this.id = json.id;
|
|
this.animated = json.animated || false;
|
|
this.owner = owner;
|
|
this.emoji = json.emoji;
|
|
}
|
|
get humanName() {
|
|
if (this.id) {
|
|
const trim = this.name.split(":")[1];
|
|
if (trim) {
|
|
return trim;
|
|
}
|
|
}
|
|
return this.name;
|
|
}
|
|
getHTML(bigemoji: boolean = false) {
|
|
if (this.id) {
|
|
const emojiElem = document.createElement("img");
|
|
emojiElem.classList.add("md-emoji");
|
|
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
|
|
emojiElem.crossOrigin = "anonymous";
|
|
emojiElem.src =
|
|
this.info.cdn + "/emojis/" + this.id + "." + (this.animated ? "gif" : "png") + "?size=32";
|
|
emojiElem.alt = this.name;
|
|
emojiElem.loading = "lazy";
|
|
|
|
const hover = new Hover(this.humanName);
|
|
hover.addEvent(emojiElem);
|
|
|
|
return emojiElem;
|
|
} else if (this.emoji) {
|
|
const emojiElem = document.createElement("span");
|
|
emojiElem.classList.add("md-emoji");
|
|
emojiElem.classList.add(bigemoji ? "bigemoji" : "smallemoji");
|
|
emojiElem.textContent = this.emoji;
|
|
|
|
const hover = new Hover(this.humanName);
|
|
hover.addEvent(emojiElem);
|
|
|
|
return emojiElem;
|
|
} else {
|
|
throw new Error("This path should *never* be gone down, this means a malformed emoji");
|
|
}
|
|
}
|
|
static decodeEmojiList(buffer: ArrayBuffer) {
|
|
const reader = new BinRead(buffer);
|
|
const build: {name: string; emojis: {name: string; emoji: string}[]}[] = [];
|
|
let cats = reader.read16();
|
|
|
|
for (; cats !== 0; cats--) {
|
|
const name = reader.readString16();
|
|
const emojis: {
|
|
name: string;
|
|
skin_tone_support: boolean;
|
|
emoji: string;
|
|
}[] = [];
|
|
let emojinumber = reader.read16();
|
|
for (; emojinumber !== 0; emojinumber--) {
|
|
//console.log(emojis);
|
|
const name = reader.readString8();
|
|
const len = reader.read8();
|
|
const skin_tone_support = len > 127;
|
|
const emoji = reader.readStringNo(len - Number(skin_tone_support) * 128);
|
|
emojis.push({
|
|
name,
|
|
skin_tone_support,
|
|
emoji,
|
|
});
|
|
}
|
|
build.push({
|
|
name,
|
|
emojis,
|
|
});
|
|
}
|
|
this.emojis = build;
|
|
}
|
|
static grabEmoji() {
|
|
fetch("/emoji.bin")
|
|
.then((e) => {
|
|
return e.arrayBuffer();
|
|
})
|
|
.then((e) => {
|
|
Emoji.decodeEmojiList(e);
|
|
});
|
|
}
|
|
static getEmojiFromIDOrString(idOrString: string, localuser: Localuser) {
|
|
for (const list of Emoji.emojis) {
|
|
const emj = list.emojis.find((_) => _.emoji === idOrString);
|
|
if (emj) {
|
|
return new Emoji(emj, localuser);
|
|
}
|
|
}
|
|
for (const guild of localuser.guilds) {
|
|
if (!guild.emojis) continue;
|
|
const emj = guild.emojis.find((_) => _.id === idOrString);
|
|
if (emj) {
|
|
return new Emoji(emj, localuser);
|
|
}
|
|
}
|
|
return new Emoji(
|
|
{
|
|
id: idOrString,
|
|
name: "",
|
|
},
|
|
localuser,
|
|
);
|
|
}
|
|
static async emojiPicker(
|
|
this: typeof Emoji,
|
|
x: number,
|
|
y: number,
|
|
localuser: Localuser,
|
|
): Promise<Emoji> {
|
|
let res: (r: Emoji) => void;
|
|
this;
|
|
const promise: Promise<Emoji> = new Promise((r) => {
|
|
res = r;
|
|
});
|
|
const menu = document.createElement("div");
|
|
menu.classList.add("flexttb", "emojiPicker");
|
|
if (y > 0) {
|
|
menu.style.top = y + "px";
|
|
} else {
|
|
menu.style.bottom = y * -1 + "px";
|
|
}
|
|
if (x > 0) {
|
|
menu.style.left = x + "px";
|
|
} else {
|
|
menu.style.right = x * -1 + "px";
|
|
}
|
|
|
|
const topBar = document.createElement("div");
|
|
topBar.classList.add("flexltr", "emojiHeading");
|
|
|
|
const title = document.createElement("h2");
|
|
title.textContent = Emoji.emojis[0].name;
|
|
title.classList.add("emojiTitle");
|
|
topBar.append(title);
|
|
|
|
const search = document.createElement("input");
|
|
search.type = "text";
|
|
topBar.append(search);
|
|
|
|
let html: HTMLElement | undefined = undefined;
|
|
let topEmoji: undefined | Emoji = undefined;
|
|
function updateSearch(this: typeof Emoji) {
|
|
if (search.value === "") {
|
|
if (html) html.click();
|
|
search.style.removeProperty("width");
|
|
topEmoji = undefined;
|
|
return;
|
|
}
|
|
|
|
search.style.setProperty("width", "3in");
|
|
title.innerText = "";
|
|
body.innerHTML = "";
|
|
const searchResults = this.searchEmoji(search.value, localuser, 200);
|
|
if (searchResults[0]) {
|
|
topEmoji = searchResults[0][0];
|
|
}
|
|
for (const [emoji] of searchResults) {
|
|
const emojiElem = document.createElement("div");
|
|
emojiElem.classList.add("emojiSelect");
|
|
|
|
emojiElem.append(emoji.getHTML());
|
|
body.append(emojiElem);
|
|
|
|
emojiElem.addEventListener("click", () => {
|
|
res(emoji);
|
|
if (Contextmenu.currentmenu !== "") {
|
|
Contextmenu.currentmenu.remove();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
search.addEventListener("input", () => {
|
|
updateSearch.call(this);
|
|
});
|
|
search.addEventListener("keyup", (e) => {
|
|
if (e.key === "Enter" && topEmoji) {
|
|
res(topEmoji);
|
|
if (Contextmenu.currentmenu !== "") {
|
|
Contextmenu.currentmenu.remove();
|
|
}
|
|
}
|
|
});
|
|
|
|
menu.append(topBar);
|
|
|
|
const selection = document.createElement("div");
|
|
selection.classList.add("flexltr", "emojirow");
|
|
const body = document.createElement("div");
|
|
body.classList.add("emojiBody");
|
|
|
|
let isFirst = true;
|
|
|
|
[
|
|
localuser.lookingguild,
|
|
...localuser.guilds.filter((guild) => guild !== localuser.lookingguild),
|
|
]
|
|
.filter((_) => _ !== undefined)
|
|
.filter((guild) => guild.id != "@me" && guild.emojis.length > 0)
|
|
.forEach((guild) => {
|
|
const select = document.createElement("div");
|
|
select.classList.add("emojiSelect");
|
|
|
|
if (guild.properties.icon) {
|
|
const img = document.createElement("img");
|
|
img.classList.add("pfp", "servericon", "emoji-server");
|
|
img.crossOrigin = "anonymous";
|
|
img.src =
|
|
localuser.info.cdn +
|
|
"/icons/" +
|
|
guild.properties.id +
|
|
"/" +
|
|
guild.properties.icon +
|
|
".png?size=48";
|
|
img.alt = "Server: " + guild.properties.name;
|
|
select.appendChild(img);
|
|
} else {
|
|
const div = document.createElement("span");
|
|
div.textContent = guild.properties.name
|
|
.replace(/'s /g, " ")
|
|
.replace(/\w+/g, (word) => word[0])
|
|
.replace(/\s/g, "");
|
|
select.append(div);
|
|
}
|
|
|
|
selection.append(select);
|
|
|
|
const clickEvent = () => {
|
|
search.value = "";
|
|
updateSearch.call(this);
|
|
title.textContent = guild.properties.name;
|
|
body.innerHTML = "";
|
|
for (const emojit of guild.emojis) {
|
|
const emojiElem = document.createElement("div");
|
|
emojiElem.classList.add("emojiSelect");
|
|
|
|
const emojiClass = new Emoji(
|
|
{
|
|
id: emojit.id as string,
|
|
name: emojit.name,
|
|
animated: emojit.animated as boolean,
|
|
},
|
|
localuser,
|
|
);
|
|
emojiElem.append(emojiClass.getHTML());
|
|
body.append(emojiElem);
|
|
|
|
emojiElem.addEventListener("click", () => {
|
|
res(emojiClass);
|
|
if (Contextmenu.currentmenu !== "") {
|
|
Contextmenu.currentmenu.remove();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
select.addEventListener("click", clickEvent);
|
|
if (isFirst) {
|
|
clickEvent();
|
|
isFirst = false;
|
|
}
|
|
});
|
|
|
|
if (Contextmenu.currentmenu !== "") {
|
|
Contextmenu.currentmenu.remove();
|
|
}
|
|
document.body.append(menu);
|
|
Contextmenu.currentmenu = menu;
|
|
Contextmenu.keepOnScreen(menu);
|
|
let i = 0;
|
|
for (const thing of Emoji.emojis) {
|
|
const select = document.createElement("div");
|
|
select.textContent = thing.emojis[0].emoji;
|
|
select.classList.add("emojiSelect");
|
|
selection.append(select);
|
|
const clickEvent = () => {
|
|
search.value = "";
|
|
updateSearch.call(this);
|
|
title.textContent = thing.name;
|
|
body.innerHTML = "";
|
|
for (const emojit of thing.emojis.map((_) => new Emoji(_, localuser))) {
|
|
const emoji = document.createElement("div");
|
|
emoji.classList.add("emojiSelect");
|
|
emoji.append(emojit.getHTML());
|
|
body.append(emoji);
|
|
emoji.onclick = (_) => {
|
|
res(emojit);
|
|
if (Contextmenu.currentmenu !== "") {
|
|
Contextmenu.currentmenu.remove();
|
|
}
|
|
};
|
|
}
|
|
};
|
|
select.onclick = clickEvent;
|
|
if (i === 0) {
|
|
html = select;
|
|
clickEvent();
|
|
}
|
|
i++;
|
|
}
|
|
menu.append(selection);
|
|
menu.append(body);
|
|
search.focus();
|
|
return promise;
|
|
}
|
|
static searchEmoji(search: string, localuser: Localuser, results = 50): [Emoji, number][] {
|
|
//NOTE this function is used for searching in the emoji picker for reactions, and the emoji auto-fill
|
|
const ranked: [emojijson, number][] = [];
|
|
function similar(json: emojijson) {
|
|
if (json.name.includes(search)) {
|
|
ranked.push([json, search.length / json.name.length]);
|
|
return true;
|
|
} else if (json.name.toLowerCase().includes(search.toLowerCase())) {
|
|
ranked.push([json, search.length / json.name.length / 1.4]);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
for (const group of this.emojis) {
|
|
for (const emoji of group.emojis) {
|
|
similar(emoji);
|
|
}
|
|
}
|
|
const weakGuild = new WeakMap<emojijson, Guild>();
|
|
for (const guild of localuser.guilds) {
|
|
if (guild.id !== "@me" && guild.emojis.length !== 0) {
|
|
for (const emoji of guild.emojis) {
|
|
if (similar(emoji)) {
|
|
weakGuild.set(emoji, guild);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ranked.sort((a, b) => b[1] - a[1]);
|
|
return ranked.splice(0, results).map((a) => {
|
|
return [new Emoji(a[0], weakGuild.get(a[0]) || localuser), a[1]];
|
|
});
|
|
}
|
|
}
|
|
Emoji.grabEmoji();
|
|
export {Emoji};
|