Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Effis support #13

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions lib/attachments/upload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import axios from 'axios';
import { FileData } from 'eludris-api-types/effis';
import { Bot } from '../bot.js';

// Make an uploadAttachment function where file.file must use FileData and Buffer
export default async function uploadAttachment(
bot: Bot,
file: {
file: Buffer,
name: string,
spoiler?: boolean,
}
): Promise<FileData> {
const fixedFile = {
file: file.file,
name: file.name,
spoiler: file.spoiler,
};
const response = await axios.post(`${bot.cdn}/attachments`, {
file: fixedFile,
},
{
headers: {
"Content-Type": "multipart/form-data",
}
}
);
return response.data as FileData;
}
173 changes: 131 additions & 42 deletions lib/bot.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { RawData, WebSocket } from 'ws'
import { EventEmitter } from 'events'
import axios from 'axios'
import { Message } from 'eludris-api-types/oprish'
import { EvangelineValueError } from './errors.js'

const DEFAULT_REST_URL = 'https://eludris.tooty.xyz/'
const DEFAULT_WS_URL = 'wss://eludris.tooty.xyz/ws/'
import fs from 'fs';
import { RawData, WebSocket } from 'ws';
import { EventEmitter } from 'events';
import axios from 'axios';
import { Message } from 'eludris-api-types/oprish';
import { EvangelineValueError } from './errors.js';
import uploadAttachment from './attachments/upload.js';
import { FileData } from 'eludris-api-types/effis';
import path from 'path';

const DEFAULT_REST_URL = 'https://eludris.tooty.xyz/';
const DEFAULT_WS_URL = 'wss://eludris.tooty.xyz/ws/';
const DEFAULT_CDN_URL = 'https://effis.tooty.xyz/';

/**
* The options for the bot.
Expand All @@ -19,14 +24,18 @@ export interface BotOptions {
* The REST URL to send requests to.
*/
restURL?: string;
/**
* The CDN URL to send requests to.
*/
cdnURL?: string;
}

export declare interface Bot {
/**
* A function to listen to certain events.
* @param event The event to listen to.
* @param listener The parameters (if needed) to be used in the event.
* @returns {@link Bot}
* @returns {@link Bot}
* @example
* import { Bot } from 'evangeline';
*
Expand All @@ -44,11 +53,12 @@ export declare interface Bot {
}

export class Bot extends EventEmitter {
public author: string
private options?: BotOptions
public ws: WebSocket | null = null
public rest: string
private interval: NodeJS.Timer | null = null
public author: string;
private options?: BotOptions;
public ws: WebSocket | null = null;
public cdn: string;
public rest: string;
private interval: NodeJS.Timer | null = null;

/**
* The main bot class.
Expand All @@ -64,12 +74,13 @@ export class Bot extends EventEmitter {
* })
*/
constructor(author: string, options?: BotOptions) {
super()
this.author = author
this.options = options
this.rest = this.options?.restURL || DEFAULT_REST_URL
super();
this.author = author;
this.options = options;
this.rest = this.options?.restURL || DEFAULT_REST_URL;
this.cdn = this.options?.cdnURL || DEFAULT_CDN_URL;
}

/**
* Connects to the Eludris gateway.
* @throws {EvangelineValueError} If `author` is not 2-32 characters long.
Expand All @@ -82,42 +93,42 @@ export class Bot extends EventEmitter {
*/
connect() {
if (this.author.length < 2 || this.author.length > 32) {
throw new EvangelineValueError('author passed is not 2-32 characters long')
throw new EvangelineValueError('author passed is not 2-32 characters long');
}

this.ws = new WebSocket(this.options?.gatewayURL || DEFAULT_WS_URL)
this.ws = new WebSocket(this.options?.gatewayURL || DEFAULT_WS_URL);

this.ws.on('open', () => {
this.emit('ready')
})
this.emit('ready');
});

this.ws.on('message', (data: RawData) => {
const event = JSON.parse(data.toString())
const event = JSON.parse(data.toString());
if (event.op == "MESSAGE_CREATE") {
this.emit('messageCreate', event.d as Message)
this.emit('messageCreate', event.d as Message);
}
})
});

