Browse Source

Update pridepack, fix Discord.js types

Use newer pridepack, and use Discord.js enums for setting up client.
master
TheoryOfNekomata 1 year ago
commit
ec53739c16
34 changed files with 4641 additions and 0 deletions
  1. +15
    -0
      .env.example
  2. +9
    -0
      .eslintrc
  3. +111
    -0
      .gitignore
  4. +4
    -0
      README.md
  5. +53
    -0
      package.json
  6. +3
    -0
      pridepack.json
  7. +10
    -0
      src/client.ts
  8. +11
    -0
      src/commands/help.ts
  9. +55
    -0
      src/commands/post.ts
  10. +40
    -0
      src/config.ts
  11. +92
    -0
      src/functions/anime.ts
  12. +46
    -0
      src/functions/meme.ts
  13. +2
    -0
      src/handlers.ts
  14. +53
    -0
      src/handlers/messageCreate.ts
  15. +5
    -0
      src/handlers/ready.ts
  16. +18
    -0
      src/index.ts
  17. +98
    -0
      src/utils/database.ts
  18. +78
    -0
      src/utils/sources.ts
  19. +82
    -0
      test/index.test.ts
  20. +21
    -0
      tsconfig.eslint.json
  21. +21
    -0
      tsconfig.json
  22. +3
    -0
      types/client.d.ts
  23. +3
    -0
      types/commands/help.d.ts
  24. +3
    -0
      types/commands/post.d.ts
  25. +14
    -0
      types/config.d.ts
  26. +4
    -0
      types/functions/anime.d.ts
  27. +2
    -0
      types/functions/meme.d.ts
  28. +2
    -0
      types/handlers.d.ts
  29. +1
    -0
      types/handlers/messageCreate.d.ts
  30. +1
    -0
      types/handlers/ready.d.ts
  31. +1
    -0
      types/index.d.ts
  32. +7
    -0
      types/utils/database.d.ts
  33. +3
    -0
      types/utils/sources.d.ts
  34. +3770
    -0
      yarn.lock

+ 15
- 0
.env.example View File

@@ -0,0 +1,15 @@
DISCORD_BOT_TOKEN=

DISCORD_BOT_INTENTS=Guilds,GuildMessages,DirectMessages

DISCORD_BOT_PARTIALS=Channel,Reaction

GELBOORU_API_KEY=

GELBOORU_API_USER_ID=

GELBOORU_API_URL=

SAUCENAO_API_KEY=

SAUCENAO_API_URL=

+ 9
- 0
.eslintrc View File

@@ -0,0 +1,9 @@
{
"root": true,
"extends": [
"lxsmnsyc/typescript"
],
"parserOptions": {
"project": "./tsconfig.eslint.json"
}
}

+ 111
- 0
.gitignore View File

@@ -0,0 +1,111 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# TypeScript v1 declaration files
typings/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env
.env.production
.env.development

# parcel-bundler cache (https://parceljs.org/)
.cache

# Next.js build output
.next

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

.npmrc
.data
.image-replies.json
.text-replies.json
.idea/

+ 4
- 0
README.md View File

@@ -0,0 +1,4 @@
**CuuBot**
Upload anime images in the channel! Available commands are `post` and `help` (WIP).

+ 53
- 0
package.json View File

@@ -0,0 +1,53 @@
{
"name": "cuubot",
"version": "0.0.0",
"files": [
"dist",
"src"
],
"engines": {
"node": ">=12"
},
"keywords": [
"pridepack"
],
"devDependencies": {
"@types/node": "^18.14.1",
"eslint": "^8.35.0",
"eslint-config-lxsmnsyc": "^0.5.0",
"pridepack": "2.4.4",
"tslib": "^2.5.0",
"typescript": "^4.9.5",
"vitest": "^0.28.1"
},
"dependencies": {
"discord.js": "^14.7.1",
"dotenv": "^16.0.3",
"fetch-ponyfill": "^7.1.0"
},
"scripts": {
"prepublishOnly": "pridepack clean && pridepack build",
"build": "pridepack build",
"type-check": "pridepack check",
"lint": "pridepack lint",
"clean": "pridepack clean",
"watch": "pridepack watch",
"start": "pridepack start",
"dev": "pridepack dev",
"test": "vitest"
},
"private": true,
"description": "CuuBot",
"repository": {
"url": "https://code.modal.sh/TheoryOfNekomata/cuubot",
"type": "git"
},
"homepage": "https://code.modal.sh/TheoryOfNekomata/cuubot",
"bugs": {
"url": "https://code.modal.sh/TheoryOfNekomata/cuubot/issues"
},
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>",
"publishConfig": {
"access": "restricted"
}
}

