mirror of
https://github.com/jaronpate/unicord.git
synced 2026-10-18 21:47:40 +00:00
Compare commits
66 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cef02241d6 | |||
| 565eece049 | |||
| 012a08cb9e | |||
| 3da661743c | |||
| 7b07c9561c | |||
| 5e2ff898c0 | |||
| fb5509cf79 | |||
| ff6ef6930e | |||
| 91ed529f28 | |||
| 3a6c3acaef | |||
| db30e0d0ec | |||
| 25f7baa1d5 | |||
| b33c32c0d8 | |||
| 969c4a916e | |||
| 2e6ab53d34 | |||
| 6eefe763e3 | |||
| c200bff8d0 | |||
| bd8b7c4fd8 | |||
| f58c14d51c | |||
| f6115b0d17 | |||
| 934db0c285 | |||
| ddfcfa798a | |||
| aab2f7a546 | |||
| 26e9a7addf | |||
| 9f1498b3ad | |||
| e080d368cc | |||
| 6338f3ab0d | |||
| 72f05e762a | |||
| d169adbf3c | |||
| 6c7fc895f8 | |||
| ae4bc3e0a9 | |||
| 713761386c | |||
| fda26a892c | |||
| 16c81c7a80 | |||
| 161b78f097 | |||
| 62254a7576 | |||
| b001b1b756 | |||
| fc77394786 | |||
| 1fca25ef31 | |||
| aaa99f9120 | |||
| 1dc1371e34 | |||
| 211c1ca4c6 | |||
| 69f32aa067 | |||
| 9043c637c0 | |||
| 929cd71881 | |||
| c756bbbc30 | |||
| b4a8f412a1 | |||
| 817e37c4e4 | |||
| 1cafdbdcc8 | |||
| 7f745d9a38 | |||
| 962a1e9909 | |||
| a3514801fe | |||
| 1227580083 | |||
| 835271a4c7 | |||
| 6f385a68e7 | |||
| dbad7168e8 | |||
| 43f83a1964 | |||
| 1480905468 | |||
| fb81a55035 | |||
| 3bb42e3751 | |||
| 8333ab2d44 | |||
| 1aa0fbb9e2 | |||
| be82580f90 | |||
| 63755fe17a | |||
| 93b7fe2ed5 | |||
| 3e2b0f24bf |
@@ -1,4 +1,4 @@
|
|||||||
name: CI
|
name: CI (template)
|
||||||
on: workflow_dispatch
|
on: workflow_dispatch
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -4,14 +4,15 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
# Pull repo
|
||||||
- uses: actions/setup-node@v1
|
- uses: actions/checkout@v2
|
||||||
with:
|
# Install bun
|
||||||
node-version: 14
|
- uses: oven-sh/setup-bun@v2
|
||||||
registry-url: https://registry.npmjs.org/
|
# Run tests
|
||||||
- run: yarn
|
# TODO: Add tests
|
||||||
- run: yarn build
|
# Build
|
||||||
# - run: yarn test
|
- run: bun build
|
||||||
- run: yarn publish --access public
|
# Publish
|
||||||
|
- run: bun publish
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
NPM_CONFIG_REGISTRY: https://registry.npmjs.org/_authToken=${{ secrets.NPM_TOKEN }}
|
||||||
|
|||||||
@@ -173,3 +173,4 @@ dist
|
|||||||
|
|
||||||
# Finder (MacOS) folder config
|
# Finder (MacOS) folder config
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.aider*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="./assets/unicord.png" alt="unicord" width="200"/>
|
<img src="https://raw.githubusercontent.com/jaronpate/unicord/master/assets/unicord.png" width="150" height="150">
|
||||||
<h2>unicord</h2>
|
<h2>unicord</h2>
|
||||||
<p><em>Build good bots fast</em></p>
|
<p><em>Build good bots fast</em></p>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,9 +112,9 @@ You can also generate a hydration function that returns a type narrowing boolean
|
|||||||
```typescript
|
```typescript
|
||||||
client.chatCommands.register('me', async (context: Context, _args: any[]) => {
|
client.chatCommands.register('me', async (context: Context, _args: any[]) => {
|
||||||
// Hydrate the message id from the context
|
// Hydrate the message id from the context
|
||||||
const hydrate = await context.hydrator(hydrated, [Expectation.Guild]);
|
const hydrate = await context.hydrator(context, [Expectation.Guild]);
|
||||||
// Check if the object is hydrated
|
// Check if the object is hydrated
|
||||||
const hasGuild = hydrate(hydrated);
|
const hasGuild = hydrate(context);
|
||||||
|
|
||||||
if (hasGuild) {
|
if (hasGuild) {
|
||||||
// Send a message stating the user that they are in a guild
|
// Send a message stating the user that they are in a guild
|
||||||
@@ -181,3 +181,46 @@ client.interactions.register('button_1', buttonInteractionHandler);
|
|||||||
client.interactions.register('button_2', buttonInteractionHandler);
|
client.interactions.register('button_2', buttonInteractionHandler);
|
||||||
client.interactions.register('button_3', buttonInteractionHandler);
|
client.interactions.register('button_3', buttonInteractionHandler);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Multi-Context Support
|
||||||
|
|
||||||
|
This is an example of how a single function can be used for both chat and application commands.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const quotesCommandHandler = createCommandHandler({
|
||||||
|
description: 'Get a list of a users quotes',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
name: 'user',
|
||||||
|
type: CommandOptionType.User,
|
||||||
|
description: 'The user to get quotes for',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
] as const,
|
||||||
|
execute: async (context: Context, args) => {
|
||||||
|
// Lookup user
|
||||||
|
const user = await findUserByDiscordId(args.user.id);
|
||||||
|
|
||||||
|
if (user === undefined) {
|
||||||
|
return context.reply('User not found', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const quotes = await getQuotesByUser(user.id);
|
||||||
|
|
||||||
|
if (quotes.length === 0) {
|
||||||
|
return context.reply('No quotes found', true);
|
||||||
|
} else {
|
||||||
|
const embed = new Embed()
|
||||||
|
.setAuthor({ icon_url: args.user.avatarURL, name: args.user.display_name })
|
||||||
|
.setDescription(quotes.map(q => `"${q.quote_content}"`).join('\n\n'))
|
||||||
|
.setColor(0xFFFFFF);
|
||||||
|
|
||||||
|
return context.reply(embed.toMessage(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.chatCommands.register('quotes', quotesCommandHandler);
|
||||||
|
client.applicationCommands.register('quotes', ApplicationCommandType.Chat, quotesCommandHandler);
|
||||||
|
```
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1012 KiB After Width: | Height: | Size: 1.7 MiB |
+10
-3
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "unicord",
|
"name": "unicord",
|
||||||
"description": "A simple discord library primarily for TypeScript. REST like and easy to use.",
|
"description": "A simple discord library primarily for TypeScript. Web server like and easy to use.",
|
||||||
"version": "0.0.1",
|
"version": "0.0.4",
|
||||||
"module": "src/index.ts",
|
"module": "src/index.ts",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "out/index.js",
|
"main": "out/index.js",
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
"out"
|
"out"
|
||||||
],
|
],
|
||||||
|
"repository": "github:jaronpate/unicord",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "bun run testing/demo.test.ts",
|
"test": "bun run testing/demo.test.ts",
|
||||||
"test:inspect": "bun --inspect testing/demo.test.ts",
|
"test:inspect": "bun --inspect testing/demo.test.ts",
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
"build:packages": "bun run build:types && bun run build:core -- --packages external",
|
"build:packages": "bun run build:types && bun run build:core -- --packages external",
|
||||||
"build:nocheck": "bun run build:types:nocheck && bun run build:core",
|
"build:nocheck": "bun run build:types:nocheck && bun run build:core",
|
||||||
|
|
||||||
"build:core": "bun build src/index.ts --outdir out --drop=console --drop=debugger",
|
"build:core": "bun build src/index.ts --outdir out --drop=debugger",
|
||||||
"build:types": "tsc --emitDeclarationOnly --project tsconfig.json",
|
"build:types": "tsc --emitDeclarationOnly --project tsconfig.json",
|
||||||
"build:types:nocheck": "bun run build:types -- --noCheck",
|
"build:types:nocheck": "bun run build:types -- --noCheck",
|
||||||
|
|
||||||
@@ -34,5 +35,11 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ws": "^8.18.0"
|
"ws": "^8.18.0"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+9
-2
@@ -1,10 +1,11 @@
|
|||||||
// Base classes
|
// Base classes
|
||||||
export { Client } from './services/client';
|
export { Client, Client as unicord } from './services/client';
|
||||||
export { Intent, Trait } from './types/common';
|
export { Intent, Trait } from './types/common';
|
||||||
// Consumable types
|
// Consumable types
|
||||||
export type { ClientConfig } from './types/common';
|
export type { ClientConfig } from './types/common';
|
||||||
export type { Context } from './types/context';
|
export type { Context } from './types/context';
|
||||||
export { Expectation } from './types/common';
|
export { Expectation } from './types/common';
|
||||||
|
export { ChannelType, ChannelFlags, VideoQualityMode, SortOrderType, ForumLayoutType } from './types/channel';
|
||||||
// Consumable util classes
|
// Consumable util classes
|
||||||
export { Guild } from './types/guild';
|
export { Guild } from './types/guild';
|
||||||
export { Role } from './types/role';
|
export { Role } from './types/role';
|
||||||
@@ -14,5 +15,11 @@ export { Message, type MessagePayload, ComponentStyle, ComponentType } from './t
|
|||||||
export { Embed } from './types/embed';
|
export { Embed } from './types/embed';
|
||||||
export { Emoji } from './types/emoji';
|
export { Emoji } from './types/emoji';
|
||||||
// Other
|
// Other
|
||||||
export { createCommandHandler, type InteractionData, type InteractionCommpoentData, type InteractionCommandData, type InteractionHandlerFunction } from './types/handler';
|
export {
|
||||||
|
createCommandHandler,
|
||||||
|
type InteractionData,
|
||||||
|
type InteractionCommpoentData,
|
||||||
|
type InteractionCommandData,
|
||||||
|
type InteractionHandlerFunction,
|
||||||
|
} from './types/handler';
|
||||||
export { ApplicationCommandType, ApplicationCommandOptionType as CommandOptionType } from './types/applicationCommand';
|
export { ApplicationCommandType, ApplicationCommandOptionType as CommandOptionType } from './types/applicationCommand';
|
||||||
+7
-2
@@ -3,8 +3,13 @@ import type { Context } from '../types/context';
|
|||||||
|
|
||||||
const EventBus = new EventEmitter() as Emitter;
|
const EventBus = new EventEmitter() as Emitter;
|
||||||
|
|
||||||
export type Emitter = EventEmitter & {
|
export class Emitter extends EventEmitter {
|
||||||
emit: (event_name: string, context: Context | null, ...args: any[]) => boolean
|
emit(event_name: string, context: Context | null, ...args: any[]): boolean {
|
||||||
|
return super.emit(event_name, context, ...args);
|
||||||
|
}
|
||||||
|
on(event_name: string, listener: (context: Context | null, ...args: any[]) => void): this {
|
||||||
|
return super.on(event_name, listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { EventBus };
|
export { EventBus };
|
||||||
+106
-13
@@ -1,15 +1,22 @@
|
|||||||
import { fromDiscord } from "../../types/common";
|
import { fromDiscord } from '../../types/common';
|
||||||
import type { Context } from "../../types/context";
|
import type { Context } from '../../types/context';
|
||||||
import type { API } from "../api";
|
import type { API } from '../api';
|
||||||
import type { Processor } from "../processor";
|
import type { Processor } from '../processor';
|
||||||
|
|
||||||
|
|
||||||
export class ObjectCache<T, K extends T> {
|
export class ObjectCache<T, K extends T> {
|
||||||
cache = new Map<string, K>();
|
cache: LRUCache<string, K>;
|
||||||
requests = new Map<string, Promise<K>>();
|
requests = new Map<string, Promise<K>>();
|
||||||
readonly type: string;
|
readonly type: string;
|
||||||
|
|
||||||
constructor(protected api: API, processor: Processor, type: string, private readonly factory: { [fromDiscord]: (data: any) => K } & (new (data: T) => T), events: string[]) {
|
constructor(
|
||||||
|
protected api: API,
|
||||||
|
processor: Processor,
|
||||||
|
type: string,
|
||||||
|
private readonly factory: { [fromDiscord]: (data: any) => K } & (new (data: T) => T),
|
||||||
|
events: string[],
|
||||||
|
maxCapacity: number = 10_000,
|
||||||
|
) {
|
||||||
|
this.cache = new LRUCache<string, K>(maxCapacity);
|
||||||
this.type = type;
|
this.type = type;
|
||||||
processor.events.register(events, async (_context: Context, e: Record<string, any>) => {
|
processor.events.register(events, async (_context: Context, e: Record<string, any>) => {
|
||||||
this.cache.set(e.id, factory[fromDiscord](e));
|
this.cache.set(e.id, factory[fromDiscord](e));
|
||||||
@@ -48,10 +55,8 @@ export class ObjectCache<T, K extends T> {
|
|||||||
});
|
});
|
||||||
// Save the request
|
// Save the request
|
||||||
this.requests.set(object_id, req);
|
this.requests.set(object_id, req);
|
||||||
// Remove the request from the cache if it succeeds
|
// Remove the request from the cache when it finishes
|
||||||
req.then(() => this.requests.delete(object_id));
|
req.finally(() => this.requests.delete(object_id));
|
||||||
// Remove the request from the cache if it fails
|
|
||||||
req.catch(() => this.requests.delete(object_id));
|
|
||||||
// Return the request
|
// Return the request
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
@@ -64,8 +69,96 @@ export class ObjectCache<T, K extends T> {
|
|||||||
return this.fetch(object_id);
|
return this.fetch(object_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
list(): Map<string, Promise<T> | T> {
|
class ListNode<K, V> {
|
||||||
return this.cache;
|
key: K;
|
||||||
|
value: V;
|
||||||
|
prev: ListNode<K, V> | null = null;
|
||||||
|
next: ListNode<K, V> | null = null;
|
||||||
|
|
||||||
|
constructor(key: K, value: V) {
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LRUCache<K, V> {
|
||||||
|
private capacity: number;
|
||||||
|
private map: Map<K, ListNode<K, V>>;
|
||||||
|
private head: ListNode<K, V>;
|
||||||
|
private tail: ListNode<K, V>;
|
||||||
|
|
||||||
|
constructor(capacity: number) {
|
||||||
|
this.capacity = capacity;
|
||||||
|
this.map = new Map();
|
||||||
|
|
||||||
|
// Dummy head and tail to simplify boundary conditions
|
||||||
|
this.head = new ListNode<K, V>(null as any, null as any);
|
||||||
|
this.tail = new ListNode<K, V>(null as any, null as any);
|
||||||
|
this.head.next = this.tail;
|
||||||
|
this.tail.prev = this.head;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key: K): V | undefined {
|
||||||
|
if (!this.map.has(key)) return undefined;
|
||||||
|
|
||||||
|
const node = this.map.get(key)!;
|
||||||
|
this.moveToFront(node);
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
has(key: K): boolean {
|
||||||
|
return this.map.has(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key: K, value: V): void {
|
||||||
|
if (this.map.has(key)) {
|
||||||
|
// Update existing node value and move it to front
|
||||||
|
const node = this.map.get(key)!;
|
||||||
|
node.value = value;
|
||||||
|
this.moveToFront(node);
|
||||||
|
} else {
|
||||||
|
// Create a new node
|
||||||
|
const newNode = new ListNode(key, value);
|
||||||
|
this.map.set(key, newNode);
|
||||||
|
this.addToFront(newNode);
|
||||||
|
|
||||||
|
// If over capacity, remove the least recently used (LRU) node
|
||||||
|
if (this.map.size > this.capacity) {
|
||||||
|
this.removeLRU();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private moveToFront(node: ListNode<K, V>): void {
|
||||||
|
this.removeNode(node);
|
||||||
|
this.addToFront(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addToFront(node: ListNode<K, V>): void {
|
||||||
|
node.next = this.head.next;
|
||||||
|
node.prev = this.head;
|
||||||
|
this.head.next!.prev = node;
|
||||||
|
this.head.next = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeNode(node: ListNode<K, V>): void {
|
||||||
|
node.prev!.next = node.next;
|
||||||
|
node.next!.prev = node.prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeLRU(): void {
|
||||||
|
const lruNode = this.tail.prev!;
|
||||||
|
this.removeNode(lruNode);
|
||||||
|
this.map.delete(lruNode.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
*[Symbol.iterator](): IterableIterator<[K, V]> {
|
||||||
|
let current = this.head.next;
|
||||||
|
while (current && current !== this.tail) {
|
||||||
|
yield [current.key, current.value];
|
||||||
|
current = current.next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { API } from "../api";
|
||||||
|
import { Processor } from "../processor";
|
||||||
|
import { Channel } from "../../types/channel";
|
||||||
|
import { ObjectCache } from "./cache";
|
||||||
|
|
||||||
|
export class Channels extends ObjectCache<Channel, Channel> {
|
||||||
|
constructor(api: API, processor: Processor) {
|
||||||
|
super(api, processor, 'channels', Channel, ['CHANNEL_CREATE', 'CHANNEL_UPDATE']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import { fromDiscord } from "../../types/common";
|
import { fromDiscord } from "../../types/common";
|
||||||
import { Message, type MessagePayload } from "../../types/message";
|
import { Message, type MessagePayload } from "../../types/message";
|
||||||
import type { API } from "../api";
|
import type { API } from "../api";
|
||||||
import type { Client } from "../client";
|
|
||||||
import type { Processor } from "../processor";
|
import type { Processor } from "../processor";
|
||||||
import { ObjectCache } from "./cache";
|
import { ObjectCache } from "./cache";
|
||||||
|
|
||||||
|
|
||||||
export class Messages extends ObjectCache<Message, MessagePayload> {
|
export class Messages extends ObjectCache<Message, MessagePayload> {
|
||||||
constructor (protected client: Client, api: API, processor: Processor) {
|
constructor (api: API, processor: Processor) {
|
||||||
super(api, processor, 'message', Message, ['MESSAGE_CREATE', 'MESSAGE_UPDATE']);
|
super(api, processor, 'messages', Message, ['MESSAGE_CREATE', 'MESSAGE_UPDATE']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(channel_id: string, message_id: string): Promise<MessagePayload> {
|
async fetch(channel_id: string, message_id: string): Promise<MessagePayload> {
|
||||||
|
|||||||
+38
-15
@@ -1,14 +1,15 @@
|
|||||||
import { API } from './api';
|
import { type DiscordAPIEvent, Expectation, HandlerType, Intent, type ClientConfig } from '../types/common';
|
||||||
import { Gateway } from './connectors/gateway';
|
import { Message, type DiscordMessage } from '../types/message';
|
||||||
import { Processor } from './processor';
|
|
||||||
import { Expectation, HandlerType, Intent, type ClientConfig } from '../types/common';
|
|
||||||
import { exists, isNil } from '../utils/index';
|
import { exists, isNil } from '../utils/index';
|
||||||
import { type DiscordMessage, Message } from '../types/message';
|
import { API } from './api';
|
||||||
|
import { EventBus, type Emitter } from './bus';
|
||||||
|
import { Channels } from './caches/channels';
|
||||||
import { Guilds } from './caches/guilds';
|
import { Guilds } from './caches/guilds';
|
||||||
import { Messages } from './caches/messages';
|
import { Messages } from './caches/messages';
|
||||||
import { Users } from './caches/users';
|
import { Users } from './caches/users';
|
||||||
|
import { Gateway } from './connectors/gateway';
|
||||||
import { hydrate, hydrator, type Hydrateable, type Hydrated } from './hydrator';
|
import { hydrate, hydrator, type Hydrateable, type Hydrated } from './hydrator';
|
||||||
import { EventBus, type Emitter } from './bus';
|
import { Processor } from './processor';
|
||||||
|
|
||||||
export class Client {
|
export class Client {
|
||||||
readonly token: string;
|
readonly token: string;
|
||||||
@@ -23,6 +24,7 @@ export class Client {
|
|||||||
guilds: Guilds;
|
guilds: Guilds;
|
||||||
users: Users;
|
users: Users;
|
||||||
messages: Messages;
|
messages: Messages;
|
||||||
|
channels: Channels;
|
||||||
chatCommands: Processor[HandlerType.ChatCommands];
|
chatCommands: Processor[HandlerType.ChatCommands];
|
||||||
applicationCommands: Processor[HandlerType.ApplicationCommands];
|
applicationCommands: Processor[HandlerType.ApplicationCommands];
|
||||||
interactions: Processor[HandlerType.Interactions];
|
interactions: Processor[HandlerType.Interactions];
|
||||||
@@ -42,7 +44,8 @@ export class Client {
|
|||||||
this.gateway = new Gateway(this, this.processor, this.api, this.bus);
|
this.gateway = new Gateway(this, this.processor, this.api, this.bus);
|
||||||
this.guilds = new Guilds(this.api, this.processor);
|
this.guilds = new Guilds(this.api, this.processor);
|
||||||
this.users = new Users(this.api, this.processor);
|
this.users = new Users(this.api, this.processor);
|
||||||
this.messages = new Messages(this, this.api, this.processor);
|
this.messages = new Messages(this.api, this.processor);
|
||||||
|
this.channels = new Channels(this.api, this.processor);
|
||||||
// Save references to command and event handlers
|
// Save references to command and event handlers
|
||||||
this.chatCommands = this.processor[HandlerType.ChatCommands];
|
this.chatCommands = this.processor[HandlerType.ChatCommands];
|
||||||
this.applicationCommands = this.processor[HandlerType.ApplicationCommands];
|
this.applicationCommands = this.processor[HandlerType.ApplicationCommands];
|
||||||
@@ -92,9 +95,12 @@ export class Client {
|
|||||||
*
|
*
|
||||||
* @throws Will throw an error if an expectation cannot be resolved.
|
* @throws Will throw an error if an expectation cannot be resolved.
|
||||||
*/
|
*/
|
||||||
hydrate = async <T extends Hydrateable | Hydrated<Hydrateable, Array<Expectation>>, K extends Array<Expectation>>(data: T, expectations: K) => {
|
hydrate = async <T extends Hydrateable | Hydrated<Hydrateable, Array<Expectation>>, K extends Array<Expectation>>(
|
||||||
|
data: T,
|
||||||
|
expectations: K,
|
||||||
|
) => {
|
||||||
return hydrate<T, K>(data, expectations, this, this.api);
|
return hydrate<T, K>(data, expectations, this, this.api);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper function that attempts to hydrate a given data object and returns a type guard function.
|
* A wrapper function that attempts to hydrate a given data object and returns a type guard function.
|
||||||
@@ -109,15 +115,32 @@ export class Client {
|
|||||||
*/
|
*/
|
||||||
hydrator = async <T extends Hydrateable, K extends Expectation[]>(data: T, expectations: K) => {
|
hydrator = async <T extends Hydrateable, K extends Expectation[]>(data: T, expectations: K) => {
|
||||||
return hydrator<T, K>(data, expectations, this, this.api);
|
return hydrator<T, K>(data, expectations, this, this.api);
|
||||||
}
|
};
|
||||||
|
|
||||||
sendMessage = async (channel_id: string, message: Message | string) => {
|
sendMessage = async (channel_id: string, message: Message | string) => {
|
||||||
if (typeof message === 'string') {
|
if (typeof message === 'string') {
|
||||||
message = new Message().setContent(message);
|
message = new Message().setContent(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Message.fromDiscord(await this.api.post<DiscordMessage>(`/channels/${channel_id}/messages`, message.toJSON()));
|
return Message.fromDiscord(
|
||||||
}
|
await this.api.post<DiscordMessage>(`/channels/${channel_id}/messages`, message.toJSON()),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a raw HTTP request to the Discord API.
|
||||||
|
* @param method HTTP method to use for the request (GET, POST, PUT, DELETE).
|
||||||
|
* @param endpoint The API endpoint to request; can start with a slash or be a full URL.
|
||||||
|
* @param body Optional body to send with the request, typically used for POST or PUT requests.
|
||||||
|
* @returns A promise that resolves to the response data of type T.
|
||||||
|
*/
|
||||||
|
rawRequest = async <T = Record<string, any>>(
|
||||||
|
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||||||
|
endpoint: string,
|
||||||
|
body?: Record<string, any>,
|
||||||
|
): Promise<T> => {
|
||||||
|
return this.api.request<T>(method, endpoint, body);
|
||||||
|
};
|
||||||
|
|
||||||
// chatCommands = {
|
// chatCommands = {
|
||||||
// register: (name: string | string[], handler: Handler) => this.processor.chat_commands.register(name, handler),
|
// register: (name: string | string[], handler: Handler) => this.processor.chat_commands.register(name, handler),
|
||||||
@@ -131,11 +154,11 @@ export class Client {
|
|||||||
// has: (name: string) => this.processor.application_commands.has(name)
|
// has: (name: string) => this.processor.application_commands.has(name)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
on = (event: string, handler: (...args: any[]) => void) => this.bus.on(event, handler);
|
on = (event: DiscordAPIEvent, handler: (...args: any[]) => void) => this.bus.on(event, handler);
|
||||||
|
|
||||||
once = (event: string, handler: (...args: any[]) => void) => this.bus.once(event, handler);
|
once = (event: DiscordAPIEvent, handler: (...args: any[]) => void) => this.bus.once(event, handler);
|
||||||
|
|
||||||
off = (event: string, handler: (...args: any[]) => void) => this.bus.off(event, handler);
|
off = (event: DiscordAPIEvent, handler: (...args: any[]) => void) => this.bus.off(event, handler);
|
||||||
|
|
||||||
connect = () => this.gateway.connect();
|
connect = () => this.gateway.connect();
|
||||||
}
|
}
|
||||||
@@ -205,15 +205,15 @@ export class Gateway {
|
|||||||
// Generate context
|
// Generate context
|
||||||
const context = new Context(this.client, this.api, { message });
|
const context = new Context(this.client, this.api, { message });
|
||||||
// Dispatch event for the command
|
// Dispatch event for the command
|
||||||
this.bus.emit(`command:${command}`, context, args);
|
// this.bus.emit(`${HandlerType.ChatCommands}:${command}`, context, args);
|
||||||
// // Check if command exists
|
// // Check if command exists
|
||||||
// if (this.processor[HandlerType.ChatCommands].has(command)) {
|
if (this.processor[HandlerType.ChatCommands].has(command)) {
|
||||||
// // Execute the handler
|
// Execute the handler
|
||||||
// this.processor[HandlerType.ChatCommands].execute(command, context, args);
|
this.processor[HandlerType.ChatCommands].execute(command, context, args);
|
||||||
// } else {
|
} else {
|
||||||
// // TODO: Custom unknown command handler
|
// TODO: Custom unknown command handler
|
||||||
// context.reply(`Unknown command: ${command}`);
|
// context.reply(`Unknown command: ${command}`);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Expectation } from "../types/common";
|
import { Expectation } from "../types/common";
|
||||||
import { Context, type HydratedContext } from "../types/context";
|
import { Context, type HydratedContext } from "../types/context";
|
||||||
import { Message, type HydratedMessage } from "../types/message";
|
import { Message, type HydratedMessage } from "../types/message";
|
||||||
|
import { Channel } from "../types/channel";
|
||||||
import { exists } from "../utils";
|
import { exists } from "../utils";
|
||||||
import type { API } from "./api";
|
import type { API } from "./api";
|
||||||
import type { Client } from "./client";
|
import type { Client } from "./client";
|
||||||
|
|
||||||
export type GatewayObject = Message;
|
export type GatewayObject = Message | Channel;
|
||||||
export type Hydrateable = Context | GatewayObject
|
export type Hydrateable = Context | GatewayObject
|
||||||
export type Hydrated<T extends Hydrateable, K extends Array<Expectation>> =
|
export type Hydrated<T extends Hydrateable, K extends Array<Expectation>> =
|
||||||
T extends Message ? HydratedMessage<K> :
|
T extends Message ? HydratedMessage<K> & Omit<T, keyof HydratedMessage<K>> :
|
||||||
T extends Context ? HydratedContext<K> : never;
|
T extends Context ? HydratedContext<K> & Omit<T, keyof HydratedContext<K>> : never;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hydrates a given data object based on the provided expectations.
|
* Hydrates a given data object based on the provided expectations.
|
||||||
@@ -26,12 +27,12 @@ export type Hydrated<T extends Hydrateable, K extends Array<Expectation>> =
|
|||||||
*
|
*
|
||||||
* @throws Will throw an error if an expectation cannot be resolved.
|
* @throws Will throw an error if an expectation cannot be resolved.
|
||||||
*/
|
*/
|
||||||
export async function hydrate<T extends Hydrateable | Hydrated<any, any>, K extends Array<Expectation>>(
|
export async function hydrate<T extends Hydrateable, K extends Array<Expectation>>(
|
||||||
data: T,
|
data: T,
|
||||||
expectations: K,
|
expectations: K,
|
||||||
client: Client,
|
client: Client,
|
||||||
api: API
|
api: API
|
||||||
): Promise<T extends Hydrated<infer U, infer H> ? Hydrated<T & U, [...H, ...K]> : Hydrated<T, K>> {
|
): Promise<Hydrated<T, K>> {
|
||||||
// TODO: add clone trait. Make it a requirement for hydrateable objects.
|
// TODO: add clone trait. Make it a requirement for hydrateable objects.
|
||||||
// Then clone the object and return the hydrated object.
|
// Then clone the object and return the hydrated object.
|
||||||
let hydrated: Hydrated<T, K>;
|
let hydrated: Hydrated<T, K>;
|
||||||
@@ -44,6 +45,9 @@ export async function hydrate<T extends Hydrateable | Hydrated<any, any>, K exte
|
|||||||
} else if (expectation === Expectation.Guild && exists(data.guild_id)) {
|
} else if (expectation === Expectation.Guild && exists(data.guild_id)) {
|
||||||
let context = data as unknown as HydratedContext<[Expectation.Guild, ...Expectation[]]>;
|
let context = data as unknown as HydratedContext<[Expectation.Guild, ...Expectation[]]>;
|
||||||
context.guild = await client.guilds.get(data.guild_id);
|
context.guild = await client.guilds.get(data.guild_id);
|
||||||
|
} else if (expectation === Expectation.Channel && exists(data.channel_id)) {
|
||||||
|
let context = data as unknown as HydratedContext<[Expectation.Channel, ...Expectation[]]>;
|
||||||
|
context.channel = await client.channels.get(data.channel_id);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid expectation requested for Context: ${expectation}`);
|
throw new Error(`Invalid expectation requested for Context: ${expectation}`);
|
||||||
}
|
}
|
||||||
@@ -56,13 +60,20 @@ export async function hydrate<T extends Hydrateable | Hydrated<any, any>, K exte
|
|||||||
} else if (expectation === Expectation.Guild && exists(data.guild_id)) {
|
} else if (expectation === Expectation.Guild && exists(data.guild_id)) {
|
||||||
let message = data as unknown as HydratedMessage<[Expectation.Guild, ...Expectation[]]>;
|
let message = data as unknown as HydratedMessage<[Expectation.Guild, ...Expectation[]]>;
|
||||||
message.guild = await client.guilds.get(data.guild_id);
|
message.guild = await client.guilds.get(data.guild_id);
|
||||||
|
} else if (expectation === Expectation.Channel && exists(data.channel_id)) {
|
||||||
|
let message = data as unknown as HydratedMessage<[Expectation.Channel, ...Expectation[]]>;
|
||||||
|
message.channel = await client.channels.get(data.channel_id);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid expectation requested for Message: ${expectation}`);
|
throw new Error(`Invalid expectation requested for Message: ${expectation}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data as unknown as (T extends Hydrated<infer U, infer H> ? Hydrated<T & U, [...H, ...K]> : Hydrated<T, K>);
|
// Create a new object that preserves all existing properties
|
||||||
|
const result = { ...data } as any;
|
||||||
|
|
||||||
|
// Ensure the result is properly typed with both old and new hydrated properties
|
||||||
|
return result as T & Hydrated<T, K>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,12 +89,12 @@ export async function hydrate<T extends Hydrateable | Hydrated<any, any>, K exte
|
|||||||
*
|
*
|
||||||
* @returns A promise that resolves to a type guard function. The type guard function returns `true` if the data object was successfully hydrated, otherwise `false`.
|
* @returns A promise that resolves to a type guard function. The type guard function returns `true` if the data object was successfully hydrated, otherwise `false`.
|
||||||
*/
|
*/
|
||||||
export async function hydrator<T extends Hydrateable | Hydrated<any, any>, K extends Array<Expectation>>(
|
export async function hydrator<T extends Hydrateable, K extends Array<Expectation>>(
|
||||||
data: T,
|
data: T,
|
||||||
expectations: K,
|
expectations: K,
|
||||||
client: Client,
|
client: Client,
|
||||||
api: API
|
api: API
|
||||||
): Promise<(data: unknown) => data is T extends Hydrated<infer U, infer H> ? Hydrated<T & U, [...H, ...K]> : Hydrated<T, K>> {
|
): Promise<(data: unknown) => data is Hydrated<T, K>> {
|
||||||
let hydrated = true;
|
let hydrated = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -95,7 +106,7 @@ export async function hydrator<T extends Hydrateable | Hydrated<any, any>, K ext
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BUG: Resolves to never after hydrations on an already hydrated object
|
// BUG: Resolves to never after hydrations on an already hydrated object
|
||||||
return function (data: unknown): data is T extends Hydrated<infer U, infer H> ? Hydrated<T & U, [...H, ...K]> : Hydrated<T, K> {
|
return function (data: unknown): data is Hydrated<T, K> {
|
||||||
return hydrated;
|
return hydrated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,9 @@ export class Processor {
|
|||||||
throw new Error('Event handlers cannot be a CommandHandler');
|
throw new Error('Event handlers cannot be a CommandHandler');
|
||||||
}
|
}
|
||||||
// Validate and resolve the args
|
// Validate and resolve the args
|
||||||
|
// TODO: There's no reason this can't use the hydrator system
|
||||||
|
// Hydrator should have a more genric subcomponent that can be used anywhere and is more dumb.
|
||||||
|
// Basically a function that accepts id's and returns values.
|
||||||
const resolvedArgs = await this.validateAndResolveArgs(argsOrPayload, handler.args);
|
const resolvedArgs = await this.validateAndResolveArgs(argsOrPayload, handler.args);
|
||||||
// Execute the handler
|
// Execute the handler
|
||||||
await Promise.resolve(handler[Trait.execute](context!, resolvedArgs));
|
await Promise.resolve(handler[Trait.execute](context!, resolvedArgs));
|
||||||
|
|||||||
+267
-28
@@ -1,39 +1,278 @@
|
|||||||
export class Channel {
|
import { GatewayObject } from "./base";
|
||||||
version: number;
|
import { Trait } from "./common";
|
||||||
type: number;
|
import { User, type DiscordUser } from "./user";
|
||||||
position: number;
|
|
||||||
permission_overwrites: any[];
|
|
||||||
name: string;
|
|
||||||
id: string;
|
|
||||||
flags: number;
|
|
||||||
topic?: string | null;
|
|
||||||
rate_limit_per_user?: number;
|
|
||||||
parent_id?: string;
|
|
||||||
last_message_id?: string | null;
|
|
||||||
user_limit?: number;
|
|
||||||
rtc_region?: string | null;
|
|
||||||
bitrate?: number;
|
|
||||||
last_pin_timestamp?: string;
|
|
||||||
|
|
||||||
constructor(data: any) {
|
export enum ChannelType {
|
||||||
this.version = data.version;
|
GUILD_TEXT = 0,
|
||||||
this.type = data.type;
|
DM = 1,
|
||||||
|
GUILD_VOICE = 2,
|
||||||
|
GROUP_DM = 3,
|
||||||
|
GUILD_CATEGORY = 4,
|
||||||
|
GUILD_ANNOUNCEMENT = 5,
|
||||||
|
ANNOUNCEMENT_THREAD = 10,
|
||||||
|
PUBLIC_THREAD = 11,
|
||||||
|
PRIVATE_THREAD = 12,
|
||||||
|
GUILD_STAGE_VOICE = 13,
|
||||||
|
GUILD_DIRECTORY = 14,
|
||||||
|
GUILD_FORUM = 15,
|
||||||
|
GUILD_MEDIA = 16
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ChannelFlags {
|
||||||
|
PINNED = 1 << 1,
|
||||||
|
REQUIRE_TAG = 1 << 4,
|
||||||
|
HIDE_MEDIA_DOWNLOAD_OPTIONS = 1 << 15
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum VideoQualityMode {
|
||||||
|
AUTO = 1,
|
||||||
|
FULL = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SortOrderType {
|
||||||
|
LATEST_ACTIVITY = 0,
|
||||||
|
CREATION_DATE = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ForumLayoutType {
|
||||||
|
NOT_SET = 0,
|
||||||
|
LIST_VIEW = 1,
|
||||||
|
GALLERY_VIEW = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PermissionOverwrite = {
|
||||||
|
id: string;
|
||||||
|
type: 0 | 1; // 0 for role, 1 for member
|
||||||
|
allow: string;
|
||||||
|
deny: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ThreadMetadata = {
|
||||||
|
archived: boolean;
|
||||||
|
auto_archive_duration: number;
|
||||||
|
archive_timestamp: string;
|
||||||
|
locked: boolean;
|
||||||
|
invitable?: boolean;
|
||||||
|
create_timestamp?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ThreadMember = {
|
||||||
|
id?: string;
|
||||||
|
user_id?: string;
|
||||||
|
join_timestamp: string;
|
||||||
|
flags: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ForumTag = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
moderated: boolean;
|
||||||
|
emoji_id?: string | null;
|
||||||
|
emoji_name?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DefaultReaction = {
|
||||||
|
emoji_id?: string | null; // the id of a guild's custom emoji
|
||||||
|
emoji_name?: string | null; // the unicode character of the emoji
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Channel extends GatewayObject {
|
||||||
|
id: string;
|
||||||
|
type: ChannelType;
|
||||||
|
guild_id?: string;
|
||||||
|
position?: number;
|
||||||
|
permission_overwrites?: PermissionOverwrite[];
|
||||||
|
name?: string;
|
||||||
|
topic?: string | null;
|
||||||
|
nsfw?: boolean;
|
||||||
|
last_message_id?: string | null;
|
||||||
|
bitrate?: number;
|
||||||
|
user_limit?: number;
|
||||||
|
rate_limit_per_user?: number;
|
||||||
|
recipients?: User[];
|
||||||
|
icon?: string | null;
|
||||||
|
owner_id?: string;
|
||||||
|
application_id?: string;
|
||||||
|
managed?: boolean;
|
||||||
|
parent_id?: string | null;
|
||||||
|
last_pin_timestamp?: Date | null;
|
||||||
|
rtc_region?: string | null;
|
||||||
|
video_quality_mode?: VideoQualityMode;
|
||||||
|
message_count?: number;
|
||||||
|
member_count?: number;
|
||||||
|
thread_metadata?: ThreadMetadata;
|
||||||
|
member?: ThreadMember;
|
||||||
|
default_auto_archive_duration?: number;
|
||||||
|
permissions?: string;
|
||||||
|
flags?: number;
|
||||||
|
total_message_sent?: number;
|
||||||
|
available_tags?: ForumTag[];
|
||||||
|
applied_tags?: string[];
|
||||||
|
default_reaction_emoji?: DefaultReaction;
|
||||||
|
default_thread_rate_limit_per_user?: number;
|
||||||
|
default_sort_order?: SortOrderType | null;
|
||||||
|
default_forum_layout?: ForumLayoutType;
|
||||||
|
|
||||||
|
constructor(data: Partial<Channel>) {
|
||||||
|
super();
|
||||||
|
this.id = data.id!;
|
||||||
|
this.type = data.type!;
|
||||||
|
this.guild_id = data.guild_id;
|
||||||
this.position = data.position;
|
this.position = data.position;
|
||||||
this.permission_overwrites = data.permission_overwrites;
|
this.permission_overwrites = data.permission_overwrites;
|
||||||
this.name = data.name;
|
this.name = data.name;
|
||||||
this.id = data.id;
|
|
||||||
this.flags = data.flags;
|
|
||||||
this.topic = data.topic;
|
this.topic = data.topic;
|
||||||
this.rate_limit_per_user = data.rate_limit_per_user;
|
this.nsfw = data.nsfw;
|
||||||
this.parent_id = data.parent_id;
|
|
||||||
this.last_message_id = data.last_message_id;
|
this.last_message_id = data.last_message_id;
|
||||||
this.user_limit = data.user_limit;
|
|
||||||
this.rtc_region = data.rtc_region;
|
|
||||||
this.bitrate = data.bitrate;
|
this.bitrate = data.bitrate;
|
||||||
this.last_pin_timestamp = data.last_pin_timestamp;
|
this.user_limit = data.user_limit;
|
||||||
|
this.rate_limit_per_user = data.rate_limit_per_user;
|
||||||
|
this.recipients = data.recipients;
|
||||||
|
this.icon = data.icon;
|
||||||
|
this.owner_id = data.owner_id;
|
||||||
|
this.application_id = data.application_id;
|
||||||
|
this.managed = data.managed;
|
||||||
|
this.parent_id = data.parent_id;
|
||||||
|
this.last_pin_timestamp = data.last_pin_timestamp instanceof Date
|
||||||
|
? data.last_pin_timestamp
|
||||||
|
: data.last_pin_timestamp
|
||||||
|
? new Date(data.last_pin_timestamp)
|
||||||
|
: null;
|
||||||
|
this.rtc_region = data.rtc_region;
|
||||||
|
this.video_quality_mode = data.video_quality_mode;
|
||||||
|
this.message_count = data.message_count;
|
||||||
|
this.member_count = data.member_count;
|
||||||
|
this.thread_metadata = data.thread_metadata;
|
||||||
|
this.member = data.member;
|
||||||
|
this.default_auto_archive_duration = data.default_auto_archive_duration;
|
||||||
|
this.permissions = data.permissions;
|
||||||
|
this.flags = data.flags;
|
||||||
|
this.total_message_sent = data.total_message_sent;
|
||||||
|
this.available_tags = data.available_tags;
|
||||||
|
this.applied_tags = data.applied_tags;
|
||||||
|
this.default_reaction_emoji = data.default_reaction_emoji;
|
||||||
|
this.default_thread_rate_limit_per_user = data.default_thread_rate_limit_per_user;
|
||||||
|
this.default_sort_order = data.default_sort_order;
|
||||||
|
this.default_forum_layout = data.default_forum_layout;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromAPIResponse(data: any): Channel {
|
public static [Trait.fromDiscord]<T = Channel>(data: DiscordChannel): T {
|
||||||
return new Channel(data);
|
return new Channel({
|
||||||
|
id: data.id,
|
||||||
|
type: data.type,
|
||||||
|
guild_id: data.guild_id,
|
||||||
|
position: data.position,
|
||||||
|
permission_overwrites: data.permission_overwrites,
|
||||||
|
name: data.name,
|
||||||
|
topic: data.topic,
|
||||||
|
nsfw: data.nsfw,
|
||||||
|
last_message_id: data.last_message_id,
|
||||||
|
bitrate: data.bitrate,
|
||||||
|
user_limit: data.user_limit,
|
||||||
|
rate_limit_per_user: data.rate_limit_per_user,
|
||||||
|
recipients: data.recipients?.map(u => User.fromDiscord(u)),
|
||||||
|
icon: data.icon,
|
||||||
|
owner_id: data.owner_id,
|
||||||
|
application_id: data.application_id,
|
||||||
|
managed: data.managed,
|
||||||
|
parent_id: data.parent_id,
|
||||||
|
last_pin_timestamp: data.last_pin_timestamp ? new Date(data.last_pin_timestamp) : null,
|
||||||
|
rtc_region: data.rtc_region,
|
||||||
|
video_quality_mode: data.video_quality_mode,
|
||||||
|
message_count: data.message_count,
|
||||||
|
member_count: data.member_count,
|
||||||
|
thread_metadata: data.thread_metadata,
|
||||||
|
member: data.member,
|
||||||
|
default_auto_archive_duration: data.default_auto_archive_duration,
|
||||||
|
permissions: data.permissions,
|
||||||
|
flags: data.flags,
|
||||||
|
total_message_sent: data.total_message_sent,
|
||||||
|
available_tags: data.available_tags,
|
||||||
|
applied_tags: data.applied_tags,
|
||||||
|
default_reaction_emoji: data.default_reaction_emoji,
|
||||||
|
default_thread_rate_limit_per_user: data.default_thread_rate_limit_per_user,
|
||||||
|
default_sort_order: data.default_sort_order,
|
||||||
|
default_forum_layout: data.default_forum_layout
|
||||||
|
}) as T;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiscordChannel {
|
||||||
|
id: string;
|
||||||
|
type: number;
|
||||||
|
guild_id?: string;
|
||||||
|
position?: number;
|
||||||
|
permission_overwrites?: PermissionOverwrite[];
|
||||||
|
name?: string;
|
||||||
|
topic?: string | null;
|
||||||
|
nsfw?: boolean;
|
||||||
|
last_message_id?: string | null;
|
||||||
|
bitrate?: number;
|
||||||
|
user_limit?: number;
|
||||||
|
rate_limit_per_user?: number;
|
||||||
|
recipients?: DiscordUser[];
|
||||||
|
icon?: string | null;
|
||||||
|
owner_id?: string;
|
||||||
|
application_id?: string;
|
||||||
|
managed?: boolean;
|
||||||
|
parent_id?: string | null;
|
||||||
|
last_pin_timestamp?: string | null;
|
||||||
|
rtc_region?: string | null;
|
||||||
|
video_quality_mode?: number;
|
||||||
|
message_count?: number;
|
||||||
|
member_count?: number;
|
||||||
|
thread_metadata?: ThreadMetadata;
|
||||||
|
member?: ThreadMember;
|
||||||
|
default_auto_archive_duration?: number;
|
||||||
|
permissions?: string;
|
||||||
|
flags?: number;
|
||||||
|
total_message_sent?: number;
|
||||||
|
available_tags?: ForumTag[];
|
||||||
|
applied_tags?: string[];
|
||||||
|
default_reaction_emoji?: DefaultReaction;
|
||||||
|
default_thread_rate_limit_per_user?: number;
|
||||||
|
default_sort_order?: number | null;
|
||||||
|
default_forum_layout?: number;
|
||||||
|
|
||||||
|
constructor(data: any) {
|
||||||
|
this.id = data.id;
|
||||||
|
this.type = data.type;
|
||||||
|
this.guild_id = data.guild_id;
|
||||||
|
this.position = data.position;
|
||||||
|
this.permission_overwrites = data.permission_overwrites;
|
||||||
|
this.name = data.name;
|
||||||
|
this.topic = data.topic;
|
||||||
|
this.nsfw = data.nsfw;
|
||||||
|
this.last_message_id = data.last_message_id;
|
||||||
|
this.bitrate = data.bitrate;
|
||||||
|
this.user_limit = data.user_limit;
|
||||||
|
this.rate_limit_per_user = data.rate_limit_per_user;
|
||||||
|
this.recipients = data.recipients;
|
||||||
|
this.icon = data.icon;
|
||||||
|
this.owner_id = data.owner_id;
|
||||||
|
this.application_id = data.application_id;
|
||||||
|
this.managed = data.managed;
|
||||||
|
this.parent_id = data.parent_id;
|
||||||
|
this.last_pin_timestamp = data.last_pin_timestamp;
|
||||||
|
this.rtc_region = data.rtc_region;
|
||||||
|
this.video_quality_mode = data.video_quality_mode;
|
||||||
|
this.message_count = data.message_count;
|
||||||
|
this.member_count = data.member_count;
|
||||||
|
this.thread_metadata = data.thread_metadata;
|
||||||
|
this.member = data.member;
|
||||||
|
this.default_auto_archive_duration = data.default_auto_archive_duration;
|
||||||
|
this.permissions = data.permissions;
|
||||||
|
this.flags = data.flags;
|
||||||
|
this.total_message_sent = data.total_message_sent;
|
||||||
|
this.available_tags = data.available_tags;
|
||||||
|
this.applied_tags = data.applied_tags;
|
||||||
|
this.default_reaction_emoji = data.default_reaction_emoji;
|
||||||
|
this.default_thread_rate_limit_per_user = data.default_thread_rate_limit_per_user;
|
||||||
|
this.default_sort_order = data.default_sort_order;
|
||||||
|
this.default_forum_layout = data.default_forum_layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromAPIResponse(data: any): DiscordChannel {
|
||||||
|
return new DiscordChannel(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+181
-100
@@ -3,7 +3,7 @@ export type ClientConfig = {
|
|||||||
application_id: string;
|
application_id: string;
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
intents?: Intent[];
|
intents?: Intent[];
|
||||||
}
|
};
|
||||||
|
|
||||||
export enum Expectation {
|
export enum Expectation {
|
||||||
Guild = 'guild',
|
Guild = 'guild',
|
||||||
@@ -16,24 +16,26 @@ export enum HandlerType {
|
|||||||
Events = 'events',
|
Events = 'events',
|
||||||
ChatCommands = 'chat_commands',
|
ChatCommands = 'chat_commands',
|
||||||
ApplicationCommands = 'application_commands',
|
ApplicationCommands = 'application_commands',
|
||||||
Interactions = 'interactions'
|
Interactions = 'interactions',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Expects<T extends Array<Expectation>, K extends Expectation> = Extract<K, T[number]> extends never ? false : true;
|
export type Expects<T extends Array<Expectation>, K extends Expectation> = Extract<K, T[number]> extends never
|
||||||
|
? false
|
||||||
|
: true;
|
||||||
|
|
||||||
export const clone: unique symbol = Symbol('clone')
|
export const clone: unique symbol = Symbol('clone');
|
||||||
export const fromDiscord: unique symbol = Symbol('fromDiscord')
|
export const fromDiscord: unique symbol = Symbol('fromDiscord');
|
||||||
export const execute: unique symbol = Symbol('execute')
|
export const execute: unique symbol = Symbol('execute');
|
||||||
|
|
||||||
export const Trait: {
|
export const Trait: {
|
||||||
clone: typeof clone
|
clone: typeof clone;
|
||||||
fromDiscord: typeof fromDiscord
|
fromDiscord: typeof fromDiscord;
|
||||||
execute: typeof execute
|
execute: typeof execute;
|
||||||
} = {
|
} = {
|
||||||
clone,
|
clone,
|
||||||
fromDiscord,
|
fromDiscord,
|
||||||
execute
|
execute,
|
||||||
}
|
};
|
||||||
|
|
||||||
export type EventPayload = any;
|
export type EventPayload = any;
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ export type Payload = {
|
|||||||
export enum InteractionResponseType {
|
export enum InteractionResponseType {
|
||||||
Pong = 1,
|
Pong = 1,
|
||||||
Message = 4,
|
Message = 4,
|
||||||
DeferredMessage = 5
|
DeferredMessage = 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Intent {
|
export enum Intent {
|
||||||
@@ -79,110 +81,189 @@ export enum Intent {
|
|||||||
DIRECT_MESSAGE_TYPING = 1 << 14,
|
DIRECT_MESSAGE_TYPING = 1 << 14,
|
||||||
MESSAGE_CONTENT = 1 << 15,
|
MESSAGE_CONTENT = 1 << 15,
|
||||||
DEFAULT = 513,
|
DEFAULT = 513,
|
||||||
ALL = 32767
|
ALL = 32767,
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IntentGroup: { [key in Intent]?: [string[], number] } = {
|
export const IntentGroup: { [key in Intent]?: [string[], number] } = {
|
||||||
[Intent.GUILDS]: [[
|
[Intent.GUILDS]: [
|
||||||
"GUILD_CREATE",
|
[
|
||||||
"GUILD_UPDATE",
|
'GUILD_CREATE',
|
||||||
"GUILD_DELETE",
|
'GUILD_UPDATE',
|
||||||
"GUILD_ROLE_CREATE",
|
'GUILD_DELETE',
|
||||||
"GUILD_ROLE_UPDATE",
|
'GUILD_ROLE_CREATE',
|
||||||
"GUILD_ROLE_DELETE",
|
'GUILD_ROLE_UPDATE',
|
||||||
"CHANNEL_CREATE",
|
'GUILD_ROLE_DELETE',
|
||||||
"CHANNEL_UPDATE",
|
'CHANNEL_CREATE',
|
||||||
"CHANNEL_DELETE",
|
'CHANNEL_UPDATE',
|
||||||
"CHANNEL_PINS_UPDATE",
|
'CHANNEL_DELETE',
|
||||||
"THREAD_CREATE",
|
'CHANNEL_PINS_UPDATE',
|
||||||
"THREAD_UPDATE",
|
'THREAD_CREATE',
|
||||||
"THREAD_DELETE",
|
'THREAD_UPDATE',
|
||||||
"THREAD_LIST_SYNC",
|
'THREAD_DELETE',
|
||||||
"THREAD_MEMBER_UPDATE",
|
'THREAD_LIST_SYNC',
|
||||||
"THREAD_MEMBERS_UPDATE *",
|
'THREAD_MEMBER_UPDATE',
|
||||||
"STAGE_INSTANCE_CREATE",
|
'THREAD_MEMBERS_UPDATE *',
|
||||||
"STAGE_INSTANCE_UPDATE",
|
'STAGE_INSTANCE_CREATE',
|
||||||
"STAGE_INSTANCE_DELETE"
|
'STAGE_INSTANCE_UPDATE',
|
||||||
], 0],
|
'STAGE_INSTANCE_DELETE',
|
||||||
|
],
|
||||||
|
0,
|
||||||
|
],
|
||||||
|
|
||||||
[Intent.GUILD_MEMBERS]: [[
|
[Intent.GUILD_MEMBERS]: [
|
||||||
"GUILD_MEMBER_ADD",
|
['GUILD_MEMBER_ADD', 'GUILD_MEMBER_UPDATE', 'GUILD_MEMBER_REMOVE', 'THREAD_MEMBERS_UPDATE *'],
|
||||||
"GUILD_MEMBER_UPDATE",
|
1,
|
||||||
"GUILD_MEMBER_REMOVE",
|
],
|
||||||
"THREAD_MEMBERS_UPDATE *",
|
|
||||||
], 1],
|
|
||||||
|
|
||||||
[Intent.GUILD_BANS]: [[
|
[Intent.GUILD_BANS]: [['GUILD_BAN_ADD', 'GUILD_BAN_REMOVE'], 2],
|
||||||
"GUILD_BAN_ADD",
|
|
||||||
"GUILD_BAN_REMOVE",
|
|
||||||
], 2],
|
|
||||||
|
|
||||||
[Intent.GUILD_EMOJIS_AND_STICKERS]: [[
|
[Intent.GUILD_EMOJIS_AND_STICKERS]: [['GUILD_EMOJIS_UPDATE', 'GUILD_STICKERS_UPDATE'], 3],
|
||||||
"GUILD_EMOJIS_UPDATE",
|
|
||||||
"GUILD_STICKERS_UPDATE",
|
|
||||||
], 3],
|
|
||||||
|
|
||||||
[Intent.GUILD_INTEGRATIONS]: [[
|
[Intent.GUILD_INTEGRATIONS]: [
|
||||||
"GUILD_INTEGRATIONS_UPDATE",
|
['GUILD_INTEGRATIONS_UPDATE', 'INTEGRATION_CREATE', 'INTEGRATION_UPDATE', 'INTEGRATION_DELETE'],
|
||||||
"INTEGRATION_CREATE",
|
4,
|
||||||
"INTEGRATION_UPDATE",
|
],
|
||||||
"INTEGRATION_DELETE",
|
|
||||||
], 4],
|
|
||||||
|
|
||||||
[Intent.GUILD_WEBHOOKS]: [[
|
[Intent.GUILD_WEBHOOKS]: [['WEBHOOKS_UPDATE'], 5],
|
||||||
"WEBHOOKS_UPDATE"
|
|
||||||
], 5],
|
|
||||||
|
|
||||||
[Intent.GUILD_INVITES]: [[
|
[Intent.GUILD_INVITES]: [['INVITE_CREATE', 'INVITE_DELETE'], 6],
|
||||||
"INVITE_CREATE",
|
|
||||||
"INVITE_DELETE",
|
|
||||||
], 6],
|
|
||||||
|
|
||||||
[Intent.GUILD_VOICE_STATES]: [[
|
[Intent.GUILD_VOICE_STATES]: [['VOICE_STATE_UPDATE'], 7],
|
||||||
"VOICE_STATE_UPDATE"
|
|
||||||
], 7],
|
|
||||||
|
|
||||||
[Intent.GUILD_PRESENCES]: [[
|
[Intent.GUILD_PRESENCES]: [['PRESENCE_UPDATE'], 8],
|
||||||
"PRESENCE_UPDATE"
|
|
||||||
], 8],
|
|
||||||
|
|
||||||
[Intent.GUILD_MESSAGES]: [[
|
[Intent.GUILD_MESSAGES]: [['MESSAGE_CREATE', 'MESSAGE_UPDATE', 'MESSAGE_DELETE', 'MESSAGE_DELETE_BULK'], 9],
|
||||||
"MESSAGE_CREATE",
|
|
||||||
"MESSAGE_UPDATE",
|
|
||||||
"MESSAGE_DELETE",
|
|
||||||
"MESSAGE_DELETE_BULK",
|
|
||||||
], 9],
|
|
||||||
|
|
||||||
[Intent.GUILD_MESSAGE_REACTIONS]: [[
|
[Intent.GUILD_MESSAGE_REACTIONS]: [
|
||||||
"MESSAGE_REACTION_ADD",
|
[
|
||||||
"MESSAGE_REACTION_REMOVE",
|
'MESSAGE_REACTION_ADD',
|
||||||
"MESSAGE_REACTION_REMOVE_ALL",
|
'MESSAGE_REACTION_REMOVE',
|
||||||
"MESSAGE_REACTION_REMOVE_EMOJI",
|
'MESSAGE_REACTION_REMOVE_ALL',
|
||||||
], 10],
|
'MESSAGE_REACTION_REMOVE_EMOJI',
|
||||||
|
],
|
||||||
|
10,
|
||||||
|
],
|
||||||
|
|
||||||
[Intent.GUILD_MESSAGE_TYPING]: [[
|
[Intent.GUILD_MESSAGE_TYPING]: [['TYPING_START'], 11],
|
||||||
"TYPING_START"
|
|
||||||
], 11],
|
|
||||||
|
|
||||||
[Intent.DIRECT_MESSAGES]: [[
|
[Intent.DIRECT_MESSAGES]: [['MESSAGE_CREATE', 'MESSAGE_UPDATE', 'MESSAGE_DELETE', 'CHANNEL_PINS_UPDATE'], 12],
|
||||||
"MESSAGE_CREATE",
|
|
||||||
"MESSAGE_UPDATE",
|
|
||||||
"MESSAGE_DELETE",
|
|
||||||
"CHANNEL_PINS_UPDATE",
|
|
||||||
], 12],
|
|
||||||
|
|
||||||
[Intent.DIRECT_MESSAGE_REACTIONS]: [[
|
[Intent.DIRECT_MESSAGE_REACTIONS]: [
|
||||||
"MESSAGE_REACTION_ADD",
|
[
|
||||||
"MESSAGE_REACTION_REMOVE",
|
'MESSAGE_REACTION_ADD',
|
||||||
"MESSAGE_REACTION_REMOVE_ALL",
|
'MESSAGE_REACTION_REMOVE',
|
||||||
"MESSAGE_REACTION_REMOVE_EMOJI",
|
'MESSAGE_REACTION_REMOVE_ALL',
|
||||||
], 13],
|
'MESSAGE_REACTION_REMOVE_EMOJI',
|
||||||
|
],
|
||||||
|
13,
|
||||||
|
],
|
||||||
|
|
||||||
[Intent.DIRECT_MESSAGE_TYPING]: [[
|
[Intent.DIRECT_MESSAGE_TYPING]: [['TYPING_START'], 14],
|
||||||
"TYPING_START"
|
|
||||||
], 14],
|
|
||||||
|
|
||||||
[Intent.MESSAGE_CONTENT]: [[
|
[Intent.MESSAGE_CONTENT]: [['MESSAGE_CONTENT'], 15],
|
||||||
"MESSAGE_CONTENT"
|
};
|
||||||
], 15]
|
|
||||||
|
export type DiscordAPIEvent =
|
||||||
|
| 'READY'
|
||||||
|
| 'RESUMED'
|
||||||
|
| 'CHANNEL_CREATE'
|
||||||
|
| 'CHANNEL_UPDATE'
|
||||||
|
| 'CHANNEL_DELETE'
|
||||||
|
| 'CHANNEL_PINS_UPDATE'
|
||||||
|
| 'THREAD_CREATE'
|
||||||
|
| 'THREAD_UPDATE'
|
||||||
|
| 'THREAD_DELETE'
|
||||||
|
| 'THREAD_LIST_SYNC'
|
||||||
|
| 'THREAD_MEMBER_UPDATE'
|
||||||
|
| 'THREAD_MEMBERS_UPDATE *'
|
||||||
|
| 'STAGE_INSTANCE_CREATE'
|
||||||
|
| 'STAGE_INSTANCE_UPDATE'
|
||||||
|
| 'STAGE_INSTANCE_DELETE'
|
||||||
|
| 'GUILD_CREATE'
|
||||||
|
| 'GUILD_UPDATE'
|
||||||
|
| 'GUILD_DELETE'
|
||||||
|
| 'GUILD_ROLE_CREATE'
|
||||||
|
| 'GUILD_ROLE_UPDATE'
|
||||||
|
| 'GUILD_ROLE_DELETE'
|
||||||
|
| 'GUILD_MEMBER_ADD'
|
||||||
|
| 'GUILD_MEMBER_UPDATE'
|
||||||
|
| 'GUILD_MEMBER_REMOVE'
|
||||||
|
| 'GUILD_MEMBERS_CHUNK *'
|
||||||
|
| 'GUILD_BAN_ADD'
|
||||||
|
| 'GUILD_BAN_REMOVE'
|
||||||
|
| 'GUILD_EMOJIS_UPDATE'
|
||||||
|
| 'GUILD_STICKERS_UPDATE'
|
||||||
|
| 'GUILD_INTEGRATIONS_UPDATE'
|
||||||
|
| 'INTEGRATION_CREATE'
|
||||||
|
| 'INTEGRATION_UPDATE'
|
||||||
|
| 'INTEGRATION_DELETE'
|
||||||
|
| 'WEBHOOKS_UPDATE'
|
||||||
|
| 'INVITE_CREATE'
|
||||||
|
| 'INVITE_DELETE'
|
||||||
|
| 'VOICE_STATE_UPDATE'
|
||||||
|
| 'MESSAGE_CREATE'
|
||||||
|
| 'MESSAGE_UPDATE'
|
||||||
|
| 'MESSAGE_DELETE'
|
||||||
|
| 'MESSAGE_DELETE_BULK'
|
||||||
|
| 'MESSAGE_REACTION_ADD'
|
||||||
|
| 'MESSAGE_REACTION_REMOVE'
|
||||||
|
| 'MESSAGE_REACTION_REMOVE_ALL'
|
||||||
|
| 'MESSAGE_REACTION_REMOVE_EMOJI'
|
||||||
|
| 'TYPING_START'
|
||||||
|
| 'PRESENCE_UPDATE'
|
||||||
|
| 'APPLICATION_COMMAND_CREATE'
|
||||||
|
| 'APPLICATION_COMMAND_UPDATE'
|
||||||
|
| 'APPLICATION_COMMAND_DELETE';
|
||||||
|
|
||||||
|
export enum DiscordAPIEvents {
|
||||||
|
READY = 'READY',
|
||||||
|
RESUMED = 'RESUMED',
|
||||||
|
CHANNEL_CREATE = 'CHANNEL_CREATE',
|
||||||
|
CHANNEL_UPDATE = 'CHANNEL_UPDATE',
|
||||||
|
CHANNEL_DELETE = 'CHANNEL_DELETE',
|
||||||
|
CHANNEL_PINS_UPDATE = 'CHANNEL_PINS_UPDATE',
|
||||||
|
THREAD_CREATE = 'THREAD_CREATE',
|
||||||
|
THREAD_UPDATE = 'THREAD_UPDATE',
|
||||||
|
THREAD_DELETE = 'THREAD_DELETE',
|
||||||
|
THREAD_LIST_SYNC = 'THREAD_LIST_SYNC',
|
||||||
|
THREAD_MEMBER_UPDATE = 'THREAD_MEMBER_UPDATE',
|
||||||
|
THREAD_MEMBERS_UPDATE = 'THREAD_MEMBERS_UPDATE *',
|
||||||
|
STAGE_INSTANCE_CREATE = 'STAGE_INSTANCE_CREATE',
|
||||||
|
STAGE_INSTANCE_UPDATE = 'STAGE_INSTANCE_UPDATE',
|
||||||
|
STAGE_INSTANCE_DELETE = 'STAGE_INSTANCE_DELETE',
|
||||||
|
GUILD_CREATE = 'GUILD_CREATE',
|
||||||
|
GUILD_UPDATE = 'GUILD_UPDATE',
|
||||||
|
GUILD_DELETE = 'GUILD_DELETE',
|
||||||
|
GUILD_ROLE_CREATE = 'GUILD_ROLE_CREATE',
|
||||||
|
GUILD_ROLE_UPDATE = 'GUILD_ROLE_UPDATE',
|
||||||
|
GUILD_ROLE_DELETE = 'GUILD_ROLE_DELETE',
|
||||||
|
GUILD_MEMBER_ADD = 'GUILD_MEMBER_ADD',
|
||||||
|
GUILD_MEMBER_UPDATE = 'GUILD_MEMBER_UPDATE',
|
||||||
|
GUILD_MEMBER_REMOVE = 'GUILD_MEMBER_REMOVE',
|
||||||
|
GUILD_MEMBERS_CHUNK = 'GUILD_MEMBERS_CHUNK *',
|
||||||
|
GUILD_BAN_ADD = 'GUILD_BAN_ADD',
|
||||||
|
GUILD_BAN_REMOVE = 'GUILD_BAN_REMOVE',
|
||||||
|
GUILD_EMOJIS_UPDATE = 'GUILD_EMOJIS_UPDATE',
|
||||||
|
GUILD_STICKERS_UPDATE = 'GUILD_STICKERS_UPDATE',
|
||||||
|
GUILD_INTEGRATIONS_UPDATE = 'GUILD_INTEGRATIONS_UPDATE',
|
||||||
|
INTEGRATION_CREATE = 'INTEGRATION_CREATE',
|
||||||
|
INTEGRATION_UPDATE = 'INTEGRATION_UPDATE',
|
||||||
|
INTEGRATION_DELETE = 'INTEGRATION_DELETE',
|
||||||
|
WEBHOOKS_UPDATE = 'WEBHOOKS_UPDATE',
|
||||||
|
INVITE_CREATE = 'INVITE_CREATE',
|
||||||
|
INVITE_DELETE = 'INVITE_DELETE',
|
||||||
|
VOICE_STATE_UPDATE = 'VOICE_STATE_UPDATE',
|
||||||
|
MESSAGE_CREATE = 'MESSAGE_CREATE',
|
||||||
|
MESSAGE_UPDATE = 'MESSAGE_UPDATE',
|
||||||
|
MESSAGE_DELETE = 'MESSAGE_DELETE',
|
||||||
|
MESSAGE_DELETE_BULK = 'MESSAGE_DELETE_BULK',
|
||||||
|
MESSAGE_REACTION_ADD = 'MESSAGE_REACTION_ADD',
|
||||||
|
MESSAGE_REACTION_REMOVE = 'MESSAGE_REACTION_REMOVE',
|
||||||
|
MESSAGE_REACTION_REMOVE_ALL = 'MESSAGE_REACTION_REMOVE_ALL',
|
||||||
|
MESSAGE_REACTION_REMOVE_EMOJI = 'MESSAGE_REACTION_REMOVE_EMOJI',
|
||||||
|
TYPING_START = 'TYPING_START',
|
||||||
|
PRESENCE_UPDATE = 'PRESENCE_UPDATE',
|
||||||
|
APPLICATION_COMMAND_CREATE = 'APPLICATION_COMMAND_CREATE',
|
||||||
|
APPLICATION_COMMAND_UPDATE = 'APPLICATION_COMMAND_UPDATE',
|
||||||
|
APPLICATION_COMMAND_DELETE = 'APPLICATION_COMMAND_DELETE',
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import { hydrate, hydrator, type Hydrateable, type Hydrated } from "../services/
|
|||||||
import { isNil, exists } from "../utils";
|
import { isNil, exists } from "../utils";
|
||||||
import { fromDiscord, InteractionResponseType, type Expectation } from "./common";
|
import { fromDiscord, InteractionResponseType, type Expectation } from "./common";
|
||||||
import type { Guild } from "./guild";
|
import type { Guild } from "./guild";
|
||||||
|
import { Channel } from "./channel";
|
||||||
import type { InteractionPayload } from "./handler";
|
import type { InteractionPayload } from "./handler";
|
||||||
import { type DiscordMessage, Message, type MessagePayload } from "./message";
|
import { type DiscordMessage, Message, type MessagePayload } from "./message";
|
||||||
/**
|
/**
|
||||||
@@ -28,6 +29,7 @@ import { type DiscordMessage, Message, type MessagePayload } from "./message";
|
|||||||
export type HydratedContext<T extends Array<Expectation>> = Context & {
|
export type HydratedContext<T extends Array<Expectation>> = Context & {
|
||||||
guild: Extract<Expectation.Guild, T[number]> extends never ? undefined : Guild;
|
guild: Extract<Expectation.Guild, T[number]> extends never ? undefined : Guild;
|
||||||
message: Extract<Expectation.Message, T[number]> extends never ? undefined : Message;
|
message: Extract<Expectation.Message, T[number]> extends never ? undefined : Message;
|
||||||
|
channel: Extract<Expectation.Channel, T[number]> extends never ? undefined : Channel;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ContextData =
|
export type ContextData =
|
||||||
@@ -92,7 +94,7 @@ export class Context<D extends ContextData = ContextData> {
|
|||||||
*
|
*
|
||||||
* @returns A promise that resolves to a type guard function. The type guard function returns `true` if the data object was successfully hydrated, otherwise `false`.
|
* @returns A promise that resolves to a type guard function. The type guard function returns `true` if the data object was successfully hydrated, otherwise `false`.
|
||||||
*/
|
*/
|
||||||
public hydrator = async <T extends Hydrateable | Hydrated<Hydrateable, Array<Expectation>>, K extends Array<Expectation>>(data: T, expectations: K) => {
|
public hydrator = async <T extends Hydrateable, K extends Array<Expectation>>(data: T, expectations: K) => {
|
||||||
return hydrator<T, K>(data, expectations, this.client, this.api);
|
return hydrator<T, K>(data, expectations, this.client, this.api);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { exists, isNil } from '../utils';
|
import { exists, isNil } from '../utils';
|
||||||
import { GatewayObject } from './base';
|
import { GatewayObject } from './base';
|
||||||
import { fromDiscord, Trait, type Expectation, type Expects } from './common';
|
import type { Channel } from './channel';
|
||||||
|
import { fromDiscord, Trait, type Expectation } from './common';
|
||||||
import type { Embed } from './embed';
|
import type { Embed } from './embed';
|
||||||
import type { Guild } from './guild';
|
import type { Guild } from './guild';
|
||||||
import { type DiscordUser, User } from './user';
|
import { User, type DiscordUser } from './user';
|
||||||
|
|
||||||
export type Component = Partial<Button | SelectMenu> & {
|
export type Component = Partial<Button | SelectMenu> & {
|
||||||
type: ComponentType;
|
type: ComponentType;
|
||||||
@@ -34,6 +35,11 @@ export type Component = Partial<Button | SelectMenu> & {
|
|||||||
// components?: Component[];
|
// components?: Component[];
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
export type DefaultReaction = {
|
||||||
|
emoji_id?: string | null; // the id of a guild's custom emoji
|
||||||
|
emoji_name?: string | null; // the unicode character of the emoji
|
||||||
|
};
|
||||||
|
|
||||||
export type Emoji = {
|
export type Emoji = {
|
||||||
name: string | null;
|
name: string | null;
|
||||||
id: string | null;
|
id: string | null;
|
||||||
@@ -83,7 +89,7 @@ export enum ComponentType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type HydratedMessage<T extends Array<Expectation>> = MessagePayload & {
|
export type HydratedMessage<T extends Array<Expectation>> = MessagePayload & {
|
||||||
// channel: Extract<Expectation.Channel, T[number]> extends never ? undefined : Channel;
|
channel: Extract<Expectation.Channel, T[number]> extends never ? undefined : Channel;
|
||||||
reference: Extract<Expectation.Message, T[number]> extends never ? undefined : MessagePayload;
|
reference: Extract<Expectation.Message, T[number]> extends never ? undefined : MessagePayload;
|
||||||
guild: Extract<Expectation.Guild, T[number]> extends never ? undefined : Guild;
|
guild: Extract<Expectation.Guild, T[number]> extends never ? undefined : Guild;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-12
@@ -1,4 +1,4 @@
|
|||||||
import { ApplicationCommandType, Client, CommandOptionType, ComponentStyle, type Context, createCommandHandler, Expectation, Intent, InteractionCommpoentData, type InteractionData, Message, type MessagePayload } from '../src/index';
|
import { ApplicationCommandType, ChannelType, Client, CommandOptionType, ComponentStyle, type Context, createCommandHandler, Expectation, Intent, InteractionCommpoentData, type InteractionData, Message, type MessagePayload } from '../src/index';
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
token: process.env.BOT_TOKEN!,
|
token: process.env.BOT_TOKEN!,
|
||||||
@@ -7,7 +7,9 @@ const client = new Client({
|
|||||||
Intent.GUILDS,
|
Intent.GUILDS,
|
||||||
Intent.GUILD_MESSAGES,
|
Intent.GUILD_MESSAGES,
|
||||||
Intent.GUILD_MESSAGE_REACTIONS,
|
Intent.GUILD_MESSAGE_REACTIONS,
|
||||||
Intent.MESSAGE_CONTENT
|
Intent.MESSAGE_CONTENT,
|
||||||
|
Intent.DIRECT_MESSAGES,
|
||||||
|
Intent.DIRECT_MESSAGE_REACTIONS
|
||||||
],
|
],
|
||||||
prefix: '!!'
|
prefix: '!!'
|
||||||
});
|
});
|
||||||
@@ -76,23 +78,19 @@ Nice to meet you, I'm ${context.self.username}!`
|
|||||||
// Repeated hydration
|
// Repeated hydration
|
||||||
client.chatCommands.register('me', async (context: Context, _args: any[]) => {
|
client.chatCommands.register('me', async (context: Context, _args: any[]) => {
|
||||||
// Note: Testing repeated hydration
|
// Note: Testing repeated hydration
|
||||||
const { message } = await context.hydrate(context, [Expectation.Message])
|
const { message } = await context.hydrate(context, [Expectation.Message]);
|
||||||
const hydrated = await context.hydrate(message, [Expectation.Channel])
|
const { channel } = await context.hydrate(message, [Expectation.Channel]);
|
||||||
const rehydrated = await context.hydrate(hydrated, [Expectation.Guild]);
|
|
||||||
rehydrated.guild.name
|
|
||||||
|
|
||||||
const hydrate = await context.hydrator(hydrated, [Expectation.Guild]);
|
const hydrate = await context.hydrator(context, [Expectation.Guild]);
|
||||||
const hasGuild = hydrate(hydrated);
|
const hasGuild = hydrate(context);
|
||||||
|
|
||||||
if (hasGuild) {
|
if (hasGuild) {
|
||||||
// Send a message stating the user that they are in a guild
|
// Send a message stating the user that they are in a guild
|
||||||
await context.reply(`You are ${message.author.username} and this is ${hydrated.guild.name}`, true);
|
await context.reply(`You are ${message.author.username} and this is ${context.guild.name}`, true);
|
||||||
} else {
|
} else {
|
||||||
// Send a message stating the user that they are not in a guild
|
// Send a message stating the user that they are not in a guild
|
||||||
await context.reply(`You are ${message.author.username} and we are not in a server`, true);
|
await context.reply(`You are ${message.author.username} and we are in ${channel.type === ChannelType.DM ? 'our DM\'s 😘' : 'a Group 👀'}`, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// await context.reply(`You are ${message.author.username} ${hasGuild ? message.guild.name : ''}`, true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Hydrator - hydration function generator
|
// Hydrator - hydration function generator
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"name": "unicord",
|
||||||
|
"path": "."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "quotes-bot",
|
||||||
|
"path": "../quotes-bot"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
Vendored
+5
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
output: 'export'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
Generated
+2057
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "unicord-website",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@monaco-editor/react": "^4.7.0",
|
||||||
|
"monaco-editor": "^0.52.2",
|
||||||
|
"monaco-editor-webpack-plugin": "^7.1.0",
|
||||||
|
"next": "15.1.7",
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-syntax-highlighter": "^15.4.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@types/react-dom": "^19",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
@@ -0,0 +1,31 @@
|
|||||||
|
:root {
|
||||||
|
--background: #252525;
|
||||||
|
--foreground: #ededed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
max-width: 100vw;
|
||||||
|
overflow-x: hidden;
|
||||||
|
color-scheme: dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: var(--foreground);
|
||||||
|
background: var(--background);
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1012 KiB |
@@ -0,0 +1,45 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Space_Grotesk, Space_Mono } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const spaceGrotesk = Space_Grotesk({
|
||||||
|
variable: "--font-space-grotesk",
|
||||||
|
subsets: ["latin"]
|
||||||
|
});
|
||||||
|
|
||||||
|
const spaceMono = Space_Mono({
|
||||||
|
variable: "--font-mono",
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: ["400"]
|
||||||
|
});
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: 'Unicord',
|
||||||
|
description: 'Unicord is a simple, type-safe, and modular framework for building Discord bots in TypeScript.',
|
||||||
|
openGraph: {
|
||||||
|
title: 'Unicord',
|
||||||
|
description: 'Unicord is a simple, type-safe, and modular framework for building Discord bots in TypeScript.',
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: 'https://unicord.hat.fish/unicord.png',
|
||||||
|
width: 1024,
|
||||||
|
height: 1024,
|
||||||
|
alt: 'Unicord logo'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={`${spaceGrotesk.variable} ${spaceMono.variable}`}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,499 @@
|
|||||||
|
.page {
|
||||||
|
--gray-rgb: 255, 255, 255;
|
||||||
|
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
|
||||||
|
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
|
||||||
|
|
||||||
|
--button-primary-hover: #ccc;
|
||||||
|
--button-secondary-hover: #1a1a1a;
|
||||||
|
|
||||||
|
font-family: var(--font-space-grotesk);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* page with header needs margin atop */
|
||||||
|
.pageWithHeader {
|
||||||
|
margin-top: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.7rem;
|
||||||
|
/* glassy background */
|
||||||
|
background: transparent;
|
||||||
|
backdrop-filter: blur(16px);
|
||||||
|
border-bottom: 1px solid var(--gray-alpha-200);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
display: flex;
|
||||||
|
gap: 2rem;
|
||||||
|
margin-right: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink {
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 1rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header img {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoMark {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0px;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logoMarkSpan {
|
||||||
|
color: #ff0080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* padding: 4rem 0em; */
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotPattern {
|
||||||
|
0% {
|
||||||
|
background-position: top right;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-position: bottom left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
padding: 0 5em 0;
|
||||||
|
height: calc(100vh - 4rem);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
gap: 2rem;
|
||||||
|
border-bottom: 1px solid var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
background-image: radial-gradient(circle, #ff0080 1px, transparent 1px);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
animation: dotPattern 30s linear infinite;
|
||||||
|
opacity: 0.5;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 50%;
|
||||||
|
background: linear-gradient(to bottom, transparent, var(--gray-alpha-100));
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeExample {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 60%;
|
||||||
|
height: 75%;
|
||||||
|
max-height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock {
|
||||||
|
background: var(--gray-alpha-100);
|
||||||
|
padding: 1em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code::before {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code::after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code:hover {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code:active {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code:focus {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code:focus-visible {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.codeBlock code:focus-within {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillars {
|
||||||
|
padding: 6rem 6em;
|
||||||
|
background: var(--gray-alpha-100);
|
||||||
|
min-height: 50vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillarContainer {
|
||||||
|
display: flex;
|
||||||
|
gap: 4rem;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
background: var(--gray-alpha-100);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--gray-alpha-200);
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillarIcon {
|
||||||
|
font-size: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar h2 {
|
||||||
|
color: #ff0080;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar p {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationSection {
|
||||||
|
padding: 6rem 6em;
|
||||||
|
min-height: 50vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: space;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationContainer {
|
||||||
|
display: flex;
|
||||||
|
gap: 4rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationCode {
|
||||||
|
flex: 2;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 780px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationText {
|
||||||
|
flex: 1;
|
||||||
|
/* padding: 2rem; */
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationText h2 {
|
||||||
|
color: #ff0080;
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationText p {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSection {
|
||||||
|
padding: 6rem 6em;
|
||||||
|
min-height: 50vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: space;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
background: var(--gray-alpha-100);
|
||||||
|
border-top: 1px solid var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeContainer {
|
||||||
|
display: flex;
|
||||||
|
gap: 4rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeText {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeCode {
|
||||||
|
flex: 2;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 780px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeText h2 {
|
||||||
|
color: #ff0080;
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeText p {
|
||||||
|
color: #fff;
|
||||||
|
opacity: 0.8;
|
||||||
|
margin: 1rem 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeText p code, .hydrationText p code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.85em;
|
||||||
|
background-color: var(--gray-alpha-100);
|
||||||
|
border-radius: 0.2em;
|
||||||
|
padding: 0.1em 0.2em;
|
||||||
|
color: var(--foreground);
|
||||||
|
border: 1px solid var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.installSection {
|
||||||
|
padding: 6rem 6em;
|
||||||
|
min-height: 30vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
position: relative;
|
||||||
|
border-top: 1px solid var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.installContainer {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installContainer h2 {
|
||||||
|
color: #fff;
|
||||||
|
margin: 0 0 2rem 0;
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.packageManagerTabs {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabButton {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--gray-alpha-200);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabButton:hover {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.activeTab {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
border-color: #ff0080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installCommandWrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--gray-alpha-100);
|
||||||
|
border: 1px solid var(--gray-alpha-200);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installCommand {
|
||||||
|
flex: 1;
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton {
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--gray-alpha-200);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyButton:hover {
|
||||||
|
background: var(--gray-alpha-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.hero {
|
||||||
|
padding: 0 2rem;
|
||||||
|
height: calc(100vh - 4rem);
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero .codeExample, .hero::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillars {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillarContainer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pillar {
|
||||||
|
max-width: 400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationSection {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationContainer {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationCode {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hydrationText {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeSection {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeContainer {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeCode {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typeText {
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installSection {
|
||||||
|
padding: 4rem 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.packageManagerTabs {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabButton {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
gap: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import styles from "./page.module.css";
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
|
||||||
|
const CodeExample = dynamic(
|
||||||
|
() => import('../components/CodeExample')
|
||||||
|
);
|
||||||
|
|
||||||
|
import { simpleExample, hydrationExample, typeCoercionExample } from '../components/CodeExample';
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
const [activeTab, setActiveTab] = useState('npm');
|
||||||
|
const [windowWidth, setWindowWidth] = useState(1200);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setWindowWidth(window.innerWidth);
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowWidth(window.innerWidth);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={[styles.page, styles.pageWithHeader].join(" ")}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<div className={styles.logoMark}>
|
||||||
|
<img src="/unicord.png" alt="Unicord logo"></img>
|
||||||
|
<div><span className={styles.logoMarkSpan}>uni</span>cord</div>
|
||||||
|
</div>
|
||||||
|
<nav className={styles.nav}>
|
||||||
|
{/* <a href="/docs" className={styles.navLink}>Docs</a> */}
|
||||||
|
<a href="https://github.com/jaronpate/unicord" className={styles.navLink}>Source</a>
|
||||||
|
<a href="https://discord.gg/6kFfqa5bde" target='_blank' className={styles.navLink}>Discord</a>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main className={styles.main}>
|
||||||
|
<div className={styles.hero}>
|
||||||
|
<div>
|
||||||
|
<h1 className={styles.title}>
|
||||||
|
Welcome to <span className={styles.logoMarkSpan}>uni</span>cord
|
||||||
|
</h1>
|
||||||
|
<p className={styles.description}>
|
||||||
|
Build good bots fast.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.codeExample}>
|
||||||
|
<CodeExample code={simpleExample} noScroll={false} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.pillars}>
|
||||||
|
<div className={styles.pillarContainer}>
|
||||||
|
<div className={styles.pillar}>
|
||||||
|
<div className={styles.pillarIcon}>⚡</div>
|
||||||
|
<h2>Simple</h2>
|
||||||
|
<p>Build Discord bots with minimal boilerplate and intuitive APIs</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pillar}>
|
||||||
|
<div className={styles.pillarIcon}>🔧</div>
|
||||||
|
<h2>Maintainable</h2>
|
||||||
|
<p>Type-safe, modular architecture that scales with your project</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pillar}>
|
||||||
|
<div className={styles.pillarIcon}>📈</div>
|
||||||
|
<h2>Scalable</h2>
|
||||||
|
<p>Efficient caching and event handling for bots of any size</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.hydrationSection}>
|
||||||
|
<div className={styles.hydrationContainer}>
|
||||||
|
<div className={styles.hydrationCode}>
|
||||||
|
<CodeExample code={hydrationExample} height={260} noScroll={windowWidth < 1200 ? false : true} />
|
||||||
|
</div>
|
||||||
|
<div className={styles.hydrationText}>
|
||||||
|
<h2>Smart Hydration</h2>
|
||||||
|
<p>
|
||||||
|
Unicord's hydration system automatically manages Discord object lifecycles.
|
||||||
|
The <code>context.hydrate</code> method intelligently fetches and caches Discord objects
|
||||||
|
like messages, users, and guilds, ensuring your bot stays efficient while
|
||||||
|
keeping your code clean and predictable.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.typeSection}>
|
||||||
|
<div className={styles.typeContainer}>
|
||||||
|
<div className={styles.typeText}>
|
||||||
|
<h2>Type Coercion</h2>
|
||||||
|
<p>
|
||||||
|
Unicord automatically handles type coercion for command arguments,
|
||||||
|
ensuring you always get the correct types in your handlers. Define your
|
||||||
|
command structure once, and get full type safety and autocompletion
|
||||||
|
throughout your codebase.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In this example, the <code>user</code> argument is automatically typed
|
||||||
|
as a <code>User</code> object, providing access to all user properties
|
||||||
|
with full TypeScript support.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className={styles.typeCode}>
|
||||||
|
<CodeExample code={typeCoercionExample} height={370} noScroll={windowWidth < 1200 ? false : true} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.installSection}>
|
||||||
|
<div className={styles.installContainer}>
|
||||||
|
<h2>Install <span className={styles.logoMarkSpan}>uni</span>cord</h2>
|
||||||
|
<div className={styles.packageManagerTabs}>
|
||||||
|
<button
|
||||||
|
className={`${styles.tabButton} ${activeTab === 'npm' ? styles.activeTab : ''}`}
|
||||||
|
onClick={() => setActiveTab('npm')}
|
||||||
|
>
|
||||||
|
npm
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.tabButton} ${activeTab === 'yarn' ? styles.activeTab : ''}`}
|
||||||
|
onClick={() => setActiveTab('yarn')}
|
||||||
|
>
|
||||||
|
yarn
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.tabButton} ${activeTab === 'pnpm' ? styles.activeTab : ''}`}
|
||||||
|
onClick={() => setActiveTab('pnpm')}
|
||||||
|
>
|
||||||
|
pnpm
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`${styles.tabButton} ${activeTab === 'bun' ? styles.activeTab : ''}`}
|
||||||
|
onClick={() => setActiveTab('bun')}
|
||||||
|
>
|
||||||
|
bun
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.installCommandWrapper}>
|
||||||
|
<code className={styles.installCommand}>
|
||||||
|
{activeTab === 'npm' && 'npm install unicord'}
|
||||||
|
{activeTab === 'yarn' && 'yarn add unicord'}
|
||||||
|
{activeTab === 'pnpm' && 'pnpm add unicord'}
|
||||||
|
{activeTab === 'bun' && 'bun add unicord'}
|
||||||
|
</code>
|
||||||
|
<button
|
||||||
|
className={styles.copyButton}
|
||||||
|
onClick={() => {
|
||||||
|
const commands = {
|
||||||
|
npm: 'npm install unicord',
|
||||||
|
yarn: 'yarn add unicord',
|
||||||
|
pnpm: 'pnpm add unicord',
|
||||||
|
bun: 'bun add unicord'
|
||||||
|
};
|
||||||
|
navigator.clipboard.writeText(commands[activeTab as keyof typeof commands]);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
.editorWrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editorContainer {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #2d2d2d;
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Editor, { Monaco } from "@monaco-editor/react";
|
||||||
|
import styles from './CodeExample.module.css';
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
type CodeExampleProps = {
|
||||||
|
code: string;
|
||||||
|
height?: string | number;
|
||||||
|
noScroll?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function CodeExample({ code, height = "100%", noScroll = false }: CodeExampleProps) {
|
||||||
|
// const [unicordLib, setUnicordLib] = useState("");
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// setUnicordLib(`
|
||||||
|
// declare module 'unicord' {
|
||||||
|
// export enum Intent {
|
||||||
|
// GUILDS = 1 << 0,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// `);
|
||||||
|
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
// const handleEditorDidMount = (monaco: Monaco) => {
|
||||||
|
// monaco.languages.typescript.typescriptDefaults.addExtraLib(unicordLib);
|
||||||
|
|
||||||
|
// monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
||||||
|
// noSemanticValidation: false,
|
||||||
|
// noSyntaxValidation: false,
|
||||||
|
// noSuggestionDiagnostics: false,
|
||||||
|
// });
|
||||||
|
// monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||||
|
// target: monaco.languages.typescript.ScriptTarget.ESNext,
|
||||||
|
// module: monaco.languages.typescript.ModuleKind.ESNext,
|
||||||
|
// allowJs: true,
|
||||||
|
// checkJs: true,
|
||||||
|
// allowNonTsExtensions: true,
|
||||||
|
// strict: true,
|
||||||
|
// noEmit: true,
|
||||||
|
// lib: [
|
||||||
|
// "ESNext",
|
||||||
|
// "DOM",
|
||||||
|
// "DOM.Iterable",
|
||||||
|
// "ESNext.AsyncIterable",
|
||||||
|
// "ESNext.BigInt"
|
||||||
|
// ],
|
||||||
|
// types: ["node", "unicord"],
|
||||||
|
// baseUrl: "./",
|
||||||
|
// paths: {
|
||||||
|
// "index": ["./src/index.ts"],
|
||||||
|
// "unicord": ["./node_modules/unicord/dist/index.d.ts"]
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// };
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// fetch("https://www.unpkg.com/unicord@0.0.4/out/types/common.d.ts")
|
||||||
|
// .then((response) => response.text())
|
||||||
|
// .then((text) => {
|
||||||
|
// setUnicordLib(text);
|
||||||
|
// console.log("Fetched extra lib:", text);
|
||||||
|
// })
|
||||||
|
// .catch((error) => {
|
||||||
|
// console.error("Error fetching extra lib:", error);
|
||||||
|
// });
|
||||||
|
// }, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.editorWrapper}>
|
||||||
|
<div className={styles.editorContainer}>
|
||||||
|
<Editor
|
||||||
|
height={height}
|
||||||
|
defaultLanguage="typescript"
|
||||||
|
defaultValue={code}
|
||||||
|
theme="vs-dark"
|
||||||
|
options={{
|
||||||
|
readOnly: true,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
fontSize: 14,
|
||||||
|
lineNumbers: 'on',
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
roundedSelection: false,
|
||||||
|
padding: { top: 16, bottom: 16 },
|
||||||
|
automaticLayout: true,
|
||||||
|
scrollbar: {
|
||||||
|
alwaysConsumeMouseWheel: !noScroll,
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
// beforeMount={handleEditorDidMount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export the example code snippets for reuse
|
||||||
|
export const simpleExample = `\
|
||||||
|
import { Client, type Context, Intent } from 'unicord';
|
||||||
|
|
||||||
|
// Initialize and configure
|
||||||
|
const client = new Client({
|
||||||
|
token: '<token>',
|
||||||
|
application_id: '<application_id>',
|
||||||
|
intents: [
|
||||||
|
Intent.GUILDS,
|
||||||
|
Intent.GUILD_MESSAGES,
|
||||||
|
Intent.GUILD_MESSAGE_REACTIONS,
|
||||||
|
Intent.MESSAGE_CONTENT
|
||||||
|
],
|
||||||
|
prefix: '??'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register a command
|
||||||
|
client.chatCommands.register('ping', (context: Context, args) => {
|
||||||
|
context.reply('Pong!', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
client.connect();`;
|
||||||
|
|
||||||
|
export const hydrationExample = `\
|
||||||
|
client.chatCommands.register('ping', async (context: Context, args: any[]) => {
|
||||||
|
// Hydrate the message id from the context
|
||||||
|
const { message } = await context.hydrate(context, [Expectation.Message]);
|
||||||
|
// Send a message to the channel
|
||||||
|
const response = await context.reply(\`Pong!: got \${args.join(', ')}\`, true);
|
||||||
|
// Calculate the time it took to send the message
|
||||||
|
const time = Date.now() - message.timestamp.getTime();
|
||||||
|
// Edit the message to include the time
|
||||||
|
await context.editMessage(response, \`Pong! Latency: \${time}ms\`);
|
||||||
|
});`;
|
||||||
|
|
||||||
|
export const typeCoercionExample = `\
|
||||||
|
const avatarCommand = createCommandHandler({
|
||||||
|
description: 'Get a users avatar',
|
||||||
|
args: [
|
||||||
|
{
|
||||||
|
id: 'user',
|
||||||
|
name: 'user',
|
||||||
|
type: CommandOptionType.User,
|
||||||
|
description: 'The user to get the avatar for',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
execute: async (context: Context, args) => {
|
||||||
|
// Args will be typed here as { user: User }
|
||||||
|
context.reply(args.user.avatarURL);
|
||||||
|
}
|
||||||
|
});`;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
:root {
|
||||||
|
--color-primary: #ff0080;
|
||||||
|
--color-background: #000;
|
||||||
|
--color-text: #fff;
|
||||||
|
|
||||||
|
--gray-rgb: 255, 255, 255;
|
||||||
|
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
|
||||||
|
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
|
||||||
|
|
||||||
|
--spacing-xs: 0.5rem;
|
||||||
|
--spacing-sm: 1rem;
|
||||||
|
--spacing-md: 2rem;
|
||||||
|
--spacing-lg: 4rem;
|
||||||
|
--spacing-xl: 6rem;
|
||||||
|
|
||||||
|
--border-radius-sm: 6px;
|
||||||
|
--border-radius-md: 8px;
|
||||||
|
--border-radius-lg: 12px;
|
||||||
|
|
||||||
|
--font-size-sm: 0.9rem;
|
||||||
|
--font-size-md: 1.1rem;
|
||||||
|
--font-size-lg: 1.5rem;
|
||||||
|
--font-size-xl: 2rem;
|
||||||
|
|
||||||
|
--section-padding-desktop: 6rem 12em;
|
||||||
|
--section-padding-mobile: 4rem 2rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user