From 0a6439161e347593d94c9ecdb2499a41f4850a20 Mon Sep 17 00:00:00 2001 From: Scott Martin Date: Sat, 22 Jan 2022 21:17:08 -0500 Subject: [PATCH] Discord chat bot to announce projects (#590) (#600) * Discord chat bot to announce projects (#590) Add support for discord chat bot to announce when images are set, with instructions on configuring for dev. This uses the REST API instead of a websocket connection, which is needed for serverless deployment. Co-authored-by: Kurt Hutten * Remove discord.js dependency. Co-authored-by: Kurt Hutten --- CONTRIBUTING.md | 23 +++++++++++++++- app/.env.defaults | 4 +-- app/api/package.json | 4 +-- app/api/src/lib/discord.ts | 33 +++++++++++++++++++++++ app/api/src/services/projects/projects.ts | 16 ++++++++++- app/redwood.toml | 2 +- app/yarn.lock | 12 +++++++++ 7 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 app/api/src/lib/discord.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 014721a..3c4223a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ Install dependencies yarn install ``` -Setting up the db, you'll need to have a postgres installed locally, you can [follow this guide](https://redwoodjs.com/docs/local-postgres-setup). +Setting up the db, you'll need to have a postgres installed locally, you can [follow this guide](https://redwoodjs.com/docs/local-postgres-setup). Run the following (Note: these commands require the `DATABASE_URL` env variable to be set. if you see no result when you run `echo $DATABASE_URL`, you can set it with a command like `export DATABASE_URL=postgres://postgres:somepassword@localhost`) ``` terminal @@ -59,6 +59,27 @@ localUser2@kurthutten.com: `abc123` localAdmin@kurthutten.com: `abc123` +### Discord bot setup + +To set up the discord bot to notify when users publish new content, we're using the [REST](https://discord.com/developers/docs/resources/channel#message-object) API directly, used more as a notification service rather than a bot since we are not listening to messages in the chat. + +1. If you're setting up the bot in a dev environment, create a new discord server (the "plus" button on the left when logged into the Discord webpage). Make note of the name of the project. +2. With [developer mode turned on](https://www.howtogeek.com/714348/how-to-enable-or-disable-developer-mode-on-discord/), right click the channel you wish the bot to announce on and select "Copy ID". Add this to `.env.defaults` as `DISCORD_CHANNEL_ID`. +3. [create a new application](https://discord.com/developers/applications), or navigate to an existing one. +4. Create a bot within that application. Copy the bot token and add it to `.env.defaults` as `DISCORD_TOKEN`. +5. Go to the "URL Generator" under "OAuth2" and create a URL with scope "bot" and text permission "Send Messages". +6. Copy the generated URL and open it in a new tab. Follow the instructions on the page to add the bot to your discord server. + +When you next start CADHub, you should see in the logs `Discord: logged in as ` and you should see a startup message from the bot in the channel. + +To send messages as the bot when things happen in the service, use the `sendChat` helper function: + +```typescript +import { sendDiscordMessage } from 'src/lib/discord' + +sendDiscordMessage("hello world!") +``` + ## Designs In progress, though can be [seen on Figma](https://www.figma.com/file/VUh53RdncjZ7NuFYj0RGB9/CadHub?node-id=0%3A1) diff --git a/app/.env.defaults b/app/.env.defaults index f698efd..c6799db 100644 --- a/app/.env.defaults +++ b/app/.env.defaults @@ -18,9 +18,9 @@ CLOUDINARY_API_KEY=476712943135152 # trace | info | debug | warn | error | silent # LOG_LEVEL=debug - # EMAIL_PASSWORD=abc123 - +# DISCORD_TOKEN=abc123 +# DISCORD_CHANNEL_ID=12345 # CAD_LAMBDA_BASE_URL="http://localhost:8080" diff --git a/app/api/package.json b/app/api/package.json index 55d32ad..d374c6b 100644 --- a/app/api/package.json +++ b/app/api/package.json @@ -6,7 +6,7 @@ "@redwoodjs/api": "^0.38.1", "@redwoodjs/graphql-server": "^0.38.1", "@sentry/node": "^6.5.1", - "axios": "^0.21.1", + "axios": "^0.25.0", "cloudinary": "^1.23.0", "cors": "^2.8.5", "express": "^4.17.1", @@ -21,4 +21,4 @@ "concurrently": "^6.0.0", "nodemon": "^2.0.7" } -} \ No newline at end of file +} diff --git a/app/api/src/lib/discord.ts b/app/api/src/lib/discord.ts new file mode 100644 index 0000000..bdd39a9 --- /dev/null +++ b/app/api/src/lib/discord.ts @@ -0,0 +1,33 @@ +import axios from 'axios' + +let inst = null; +if (!process.env.DISCORD_TOKEN || !process.env.DISCORD_CHANNEL_ID) { + console.warn("Discord bot not configured - please set process.env.DISCORD_TOKEN and process.env.DISCORD_CHANNEL_ID to send discord chats"); +} else { + inst = axios.create({ + baseURL: 'https://discord.com/api' + }); + inst.defaults.headers.common['Authorization'] = `Bot ${process.env.DISCORD_TOKEN}` + console.log(`Discord: using API token ${process.env.DISCORD_TOKEN}`); +} + +export async function sendDiscordMessage(text: string, url?: string) { + if (!inst) { + console.error(`Discord: not configured to send message ("${text}")`); + } else { + const API_URL = `/channels/${process.env.DISCORD_CHANNEL_ID}/messages`; + if (url) { + return inst.post(API_URL, { embeds: [{ + title: text, + image: { + url, + }, + }] }); + } else { + return inst.post(API_URL, { + content: text, + }); + } + } +} + diff --git a/app/api/src/services/projects/projects.ts b/app/api/src/services/projects/projects.ts index 200e40b..2660a67 100644 --- a/app/api/src/services/projects/projects.ts +++ b/app/api/src/services/projects/projects.ts @@ -12,6 +12,8 @@ import { } from 'src/services/helpers' import { requireAuth } from 'src/lib/auth' import { requireOwnership, requireProjectOwnership } from 'src/lib/owner' +import { sendDiscordMessage } from 'src/lib/discord' + export const projects = ({ userName }) => { if (!userName) { @@ -243,7 +245,19 @@ export const updateProjectImages = async ({ const [updatedProject] = await Promise.all([ projectPromise, imageDestroyPromise, - ]) + ]).then(async (result) => { + const { userName } = await db.user.findUnique({ + where: { id: project.userId }, + }) + sendDiscordMessage([ + `${userName} just added an image to their ${project.cadPackage} project:`, + ` => ${project.title}`, + ``, + `Check it out, leave a comment, make them feel welcome!`, + `https://cadhub.xyz/u/${userName}/${project.title}` + ].join('\n'), `https://res.cloudinary.com/irevdev/image/upload/c_scale,w_700/v1/${mainImage}`) + return result + }) return updatedProject } diff --git a/app/redwood.toml b/app/redwood.toml index ba5c9a4..4177995 100644 --- a/app/redwood.toml +++ b/app/redwood.toml @@ -17,7 +17,7 @@ 'SENTRY_AUTH_TOKEN', 'SENTRY_ORG', 'SENTRY_PROJECT', - 'EMAIL_PASSWORD' + 'EMAIL_PASSWORD', ] # experimentalFastRefresh = true # this seems to break cascadeStudio [api] diff --git a/app/yarn.lock b/app/yarn.lock index c4f9b3e..19cd422 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -5300,6 +5300,13 @@ axios@^0.21.1: dependencies: follow-redirects "^1.14.0" +axios@^0.25.0: + version "0.25.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.25.0.tgz#349cfbb31331a9b4453190791760a8d35b093e0a" + integrity sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g== + dependencies: + follow-redirects "^1.14.7" + axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" @@ -9205,6 +9212,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.4.tgz#838fdf48a8bbdd79e52ee51fb1c94e3ed98b9379" integrity sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g== +follow-redirects@^1.14.7: + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"