+ 3
- 0
pridepack.json View File

@@ -0,0 +1,3 @@
{
"target": "es2018"
}

+ 10
- 0
src/client.ts View File

@@ -0,0 +1,10 @@
import { Client } from 'discord.js';

import * as config from './config';

const CLIENT = new Client({
intents: config.discord.botIntents,
partials: config.discord.botPartials,
});

export default CLIENT;

+ 11
- 0
src/commands/help.ts View File

@@ -0,0 +1,11 @@
import { readFile } from 'fs/promises';
import { Message } from 'discord.js';

export default async (message: Message): Promise<void> => {
await message.reply('Sent you a DM <:chinesedoge:649753972395081788>');
const readmeContentBuffer = await readFile('README.md');
const readmeContentString = readmeContentBuffer.toString('utf-8');
await message.author.send({
content: readmeContentString,
});
};

+ 55
- 0
src/commands/post.ts View File

@@ -0,0 +1,55 @@
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */

import { Message } from 'discord.js';
import * as database from '../utils/database';
import * as anime from '../functions/anime';
import * as sources from '../utils/sources';

export default async (message: Message, ...args: string[]): Promise<void> => {
if (args.length <= 0) {
const data = await database.load(message.author.id);
if (data.length > 0) {
await message.channel.send({
embeds: data.map((d) => ({
title: 'CuuBot',
image: {
url: d.url,
},
})),
});
await database.remove(message.author.id);
}
return;
}

if (args.join(' ').toLowerCase() === 'cuu') {
const data = await sources.fetchBooru('thighhighs');
const NOT_FOUND_MESSAGES = [
'No images found.',
];
if (data.post.length <= 0) {
await message.channel.send(
NOT_FOUND_MESSAGES[Math.floor(Math.random() * NOT_FOUND_MESSAGES.length)]
);
return;
}
await message.channel.send({
embeds: [
{
title: 'CuuBot',
image: {
url: data.post[0].file_url as string,
},
},
],
});
return;
}

const [subject] = args;
await anime.replyRandomImage(message, subject);
};

+ 40
- 0
src/config.ts View File

@@ -0,0 +1,40 @@
import {
BitFieldResolvable,
GatewayIntentsString,
IntentsBitField,
Partials,
} from 'discord.js';

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace discord {
export const botIntents = (
(process.env.DISCORD_BOT_INTENTS as string)
.split(',')
.map((s) => (
Object.entries(IntentsBitField.Flags).find(([key]) => key === s)?.[1]
))
.filter((v) => typeof v === 'number') as unknown as BitFieldResolvable<GatewayIntentsString, number>
);
export const botPartials = (
(process.env.DISCORD_BOT_PARTIALS as string)
.split(',')
.map((s) => (
Object.entries(Partials)
.find(([key]) => key === s)?.[1]
))
.filter((v) => typeof v === 'number') as unknown as Partials[]
);
}

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace gelbooru {
export const apiUrl = process.env.GELBOORU_API_URL as string;
export const apiKey = process.env.GELBOORU_API_KEY as string;
export const userId = process.env.GELBOORU_API_USER_ID as string;
}

// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace saucenao {
export const apiUrl = process.env.SAUCENAO_API_URL as string;
export const apiKey = process.env.SAUCENAO_API_KEY as string;
}

+ 92
- 0
src/functions/anime.ts View File

@@ -0,0 +1,92 @@
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */

