@@ -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 | |||
} | |||
} |