diff --git a/README.md b/README.md index 3ff6659..d37efc4 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ _Every Slash command comes with a help message. For example, to learn to use `/s - ‡[`/airtable`](/src/interactions/airtable.js) Post the database links of a Slack user - ‡[`/address` `/leader-address`](src/interactions/address.js) Post the current user's address with a link to edit -- ‡[`/stats @USER`](/src/interactions/stats.js) Get tagged user's meeting stats -- ‡[`/stats #CHANNEL`](/src/interactions/stats.js) Get tagged channel's meeting stats -- ‡[`/get`](/src/interactions/get.js) See a list of available promotions +- **†** ‡[`/stats @USER`](/src/interactions/stats.js) Get tagged user's meeting stats +- **†** ‡[`/stats #CHANNEL`](/src/interactions/stats.js) Get tagged channel's meeting stats +- **†** ‡[`/get`](/src/interactions/get.js) See a list of available promotions - **†** ‡[`/get notion premium`](src/interactions/promos/notionPremium.js) Get a Premium Notion account - **†** ‡[`/get adafruit discount`](src/interactions/promos/adafruitDiscount.js) Get a discount code for Adafruit -- ‡`/report` File a misconduct report +- **†** ‡`/report` File a misconduct report - **†** ‡`/som-report` Report a Slack user's behavior - **†** ‡[`/promo`](src/interactions/promo.js) Renamed to `/get` @@ -47,7 +47,7 @@ _**†** = deprecated_ _This excludes Slack guests, such as multi-channel or single-channel users._ -- §[`/som-lookup`](src/interactions/som/lookup.js) Lookup who promoted a multi-channel guest to a full Slack user during the Summer of Making. +- **†** §[`/som-lookup`](src/interactions/som/lookup.js) Lookup who promoted a multi-channel guest to a full Slack user during the Summer of Making. - **†** §`/som-invite` Invite a multi-channel guest for the Summer of Making. - **†** §[`/club-init`](src/interactions/clubInit.js) Become a club leader by creating a club @@ -56,25 +56,25 @@ _**†** = deprecated_ #### Club leaders Δ -- Δ[`/club-address`](src/interactions/clubAddress.js) Post the current user's club address with a link to edit -- Δ[`/rename-channel`](src/interactions/rename.js) Rename your club channel -- Δ[`/slack-invite`](src/interactions/slack-invite.js) Get custom club Slack invite link & optionally invite an email to Slack -- Δ[`/moderate`](src/interactions/moderate.js) Use this command to moderate your club's community channel, first run `/moderate` to link the channel and then `/moderate ` to delete an inappropriate message. WIP. -- Δ[`/meeting-add`](src/interactions/meetingAdd.js) Add a meeting to your club's stats -- Δ[`/meeting-remove`](src/interactions/meetingRemove.js) Remove a mis-recorded meeting -- Δ[`/meeting-list`](src/interactions/meetingList.js) Get a list of club meetings (useful for /meeting-remove) -- Δ[`/meeting-stats` `/stats`](src/interactions/stats.js) Get current user's meeting stats - - ‡[`/stats @USER`](src/interactions/stats.js) Get tagged user's meeting stats - - ‡[`/stats #CHANNEL`](src/interactions/stats.js) Get tagged channel's meeting stats -- Δ[`/orpheus-tutorial` `/meeting-tutorial`](src/interactions/tutorial.js) +- **†** Δ[`/club-address`](src/interactions/clubAddress.js) Post the current user's club address with a link to edit +- **†** Δ[`/rename-channel`](src/interactions/rename.js) Rename your club channel +- **†** Δ[`/slack-invite`](src/interactions/slack-invite.js) Get custom club Slack invite link & optionally invite an email to Slack +- **†** Δ[`/moderate`](src/interactions/moderate.js) Use this command to moderate your club's community channel, first run `/moderate` to link the channel and then `/moderate ` to delete an inappropriate message. WIP. +- **†** Δ[`/meeting-add`](src/interactions/meetingAdd.js) Add a meeting to your club's stats +- **†** Δ[`/meeting-remove`](src/interactions/meetingRemove.js) Remove a mis-recorded meeting +- **†** Δ[`/meeting-list`](src/interactions/meetingList.js) Get a list of club meetings (useful for /meeting-remove) +- **†** Δ[`/meeting-stats` `/stats`](src/interactions/stats.js) Get current user's meeting stats + - **†** ‡[`/stats @USER`](src/interactions/stats.js) Get tagged user's meeting stats + - **†** [`/stats #CHANNEL`](src/interactions/stats.js) Get tagged channel's meeting stats +- **†** Δ[`/orpheus-tutorial` `/meeting-tutorial`](src/interactions/tutorial.js) - Use `@orpheus forget` before running the command to restart the tutorial from scratch -- Δ[`/leader-add @USER`](src/interactions/leaderAdd.js) Add another Slack user as a leader for your club -- Δ[`/leader-list`](src/interactions/leaderList.js) Print out the Slack accounts of registered co-leads -- ‡[`/get`](src/interactions/get.js) See a list of available promotions +- **†** Δ[`/leader-add @USER`](src/interactions/leaderAdd.js) Add another Slack user as a leader for your club +- **†** Δ[`/leader-list`](src/interactions/leaderList.js) Print out the Slack accounts of registered co-leads +- **†** ‡[`/get`](src/interactions/get.js) See a list of available promotions - **†** Δ[`/get zoom pro`](src/interactions/promos/zoom.js) Upgrade to a Zoom Pro account. Deprecated in favor of https://github.com/hackclub/slash-z - **†** Δ[`/get hack pack`](src/interactions/promos/hackPack.js) Add club to list of Hack Pack approved clubs - - Δ[`/get sticker envelope`](src/interactions/promos/stickerEnvelope.js) Order a sticker envelope for yourself or another slack user - - Δ[`/get stickermule`](src/interactions/promos/stickermule.js) Request credit on StickerMule + - **†** Δ[`/get sticker envelope`](src/interactions/promos/stickerEnvelope.js) Order a sticker envelope for yourself or another slack user + - **†** Δ[`/get stickermule`](src/interactions/promos/stickermule.js) Request credit on StickerMule - **†** Δ[`/get github grant`](src/interactions/promos/githubGrant.js) Request a $100 grant for your club, paid by GitHub - **†** Δ[`/club-card`](src/interactions/clubCard.js) Issue a credit card number for your club - **†** Δ[`/meeting-time`](src/interactions/meetingTime.js) Set the meeting time to get meeting notifications weekly @@ -85,12 +85,12 @@ _**†** = deprecated_ #### Slack Owner/Admin ◊ -- ◊[`/announcement`](src/interactions/announcement.js) Send an announcement to all clubs in Airtable queued for announcements - - ◊[`/announcement address`](src/interactions/announcement.js) See a list of the enqueued clubs - - ◊[`/announcement status`](src/interactions/announcement.js) Get the number of successful messages sent / the total messages to send - - ◊[`/announcement send`](src/interactions/announcement.js) Start sending announcements to enqueued clubs - - ◊[`/announcement record`](src/interactions/announcement.js) Record a Slack message to the announcement buffer -- ◊[`/som-promote @USER`](src/interactions/som/promote.js) Promote a multi-channel guest to a full Slack user. +- **†** ◊[`/announcement`](src/interactions/announcement.js) Send an announcement to all clubs in Airtable queued for announcements + - **†** ◊[`/announcement address`](src/interactions/announcement.js) See a list of the enqueued clubs + - **†** ◊[`/announcement status`](src/interactions/announcement.js) Get the number of successful messages sent / the total messages to send + - **†** ◊[`/announcement send`](src/interactions/announcement.js) Start sending announcements to enqueued clubs + - **†** ◊[`/announcement record`](src/interactions/announcement.js) Record a Slack message to the announcement buffer +- **†** ◊[`/som-promote @USER`](src/interactions/som/promote.js) Promote a multi-channel guest to a full Slack user. - **†** ◊`/som-ban` Deactivate a Slack user _Δ = club leader only_ diff --git a/slack-manifest.yml b/slack-manifest.yml new file mode 100644 index 0000000..4846fd1 --- /dev/null +++ b/slack-manifest.yml @@ -0,0 +1,189 @@ +display_information: + name: orpheus + description: Howdy! + background_color: "#e42d42" +features: + bot_user: + display_name: orpheus + always_online: true + shortcuts: + - name: Test Flag post + type: message + callback_id: flag_comment + description: Flag this message for Community Team review + slash_commands: + - command: /address + url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + description: View/edit your address + should_escape: false + - command: /airtable + url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + description: Get user's Airtable record + usage_hint: (admin only) + should_escape: true + - command: /my-email + url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + description: Check what address is used by HQ + should_escape: false + # - command: /rename-channel + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Rename your club channel + # usage_hint: meme-academy + # should_escape: false + # - command: /meeting-list + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: See your club's record + # should_escape: false + # - command: /meeting-add + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Record a new meeting + # usage_hint: "[help]" + # should_escape: false + # - command: /meeting-time + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Shows future meeting dates + # usage_hint: "[help]" + # should_escape: false + # - command: /meeting-tutorial + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Learn how to use @orpheus + # should_escape: false + # - command: /leader-add + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Adds a leader to your club + # usage_hint: "@orpheus" + # should_escape: true + # - command: /leader-list + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: List all this channel's leaders + # should_escape: false + # - command: /orpheus-tutorial + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Learn how to use @orpheus + # should_escape: false + # - command: /announcement + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Send an announcement + # usage_hint: "[help]" + # should_escape: false + # - command: /club-address + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: View/edit your club's address + # should_escape: false + # - command: /meeting-remove + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Remove a meeting + # usage_hint: "[help]" + # should_escape: false + # - command: /get + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Get things from Hack Club + # usage_hint: "[help]" + # should_escape: true + # - command: /club-card + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Get your club's card nuber + # should_escape: false + # - command: /som-promote + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Promote SOM to full user + # should_escape: true + # - command: /som-lookup + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Lookup who promoted a guest + # should_escape: true + # - command: /som-ban + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Deactivates a user (admin only) + # should_escape: true + # - command: /club-init + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Starts a new club! + # should_escape: false + # - command: /meeting-stats + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: See club's meeting stats + # usage_hint: "[help]" + # should_escape: true + # - command: /stats + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Post club stats for user + # usage_hint: "[help]" + # should_escape: true + # - command: /slack-invite + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Get your club's custom Slack Invite link! + # should_escape: false + # - command: /moderate + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Use this command to moderate your club's community channel + # usage_hint: + # should_escape: false + # - command: /report + # url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + # description: Report misconduct + # should_escape: true +oauth_config: + redirect_urls: + - https://orpheus-bot-hackclub.herokuapp.com/oauth + scopes: + user: + - channels:history + - channels:write + - groups:history + - im:history + - mpim:history + - reactions:read + - users:read + - users:read.email + bot: + - calls:read + - calls:write + - channels:history + - channels:join + - channels:manage + - channels:read + - chat:write + - commands + - dnd:read + - emails:write + - files:read + - groups:history + - groups:read + - groups:write + - im:history + - im:read + - im:write + - mpim:history + - mpim:read + - mpim:write + - pins:write + - reactions:read + - reactions:write + - remote_files:read + - remote_files:share + - remote_files:write + - team:read + - users.profile:read + - users:read + - users:read.email + - users:write + - files:write +settings: + event_subscriptions: + request_url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + user_events: + - message.channels + bot_events: + - message.channels + - message.groups + - message.im + - message.mpim + - reaction_added + - user_change + interactivity: + is_enabled: true + request_url: https://orpheus-bot-hackclub.herokuapp.com/slack/receive + org_deploy_enabled: false + socket_mode_enabled: false + token_rotation_enabled: false diff --git a/src/index.js b/src/index.js index c8b9561..9ca5467 100644 --- a/src/index.js +++ b/src/index.js @@ -265,103 +265,104 @@ controller.on("slash_command", async (bot, message) => { try { switch (command) { - case "/som-promote": - await interactionSOMPromote(bot, message); - break; - case "/som-lookup": - await interactionSOMLookup(bot, message); - break; - // case '/som-report': - // await interactionSOMReport(bot, message) - // break - // case '/som-ban': - // await interactionSOMBan(bot, message) - // break - // case '/som-invite': - // await interactionSOMInvite(bot, message) - // break - case "/meeting-stats": - case "/stats": - await interactionStats(bot, message); - break; - - case "/announcement": - await interactionAnnouncement(bot, message); - break; case "/airtable": await interactionAirtable(bot, message); break; case "/address": - case "/leader-address": + // case "/leader-address": await interactionAddress(bot, message); break; - case "/club-address": - await interactionClubAddress(bot, message); - break; - - case "/club-card": - await interactionClubCard(bot, message); - break; - - case "/promo": - await interactionPromo(bot, message); - break; - - case "/get": - await interactionGet(bot, message); - break; - case "/my-email": await interactionEmail(bot, message); break; - case "/rename-channel": - await interactionRename(bot, message); - break; - - case "/moderate": - await interactionModerate(bot, message); - break; - - case "/slack-invite": - await interactionSlackInvite(bot, message); - break; - - case "/report": - await interactionReport(bot, message); - break; - - case "/meeting-time": - await interactionMeetingTime(bot, message); - break; - - case "/meeting-add": - await interactionMeetingAdd(bot, message); - break; - - case "/meeting-remove": - await interactionMeetingRemove(bot, message); - break; - - case "/meeting-list": - await interactionMeetingList(bot, message); - break; - - case "/orpheus-tutorial": - case "/meeting-tutorial": - await interactionTutorial(bot, message); - break; - - case "/leader-add": - await interactionLeaderAdd(bot, message); - break; - - case "/leader-list": - await interactionLeaderList(bot, message); - break; + // case "/som-promote": + // await interactionSOMPromote(bot, message); + // break; + // case "/som-lookup": + // await interactionSOMLookup(bot, message); + // break; + // case '/som-report': + // await interactionSOMReport(bot, message) + // break + // case '/som-ban': + // await interactionSOMBan(bot, message) + // break + // case '/som-invite': + // await interactionSOMInvite(bot, message) + // break + // case "/meeting-stats": + // case "/stats": + // await interactionStats(bot, message); + // break; + + // case "/announcement": + // await interactionAnnouncement(bot, message); + // break; + + // case "/club-address": + // await interactionClubAddress(bot, message); + // break; + + // case "/club-card": + // await interactionClubCard(bot, message); + // break; + + // case "/promo": + // await interactionPromo(bot, message); + // break; + + // case "/get": + // await interactionGet(bot, message); + // break; + + // case "/rename-channel": + // await interactionRename(bot, message); + // break; + + // case "/moderate": + // await interactionModerate(bot, message); + // break; + + // case "/slack-invite": + // await interactionSlackInvite(bot, message); + // break; + + // case "/report": + // await interactionReport(bot, message); + // break; + + // case "/meeting-time": + // await interactionMeetingTime(bot, message); + // break; + + // case "/meeting-add": + // await interactionMeetingAdd(bot, message); + // break; + + // case "/meeting-remove": + // await interactionMeetingRemove(bot, message); + // break; + + // case "/meeting-list": + // await interactionMeetingList(bot, message); + // break; + + // case "/orpheus-tutorial": + // case "/meeting-tutorial": + // await interactionTutorial(bot, message); + // break; + + // case "/leader-add": + // await interactionLeaderAdd(bot, message); + // break; + + // case "/leader-list": + // await interactionLeaderList(bot, message); + // break; default: bot.replyPrivateDelayed( diff --git a/src/interactions/promos/zoom.js b/src/interactions/promos/zoom.js deleted file mode 100644 index 5eb23a6..0000000 --- a/src/interactions/promos/zoom.js +++ /dev/null @@ -1,183 +0,0 @@ -import fetch from 'isomorphic-unfetch' - -import { getInfoForUser, airFind, airPatch, transcript } from '../../utils' - -export const names = ['Zoom Pro', 'zoom', 'zoompro'] -export const details = - 'Available to all clubs and anyone on the Slack' -export async function run(bot, message) { - const { user } = message - const { leader, club } = await getInfoForUser(user) - - await bot.replyPrivateDelayed( - message, - transcript('promos.zoom.discontinued') - ) - return - - if (!leader || !club) { - await bot.replyPrivateDelayed( - message, - transcript('promos.zoom.notAuthorized') - ) - return - } - - const pocAirtableID = club.fields['POC'] - if (leader.id != pocAirtableID) { - if (pocAirtableID) { - const poc = await airFind('People', `RECORD_ID() = '${pocAirtableID}'`) - const pocID = poc.fields['Slack ID'] - - await bot.replyPrivateDelayed( - message, - transcript('promos.zoom.notPOC.exists', { pocID }) - ) - } else { - await bot.replyPrivateDelayed( - message, - transcript('promos.zoom.notPOC.doesntExist') - ) - } - return - } - - // They're a leader & POC - // let's do the thing - - // Find or Init their Zoom account - let zoomID = leader.fields['Zoom ID'] || (await createZoomUser(leader)).id - if (!leader.fields['Zoom ID']) { - // no Zoom ID set in Airtable? We'll update their airRecord with the newly - // created Zoom account ID in the background - airPatch('People', leader.id, { 'Zoom ID': zoomID }) - bot.replyPrivateDelayed( - message, - transcript('promos.zoom.createdAccount', { - email: leader.fields['Email'], - }) - ) - } else { - // they already have a Zoom account– let's toggle the account type - const zoomAccount = await getZoomUser(zoomID) - if (!zoomAccount.type) { - // account is 'pending' activation– ask the user to check their email - bot.replyPrivateDelayed( - message, - transcript('promos.zoom.pendingAccount', { - email: leader.fields['Email'], - }) - ) - } else { - // toggle account type - bot.replyPrivateDelayed(message, transcript('promos.zoom.toggleAccount')) - } - } - - const zoomUsage = await getZoomUsage() - const { hosts, usage } = zoomUsage.plan_base - const BOT_SPAM_CHANNEL = 'C0P5NE354' - console.log('Zoom license usage is at', usage, '/', hosts) - if (hosts == usage) { - bot.say({ - channel: BOT_SPAM_CHANNEL, - text: transcript('promos.zoom.reachedLimit', { usage, user }), - }) - bot.say({ - channel: BOT_SPAM_CHANNEL, - text: transcript('promos.zoom.upgradeLink', { usage }), - }) - } else if (hosts < usage + 3) { - bot.say({ - channel: BOT_SPAM_CHANNEL, - text: transcript('promos.zoom.dangerZone', { usage, hosts, user }), - }) - bot.say({ text, channel: BOT_SPAM_CHANNEL }) - } else if (hosts < usage + 10) { - bot.say({ - channel: BOT_SPAM_CHANNEL, - text: transcript('promos.zoom.approachingLimit', { usage, hosts, user }), - }) - bot.say({ - channel: BOT_SPAM_CHANNEL, - text: transcript('promos.zoom.upgradeLink', { usage }), - }) - } -} - -// Zoom function stuff -import jwt from 'jsonwebtoken' -const payload = { - iss: process.env.TEMP_ZOOM_KEY, - exp: new Date().getTime() + 5000, -} -const token = jwt.sign(payload, process.env.TEMP_ZOOM_SECRET) - -async function getZoomUsage() { - const result = await fetch(`https://api.zoom.us/v2/accounts/me/plans/usage`, { - method: 'GET', - headers: { - Authorization: `Bearer ${token}`, - }, - }) - - return await result.json() -} - -async function getZoomUser(zoomID) { - const result = await fetch(`https://api.zoom.us/v2/users/${zoomID}`, { - method: 'GET', - headers: { - authorization: `Bearer ${token}`, - }, - }) - - return await result.json() -} - -async function createZoomUser(person) { - // Autofill name b/c 'first' & 'last' are required by Zoom, but not by us - const nameArray = person.fields['Full Name'].split(' ') - const first_name = nameArray[0] || 'Orpheus' - const last_name = - nameArray.slice(1, nameArray.length).reverse()[0] || 'Hacksworth' - const email = person.fields['Email'] - - const body = JSON.stringify({ - action: 'create', - user_info: { - type: 2, // Licensed - first_name, - last_name, - email, - }, - }) - - const result = await fetch('https://api.zoom.us/v2/users', { - method: 'POST', - headers: { - 'content-type': 'application/json', - authorization: `Bearer ${token}`, - }, - body, - }) - - return await result.json() -} - -async function updateZoomUser(zoomID, upOrDowngrade) { - const body = JSON.stringify({ - type: upOrDowngrade == 'upgrade' ? 2 : 1, - }) - - const result = await fetch(`https://api.zoom.us/v2/users/${zoomID}`, { - method: 'PATCH', - headers: { - 'content-type': 'application/json', - authorization: `Bearer ${token}`, - }, - body, - }) - - return await result.json() -}