import { readFile } from 'fs/promises';
import { GuildEmoji, Message } from 'discord.js';
import * as sources from '../utils/sources';
import * as database from '../utils/database';

type ImageReply = {
tags: (string | string[])[],
emojiName?: string[],
content?: string,
}

export const listenForImagesAndReply = async (message: Message): Promise<void> => {
const promises = Array
.from(message.attachments.values())
.filter((a) => a.url.endsWith('.jpg') || a.url.endsWith('.jpeg') || a.url.endsWith('.png'))
.map(async (a) => sources.reverseSearch(a.url));

const reverseSearchData = await Promise.all(promises);
const imageInfoPromises = reverseSearchData.map(async (d) => {
const [booruResult] = d.results;
if (!booruResult) {
return null;
}
return sources.fetchImage(booruResult.data.gelbooru_id.toString());
});
const imageInfos = await Promise.all(imageInfoPromises);
const tags = imageInfos.filter((i) => Boolean(i)).map((info) => info.tags.split(' ')).flat();
const repliesBuffer = await readFile('.image-replies.json');
const repliesString = repliesBuffer.toString('utf-8');
const replies = JSON.parse(repliesString) as ImageReply[];
const theReply = replies.reduce<ImageReply | null>(
(chosenReply, reply) => {
if (chosenReply === null && reply.tags.some((t) => {
if (Array.isArray(t)) {
return t.every((tt) => tags.includes(tt));
}
return tags.includes(t);
})) {
return reply;
}
return chosenReply;
},
null,
);

console.log(`${message.id}: ${tags.join(' ')}`);
if (theReply) {
const { emojiName: emojiNames = [], content: replyContent = '' } = theReply;
console.log([...emojiNames.map((em) => `+:${em}:`), replyContent].join(' '));
if (emojiNames.length > 0) {
const emojis = emojiNames
.map((emName) => (
message.guild?.emojis.cache.find((em) => em.name === emName)
))
.filter((em) => Boolean(em)) as GuildEmoji[];

await Promise.all(emojis.map((em) => message.react(em)));
}
if (theReply.content) {
await message.reply(theReply.content);
}
}
};

export const replyRandomImage = async (message: Message, topic: string): Promise<void> => {
const data = await sources.fetchBooru(topic);
await message.channel.send({
embeds: [
{
title: 'CuuBot',
image: {
url: data.post[0].file_url as string,
},
},
],
});
};

export const queueImage = async (message: Message): Promise<void> => {
const promises = Array
.from(message.attachments.values())
.map(async (attachment) => database.save(message.author.id, { url: attachment.url }));

await Promise.all(promises);
await message.react('✅');
};

+ 46
- 0
src/functions/meme.ts View File

@@ -0,0 +1,46 @@
/* eslint-disable import/prefer-default-export */

import { GuildEmoji, Message } from 'discord.js';
import { readFile } from 'fs/promises';

type TextReply = {
keywords: (string | string[])[],
emojiName?: string[],
content?: string,
}

export const listenForKeywordsAndReply = async (message: Message): Promise<void> => {
const repliesBuffer = await readFile('.text-replies.json');
const repliesString = repliesBuffer.toString('utf-8');
const replies = JSON.parse(repliesString) as TextReply[];
const theReply = replies.reduce<TextReply | null>(
(chosenReply, reply) => {
if (chosenReply === null && reply.keywords.some((t) => {
if (Array.isArray(t)) {
return t.every((tt) => message.content === tt);
}
return message.content === t;
})) {
return reply;
}
return chosenReply;
},
null,
);

if (theReply) {
const emojiNames = theReply.emojiName;
if (emojiNames) {
const emojis = emojiNames
.map((emName) => (
message.guild?.emojis.cache.find((em) => em.name === emName)
))
.filter((em) => Boolean(em)) as GuildEmoji[];

await Promise.all(emojis.map((em) => message.react(em)));
}
if (theReply.content) {
await message.channel.send(theReply.content);
}
}
};

+ 2
- 0
src/handlers.ts View File

@@ -0,0 +1,2 @@
import './handlers/ready';
import './handlers/messageCreate';

