webhooks and bug fixes

does not allow for editing/deleting them yet
This commit is contained in:
MathMan05 2025-03-21 11:45:23 -05:00
parent 7068b03757
commit 995961749e
11 changed files with 400 additions and 15 deletions

View file

@ -16,6 +16,7 @@ import {
messagejson,
readyjson,
startTypingjson,
webhookType,
} from "./jsontypes.js";
import {MarkDown} from "./markdown.js";
import {Member} from "./member.js";
@ -222,7 +223,7 @@ class Channel extends SnowFlake {
this.sortPerms();
const settings = new Settings(I18n.getTranslation("channel.settingsFor", this.name));
{
const gensettings = settings.addButton("Settings");
const gensettings = settings.addButton(I18n.channel.settings());
const form = gensettings.addForm("", () => {}, {
fetchURL: this.info.api + "/channels/" + this.id,
method: "PATCH",
@ -256,7 +257,7 @@ class Channel extends SnowFlake {
});
}
}
const s1 = settings.addButton("Permissions");
const s1 = settings.addButton(I18n.channel.permissions());
s1.options.push(
new RoleList(
this.permission_overwritesar,
@ -265,6 +266,131 @@ class Channel extends SnowFlake {
this,
),
);
const webhooks = settings.addButton(I18n.webhooks.base());
(async () => {
const hooks = (await (
await fetch(this.info.api + `/channels/${this.id}/webhooks`, {headers: this.headers})
).json()) as webhookType[];
webhooks.addButtonInput("", I18n.webhooks.newWebHook(), () => {
const nameBox = new Dialog(I18n.webhooks.EnterWebhookName());
const options = nameBox.float.options;
options.addTextInput(I18n.webhooks.name(), async (name) => {
const json = await (
await fetch(`${this.info.api}/channels/${this.id}/webhooks/`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({name}),
})
).json();
makeHook(json);
});
options.addButtonInput("", I18n.submit(), () => {
options.submit();
nameBox.hide();
});
nameBox.show();
});
const makeHook = (hook: webhookType) => {
const div = document.createElement("div");
div.classList.add("flexltr", "webhookArea");
const pfp = document.createElement("img");
if (hook.avatar) {
pfp.src = `${this.info.cdn}/avatars/${hook.id}/${hook.avatar}`;
} else {
const int = Number((BigInt(hook.id) >> 22n) % 6n);
pfp.src = `${this.info.cdn}/embed/avatars/${int}.png`;
}
pfp.classList.add("webhookpfppreview");
const namePlate = document.createElement("div");
namePlate.classList.add("flexttb");
const name = document.createElement("b");
name.textContent = hook.name;
const createdAt = document.createElement("span");
createdAt.textContent = I18n.webhooks.createdAt(
new Intl.DateTimeFormat(I18n.lang).format(SnowFlake.stringToUnixTime(hook.id)),
);
namePlate.append(name, createdAt);
const icon = document.createElement("span");
icon.classList.add("svg-intoMenu", "svgicon");
div.append(pfp, namePlate, icon);
div.onclick = () => {
const form = webhooks.addSubForm(
hook.name,
(e) => {
console.log(e);
},
{traditionalSubmit: true},
);
form.addTextInput(I18n.webhooks.name(), "name", {initText: hook.name});
form.addFileInput(I18n.webhooks.avatar(), "avatar", {clear: true});
const moveChannels = this.guild.channels.filter(
(_) => _.hasPermission("MANAGE_WEBHOOKS") && _.type !== 4,
);
form.addSelect(
I18n.webhooks.channel(),
"channel_id",
moveChannels.map((_) => _.name),
{
defaultIndex: moveChannels.findIndex((_) => _.id === this.id),
},
moveChannels.map((_) => _.id),
);
form.addMDText(I18n.webhooks.token(hook.token));
form.addMDText(I18n.webhooks.url(hook.url));
form.addButtonInput("", I18n.webhooks.copyURL(), () => {
navigator.clipboard.writeText(hook.url);
});
form.addText(I18n.webhooks.createdBy());
try {
const div = document.createElement("div");
div.classList.add("flexltr", "createdWebhook");
//TODO make sure this is something I can actually do here
const user = new User(hook.user, this.localuser);
const name = document.createElement("b");
name.textContent = user.name;
const nameBox = document.createElement("div");
nameBox.classList.add("flexttb");
nameBox.append(name);
const pfp = user.buildpfp();
div.append(pfp, nameBox);
form.addHTMLArea(div);
Member.resolveMember(user, this.guild).then((_) => {
if (_) {
name.textContent = _.name;
pfp.src = _.getpfpsrc();
} else {
const notFound = document.createElement("span");
notFound.textContent = I18n.webhooks.notFound();
nameBox.append(notFound);
}
});
user.bind(div, this.guild);
} catch {}
};
console.log(hook);
webhooks.addHTMLArea(div);
};
for (const hook of hooks) {
makeHook(hook);
}
})();
settings.show();
}
sortPerms() {
@ -799,7 +925,9 @@ class Channel extends SnowFlake {
parent_id: this.id,
permission_overwrites: [],
}),
});
})
.then((_) => _.json())
.then((_) => this.guild.goToChannelDelay(_.id));
}
deleteChannel() {
fetch(this.info.api + "/channels/" + this.id, {

View file

@ -14,6 +14,7 @@ import {
rolesjson,
emojipjson,
extendedProperties,
webhookType,
} from "./jsontypes.js";
import {User} from "./user.js";
import {I18n} from "./i18n.js";
@ -316,6 +317,137 @@ class Guild extends SnowFlake {
genDiv();
emoji.addHTMLArea(containdiv);
}
const webhooks = settings.addButton(I18n.webhooks.base());
(async () => {
const moveChannels = this.channels.filter(
(_) => _.hasPermission("MANAGE_WEBHOOKS") && _.type !== 4,
);
const hooks = (await (
await fetch(this.info.api + `/guilds/${this.id}/webhooks`, {headers: this.headers})
).json()) as webhookType[];
webhooks.addButtonInput("", I18n.webhooks.newWebHook(), () => {
const nameBox = new Dialog(I18n.webhooks.EnterWebhookName());
const options = nameBox.float.options;
options.addTextInput(I18n.webhooks.name(), async (name) => {
const json = await (
await fetch(`${this.info.api}/channels/${moveChannels[select.index].id}/webhooks/`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({name}),
})
).json();
makeHook(json);
});
const select = options.addSelect(
I18n.webhooks.channel(),
() => {},
moveChannels.map((_) => _.name),
{
defaultIndex: 0,
},
);
options.addButtonInput("", I18n.submit(), () => {
options.submit();
nameBox.hide();
});
nameBox.show();
});
const makeHook = (hook: webhookType) => {
const div = document.createElement("div");
div.classList.add("flexltr", "webhookArea");
const pfp = document.createElement("img");
if (hook.avatar) {
pfp.src = `${this.info.cdn}/avatars/${hook.id}/${hook.avatar}`;
} else {
const int = Number((BigInt(hook.id) >> 22n) % 6n);
pfp.src = `${this.info.cdn}/embed/avatars/${int}.png`;
}
pfp.classList.add("webhookpfppreview");
const namePlate = document.createElement("div");
namePlate.classList.add("flexttb");
const name = document.createElement("b");
name.textContent = hook.name;
const createdAt = document.createElement("span");
createdAt.textContent = I18n.webhooks.createdAt(
new Intl.DateTimeFormat(I18n.lang).format(SnowFlake.stringToUnixTime(hook.id)),
);
namePlate.append(name, createdAt);
const icon = document.createElement("span");
icon.classList.add("svg-intoMenu", "svgicon");
div.append(pfp, namePlate, icon);
div.onclick = () => {
const form = webhooks.addSubForm(
hook.name,
(e) => {
console.log(e);
},
{traditionalSubmit: true},
);
form.addTextInput(I18n.webhooks.name(), "name", {initText: hook.name});
form.addFileInput(I18n.webhooks.avatar(), "avatar", {clear: true});
form.addSelect(
I18n.webhooks.channel(),
"channel_id",
moveChannels.map((_) => _.name),
{
defaultIndex: moveChannels.findIndex((_) => _.id === hook.channel_id),
},
moveChannels.map((_) => _.id),
);
form.addMDText(I18n.webhooks.token(hook.token));
form.addMDText(I18n.webhooks.url(hook.url));
form.addButtonInput("", I18n.webhooks.copyURL(), () => {
navigator.clipboard.writeText(hook.url);
});
form.addText(I18n.webhooks.createdBy());
try {
const div = document.createElement("div");
div.classList.add("flexltr", "createdWebhook");
//TODO make sure this is something I can actually do here
const user = new User(hook.user, this.localuser);
const name = document.createElement("b");
name.textContent = user.name;
const nameBox = document.createElement("div");
nameBox.classList.add("flexttb");
nameBox.append(name);
const pfp = user.buildpfp();
div.append(pfp, nameBox);
form.addHTMLArea(div);
Member.resolveMember(user, this).then((_) => {
if (_) {
name.textContent = _.name;
pfp.src = _.getpfpsrc();
} else {
const notFound = document.createElement("span");
notFound.textContent = I18n.webhooks.notFound();
nameBox.append(notFound);
}
});
user.bind(div, this);
} catch {}
};
console.log(hook);
webhooks.addHTMLArea(div);
};
for (const hook of hooks) {
makeHook(hook);
}
})();
settings.show();
}
makeInviteMenu(options: Options, valid: void | Channel[]) {
@ -970,6 +1102,14 @@ class Guild extends SnowFlake {
this.printServers();
return thischannel;
}
goToChannelDelay(id: string) {
const channel = this.channels.find((_) => _.id == id);
if (channel) {
this.loadChannel(channel.id);
} else {
this.localuser.gotoid = id;
}
}
createchannels(func = this.createChannel.bind(this)) {
const options = ["text", "announcement", "voice"].map((e) =>
I18n.getTranslation("channel." + e),
@ -1036,7 +1176,9 @@ class Guild extends SnowFlake {
method: "POST",
headers: this.headers,
body: JSON.stringify({name, type}),
});
})
.then((_) => _.json())
.then((_) => this.goToChannelDelay(_.id));
}
async createRole(name: string) {
const fetched = await fetch(this.info.api + "/guilds/" + this.id + "roles", {

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 180"><path fill="none" stroke="red" stroke-linecap="round" stroke-linejoin="round" stroke-width="29" d="m58 165 64-75-64-75"/></svg>

After

Width:  |  Height:  |  Size: 189 B

View file

@ -148,6 +148,8 @@ type userjson = {
theme_colors: string;
pronouns?: string;
badge_ids: string[];
webhook?: webhookInfo;
uid?: string;
};
type memberjson = {
index?: number;
@ -300,6 +302,30 @@ type dirrectjson = {
recipients: userjson[];
is_spam: boolean;
};
type webhookType = {
application_id: null | string;
avatar: null | string;
channel_id: string;
guild_id: string;
id: string;
name: string;
type: 1;
user: userjson;
token: string;
url: string;
};
type webhookInfo = {
id: string;
type: 1;
name: string;
avatar: null | string;
guild_id: string;
channel_id: string;
application_id: null | string;
user_id: string;
source_guild_id: string;
source_channel_id: string;
};
type messagejson = {
id: string;
channel_id: string;
@ -323,6 +349,7 @@ type messagejson = {
nonce: string;
pinned: boolean;
type: number;
webhook: webhookInfo;
};
type filejson = {
id: string;
@ -760,4 +787,6 @@ export {
opRTC12,
emojipjson,
extendedProperties,
webhookInfo,
webhookType,
};

View file

@ -892,7 +892,7 @@ class Localuser {
if (!forceReload && this.lookingguild === guild) {
return guild;
}
if (this.channelfocus) {
if (this.channelfocus && this.lookingguild !== guild) {
this.channelfocus.infinite.delete();
this.channelfocus = undefined;
}
@ -1070,7 +1070,11 @@ class Localuser {
headers: this.headers,
});
const json = await res.json();
console.log([...json.guilds], json.guilds);
//@ts-ignore
json.guilds = json.guilds.sort((a, b) => {
return b.member_count - a.member_count;
});
content.innerHTML = "";
const title = document.createElement("h2");
title.textContent = I18n.getTranslation("guild.disoveryTitle", json.guilds.length + "");

View file

@ -386,6 +386,7 @@ class Member extends SnowFlake {
);
}
static async resolveMember(user: User, guild: Guild): Promise<Member | undefined> {
if (user.webhook) return undefined;
const maybe = user.members.get(guild);
if (!user.members.has(guild)) {
const membpromise = guild.localuser.resolvemember(user.id, guild.id);

View file

@ -207,8 +207,11 @@ class Message extends SnowFlake {
if (messagejson.reactions?.length) {
console.log(messagejson.reactions, ":3");
}
this.author = new User(messagejson.author, this.localuser);
console.log(messagejson.webhook);
if (messagejson.webhook) {
messagejson.author.webhook = messagejson.webhook;
}
this.author = new User(messagejson.author, this.localuser, false);
for (const thing in messagejson.mentions) {
this.mentions[thing] = new User(messagejson.mentions[thing], this.localuser);
}
@ -592,7 +595,7 @@ class Message extends SnowFlake {
if (this.author.bot) {
const username = document.createElement("span");
username.classList.add("bot");
username.textContent = "BOT";
username.textContent = this.author.webhook ? I18n.webhook() : I18n.bot();
userwrap.appendChild(username);
}
const time = document.createElement("span");

View file

@ -1043,6 +1043,9 @@ class Form implements OptionsElement<object> {
}
return this.options.addSubOptions(name, {ltr, noSubmit});
}
addHTMLArea(html: (() => HTMLElement) | HTMLElement, onSubmit = () => {}) {
return this.options.addHTMLArea(html, onSubmit);
}
addSubForm(
name: string,
onSubmit: (arg1: object, sent: object) => void,
@ -1168,6 +1171,9 @@ class Form implements OptionsElement<object> {
addText(str: string) {
return this.options.addText(str);
}
addMDText(str: string) {
return this.options.addMDText(str);
}
addHR() {
return this.options.addHR();
}
@ -1263,6 +1269,9 @@ class Form implements OptionsElement<object> {
};
});
promises.push(promise);
continue;
} else if (input.value === undefined) {
continue;
}
} else {
console.error(options.files + " is not currently implemented");

View file

@ -12,6 +12,22 @@ body {
height: 100svh;
background: var(--primary-bg);
}
.createdWebhook {
display: flex;
align-items: center;
width: fit-content;
padding: 0.1in;
border-radius: 0.1in;
background: var(--secondary-bg);
user-select: none;
cursor: pointer;
.pfp {
width: 0.5in;
height: 0.5in;
margin-right: 0.15in;
}
}
.flexltr {
min-height: 0;
display: flex;
@ -268,6 +284,9 @@ textarea {
.svg-category {
mask: url(/icons/category.svg);
}
.svg-intoMenu {
mask: url(/icons/intoMenu.svg);
}
.svg-channel {
mask: url(/icons/channel.svg);
}
@ -2033,6 +2052,29 @@ fieldset input[type="radio"] {
.FormSettings {
padding-bottom: 32px;
}
.webhookArea {
background: var(--secondary-bg);
padding: 0.2in;
display: flex;
align-items: center;
border-radius: 0.1in;
user-select: none;
cursor: pointer;
span {
color: var(--secondary-text-soft);
}
.svgicon {
width: 0.4in;
height: 0.4in;
margin-left: auto;
}
}
.webhookpfppreview {
width: 0.8in;
height: 0.8in;
border-radius: 1in;
margin-right: 0.2in;
}
.optionElement,
.FormSettings > button {
margin: 16px 16px 0 16px;

View file

@ -4,7 +4,7 @@ import {Contextmenu} from "./contextmenu.js";
import {Localuser} from "./localuser.js";
import {Guild} from "./guild.js";
import {SnowFlake} from "./snowflake.js";
import {presencejson, userjson} from "./jsontypes.js";
import {presencejson, userjson, webhookInfo} from "./jsontypes.js";
import {Role} from "./role.js";
import {Search} from "./search.js";
import {I18n} from "./i18n.js";
@ -16,6 +16,7 @@ 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;
@ -24,6 +25,7 @@ class User extends SnowFlake {
pronouns?: string;
bot!: boolean;
public_flags!: number;
webhook?: webhookInfo;
accent_color!: number;
banner: string | undefined;
hypotheticalbanner!: boolean;
@ -35,7 +37,7 @@ class User extends SnowFlake {
status!: string;
resolving: false | Promise<any> = false;
constructor(userjson: userjson, owner: Localuser, dontclone = false) {
constructor(userjson: userjson, owner: Localuser, dontclone: boolean = false) {
super(userjson.id);
this.owner = owner;
if (localStorage.getItem("logbad") && owner.user && owner.user.id !== userjson.id) {
@ -44,6 +46,12 @@ class User extends SnowFlake {
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;
@ -384,7 +392,7 @@ class User extends SnowFlake {
}
static checkuser(user: User | userjson, owner: Localuser): User {
const tempUser = owner.userMap.get(user.id);
const tempUser = owner.userMap.get(user.uid || user.id);
if (tempUser) {
if (!(user instanceof User)) {
tempUser.userupdate(user);
@ -392,7 +400,7 @@ class User extends SnowFlake {
return tempUser;
} else {
const tempuser = new User(user as userjson, owner, true);
owner.userMap.set(user.id, tempuser);
owner.userMap.set(user.uid || user.id, tempuser);
return tempuser;
}
}
@ -512,6 +520,7 @@ class User extends SnowFlake {
.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");

View file

@ -152,7 +152,22 @@
"selectName": "Name of channel",
"selectCatName": "Name of category",
"createChannel": "Create channel",
"createCatagory": "Create category"
"createCatagory": "Create category",
"permissions": "Permissions"
},
"webhooks": {
"createdAt": "Created at $1",
"name": "Name:",
"token": "Webhook token: `$1`",
"url": "Webhook url: `$1`",
"avatar": "Avatar",
"createdBy": "Created by:",
"notFound": "User no longer is in the guild",
"channel": "Channel",
"copyURL": "Copy Webhook URL",
"newWebHook": "New Webhook",
"EnterWebhookName": "Enter Webhook name",
"base": "Webhooks"
},
"switchAccounts": "Switch accounts ⇌",
"accountNotStart": "Account unable to start",
@ -483,5 +498,7 @@
"uploadFilesText": "Upload your files here!",
"errorReconnect": "Unable to connect to the server, retrying in **$1** seconds...",
"retrying": "Retrying...",
"unableToConnect": "Unable to connect to the Spacebar server. Please try logging out and back in."
"unableToConnect": "Unable to connect to the Spacebar server. Please try logging out and back in.",
"bot": "BOT",
"webhook": "WEBHOOK"
}