@@ -0,0 +1,9 @@ | |||||
{ | |||||
"root": true, | |||||
"extends": [ | |||||
"lxsmnsyc/typescript" | |||||
], | |||||
"parserOptions": { | |||||
"project": "./tsconfig.eslint.json" | |||||
} | |||||
} |
@@ -0,0 +1,109 @@ | |||||
# 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 | |||||
src/messages.json | |||||
.idea/ |
@@ -0,0 +1,48 @@ | |||||
{ | |||||
"version": "0.0.0", | |||||
"types": "dist/types/index.d.ts", | |||||
"main": "dist/cjs/index.js", | |||||
"module": "dist/esm/index.js", | |||||
"exports": { | |||||
"require": "./dist/cjs/index.js", | |||||
"import": "./dist/esm/index.js" | |||||
}, | |||||
"files": [ | |||||
"dist", | |||||
"src" | |||||
], | |||||
"engines": { | |||||
"node": ">=16" | |||||
}, | |||||
"license": "MIT", | |||||
"keywords": [ | |||||
"pridepack" | |||||
], | |||||
"name": "asshurtstitution", | |||||
"dependencies": { | |||||
"discord.js": "^13.0.1", | |||||
"dotenv": "^10.0.0", | |||||
"fastify": "^3.20.1" | |||||
}, | |||||
"devDependencies": { | |||||
"@types/jest": "^26.0.24", | |||||
"@types/node": "^16.4.13", | |||||
"@types/node-fetch": "^2.5.12", | |||||
"eslint": "^7.32.0", | |||||
"eslint-config-lxsmnsyc": "^0.2.3", | |||||
"pridepack": "^0.10.0", | |||||
"tslib": "^2.3.0", | |||||
"typescript": "^4.3.5" | |||||
}, | |||||
"peerDependencies": {}, | |||||
"scripts": { | |||||
"prepublish": "pridepack clean && pridepack build", | |||||
"build": "pridepack build", | |||||
"type-check": "pridepack check", | |||||
"lint": "pridepack lint", | |||||
"test": "pridepack test --passWithNoTests", | |||||
"clean": "pridepack clean", | |||||
"watch": "pridepack watch", | |||||
"start": "node ./dist/cjs/index.js" | |||||
} | |||||
} |
@@ -0,0 +1,3 @@ | |||||
{ | |||||
"target": "es2017" | |||||
} |
@@ -0,0 +1,20 @@ | |||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ | |||||
import { Message, TextChannel } from 'discord.js'; | |||||
import messages from '../messages.json'; | |||||
import { transferPin } from '../utils/common'; | |||||
const pin = async ( | |||||
message: Message, pinTextChannel: TextChannel, silent = false, | |||||
): Promise<void> => { | |||||
const isReply = message.type === 'REPLY'; | |||||
if (!isReply) { | |||||
await message.reply(messages.NOTHING_TO_PIN); | |||||
return; | |||||
} | |||||
const reply = await message.fetchReference(); | |||||
await transferPin(reply, pinTextChannel, silent); | |||||
await message.reply(messages.PIN_COMPLETE); | |||||
}; | |||||
export default pin; |
@@ -0,0 +1,29 @@ | |||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ | |||||
import { Message, TextChannel } from 'discord.js'; | |||||
import messages from '../messages.json'; | |||||
import { transferPin } from '../utils/common'; | |||||
const save = async ( | |||||
message: Message, mainTextChannel: TextChannel, pinTextChannel: TextChannel, silent = true, | |||||
): Promise<void> => { | |||||
const pinnedMessages = await mainTextChannel.messages.fetchPinned(); | |||||
const pinnedMessagesCollection = Array.from(pinnedMessages.values()); | |||||
if (pinnedMessagesCollection.length <= 0) { | |||||
await message.reply(messages.NOTHING_TO_SAVE); | |||||
return; | |||||
} | |||||
const promises = pinnedMessagesCollection | |||||
.reverse() | |||||
.map((reply, i) => new Promise<void>((resolve, reject) => { | |||||
setTimeout(() => { | |||||
transferPin(reply, pinTextChannel, silent) | |||||
.then(resolve) | |||||
.catch(reject); | |||||
}, i * Number(process.env.DISCORD_MESSAGES_THROTTLE || '1000')); | |||||
})); | |||||
await Promise.all(promises); | |||||
await message.reply(messages.SAVE_COMPLETE); | |||||
}; | |||||
export default save; |
@@ -0,0 +1,83 @@ | |||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access, no-void */ | |||||
import dotenv from 'dotenv'; | |||||
import { Client, Intents, TextChannel } from 'discord.js'; | |||||
import messages from './messages.json'; | |||||
import { transferPin, getCommandPattern, log } from './utils/common'; | |||||
import pin from './commands/pin'; | |||||
import save from './commands/save'; | |||||
dotenv.config(); | |||||
const listen = async (channelIDMap: Map<string, string>) => { | |||||
const client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] }); | |||||
client.on('ready', () => { | |||||
log(`Logged in as ${client.user?.tag || ''}!`); | |||||
}); | |||||
Array.from(channelIDMap).forEach(([mainChannelID, pinChannelID]) => { | |||||
client.on('messageCreate', async (message) => { | |||||
if (message.channel.id !== mainChannelID) { | |||||
return; | |||||
} | |||||
const mainChannel = await client.channels.fetch(mainChannelID); | |||||
const pinChannel = await client.channels.fetch(pinChannelID); | |||||
if (mainChannel?.type !== 'GUILD_TEXT' || pinChannel?.type !== 'GUILD_TEXT') { | |||||
return; | |||||
} | |||||
const mainTextChannel = mainChannel as TextChannel; | |||||
const pinTextChannel = pinChannel as TextChannel; | |||||
const clientUser = client.user; | |||||
if (!clientUser) { | |||||
return; | |||||
} | |||||
if (message.type === 'CHANNEL_PINNED_MESSAGE') { | |||||
const reply = await message.fetchReference(); | |||||
await transferPin(reply, pinTextChannel, false); | |||||
await reply.unpin(); | |||||
await mainTextChannel.send(`${message.author.toString()} ${messages.PIN_COMPLETE as string}`); | |||||
await message.delete(); | |||||
return; | |||||
} | |||||
if (message.author.id === clientUser.id) { | |||||
return; | |||||
} | |||||
const mentioned = message.mentions.users.has(clientUser.id); | |||||
if (!mentioned) { | |||||
return; | |||||
} | |||||
switch (message.content) { | |||||
case getCommandPattern(clientUser, 'pin'): | |||||
await pin(message, pinTextChannel); | |||||
return; | |||||
case getCommandPattern(clientUser, 'save'): | |||||
await save(message, mainTextChannel, pinTextChannel); | |||||
return; | |||||
default: | |||||
break; | |||||
} | |||||
await message.reply(messages.UNKNOWN_COMMAND); | |||||
}); | |||||
}); | |||||
await client.login(process.env.DISCORD_BOT_TOKEN as string); | |||||
return client; | |||||
}; | |||||
const main = async (channelMap: Map<string, string>) => { | |||||
await listen(channelMap); | |||||
}; | |||||
const channelMap = new Map<string, string>(); | |||||
channelMap.set( | |||||
process.env.DISCORD_MAIN_CHANNEL as string, | |||||
process.env.DISCORD_PIN_CHANNEL as string, | |||||
); | |||||
void main(channelMap); |
@@ -0,0 +1,31 @@ | |||||
import { ClientUser, Message, TextChannel } from 'discord.js'; | |||||
export const transferPin = async ( | |||||
reply: Message, pinTextChannel: TextChannel, silent = false, | |||||
): Promise<void> => { | |||||
await pinTextChannel.send({ | |||||
content: `\`${reply.createdAt.toISOString()}\` ${silent | |||||
? reply.author.username | |||||
: reply.author.toString()}: | |||||
${reply.content}`, | |||||
embeds: Array.from(reply.attachments.values()) | |||||
.map((a) => ({ | |||||
image: { | |||||
height: a.height as number, | |||||
url: a.url, | |||||
width: a.width as number, | |||||
proxy_url: a.proxyURL, | |||||
}, | |||||
})), | |||||
}); | |||||
}; | |||||
export const getCommandPattern = (clientUser: ClientUser, commandName: string): string => ( | |||||
`<@!${clientUser.id}> ${commandName.toLowerCase()}` | |||||
); | |||||
export const log = (...args: unknown[]): void => { | |||||
// eslint-disable-next-line no-console | |||||
console.log(...args); | |||||
}; |
@@ -0,0 +1,22 @@ | |||||
{ | |||||
"exclude": ["node_modules"], | |||||
"include": ["src", "types", "test"], | |||||
"compilerOptions": { | |||||
"module": "ESNext", | |||||
"lib": ["ESNext"], | |||||
"importHelpers": true, | |||||
"declaration": true, | |||||
"sourceMap": true, | |||||
"rootDir": "./", | |||||
"strict": true, | |||||
"noUnusedLocals": true, | |||||
"noUnusedParameters": true, | |||||
"noImplicitReturns": true, | |||||
"noFallthroughCasesInSwitch": true, | |||||
"moduleResolution": "node", | |||||
"esModuleInterop": true, | |||||
"target": "ES2017", | |||||
"skipLibCheck": true, | |||||
"resolveJsonModule": true | |||||
} | |||||
} |
@@ -0,0 +1,22 @@ | |||||
{ | |||||
"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", | |||||
"esModuleInterop": true, | |||||
"target": "ES2017", | |||||
"skipLibCheck": true, | |||||
"resolveJsonModule": true | |||||
} | |||||
} |