Archived
0
0
Fork 0

Compare commits

..

No commits in common. "9bb30097a40c91fb476af8d6d0480a96b0d111b3" and "8de53c0a23d258592b60a8098295fa2cb15be676" have entirely different histories.

20 changed files with 1049 additions and 1103 deletions

View file

@ -7,6 +7,8 @@
},
"extends": [
"eslint:recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
@ -120,7 +122,27 @@
{
"case": "camelCase"
}
],
// Import Rules
"import/order": [
"error",
{
"newlines-between": "always-and-inside-groups",
"groups": [
[
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
"object",
"type"
]
]
}
],
"import/newline-after-import": "error"
},
"reportUnusedDisableDirectives": true
}

3
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,5 @@
nodeLinker: node-modules
nmMode: hardlinks-local
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-version.cjs
spec: "@yarnpkg/plugin-version"
@ -12,4 +10,4 @@ plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
yarnPath: .yarn/releases/yarn-3.0.0.cjs
yarnPath: .yarn/releases/yarn-sources.cjs

18
Jenkinsfile vendored
View file

@ -17,18 +17,6 @@ pipeline {
}
stages {
stage('Checkout') {
steps {
script {
echo '==========Checking out=========='
if (sh (script: "git log -1 | grep '.*\\[ci skip\\].*'", returnStatus: true)) {
error "'[ci skip]' found in git commit message. Aborting."
currentBuild.result = 'NOT_BUILT'
}
}
}
}
stage('Dependencies') {
steps {
echo '==========Installing Dependencies=========='
@ -66,9 +54,6 @@ pipeline {
cleanWs()
}
success {
sh 'tar -cf - dist/ | xz -9 -c - > neonjs-library.tar.xz'
archiveArtifacts artifacts: 'neonjs-library.tar.xz', fingerprint: true
discordSend description: 'CI build was a Success',
link: env.BUILD_URL,
result: 'SUCCESS',
@ -76,9 +61,6 @@ pipeline {
webhookURL: env.DISCORD_WEBHOOK
}
unstable {
sh 'tar -cf - dist/ | xz -9 -c - > neonjs-library.tar.xz'
archiveArtifacts artifacts: 'neonjs-library.tar.xz', fingerprint: true
discordSend description: 'CI build was Unstable.',
link: env.BUILD_URL,
result: 'UNSTABLE',

View file

@ -29,7 +29,6 @@
"build": "tsc",
"commit": "cz",
"lint": "eslint --format=pretty src",
"lint:fix": "eslint --format=pretty src --fix",
"postinstall": "husky install",
"prepublishOnly": "pinst --disable",
"postpublish": "pinst --enable",
@ -38,19 +37,16 @@
"dependencies": {
"@sapphire/async-queue": "^1.1.4",
"abort-controller": "^3.0.0",
"cache-manager": "^3.4.4",
"cache-manager-ioredis": "^2.1.0",
"fast-zlib": "^2.0.1",
"form-data": "^4.0.0",
"ioredis": "^4.27.7",
"lodash": "^4.17.21",
"node-fetch": "^2.6.1",
"ws": "^8.0.0"
"ws": "^7.5.3"
},
"devDependencies": {
"@commitlint/cli": "^13.1.0",
"@commitlint/config-conventional": "^13.1.0",
"@commitlint/cz-commitlint": "^13.1.0",
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@commitlint/cz-commitlint": "^12.1.4",
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@saithodev/semantic-release-gitea": "^2.1.0",
"@semantic-release/changelog": "^5.0.1",
@ -58,36 +54,35 @@
"@semantic-release/git": "^9.0.0",
"@semantic-release/npm": "^7.1.3",
"@semantic-release/release-notes-generator": "^9.0.3",
"@types/cache-manager": "^3.4.2",
"@types/cache-manager-ioredis": "^2.0.2",
"@types/chai": "^4.2.21",
"@types/eslint": "^7.28.0",
"@types/ioredis": "^4.26.7",
"@types/lodash": "^4.14.172",
"@types/mocha": "^9.0.0",
"@types/node": "^16.4.13",
"@types/node-fetch": "^2.5.12",
"@types/lodash": "^4.14.171",
"@types/mocha": "^8.2.3",
"@types/node": "^16.3.2",
"@types/node-fetch": "^2.5.11",
"@types/semantic-release": "^17.2.1",
"@types/sinon": "^10.0.2",
"@types/source-map-support": "^0.5.4",
"@types/ws": "^7.4.7",
"@typescript-eslint/eslint-plugin": "^4.29.0",
"@typescript-eslint/parser": "^4.29.0",
"@typescript-eslint/typescript-estree": "^4.29.0",
"@types/ws": "^7.4.6",
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",
"@typescript-eslint/typescript-estree": "^4.28.3",
"chai": "^4.3.4",
"commitizen": "^4.2.4",
"eslint": "^7.32.0",
"eslint": "^7.30.0",
"eslint-formatter-pretty": "^4.1.0",
"eslint-plugin-unicorn": "^35.0.0",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-import": "^2.23.4",
"eslint-plugin-unicorn": "^34.0.1",
"husky": "^7.0.1",
"inquirer": "^8.1.2",
"lint-staged": "^11.1.2",
"mocha": "^9.0.3",
"lint-staged": "^11.0.1",
"mocha": "^9.0.2",
"nyc": "^15.1.0",
"pinst": "^2.1.6",
"rimraf": "^3.0.2",
"semantic-release": "^17.4.4",
"sinon": "^11.1.2",
"sinon": "^11.1.1",
"source-map-support": "^0.5.19",
"ts-node": "^10.1.0",
"tsconfig-paths": "^3.10.1",
@ -111,6 +106,5 @@
"commitizen": {
"path": "@commitlint/cz-commitlint"
}
},
"packageManager": "yarn@3.0.0"
}
}

View file

@ -53,7 +53,7 @@ export class ApiClient {
}
public async post<T>(options: IRequestOptions): Promise<T> {
return await this.manager.createRequest<T>({
return this.manager.createRequest<T>({
method: 'POST',
body: options.body,
headers: options.headers,

View file

@ -2,11 +2,12 @@ import { AsyncQueue } from '@sapphire/async-queue';
import { AbortController } from 'abort-controller';
import FormData from 'form-data';
import fetch from 'node-fetch';
import type { Client } from '../client/client';
import { sleep } from '../utils/sleep';
import type { IMakeRequestOptions, IRouteIdentifier } from '../utils/types';
import type { ApiManager } from './apiManager';
import { sleep } from '../utils/sleep';
import type { ApiManager } from './apiManager';
import type { Client } from '../client/client';
import type { IMakeRequestOptions, IRouteIdentifier } from '../utils/types';
function calculateReset(reset: number, resetAfter: number, serverDate: number): number {
if (resetAfter) {

View file

@ -1,7 +1,7 @@
import { ApiClient } from './apiClient';
import type { Client } from '../client/client';
import type { IApiCreateMessage, IApiCreateSlashCommand, IApiUser, IFile } from '../utils/types';
import type { IApiCreateMessage } from '../utils/types';
export class ApiHelper {
public apiClient: ApiClient;
@ -16,35 +16,11 @@ export class ApiHelper {
this.apiClient = new ApiClient(this.client, this._token);
}
// TODO: Return message object
public async createMessage(channelId: string, options: IApiCreateMessage, files?: IFile[]): Promise<void> {
await this.apiClient.post({
public createMessage(channelId: string, options: IApiCreateMessage): void {
this.apiClient.post({
path: `/channels/${channelId}/messages`,
requireAuth: true,
body: options,
files: files,
});
}
public async getCurrentUser(): Promise<IApiUser> {
return await this.apiClient.get<IApiUser>({
path: '/users/@me',
requireAuth: true,
});
}
public async createSlashCommand(options: IApiCreateSlashCommand): Promise<void> {
await this.apiClient.post({
path: `/applications/${this.client.user.id}/commands`,
requireAuth: true,
body: options,
});
}
public async getUser(id: string): Promise<IApiUser> {
return await this.apiClient.get<IApiUser>({
path: `/users/${id}`,
requireAuth: true,
});
}
}

View file

@ -1,10 +1,9 @@
import { Snowflake } from '../utils/snowflake';
import { ApiHandler } from './apiHandler';
import { Snowflake } from '../utils/snowflake';
import type { Client } from '../client/client';
import type { ApiMethods, IMakeRequestOptions, IRouteIdentifier } from '../utils/types';
export class ApiManager {
public client: Client;
public globalReset: number;

View file

@ -1,29 +1,27 @@
import _ from 'lodash';
import EventEmitter from 'events';
import { ApiHelper } from '../api/apiHelper';
import { ClientUser } from '../structures/clientUser';
import { GatewayClient } from '../gateway/gatewayClient';
import { defaults } from '../utils/defaults';
import type { DeepRequired, IClientOptions } from '../utils/types';
export class Client extends EventEmitter {
public readonly api: ApiHelper;
public api: ApiHelper;
public readonly options: DeepRequired<IClientOptions>;
public readonly user: ClientUser;
public readonly ws: GatewayClient;
public ws: GatewayClient;
private _token: string;
public constructor(token: string, options?: IClientOptions) {
public constructor(token: string, options: IClientOptions = {}) {
super();
this.options = _.merge(defaults.clientOptions, options as DeepRequired<IClientOptions>);
this._token = token;
this.api = new ApiHelper(this, this._token);
this.user = new ClientUser(this);
this.ws = new GatewayClient(this, this._token);
this.ws = new GatewayClient(this);
}
public async login(): Promise<void> {

View file

@ -1,37 +1,31 @@
import zlib from 'fast-zlib';
import os from 'os';
import WebSocket from 'ws';
import type { Client } from '../client/client';
import type { GatewayReceiveMessage, GatewaySendMessage } from '../utils/types';
import type { IGatewayMessage } from '../utils/types';
export class GatewayClient {
public client: Client;
public connection: WebSocket | null;
public deflate: zlib.Deflate;
public inflate: zlib.Inflate;
private _heartbeatInterval: number;
private _heartbeatIntervalTimer: NodeJS.Timer | null;
private _sequence: number;
private _token: string;
public constructor(client: Client, token: string) {
public constructor(client: Client) {
this.client = client;
this.connection = null;
this.deflate = new zlib.Deflate();
this.inflate = new zlib.Inflate();
this._heartbeatInterval = 0;
this._heartbeatIntervalTimer= null;
this._sequence = 0;
this._token= token;
}
public async close(): Promise<void> {
if (!this.connection) throw new Error('You are not connected to the Discord WebSocket Gateway!');
this.connection.close(1000);
if (this._heartbeatIntervalTimer) clearTimeout(this._heartbeatIntervalTimer);
}
public async connect(): Promise<void> {
@ -42,50 +36,13 @@ export class GatewayClient {
);
this.connection.on('message', async (msg: Buffer | string) => {
let parsedMessage: GatewayReceiveMessage;
let parsedMessage: IGatewayMessage<null>;
if (this.client.options.ws.compression && typeof msg === 'object') parsedMessage = JSON.parse(this.inflate.process(msg).toString('utf8'));
else if (!this.client.options.ws.compression && typeof msg === 'string') parsedMessage = JSON.parse(msg);
else parsedMessage = JSON.parse(msg.toString('utf8'));
// if (parsedMessage.s) this._sequence = parsedMessage.s;
this.client.emit('raw', parsedMessage);
switch (parsedMessage.op) {
case 10:
this._heartbeatInterval = parsedMessage.d.heartbeat_interval;
this.send({
op: 2, d: {
compress: this.client.options.ws.compression,
intents: this.client.options.ws.intents,
large_threshold: this.client.options.ws.largeThreshold,
presence: this.client.options.presence,
properties: {
$browser: '@neonjs/library',
$device: '@neonjs/library',
$os: os.platform(),
},
token: this._token,
},
});
this._heartbeatIntervalTimer = setInterval(() => this._sendHeartbeat(), this._heartbeatInterval * 1000);
break;
default:
break;
}
if (parsedMessage.s) this._sequence = parsedMessage.s;
});
}
public send(data: GatewaySendMessage): void {
if (!this.connection) throw new Error('You are not connected to the Discord WebSocket Gateway!');
this.connection.send(JSON.stringify(data));
}
private _sendHeartbeat(): void {
this.send({ op: 1, d: this._sequence });
}
}

View file

@ -1,9 +0,0 @@
import { User } from './user';
import type { Client } from '../client/client';
export class ClientUser extends User {
public constructor(client: Client) {
super(client);
}
}

View file

@ -1,18 +0,0 @@
import type { IApiUser } from '..';
import type { Client } from '../client/client';
export class User {
public client: Client;
public id: string;
private _options: IApiUser;
public constructor(client: Client, options: IApiUser) {
this.client = client;
this._options = options;
}
public async fetch(id: string): Promise<IApiUser> {
return this.client.api.getUser(id);
}
}

View file

@ -1,51 +0,0 @@
import cacheManager from 'cache-manager';
import redisStore from 'cache-manager-ioredis';
import type { Client } from '../client/client';
export class CacheManager {
public client: Client;
private readonly _cacheManager: cacheManager.Cache;
public constructor(client: Client) {
this.client = client;
if (this.client.options.cache.cache === 'redis') this._cacheManager = cacheManager.caching({
store: redisStore,
ttl: this.client.options.cache.ttl,
...this.client.options.cache.options,
});
else this._cacheManager = cacheManager.caching({
store: 'memory',
ttl: this.client.options.cache.ttl,
});
}
public async del(key: string): Promise<void> {
return new Promise((res, rej) => {
this._cacheManager.del(key, (err) => {
if (err) rej(err);
else res();
});
});
}
public async get<T>(key: string): Promise<T | undefined> {
return new Promise((res, rej) => {
this._cacheManager.get<T>(key, (err, result) => {
if (err) rej(err);
else res(result);
});
});
}
public async set<T>(key: string, value: T): Promise<T> {
return new Promise((res, rej) => {
this._cacheManager.set<T>(key, value, this.client.options.cache.ttl, (err) => {
if (err) rej(err);
else res(value);
});
});
}
}

View file

@ -10,16 +10,9 @@ export const defaults: IDefaultOptions = {
url: 'https://discord.com/api',
version: 9,
},
cache: {
cache: 'memory',
ttl: 60,
},
presence: {},
ws: {
compression: true,
encoding: 'json',
intents: 0,
largeThreshold: 250,
url: 'wss://gateway.discord.gg',
version: 9,
},

View file

@ -1,5 +1,3 @@
import type IORedis from 'ioredis';
// Interfaces
export interface IApiClientOptions {
offset?: number;
@ -10,21 +8,8 @@ export interface IApiClientOptions {
version?: number;
}
export interface ICacheMemoryClientOptions {
cache?: 'memory';
ttl?: number;
}
export interface ICacheRedisClientOptions {
cache?: 'redis';
ttl?: number;
options: IORedis.RedisOptions;
}
export interface IClientOptions {
api?: IApiClientOptions;
cache?: ICacheMemoryClientOptions | ICacheRedisClientOptions;
presence?: IUpdatePresence;
ws?: IWebSocketClientOptions;
}
@ -71,64 +56,16 @@ export interface IRouteIdentifier {
route: string;
}
export interface IUpdatePresence {
activities?: {
name?: string;
type?: EActivityType;
url?: string;
}[];
afk?: boolean;
since?: number;
status?: EStatus;
}
export interface IWebSocketClientOptions {
compression?: boolean;
encoding?: 'json';
intents?: number;
largeThreshold?: number;
url?: string;
version?: number;
}
// Api Interfaces
// Api Rest Request Interfaces
// TODO: Add message components
// Dont add the files option. That goes into a separate option when creating a request
export interface IApiCreateMessage {
allowed_mentions?: {
parse?: 'everyone' | 'roles' | 'users'[];
replied_user?: boolean;
roles?: string[];
users?: string[];
};
content?: string;
embeds?: IMessageEmbed[];
message_reference?: {
channel_id?: string;
fail_if_not_exists?: boolean;
guild_id?: string;
message_id?: string;
};
tts?: boolean;
}
export interface IApiUser {
avatar: string;
bot?: boolean;
discriminator: string;
email?: string;
flags?: number;
id: string;
locale?: string;
mfa_enabled?: boolean;
premium_type?: number;
public_flags?: number;
username: string;
system?: boolean;
verified?: boolean;
}
export interface IApiCreateSlashCommand {
allowed_mentions?: {
parse?: 'everyone' | 'roles' | 'users'[];
replied_user?: boolean;
@ -148,55 +85,14 @@ export interface IApiCreateSlashCommand {
}
// WebSocket Gateway Message Interfaces
export interface IGatewayHeartbeatSend {
op: 1;
d: number;
}
// TODO: Add sharding when i get to it
export interface IGatewayIdentify {
op: 2;
d: {
compress: boolean;
intents: number;
large_threshold: number;
presence?: IUpdatePresence;
properties: {
$browser: string;
$device: string;
$os: string;
};
token: string;
};
}
export interface IGatewayHeartbeat {
op: 10;
d: {
heartbeat_interval: number;
};
}
export interface IGatewayHeartbeatAck {
op: 11;
export interface IGatewayMessage<T> {
d?: T;
op: number;
s?: number;
t?: string;
}
// Enums
export enum EActivityType {
GAME = 0,
STREAMING = 1,
LISTENING = 2,
WATCHING = 3,
COMPETING = 5,
}
export enum EStatus {
DND = 'dnd',
IDLE = 'idle',
INVISIBLE = 'invisible',
OFFLINE = 'offline',
ONLINE = 'online',
}
// Type Aliases
export type ApiMethods = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT' ;
@ -222,6 +118,4 @@ export type DeepRequired<T> = T extends Builtin
: T extends {}
? { [K in keyof T]-?: DeepRequired<T[K]> }
: NonNullable<T>;
export type GatewayReceiveMessage = IGatewayHeartbeat | IGatewayHeartbeatAck;
export type GatewaySendMessage = IGatewayHeartbeatSend | IGatewayIdentify;
export type Primitives = bigint | boolean | null | number | string | symbol | undefined;

1030
yarn.lock

File diff suppressed because it is too large Load diff