this.ws.on('close', (code: number, reason: Buffer) => {
this.emit('close', code, reason)
})
this.emit('close', code, reason);
});

this.ws.on('error', (error: Error) => {
this.emit('error', error)
})
this.emit('error', error);
});

this.interval = setInterval(() => {
this.ws?.send(JSON.stringify({op: "PING"}))
}, 45 * 1000)
this.ws?.send(JSON.stringify({ op: "PING" }));
}, 45 * 1000);
}

/**
* Closes the connection to the Eludris gateway.
*/
close() {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.close()
clearInterval(this.interval!)
this.ws.close();
clearInterval(this.interval!);
}
}

Expand All @@ -133,23 +144,101 @@ export class Bot extends EventEmitter {
* await bot.sendMessage('woah, I\'m alive!')
* })
*/
async sendMessage(content: string): Promise<Message> {
if (content === '' || content === undefined || content === null) {
throw new EvangelineValueError('Message content cannot be empty')
// make use of Buffer.from() to send files
async sendMessage(content?: string, file?: {
name: string,
file: Buffer,
spoiler?: boolean;
}): Promise<Message> {
let fullMessage: string;
if (file) {
const attachment = await uploadAttachment(this, file);
const fixedURL = `${this.cdn}${attachment.id}`;
fullMessage = `${content || ''} ${fixedURL}`;
} else {
fullMessage = content || '';
}

return await axios.post(`${this.rest}/messages`, {
return (await axios.post(`${this.rest}/messages`, {
author: this.author,
content: content
}).then((v) => v.data) as Promise<Message>
content: fullMessage,
})).data as Message;
}

/**
* Send a message to Eludris. (Alias for {@link sendMessage})
* @param content The content of the message.
*/
async send(content: string): Promise<Message> {
return await this.sendMessage(content)
async send(content?: string, file?: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not allow multiple files?

name: string,
file: Buffer,
spoiler?: boolean;
}): Promise<Message> {
return await this.sendMessage(content, file);
}

/**
* Fetches an attachment from the attachment bucket.
* @param id The ID of the attachment.
* @returns {Buffer} The attachment.
*/
async fetchAttachment(id: string): Promise<Buffer> {
return (await axios.get(`${this.cdn}/attachments/${id}`, {
responseType: 'arraybuffer'
})).data as Buffer;
}

/**
* Fetches an attachment's data from the attachment bucket.
* @param id The ID of the attachment.
* @returns {FileData} The attachment's data.
*/
async fetchAttachmentData(id: string): Promise<FileData> {
return (await axios.get(`${this.cdn}/attachments/${id}/data`, {
responseType: 'json'
})).data as FileData;
}

/**
* Downloads an attachment from the attachment bucket.
* @param id The ID of the attachment.
* @param name The name of the attachment.
* @returns {Promise<void>} A promise that resolves when the attachment is downloaded.
*/
async downloadAttachment(id: string, name: string): Promise<void> {
const data = this.fetchAttachment(id);
data.then((attachment) => {
return fs.writeFileSync(
path.join(process.cwd(), name),
attachment
);
});
}

/**
* Fetch a static file with the given name. These are added only by the instance owner.
* @param name The name of the file.
* @returns {Buffer} The file.
*/
async fetchStaticFile(name: string): Promise<Buffer> {
return (await axios.get(`${this.cdn}/static/${name}`, {
responseType: 'arraybuffer'
})).data as Buffer;
}

/**
* Download a static file with the given name. These are added only by the instance owner.
* @param name The name of the file.
* @returns {Promise<void>} A promise that resolves when the file is downloaded.
*/
async downloadStaticFile(name: string): Promise<void> {
const response = (await axios.get(`${this.cdn}/static/${name}/download`, {
responseType: 'arraybuffer'
})).data as Buffer;

return fs.writeFileSync(
path.join(process.cwd(), name),
response
);
}
}