+ 53
- 0
src/handlers/messageCreate.ts View File

@@ -0,0 +1,53 @@
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */

import { ChannelType } from 'discord.js';
import CLIENT from '../client';
import * as anime from '../functions/anime';
import * as meme from '../functions/meme';
import post from '../commands/post';
import help from '../commands/help';

CLIENT.on('messageCreate', async (message) => {
if (!CLIENT.user) {
return;
}

if (message.author.id === CLIENT.user.id) {
// Do not listen to own messages.
return;
}

const content = message.content?.replaceAll(/\s\s+/g, ' ').toLowerCase() ?? '';

if (message.channel.type === ChannelType.DM) {
const [command] = content.split(' ');

if (command === 'save') {
await anime.queueImage(message);
}

return;
}

if (content.startsWith(`<@!${CLIENT.user.id}>`) || content.startsWith(`<@${CLIENT.user.id}>`)) {
const [, command, ...args] = content.split(' ');
if (command === 'post') {
await post(message, ...args);
return;
}

if (command === 'help') {
await help(message);
return;
}

return;
}

await meme.listenForKeywordsAndReply(message);
await anime.listenForImagesAndReply(message);
});

+ 5
- 0
src/handlers/ready.ts View File

@@ -0,0 +1,5 @@
import CLIENT from '../client';

CLIENT.on('ready', () => {
console.log('client ready');
});

+ 18
- 0
src/index.ts View File

@@ -0,0 +1,18 @@
import CLIENT from './client';
import './handlers';

if (!process.env.DISCORD_BOT_TOKEN) {
console.error('No bot token specified.');
process.exit(1);
}

CLIENT.login(process.env.DISCORD_BOT_TOKEN)
.then(() => {
if (CLIENT.user) {
console.log(`Bot client logged in as ${CLIENT.user.tag}`);
}
})
.catch((err: Error) => {
console.error(err);
process.exit(1);
});

+ 98
- 0
src/utils/database.ts View File

@@ -0,0 +1,98 @@
import {
createWriteStream,
createReadStream,
promises,
ReadStream,
} from 'fs';
import readline from 'readline';

export type Item = {
url: string,
reactions?: string[],
}

export const save = async (userId: string, item: Item): Promise<void> => {
try {
await promises.stat('.data');
} catch {
await promises.mkdir('.data');
}

return new Promise((resolve, reject) => {
const ws = createWriteStream(`.data/${userId}.txt`, { flags: 'a' });
ws.on('error', reject);
ws.on('finish', resolve);
ws.end(`${[item.url, ...(item.reactions || [])].join(' ')}\n`);
});
};

export const load = async (userId: string, count = 1): Promise<Item[]> => {
try {
await promises.stat('.data');
} catch {
await promises.mkdir('.data');
}

return new Promise<Item[]>((resolve, reject) => {
let rs: ReadStream;
const items: Item[] = [];
try {
rs = createReadStream(`.data/${userId}.txt`, { flags: 'r' });
rs.on('error', reject);
const rl = readline.createInterface(rs);
rl.on('line', (line) => {
if (items.length >= count) {
return;
}
const [url, ...reactions] = line.split(' ');
items.push({
url,
reactions,
});
});
rl.on('close', () => {
resolve(items);
});
} catch {
// noop
}
});
};

export const remove = async (userId: string, count = 1): Promise<void> => {
try {
await promises.stat('.data');
} catch {
await promises.mkdir('.data');
}

return new Promise((resolve, reject) => {
let rs: ReadStream;
let lines = 0;
try {
rs = createReadStream(`.data/${userId}.txt`, { flags: 'r' });
rs.on('error', reject);
const rl = readline.createInterface(rs);
const ws = createWriteStream(`.data/${userId}.txt.tmp`, { flags: 'w' });
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on('close', async () => {
await promises.unlink(`.data/${userId}.txt`);
await promises.rename(`.data/${userId}.txt.tmp`, `.data/${userId}.txt`);
resolve();
});
rl.on('line', (line) => {
lines += 1;
if (lines <= count) {
return;
}
ws.write(`${line}\n`);
});
rl.on('close', () => {
ws.close();
});
} catch (e) {
// noop
reject(e);
}
});
};

