jank-client-fork/src/webpage/emoji.ts
2024-11-16 15:12:12 -06:00

293 lines
7.4 KiB
TypeScript

import{ Contextmenu }from"./contextmenu.js";
import{ Guild }from"./guild.js";
import { emojijson } from "./jsontypes.js";
import{ Localuser }from"./localuser.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;
}
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";
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;
return emojiElem;
}else{
throw new Error("This path should *never* be gone down, this means a malformed emoji");
}
}
static decodeEmojiList(buffer: ArrayBuffer){
const view = new DataView(buffer, 0);
let i = 0;
function read16(){
const int = view.getUint16(i);
i += 2;
return int;
}
function read8(){
const int = view.getUint8(i);
i += 1;
return int;
}
function readString8(){
return readStringNo(read8());
}
function readString16(){
return readStringNo(read16());
}
function readStringNo(length: number){
const array = new Uint8Array(length);
for(let i = 0; i < length; i++){
array[i] = read8();
}
//console.log(array);
return new TextDecoder("utf8").decode(array.buffer as ArrayBuffer);
}
const build: { name: string; emojis: { name: string; emoji: string }[] }[] =
[];
let cats = read16();
for(; cats !== 0; cats--){
const name = readString16();
const emojis: {
name: string;
skin_tone_support: boolean;
emoji: string;
}[] = [];
let emojinumber = read16();
for(; emojinumber !== 0; emojinumber--){
//console.log(emojis);
const name = readString8();
const len = read8();
const skin_tone_support = len > 127;
const emoji = 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 async emojiPicker(
x: number,
y: number,
localuser: Localuser
): Promise<Emoji | string>{
let res: (r: Emoji | string) => void;
const promise: Promise<Emoji | string> = new Promise(r=>{
res = r;
});
const menu = document.createElement("div");
menu.classList.add("flexttb", "emojiPicker");
menu.style.top = y + "px";
menu.style.left = x + "px";
const title = document.createElement("h2");
title.textContent = Emoji.emojis[0].name;
title.classList.add("emojiTitle");
menu.append(title);
const selection = document.createElement("div");
selection.classList.add("flexltr", "emojirow");
const body = document.createElement("div");
body.classList.add("emojiBody");
let isFirst = true;
localuser.guilds
.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 = ()=>{
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;
}
});
setTimeout(()=>{
if(Contextmenu.currentmenu != ""){
Contextmenu.currentmenu.remove();
}
document.body.append(menu);
Contextmenu.currentmenu = menu;
Contextmenu.keepOnScreen(menu);
}, 10);
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 = ()=>{
title.textContent = thing.name;
body.innerHTML = "";
for(const emojit of thing.emojis){
const emoji = document.createElement("div");
emoji.classList.add("emojiSelect");
emoji.textContent = emojit.emoji;
body.append(emoji);
emoji.onclick = _=>{
res(emojit.emoji);
if(Contextmenu.currentmenu !== ""){
Contextmenu.currentmenu.remove();
}
};
}
};
select.onclick = clickEvent;
if(i === 0){
clickEvent();
}
i++;
}
menu.append(selection);
menu.append(body);
return promise;
}
static searchEmoji(search:string,localuser:Localuser,results=50):[Emoji,number][]{
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 };