diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts
index d56c773..9f8a4aa 100644
--- a/src/webpage/channel.ts
+++ b/src/webpage/channel.ts
@@ -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, {
diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts
index 23d3320..64a7de7 100644
--- a/src/webpage/guild.ts
+++ b/src/webpage/guild.ts
@@ -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", {
diff --git a/src/webpage/icons/intoMenu.svg b/src/webpage/icons/intoMenu.svg
new file mode 100644
index 0000000..c534cd2
--- /dev/null
+++ b/src/webpage/icons/intoMenu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/webpage/jsontypes.ts b/src/webpage/jsontypes.ts
index 67e0617..b9d6dc7 100644
--- a/src/webpage/jsontypes.ts
+++ b/src/webpage/jsontypes.ts
@@ -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,
};
diff --git a/src/webpage/localuser.ts b/src/webpage/localuser.ts
index e8ccf5c..c2c775e 100644
--- a/src/webpage/localuser.ts
+++ b/src/webpage/localuser.ts
@@ -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 + "");
diff --git a/src/webpage/member.ts b/src/webpage/member.ts
index ae96e2b..cd0b2f6 100644
--- a/src/webpage/member.ts
+++ b/src/webpage/member.ts
@@ -386,6 +386,7 @@ class Member extends SnowFlake {
);
}
static async resolveMember(user: User, guild: Guild): Promise {
+ 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);
diff --git a/src/webpage/message.ts b/src/webpage/message.ts
index e0119aa..212c6ec 100644
--- a/src/webpage/message.ts
+++ b/src/webpage/message.ts
@@ -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");
diff --git a/src/webpage/settings.ts b/src/webpage/settings.ts
index d7cec7e..d991c37 100644
--- a/src/webpage/settings.ts
+++ b/src/webpage/settings.ts
@@ -1043,6 +1043,9 @@ class Form implements OptionsElement