Merge branch 'main' into stupid-deno-type-errors

This commit is contained in:
ITOH
2021-05-04 14:53:44 +02:00
23 changed files with 194 additions and 82 deletions

View File

@@ -1,7 +1,7 @@
# Contributing
- Read the [style guide](#style-guide).
- Ask for help on the [official Discord server](https:)
- Ask for help on the [official Discord server](https://discord.gg/5vBgXk3UcZ)
- If you are going to work on an issue, mention so in the issue comments before
you start working on the issue.
- If you are going to work on a new feature, create an issue and discuss with
@@ -38,10 +38,9 @@
## Types Guide
- Must use snake case (according to Discord API).
- Must use camel case (same property name as in the docs just in camel case).
- Each field or property must be accompanied with a reasonable JSDoc comment
right above its type definition.
- The name of the type must be prefixed with `Discord`.
- Must be placed inside of the types module (in `src/types` directory).
Example:
@@ -61,6 +60,4 @@ export interface User {
flags?: number;
premiumType?: number;
}
export type DiscordUser = SnakeCasedPropertiesDeep<DiscordUserInternal>;
```

View File

@@ -118,7 +118,7 @@ import { deleteWebhookWithToken } from "./webhooks/delete_webhook_with_token.ts"
import { editWebhook } from "./webhooks/edit_webhook.ts";
import { editWebhookMessage } from "./webhooks/edit_webhook_message.ts";
import { editWebhookWithToken } from "./webhooks/edit_webhook_with_token.ts";
import { executeWebhook } from "./webhooks/execute_webhook.ts";
import { sendWebhook } from "./webhooks/send_webhook.ts";
import { getWebhook } from "./webhooks/get_webhook.ts";
import { getWebhooks } from "./webhooks/get_webhooks.ts";
import { getWebhookWithToken } from "./webhooks/get_webhook_with_token.ts";
@@ -181,7 +181,6 @@ export {
editWelcomeScreen,
editWidget,
emojiURL,
executeWebhook,
fetchMembers,
followChannel,
getAuditLogs,
@@ -247,6 +246,7 @@ export {
sendDirectMessage,
sendInteractionResponse,
sendMessage,
sendWebhook,
startTyping,
swapChannels,
syncGuildTemplate,
@@ -392,7 +392,7 @@ export let helpers = {
editWebhookMessage,
editWebhookWithToken,
editWebhook,
executeWebhook,
sendWebhook,
getWebhookWithToken,
getWebhook,
getWebhooks,

View File

@@ -31,9 +31,13 @@ export async function createRole(
role: result,
guildId,
});
const guild = await cacheHandlers.get("guilds", guildId);
guild?.roles.set(role.id, role);
// TODO: ADD TO CACHE?
const guild = await cacheHandlers.get("guilds", guildId);
if (guild) {
guild.roles.set(role.id, role);
await cacheHandlers.set("guilds", guildId, guild);
}
return role;
}

View File

@@ -1,4 +1,6 @@
import { cacheHandlers } from "../../cache.ts";
import { rest } from "../../rest/rest.ts";
import { structures } from "../../structures/mod.ts";
import type { Role } from "../../types/permissions/role.ts";
import { Collection } from "../../util/collection.ts";
import { endpoints } from "../../util/constants.ts";
@@ -8,7 +10,7 @@ import { requireBotGuildPermissions } from "../../util/permissions.ts";
*
* ⚠️ **If you need this, you are probably doing something wrong. This is not intended for use. Your roles will be cached in your guild.**
*/
export async function getRoles(guildId: bigint) {
export async function getRoles(guildId: bigint, addToCache = true) {
await requireBotGuildPermissions(guildId, ["MANAGE_ROLES"]);
const result = await rest.runMethod<Role[]>(
@@ -16,9 +18,23 @@ export async function getRoles(guildId: bigint) {
endpoints.GUILD_ROLES(guildId),
);
// TODO: addToCache
return new Collection(
result.map((role) => [role.id, role]),
const roleStructures = await Promise.all(
result.map(async (role) =>
await structures.createDiscordenoRole({ role, guildId })
),
);
const roles = new Collection(
roleStructures.map((role) => [role.id, role]),
);
if (addToCache) {
const guild = await cacheHandlers.get("guilds", guildId);
if (guild) {
guild.roles = roles;
await cacheHandlers.set("guilds", guild.id, guild);
}
}
return roleStructures;
}

View File

@@ -1,9 +1,11 @@
import { cacheHandlers } from "../../cache.ts";
import { rest } from "../../rest/rest.ts";
import { structures } from "../../structures/mod.ts";
import type { Guild } from "../../types/guilds/guild.ts";
import type { CreateGuildFromTemplate } from "../../types/templates/create_guild_from_template.ts";
import { endpoints } from "../../util/constants.ts";
import { urlToBase64 } from "../../util/utils.ts";
import { ws } from "../../ws/ws.ts";
/**
* Create a new guild based on a template
@@ -23,11 +25,17 @@ export async function createGuildFromTemplate(
data.icon = await urlToBase64(data.icon);
}
// TODO: discordeno guild?
return await rest.runMethod<Guild>(
const createdGuild = await rest.runMethod<Guild>(
"post",
endpoints.GUILD_TEMPLATE(templateCode),
data,
);
return await structures.createDiscordenoGuild(
createdGuild,
Number(
(BigInt(createdGuild.id) >> 22n % BigInt(ws.botGatewayData.shards))
.toString(),
),
);
}

View File

@@ -6,8 +6,8 @@ import { Errors } from "../../types/misc/errors.ts";
import type { ExecuteWebhook } from "../../types/webhooks/execute_webhook.ts";
import { endpoints } from "../../util/constants.ts";
/** Execute a webhook with webhook Id and webhook token */
export async function executeWebhook(
/** Send a webhook with webhook Id and webhook token */
export async function sendWebhook(
webhookId: bigint,
webhookToken: string,
options: ExecuteWebhook,
@@ -75,7 +75,6 @@ export async function executeWebhook(
avatar_url: options.avatarUrl,
},
);
// TODO: not sure
if (!options.wait) return;
return structures.createDiscordenoMessage(result);

View File

@@ -47,6 +47,19 @@ const GUILD_SNOWFLAKES = [
"publicUpdatesChannelId",
];
export const guildToggles = {
/** Whether this user is owner of this guild */
owner: 1n,
/** Whether the guild widget is enabled */
widgetEnabled: 2n,
/** Whether this is a large guild */
large: 4n,
/** Whether this guild is unavailable due to an outage */
unavailable: 8n,
/** Whether this server is an nsfw guild */
nsfw: 16n,
};
const baseGuild: Partial<DiscordenoGuild> = {
get members() {
return cache.members.filter((member) => member.guilds.has(this.id!));
@@ -120,6 +133,21 @@ const baseGuild: Partial<DiscordenoGuild> = {
leave() {
return leaveGuild(this.id!);
},
get isOwner() {
return Boolean(this.bitfield! & guildToggles.owner);
},
get widgetEnabled() {
return Boolean(this.bitfield! & guildToggles.widgetEnabled);
},
get large() {
return Boolean(this.bitfield! & guildToggles.large);
},
get unavailable() {
return Boolean(this.bitfield! & guildToggles.unavailable);
},
get nsfw() {
return Boolean(this.bitfield! & guildToggles.nsfw);
},
};
export async function createDiscordenoGuild(
@@ -137,6 +165,7 @@ export async function createDiscordenoGuild(
...rest
} = data;
let bitfield = 0n;
const guildId = snowflakeToBigint(rest.id);
const roles = await Promise.all(
@@ -172,6 +201,12 @@ export async function createDiscordenoGuild(
`Running for of loop in createDiscordenoGuild function.`,
);
const toggleBits = guildToggles[key as keyof typeof guildToggles];
if (toggleBits) {
bitfield |= value ? toggleBits : 0n;
continue;
}
props[key] = createNewProp(
GUILD_SNOWFLAKES.includes(key)
? value ? snowflakeToBigint(value) : undefined
@@ -278,6 +313,10 @@ export interface DiscordenoGuild extends
voiceStates: Collection<bigint, DiscordenoVoiceState>;
/** Custom guild emojis */
emojis: Collection<bigint, Emoji>;
/** Whether the bot is the owner of this guild */
isOwner: boolean;
/** Holds all the boolean toggles. */
bitfield: bigint;
// GETTERS
/** Members in this guild. */

View File

@@ -23,11 +23,21 @@ import { createNewProp } from "../util/utils.ts";
import { DiscordenoGuild } from "./guild.ts";
const MEMBER_SNOWFLAKES = [
"roles",
"id",
"discriminator",
];
export const memberToggles = {
/** Whether the user belongs to an OAuth2 application */
bot: 1n,
/** Whether the user is an Official Discord System user (part of the urgent message system) */
system: 2n,
/** Whether the user has two factor enabled on their account */
mfaEnabled: 4n,
/** Whether the email on this account has been verified */
verified: 8n,
};
const baseMember: Partial<DiscordenoMember> = {
get avatarURL() {
return avatarURL(this.id!, this.discriminator!, this.avatar!);
@@ -76,6 +86,18 @@ const baseMember: Partial<DiscordenoMember> = {
removeRole(guildId, roleId, reason) {
return removeRole(guildId, this.id!, roleId, reason);
},
get bot() {
return Boolean(this.bitfield! & memberToggles.bot);
},
get system() {
return Boolean(this.bitfield! & memberToggles.system);
},
get mfaEnabled() {
return Boolean(this.bitfield! & memberToggles.mfaEnabled);
},
get verified() {
return Boolean(this.bitfield! & memberToggles.verified);
},
};
export async function createDiscordenoMember(
@@ -87,35 +109,23 @@ export async function createDiscordenoMember(
user,
joinedAt,
premiumSince,
...rest
} = data;
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
for (const [key, value] of Object.entries(rest)) {
eventHandlers.debug?.(
"loop",
`Running for of loop for Object.keys(rest) in DiscordenoMember function.`,
);
if (key === "roles") {
props[key] = value.map((id: string) => snowflakeToBigint(id));
continue;
}
props[key] = createNewProp(
MEMBER_SNOWFLAKES.includes(key)
? value ? snowflakeToBigint(value) : undefined
: value,
);
}
for (const [key, value] of Object.entries(user)) {
eventHandlers.debug?.(
"loop",
`Running for of for Object.keys(user) loop in DiscordenoMember function.`,
);
const toggleBits = memberToggles[key as keyof typeof memberToggles];
if (toggleBits) {
bitfield |= value ? toggleBits : 0n;
continue;
}
props[key] = createNewProp(
MEMBER_SNOWFLAKES.includes(key)
? value ? snowflakeToBigint(value) : undefined
@@ -142,29 +152,23 @@ export async function createDiscordenoMember(
// User was never cached before
member.guilds.set(guildId, {
nick: rest.nick,
roles: rest.roles.map((id) => snowflakeToBigint(id)),
nick: data.nick,
roles: data.roles.map((id) => snowflakeToBigint(id)),
joinedAt: Date.parse(joinedAt),
premiumSince: premiumSince ? Date.parse(premiumSince) : undefined,
deaf: rest.deaf,
mute: rest.mute,
deaf: data.deaf,
mute: data.mute,
});
return member;
}
export interface DiscordenoMember extends
Omit<
GuildMember,
"roles"
>,
Omit<
User,
| "discriminator"
| "id"
> {
/** Array of role object ids */
roles: bigint[];
/** The user's id */
id: bigint;
/** The user's 4-digit discord-tag */
@@ -178,6 +182,8 @@ export interface DiscordenoMember extends
roles: bigint[];
}
>;
/** Holds all the boolean toggles. */
bitfield: bigint;
// GETTERS
/** The avatar url using the default format and size. */

View File

@@ -29,6 +29,15 @@ const MESSAGE_SNOWFLAKES = [
"webhookId",
];
const messageToggles = {
/** Whether this was a TTS message */
tts: 1n,
/** Whether this message mentions everyone */
mentionEveryone: 2n,
/** Whether this message is pinned */
pinned: 4n,
};
const baseMessage: Partial<DiscordenoMessage> = {
get channel() {
if (this.guildId) return cache.channels.get(this.channelId!);
@@ -132,6 +141,15 @@ const baseMessage: Partial<DiscordenoMessage> = {
removeReaction(reaction, userId) {
return removeReaction(this.channelId!, this.id!, reaction, { userId });
},
get tts() {
return Boolean(this.bitfield! & messageToggles.tts);
},
get mentionEveryone() {
return Boolean(this.bitfield! & messageToggles.mentionEveryone);
},
get pinned() {
return Boolean(this.bitfield! & messageToggles.pinned);
},
};
export async function createDiscordenoMessage(data: Message) {
@@ -146,6 +164,8 @@ export async function createDiscordenoMessage(data: Message) {
...rest
} = data;
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
for (const [key, value] of Object.entries(rest)) {
eventHandlers.debug?.(
@@ -153,6 +173,12 @@ export async function createDiscordenoMessage(data: Message) {
`Running for of loop in createDiscordenoMessage function.`,
);
const toggleBits = messageToggles[key as keyof typeof messageToggles];
if (toggleBits) {
bitfield |= value ? toggleBits : 0n;
continue;
}
props[key] = createNewProp(
MESSAGE_SNOWFLAKES.includes(key)
? value ? snowflakeToBigint(value) : undefined
@@ -231,7 +257,11 @@ export interface DiscordenoMessage extends
isBot: boolean;
/** The username#discrimnator for the user who sent this message */
tag: string;
/** Holds all the boolean toggles. */
bitfield: bigint;
// For better user experience
/** Id of the guild which the massage has been send in. "0n" if it a DM */
guildId: bigint;
/** id of the channel the message was sent in */
@@ -252,6 +282,7 @@ export interface DiscordenoMessage extends
timestamp: number;
/** When this message was edited (or undefined if never) */
editedTimestamp?: number;
// GETTERS
/** The channel where this message was sent. Can be undefined if uncached. */

View File

@@ -19,6 +19,17 @@ const ROLE_SNOWFLAKES = [
"guildId",
];
const roleToggles = {
/** If this role is showed seperately in the user listing */
hoist: 1n,
/** Whether this role is managed by an integration */
managed: 2n,
/** Whether this role is mentionable */
mentionable: 4n,
/** If this role is the nitro boost role. */
isNitroBoostRole: 8n,
};
const baseRole: Partial<DiscordenoRole> = {
get guild() {
return cache.guilds.get(this.guildId!);
@@ -71,6 +82,18 @@ const baseRole: Partial<DiscordenoRole> = {
memberHighestRole.position,
);
},
get hoist() {
return Boolean(this.bitfield! & roleToggles.hoist);
},
get managed() {
return Boolean(this.bitfield! & roleToggles.managed);
},
get mentionable() {
return Boolean(this.bitfield! & roleToggles.mentionable);
},
get isNitroBoostRole() {
return Boolean(this.bitfield! & roleToggles.isNitroBoostRole);
},
};
// deno-lint-ignore require-await
@@ -84,6 +107,8 @@ export async function createDiscordenoRole(
...rest
} = ({ guildId: data.guildId, ...data.role });
let bitfield = 0n;
const props: Record<string, ReturnType<typeof createNewProp>> = {};
for (const [key, value] of Object.entries(rest)) {
eventHandlers.debug?.(
@@ -91,6 +116,12 @@ export async function createDiscordenoRole(
`Running for of loop in createDiscordenoRole function.`,
);
const toggleBits = roleToggles[key as keyof typeof roleToggles];
if (toggleBits) {
bitfield |= value ? toggleBits : 0n;
continue;
}
props[key] = createNewProp(
ROLE_SNOWFLAKES.includes(key)
? value ? snowflakeToBigint(value) : undefined
@@ -123,6 +154,8 @@ export interface DiscordenoRole extends Omit<Role, "tags" | "id"> {
integrationId: bigint;
/** The roles guildId */
guildId: bigint;
/** Holds all the boolean toggles. */
bitfield: bigint;
// GETTERS

View File

@@ -82,7 +82,7 @@ export async function createDiscordenoVoiceState(
const toggleBits = voiceStateToggles[key as keyof typeof voiceStateToggles];
if (toggleBits) {
bitfield |= toggleBits;
bitfield |= value ? toggleBits : 0n;
continue;
}

View File

@@ -35,7 +35,6 @@ export interface DiscordModifyChannel extends
SnakeCasedPropertiesDeep<
Omit<ModifyChannel, "permissionOverwrites">
> {
// deno-lint-ignore camelcase
permission_overwrites?: DiscordOverwrite[];
}
//TODO: check this

View File

@@ -30,6 +30,6 @@ export interface DiscordCreateGuildChannel extends
SnakeCasedPropertiesDeep<
Omit<CreateGuildChannel, "permissionOverwrites">
> {
// deno-lint-ignore camelcase
permission_overwrites: DiscordOverwrite[];
}
// TODO: check this

View File

@@ -18,4 +18,3 @@ export interface DiscordCreateGuildRole
extends Omit<CreateGuildRole, "permissions"> {
permissions?: string;
}
// TODO: check this

View File

@@ -105,7 +105,7 @@ export interface Guild {
approximateMemberCount?: number;
/** Approximate number of non-offline members in this guild, returned from the GET /guilds/<id> endpoint when with_counts is true */
approximatePresenceCount?: number;
/** The welcome screen of a Community guild, shown to new members, returned when in the invite object */
/** The welcome screen of a Community guild, shown to new members, returned in an Invite's guild object */
welcomeScreen?: WelcomeScreen;
/** `true` if this guild is designated as NSFW */
nsfw: boolean;

View File

@@ -1,4 +1,3 @@
// TODO: most likely it is but check if lockPositions and parentId really are optional
/** https://discord.com/developers/docs/resources/guild#modify-guild-channel-positions */
export interface ModifyGuildChannelPositions {
/** Channel id */

View File

@@ -18,5 +18,3 @@ export interface DiscordModifyGuildRole
extends Omit<ModifyGuildRole, "permissions"> {
permissions?: string | null;
}
// TODO: check this

View File

@@ -28,5 +28,3 @@ export interface CreateMessage {
export type DiscordCreateMessage = SnakeCasedPropertiesDeep<
Omit<CreateMessage, "file">
>;
// TODO: check this

View File

@@ -1,5 +1,4 @@
// https://github.com/discord/discord-api-docs/pull/2624
// TODO: add documentation link
/** https://discord.com/developers/docs/topics/oauth2#application-application-flags */
export enum DiscordApplicationFlags {
GatewayPresence = 1 << 12,
GatewayPresenceLimited = 1 << 13,

View File

@@ -26,4 +26,3 @@ export interface ExecuteWebhook {
export type DiscordExecuteWebhook = SnakeCasedPropertiesDeep<
Omit<ExecuteWebhook, "wait">
>;
//TODO: check this

View File

@@ -10,8 +10,7 @@ import { assertEquals, assertExists } from "../deps.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
// TODO: whats save?
async function ifItFailsBlameWolf(options: CreateGuildChannel, _save = false) {
async function ifItFailsBlameWolf(options: CreateGuildChannel) {
const channel = await createChannel(tempData.guildId, options);
// Assertions
@@ -60,7 +59,6 @@ Deno.test({
},
],
},
true,
);
},
...defaultTestOptions,

View File

@@ -10,8 +10,7 @@ import { assertEquals, assertExists } from "../deps.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
// TODO: whats save
async function ifItFailsBlameWolf(options: CreateGuildChannel, _save = false) {
async function ifItFailsBlameWolf(options: CreateGuildChannel) {
const channel = await createChannel(tempData.guildId, options);
const cloned = await cloneChannel(channel.id);
@@ -51,7 +50,7 @@ async function ifItFailsBlameWolf(options: CreateGuildChannel, _save = false) {
Deno.test({
name: "[channel] clone a new text channel",
async fn() {
await ifItFailsBlameWolf({ name: "Discordeno-clone-test" }, false);
await ifItFailsBlameWolf({ name: "Discordeno-clone-test" });
},
...defaultTestOptions,
});
@@ -64,7 +63,6 @@ Deno.test({
name: "Discordeno-clone-test",
type: DiscordChannelTypes.GUILD_CATEGORY,
},
false,
);
},
...defaultTestOptions,
@@ -78,7 +76,6 @@ Deno.test({
name: "Discordeno-clone-test",
type: DiscordChannelTypes.GUILD_VOICE,
},
false,
);
},
...defaultTestOptions,
@@ -93,7 +90,6 @@ Deno.test({
type: DiscordChannelTypes.GUILD_VOICE,
bitrate: 32000,
},
false,
);
},
...defaultTestOptions,
@@ -108,7 +104,6 @@ Deno.test({
type: DiscordChannelTypes.GUILD_VOICE,
userLimit: 32,
},
false,
);
},
...defaultTestOptions,
@@ -122,7 +117,6 @@ Deno.test({
name: "Discordeno-clone-test",
rateLimitPerUser: 2423,
},
false,
);
},
...defaultTestOptions,
@@ -133,7 +127,6 @@ Deno.test({
async fn() {
await ifItFailsBlameWolf(
{ name: "Discordeno-clone-test", nsfw: true },
false,
);
},
...defaultTestOptions,
@@ -154,7 +147,6 @@ Deno.test({
},
],
},
false,
);
},
...defaultTestOptions,

View File

@@ -11,8 +11,7 @@ import { assertEquals, assertExists } from "../deps.ts";
import { delayUntil } from "../util/delay_until.ts";
import { defaultTestOptions, tempData } from "../ws/start_bot.ts";
// TODO: whats save
async function ifItFailsBlameWolf(options: CreateGuildChannel, _save = false) {
async function ifItFailsBlameWolf(options: CreateGuildChannel) {
const channel = await createChannel(tempData.guildId, options);
// Assertions
@@ -78,7 +77,6 @@ Deno.test({
},
],
},
true,
);
},
...defaultTestOptions,