+ 78
- 0
src/utils/sources.ts View File

@@ -0,0 +1,78 @@
/* eslint-disable import/prefer-default-export */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-return */

import fetchPonyfill from 'fetch-ponyfill';
import * as config from '../config';

export const fetchBooru = async (q: string, page = 0) => {
if (q.trim().length <= 0) {
throw new Error('Specify search terms');
}

const url = new URL(config.gelbooru.apiUrl);
const search = new URLSearchParams({
page: 'dapi',
s: 'post',
api_key: config.gelbooru.apiKey,
user_id: config.gelbooru.userId,
q: 'index',
json: '1',
tags: encodeURIComponent(q),
pid: page.toString(),
});
url.search = search.toString();
const { fetch } = fetchPonyfill();
const response = await fetch(url.toString());
if (response.ok) {
const data = await response.json();
console.log('OK', data);
return data;
}

const data = await response.text();
console.log('NOT OK', data);
throw new Error('Gelbooru API error');
};

export const fetchImage = async (id: string) => {
const url = new URL(config.gelbooru.apiUrl);
const search = new URLSearchParams({
page: 'dapi',
s: 'post',
api_key: config.gelbooru.apiKey,
user_id: config.gelbooru.userId,
q: 'index',
json: '1',
id,
});
url.search = search.toString();
const { fetch } = fetchPonyfill();
const response = await fetch(url.toString());
if (response.ok) {
const data = await response.json();
return data.post[0];
}
throw new Error('Gelbooru API error');
};

export const reverseSearch = async (u: string) => {
const url = new URL(config.saucenao.apiUrl);
const search = new URLSearchParams({
db: '25',
output_type: '2',
testmode: '1',
numres: '16',
api_key: config.saucenao.apiKey,
url: u,
});
url.search = search.toString();
const { fetch } = fetchPonyfill();
const response = await fetch(url.toString());
if (response.ok) {
return response.json();
}
throw new Error('SauceNAO API error');
};

+ 82
- 0
test/index.test.ts View File

@@ -0,0 +1,82 @@
import { EventEmitter } from 'events';
import CLIENT from '../src/client';
import '../src/handlers';

jest.mock('dotenv', () => ({
config: () => {
process.env.DISCORD_BOT_TOKEN = 'token';
process.env.DISCORD_BOT_INTENTS = 'GUILDS,GUILD_MESSAGES';
},
}));

jest.mock('discord.js', () => ({
Client: class MockClient extends EventEmitter {
public user: any = {};

public token = '';

async login(token: string) {
this.token = token;
this.user = {
id: '0',
tag: 'User#0000',
};
return Promise.resolve(token);
}
},
}));

describe('Example', () => {
let defaultConsoleLog: typeof console.log;

beforeEach(() => {
defaultConsoleLog = console.log;
console.log = jest.fn();
});

afterEach(() => {
console.log = defaultConsoleLog;
});

beforeEach(async () => {
await CLIENT.login('token');
});

it('should ensure logged in user exists', () => {
expect(CLIENT.user).toEqual({
id: '0',
tag: 'User#0000',
});
});

it('should handle ready event', () => {
CLIENT.emit('ready', CLIENT);
expect(console.log).toBeCalledWith('client ready');
});

it('should handle messageCreate event by echoing user message', () => {
const payload: Record<string, any> = {
author: {
id: '1',
tag: 'Anon#1337',
},
embeds: [],
content: 'Test content.',
mentions: {
users: new Map([
['0', {
id: '0',
tag: 'User#0000',
}],
]),
},
reply: jest.fn(),
};

CLIENT.emit('messageCreate', payload as any);
expect(payload.reply).toBeCalledWith({
embeds: [],
content: 'Test content.',
});
});
});

+ 21
- 0
tsconfig.eslint.json View File

@@ -0,0 +1,21 @@
{
"exclude": ["node_modules"],
"include": ["src", "types", "test"],
"compilerOptions": {
"module": "ESNext",
"lib": ["DOM", "ESNext"],
"importHelpers": true,
"declaration": true,
"sourceMap": true,
"rootDir": "./",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "es2018"
}
}

+ 21
- 0
tsconfig.json View File

@@ -0,0 +1,21 @@
{
"exclude": ["node_modules"],
"include": ["src", "types"],
"compilerOptions": {
"module": "ESNext",
"lib": ["ESNext"],
"importHelpers": true,
"declaration": true,
"sourceMap": true,
"rootDir": "./src",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"jsx": "react",
"esModuleInterop": true,
"target": "es2018"
}
}

+ 3
- 0
types/client.d.ts View File

@@ -0,0 +1,3 @@
import { Client } from 'discord.js';
declare const CLIENT: Client<boolean>;
export default CLIENT;

+ 3
- 0
types/commands/help.d.ts View File

@@ -0,0 +1,3 @@
import { Message } from 'discord.js';
declare const _default: (message: Message) => Promise<void>;
export default _default;

+ 3
- 0
types/commands/post.d.ts View File

@@ -0,0 +1,3 @@
import { Message } from 'discord.js';
declare const _default: (message: Message, ...args: string[]) => Promise<void>;
export default _default;

+ 14
- 0
types/config.d.ts View File

@@ -0,0 +1,14 @@
import { BitFieldResolvable, Partials } from 'discord.js';
export declare namespace discord {
const botIntents: BitFieldResolvable<"Guilds" | "GuildMembers" | "GuildModeration" | "GuildBans" | "GuildEmojisAndStickers" | "GuildIntegrations" | "GuildWebhooks" | "GuildInvites" | "GuildVoiceStates" | "GuildPresences" | "GuildMessages" | "GuildMessageReactions" | "GuildMessageTyping" | "DirectMessages" | "DirectMessageReactions" | "DirectMessageTyping" | "MessageContent" | "GuildScheduledEvents" | "AutoModerationConfiguration" | "AutoModerationExecution", number>;
const botPartials: Partials[];
}
export declare namespace gelbooru {
const apiUrl: string;
const apiKey: string;
const userId: string;
}
export declare namespace saucenao {
const apiUrl: string;
const apiKey: string;
}

+ 4
- 0
types/functions/anime.d.ts View File

@@ -0,0 +1,4 @@
import { Message } from 'discord.js';
export declare const listenForImagesAndReply: (message: Message) => Promise<void>;
export declare const replyRandomImage: (message: Message, topic: string) => Promise<void>;
export declare const queueImage: (message: Message) => Promise<void>;

+ 2
- 0
types/functions/meme.d.ts View File

@@ -0,0 +1,2 @@
import { Message } from 'discord.js';
export declare const listenForKeywordsAndReply: (message: Message) => Promise<void>;

+ 2
- 0
types/handlers.d.ts View File

@@ -0,0 +1,2 @@
import './handlers/ready';
import './handlers/messageCreate';

+ 1
- 0
types/handlers/messageCreate.d.ts View File

@@ -0,0 +1 @@
export {};

+ 1
- 0
types/handlers/ready.d.ts View File

@@ -0,0 +1 @@
export {};

+ 1
- 0
types/index.d.ts View File

@@ -0,0 +1 @@
import './handlers';

+ 7
- 0
types/utils/database.d.ts View File

@@ -0,0 +1,7 @@
export type Item = {
url: string;
reactions?: string[];
};
export declare const save: (userId: string, item: Item) => Promise<void>;
export declare const load: (userId: string, count?: number) => Promise<Item[]>;
export declare const remove: (userId: string, count?: number) => Promise<void>;

+ 3
- 0
types/utils/sources.d.ts View File

@@ -0,0 +1,3 @@
export declare const fetchBooru: (q: string, page?: number) => Promise<any>;
export declare const fetchImage: (id: string) => Promise<any>;
export declare const reverseSearch: (u: string) => Promise<any>;

+ 3770
- 0
yarn.lock
File diff suppressed because it is too large
View File


Loading…
Cancel
Save