diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9898803 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +dist +package +.DS_Store +.slack/apps.dev.json +.idea +.slack* +node_modules +tests +keys \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..399e41d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2022 Slack Technologies, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..76fb77d --- /dev/null +++ b/README.md @@ -0,0 +1,238 @@ +# Welcome Bot + +This sample automation creates, stores, and sends friendly welcome messages when +users join a channel. + +**Guide Outline**: + +- [Included Workflows](#included-workflows) +- [Understanding Welcome Bot](#understanding-welcome-bot) +- [Setup](#setup) + - [Install the Slack CLI](#install-the-slack-cli) + - [Clone the Sample App](#clone-the-sample-app) +- [Creating Triggers](#creating-triggers) +- [Datastores](#datastores) +- [Testing](#testing) +- [Deploying Your App](#deploying-your-app) +- [Viewing Activity Logs](#viewing-activity-logs) +- [Project Structure](#project-structure) +- [Resources](#resources) + +--- + +## Included Workflows + +- **Welcome Message Setup**: Create and store a welcome message for a specified + channel +- **Send Welcome Message**: Retrieve a stored message and send it when a new + user joins the channel + +## Understanding Welcome Bot + +When working with this app it helps to think about it as two separate series of +steps. + +Welcome bot flow diagram + +### Creating and storing messages + +- A link trigger starts the `MessageSetupWorkflow` workflow. +- The `MessageSetupWorkflow` workflow has three steps, steps are the action + components of a workflow. + 1. The `OpenForm` Slack function that opens a form. + 2. The `SendEphemeralMessage` Slack function that sends a confirmation + message. + 3. Passes data to the `WelcomeMessageSetupFunction` custom function. +- When the form is submitted, the `WelcomeMessageSetupFunction` function saves + the message to the datastore and creates an event trigger to listen in on + `user_joined_channel` events in the specified channel. + +### Sending messages + +- The `user_joined_channel` event trigger starts the + `RenameChannelWorkflow` workflow. +- The `RenameChannelWorkflow` workflow has one additional step: + 1. Pass data to the `RenameChannelFunction` custom function. +- The `RenameChannelFunction` function retrieves the saved message and + sends it to the selected channel. + +**⚠️ Note: In order for this automation to send welcome messages, please make +sure to invite your app to the channel(s) where you are configuring the messages +once it has been [installed to your workspace](#running-your-project-locally).** + +## Setup + +Before getting started, first make sure you have a development workspace where +you have permission to install apps. **Please note that the features in this +project require that the workspace be part of +[a Slack paid plan](https://slack.com/pricing).** + +### Install the Slack CLI + +To use this template, you need to install and configure the Slack CLI. +Step-by-step instructions can be found in our +[Quickstart Guide](https://api.slack.com/automation/quickstart). + +### Clone the Sample App + +Start by cloning this repository: + +```zsh +# Clone this project onto your machine +$ slack create my-app -t slack-samples/deno-welcome-bot + +# Change into the project directory +$ cd my-app +``` + +## Running Your Project Locally + +While building your app, you can see your changes appear in your workspace in +real-time with `slack run`. You'll know an app is the development version if the +name has the string `(local)` appended. + +```zsh +# Run app locally +$ slack run + +Connected, awaiting events +``` + +To stop running locally, press ` + C` to end the process. + +## Creating Triggers + +[Triggers](https://api.slack.com/automation/triggers) are what cause workflows +to run. These triggers can be invoked by a user, or automatically as a response +to an event within Slack. + +When you `run` or `deploy` your project for the first time, the CLI will prompt +you to create a trigger if one is found in the `triggers/` directory. For any +subsequent triggers added to the application, each must be +[manually added using the `trigger create` command](#manual-trigger-creation). + +When creating triggers, you must select the workspace and environment that you'd +like to create the trigger in. Each workspace can have a local development +version (denoted by `(local)`), as well as a deployed version. _Triggers created +in a local environment will only be available to use when running the +application locally._ + +### Link Triggers + +A [link trigger](https://api.slack.com/automation/triggers/link) is a type of +trigger that generates a **Shortcut URL** which, when posted in a channel or +added as a bookmark, becomes a link. When clicked, the link trigger will run the +associated workflow. + +Link triggers are _unique to each installed version of your app_. This means +that Shortcut URLs will be different across each workspace, as well as between +[locally run](#running-your-project-locally) and +[deployed apps](#deploying-your-app). + +With link triggers, after selecting a workspace and environment, the output +provided will include a Shortcut URL. Copy and paste this URL into a channel as +a message, or add it as a bookmark in a channel of the workspace you selected. +Interacting with this link will run the associated workflow. + +**Note: triggers won't run the workflow unless the app is either running locally +or deployed!** + +### Manual Trigger Creation + +To manually create a trigger, use the following command: + +```zsh +$ slack trigger create --trigger-def triggers/welcome_message_trigger.ts +``` + +## Datastores + +For storing data related to your app, datastores offer secure storage on Slack +infrastructure. For an example of a datastore, see +`datastores/welcome_message_db.ts`. The use of a datastore requires the +`datastore:write`/`datastore:read` scopes to be present in your manifest. + +## Testing + +Test filenames should be suffixed with `_test`. + +Run all tests with `deno test`: + +```zsh +$ deno test +``` + +## Deploying Your App + +Once development is complete, deploy the app to Slack infrastructure using +`slack deploy`: + +```zsh +$ slack deploy +``` + +When deploying for the first time, you'll be prompted to +[create a new link trigger](#creating-triggers) for the deployed version of your +app. When that trigger is invoked, the workflow should run just as it did when +developing locally (but without requiring your server to be running). + +## Viewing Activity Logs + +Activity logs of your application can be viewed live and as they occur with the +following command: + +```zsh +$ slack activity --tail +``` + +## Project Structure + +### `.slack/` + +Contains `apps.dev.json` and `apps.json`, which include installation details for +development and deployed apps. + +### `datastores/` + +[Datastores](https://api.slack.com/automation/datastores) securely store data +for your application on Slack infrastructure. Required scopes to use datastores +include `datastore:write` and `datastore:read`. + +### `functions/` + +[Functions](https://api.slack.com/automation/functions) are reusable building +blocks of automation that accept inputs, perform calculations, and provide +outputs. Functions can be used independently or as steps in workflows. + +### `triggers/` + +[Triggers](https://api.slack.com/automation/triggers) determine when workflows +are run. A trigger file describes the scenario in which a workflow should be +run, such as a user pressing a button or when a specific event occurs. + +### `workflows/` + +A [workflow](https://api.slack.com/automation/workflows) is a set of steps +(functions) that are executed in order. + +Workflows can be configured to run without user input or they can collect input +by beginning with a [form](https://api.slack.com/automation/forms) before +continuing to the next step. + +### `manifest.ts` + +The [app manifest](https://api.slack.com/automation/manifest) contains the app's +configuration. This file defines attributes like app name and description. + +### `slack.json` + +Used by the CLI to interact with the project's SDK dependencies. It contains +script hooks that are executed by the CLI and implemented by the SDK. + +## Resources + +To learn more about developing automations on Slack, visit the following: + +- [Automation Overview](https://api.slack.com/automation) +- [CLI Quick Reference](https://api.slack.com/automation/cli/quick-reference) +- [Samples and Templates](https://api.slack.com/automation/samples) diff --git a/assets/default_new_app_icon.png b/assets/default_new_app_icon.png new file mode 100644 index 0000000..812445a Binary files /dev/null and b/assets/default_new_app_icon.png differ diff --git a/assets/deno-welcome-bot-flow.png b/assets/deno-welcome-bot-flow.png new file mode 100644 index 0000000..96d4b3f Binary files /dev/null and b/assets/deno-welcome-bot-flow.png differ diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..c52cc99 --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,19 @@ +{ + "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json", + "fmt": { + "files": { + "include": ["README.md", "datastores", "external_auth", "functions", "manifest.ts", "triggers", "types", "views", "workflows"] + } + }, + "importMap": "import_map.json", + "lint": { + "files": { + "include": ["datastores", "external_auth", "functions", "manifest.ts", "triggers", "types", "views", "workflows"] + } + }, + "lock": false, + "tasks": { + "test": "deno fmt --check && deno lint && deno test --allow-read --allow-none" + }, + "lock": false +} diff --git a/functions/rename_channel.ts b/functions/rename_channel.ts new file mode 100644 index 0000000..63a3d67 --- /dev/null +++ b/functions/rename_channel.ts @@ -0,0 +1,26 @@ +// deno-lint-ignore-file +import { ChannelType } from "../helpers/types.ts"; +import { renameChannel } from "../services/rename_channels.ts"; +import { DefineFunction, SlackFunction } from "deno-slack-sdk/mod.ts"; +import { getRecentlyAdded } from "../services/recently_added.ts"; + +export const RenameChannelFunction = DefineFunction({ + callback_id: "rename_channel_function", + title: "Rename Channel", + description: "Renames a channel according to Tikal's naming conventions", + source_file: "functions/rename_channel.ts", +}); + +export default SlackFunction(RenameChannelFunction, async ( + { client }, +) => { + const recentlyAdded = await getRecentlyAdded(client); + + recentlyAdded.forEach( + (channel: ChannelType) => renameChannel(channel, client) + ); + + return { + outputs: {}, + }; +}); diff --git a/functions/sync_google_groups.ts b/functions/sync_google_groups.ts new file mode 100644 index 0000000..636728d --- /dev/null +++ b/functions/sync_google_groups.ts @@ -0,0 +1,20 @@ +// deno-lint-ignore-file +import { syncGoogleGroups } from "../services/sync_google_groups.ts"; +import { DefineFunction, SlackFunction } from "deno-slack-sdk/mod.ts"; + +export const SyncGoogleGroupsFunction = DefineFunction({ + callback_id: "sync_google_groups_function", + title: "Sync Google Groups", + description: "Renames a channel according to Tikal's naming conventions", + source_file: "functions/sync_google_groups.ts", +}); + +export default SlackFunction(SyncGoogleGroupsFunction, async ( + { client }, +) => { + console.log("syncGoogleGroups call") + await syncGoogleGroups(client) + return { + outputs: {}, + }; +}); diff --git a/helpers/index.ts b/helpers/index.ts new file mode 100644 index 0000000..7d8a203 --- /dev/null +++ b/helpers/index.ts @@ -0,0 +1,33 @@ +// deno-lint-ignore-file +import { allowedPrefixes } from "./prefixes.ts"; +import { allowedPrefixesType } from "./types.ts"; + +export const isLessThanHalfAMinuteAgo = (channel: { created: number; }) => { + const timestamp = channel.created + const currentTimestamp = Math.floor(Date.now() / 1000); + const differenceInSeconds = currentTimestamp - timestamp; + return differenceInSeconds < 30; +} + +export const getRenameMessageText = ( + userRealName: string, + channelName: string, + allowed: string[], + channelType: allowedPrefixesType, +) => { + const prefList = + (allowed.length > 1 ? " one of the following prefixes: " : "") + + allowed.map((pref) => `"${pref}-"`).join(); + return `Dear ${userRealName}, \n +Your channel "${channelName}" was archived as ${channelType} channel names must start with a *${prefList} prefix*. \n +A new channel was created: *${allowed[0]}-${channelName}*. \n +Kind regards,\n +Tikal Slack Bot +`; +}; + +export const isLegalChannelName = (channelName: string, channelType: allowedPrefixesType) => (allowedPrefixes[channelType] as any[]).reduce( + (accumulator: boolean, prefix: allowedPrefixesType) => accumulator || channelName.startsWith(prefix), + false +); + diff --git a/helpers/prefixes.ts b/helpers/prefixes.ts new file mode 100644 index 0000000..dd1a8ac --- /dev/null +++ b/helpers/prefixes.ts @@ -0,0 +1,49 @@ + +export const allowedPrefixes = { + public: ["tikal"], + private: ["biz","backend-bp","backend-biz","devops-bp", + "devops-biz", + "frontend-bp", + "frontend-biz", + "mobile-bp", + "mobile-biz", + "sb", + "strategy", + "strategy-bp", + "backend-roadmaps", + "devops-roadmaps", + "eng-academy", + "eng-architecture", + "eng-innovation-lab", + "eng-roadmaps", + "frontend-roadmaps", + "mobile-roadmaps", + "finance", + "legal", + "infra", + "mrkt", + "mrkt-campaigns", + "mrkt-websites", + "backend-collab", + "backend-gl", + "devops-collab", + "devops-gl", + "frontend-collab", + "frontend-gl", + "gl", + "groups-bud", + "mobile-collab", + "mobile-gl", + "people", + "people-collab", + "people-exp", + "recrt", + "womens-in-tikal", + "backend-techradar", + "devops-techradar", + "frontend-techradar", + "mobile-techradar", + "prd-techradar", + "sales" +] +} \ No newline at end of file diff --git a/helpers/types.ts b/helpers/types.ts new file mode 100644 index 0000000..09c6880 --- /dev/null +++ b/helpers/types.ts @@ -0,0 +1,13 @@ +// deno-lint-ignore-file +import { allowedPrefixes } from "./prefixes.ts"; + +export type ChannelType = { + id?: any; + name?: any; + creator?: any; + is_channel: any; + is_private: any; + is_archived: any; +}; + +export type allowedPrefixesType = keyof typeof allowedPrefixes; diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..12819b8 --- /dev/null +++ b/import_map.json @@ -0,0 +1,6 @@ +{ + "imports": { + "deno-slack-sdk/": "https://deno.land/x/deno_slack_sdk@2.2.0/", + "deno-slack-api/": "https://deno.land/x/deno_slack_api@2.1.1/" + } +} diff --git a/manifest.ts b/manifest.ts new file mode 100644 index 0000000..bbefdd8 --- /dev/null +++ b/manifest.ts @@ -0,0 +1,36 @@ +import { Manifest } from "deno-slack-sdk/mod.ts"; +import { RenameChannelWorkflow } from "./workflows/rename_channel.ts"; +import { SyncGoogleGroupsWorkflow } from "./workflows/sync_google_groups.ts"; +import ArchiveCreateWorkflow from "./workflows/archive_create_channel.ts"; + +export default Manifest({ + name: "Tikal Bot", + description: "Keep channel naming conventions aligned", + icon: "assets/default_new_app_icon.png", + workflows: [ + RenameChannelWorkflow, + ArchiveCreateWorkflow, + SyncGoogleGroupsWorkflow + ], + outgoingDomains: [], + botScopes: [ + "chat:write", + "chat:write.public", + "channels:read", + "triggers:write", + "triggers:read", + "groups:read", + "groups:write.invites", + "im:read", + "mpim:read", + "mpim:write", + "mpim:write.invites", + "users:read", + "channels:manage", + "channels:join", + "channels:write.invites", + "groups:write", + "usergroups:write", + "im:write", + ], +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6cb77e1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,505 @@ +{ + "name": "welcome-bot-app", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@google-cloud/local-auth": "^2.1.0", + "googleapis": "^105.0.0" + } + }, + "node_modules/@google-cloud/local-auth": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/local-auth/-/local-auth-2.1.0.tgz", + "integrity": "sha512-ymZ1XuyKcRcro0aiMYz3hVGbZ+QZmN5V1Eyjvw2k1xqq76PwmDer0DIPxdxkLzfW9Inr8+g+MS9t9fZ7dOlTOQ==", + "dependencies": { + "arrify": "^2.0.1", + "google-auth-library": "^8.0.2", + "open": "^7.0.3", + "server-destroy": "^1.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis": { + "version": "105.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-105.0.0.tgz", + "integrity": "sha512-wH/jU/6QpqwsjTKj4vfKZz97ne7xT7BBbKwzQEwnbsG8iH9Seyw19P+AuLJcxNNrmgblwLqfr3LORg4Okat1BQ==", + "dependencies": { + "google-auth-library": "^8.0.2", + "googleapis-common": "^6.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-6.0.4.tgz", + "integrity": "sha512-m4ErxGE8unR1z0VajT6AYk3s6a9gIMM6EkDZfkPnES8joeOlEtFEJeF8IyZkb0tjPXkktUfYrE4b3Li1DNyOwA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^5.0.1", + "google-auth-library": "^8.0.2", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==" + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..fd85353 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@google-cloud/local-auth": "^2.1.0", + "googleapis": "^105.0.0" + } +} diff --git a/services/create_rename_trigger.ts b/services/create_rename_trigger.ts new file mode 100644 index 0000000..1a4ebac --- /dev/null +++ b/services/create_rename_trigger.ts @@ -0,0 +1,42 @@ +import { SlackAPIClient } from "https://deno.land/x/deno_slack_api@2.1.1/types.ts"; +import { TriggerTypes } from "deno-slack-api/mod.ts"; +import { ChannelType } from "../helpers/types.ts"; + +export const createRenameTrigger = async (channel: ChannelType, client: SlackAPIClient) => { + + const msSchedule = 1000 + const scheduledTrigger = await client.workflows.triggers.create({ + name: `Channel Archive Rename Schedule`, + workflow: "#/workflows/archive_create_channel", + type: TriggerTypes.Scheduled, + inputs: { + channel_to_archive_id: { value: channel.id }, + channel_to_create_name: { value: "tikal-" + channel.name }, + creator: { value: channel.creator }, + is_private: { value: channel.is_private }, + }, + schedule: { + // Starts 5 seconds after creation + start_time: new Date(new Date().getTime() + msSchedule).toISOString(), + timezone: "asia/jerusalem", + frequency: { + type: "once", + }, + }, + }); + + if (!scheduledTrigger.trigger) { + return { + error: "Trigger could not be created", + }; + } + + setTimeout(async () => { + console.log('Deleting trigger', scheduledTrigger.id); + await client.workflows.triggers.delete({ + trigger_id: scheduledTrigger.id + }); + }, 14000) // Need 15 seconds or less + + console.log("scheduledTrigger has been created"); +} \ No newline at end of file diff --git a/services/oauth.js b/services/oauth.js new file mode 100644 index 0000000..a0a71ef --- /dev/null +++ b/services/oauth.js @@ -0,0 +1,101 @@ +const fs = require('fs').promises; +const path = require('path'); +const process = require('process'); +const {authenticate} = require('@google-cloud/local-auth'); +const {google} = require('googleapis'); + +// If modifying these scopes, delete token.json. +const SCOPES = ['https://www.googleapis.com/auth/admin.directory.user', + 'https://www.googleapis.com/auth/admin.directory.group', + 'https://www.googleapis.com/auth/admin.directory.group.member', + 'https://apps-apis.google.com/a/feeds/groups/', + 'https://www.googleapis.com/auth/admin.directory.group.readonly', +]; +// The file token.json stores the user's access and refresh tokens, and is +// created automatically when the authorization flow completes for the first +// time. +const TOKEN_PATH = path.join(process.cwd(), 'token.json'); +const CREDENTIALS_PATH = path.join(process.cwd(), 'keys/oauth-test.json'); + +/** + * Reads previously authorized credentials from the save file. + * + * @return {Promise} + */ +async function loadSavedCredentialsIfExist() { + try { + const content = await fs.readFile("/Users/ofer/projects/tikal/slack/welcome-bot-app/tests/token.json"); + const credentials = JSON.parse(content); + return google.auth.fromJSON(credentials); + } catch (err) { + return null; + } +} + +/** + * Serializes credentials to a file comptible with GoogleAUth.fromJSON. + * + * @param {OAuth2Client} client + * @return {Promise} + */ +async function saveCredentials(client) { + const content = await fs.readFile(CREDENTIALS_PATH); + const keys = JSON.parse(content); + const key = keys.installed || keys.web; + const payload = JSON.stringify({ + type: 'authorized_user', + client_id: key.client_id, + client_secret: key.client_secret, + refresh_token: client.credentials.refresh_token, + }); + await fs.writeFile(TOKEN_PATH, payload); +} + +/** + * Load or request or authorization to call APIs. + * + */ +async function authorize() { + let client = await loadSavedCredentialsIfExist(); + if (client) { + return client; + } + client = await authenticate({ + scopes: SCOPES, + keyfilePath: CREDENTIALS_PATH, + }); + if (client.credentials) { + await saveCredentials(client); + } + return client; +} + +/** + * Lists the first 10 users in the domain. + * + * @param {google.auth.OAuth2} auth An authorized OAuth2 client. + */ +async function listUsers(auth) { + const service = google.admin({version: 'directory_v1', auth}); + const res2 = await service.users.list({ + customer: 'my_customer', + maxResults: 10, + orderBy: 'email', + groupKey: "00zu0gcz2zku8z0" + }); + const res = await service.groups.list({ domain: "tikalk.com" }); + const res1 = await service.members.list({ domain: "tikalk.com", groupKey: "00zu0gcz2zku8z0", memberKey: "*"}); + return console.log(res1.data) + const users = res.data.users; + if (!users || users.length === 0) { + console.log('No users found.'); + return; + } + + console.log('Users:'); + users.forEach((user) => { + console.log(`${user.primaryEmail} (${user.name.fullName})`); + }); +} + +authorize().then(listUsers).catch(console.error); \ No newline at end of file diff --git a/services/recently_added.ts b/services/recently_added.ts new file mode 100644 index 0000000..753a48a --- /dev/null +++ b/services/recently_added.ts @@ -0,0 +1,22 @@ +import { SlackAPIClient } from "https://deno.land/x/deno_slack_sdk@2.1.4/types.ts"; +import { isLessThanHalfAMinuteAgo } from "../helpers/index.ts"; + +export const getRecentlyAdded = async (client: SlackAPIClient) => { + let response; + let allChannels: any[] = []; + + // Get all channels + while (!response || response.response_metadata?.next_cursor) { + response = await client.conversations.list({ + cursor: response?.response_metadata?.next_cursor, + }); + allChannels = allChannels.concat(response.channels); + } + + + // Filter recent channels + const recent = allChannels.filter(isLessThanHalfAMinuteAgo); + + + return recent; +} diff --git a/services/rename_channels.ts b/services/rename_channels.ts new file mode 100644 index 0000000..b45095c --- /dev/null +++ b/services/rename_channels.ts @@ -0,0 +1,22 @@ +// deno-lint-ignore-file +import { alertUser } from "./send_message.ts"; +import { ChannelType } from "../helpers/types.ts"; +import { isLegalChannelName } from "../helpers/index.ts" +import { createRenameTrigger } from "./create_rename_trigger.ts"; +import { SlackAPIClient } from "https://deno.land/x/deno_slack_api@2.1.1/types.ts"; + +export const renameChannel = async ( + channel: ChannelType, + client: SlackAPIClient, +) => { + + if (!channel?.is_channel || channel?.is_archived) return; + + const typeOfChannel = channel.is_private ? "private" : "public" + + if (isLegalChannelName(channel.name, typeOfChannel)) return; + + createRenameTrigger(channel, client) + await alertUser(channel, typeOfChannel, client) + +}; diff --git a/services/send_message.ts b/services/send_message.ts new file mode 100644 index 0000000..cf2b096 --- /dev/null +++ b/services/send_message.ts @@ -0,0 +1,23 @@ +import { SlackAPIClient } from "https://deno.land/x/deno_slack_api@2.1.1/types.ts"; +import { getRenameMessageText } from "../helpers/index.ts" +import { allowedPrefixes } from "../helpers/prefixes.ts"; +import { ChannelType, allowedPrefixesType } from "../helpers/types.ts"; + + +export const alertUser = async (channel: ChannelType, channelType: allowedPrefixesType, client: SlackAPIClient) => { + + const info = await client.users.info({ + user: channel.creator + }); + + const text = getRenameMessageText( + info.user.real_name, + channel.name, + allowedPrefixes[channelType], + channelType, + ) + await client.chat.postMessage({ + channel: channel.creator, + text + }); +} \ No newline at end of file diff --git a/services/sync_google_groups.ts b/services/sync_google_groups.ts new file mode 100644 index 0000000..654bca9 --- /dev/null +++ b/services/sync_google_groups.ts @@ -0,0 +1,20 @@ +// deno-lint-ignore-file +import { SlackAPIClient } from "https://deno.land/x/deno_slack_api@2.1.1/types.ts"; + +export const syncGoogleGroups = async (client: SlackAPIClient) => { + console.log("syncGoogleGroups start") + + // Getting "permission denied" error result + const res = await client.usergroups.create({ + name: `Test User Group` + }) + + // Same "permission_denied" error for this + // const res = await client.usergroups.create({ + // name: `Test User Group`, + // handle: "test-user-group", + // team_id: "T02A1ARR8", + // channels: "C05R7NMK27R" + // }) + console.log("syncGoogleGroups result: ", res) +}; diff --git a/slack.json b/slack.json new file mode 100644 index 0000000..59a554b --- /dev/null +++ b/slack.json @@ -0,0 +1,5 @@ +{ + "hooks": { + "get-hooks": "deno run -q --allow-read --allow-net https://deno.land/x/deno_slack_hooks@1.1.0/mod.ts" + } +} diff --git a/test b/test new file mode 100644 index 0000000..e69de29 diff --git a/triggers/channel_created.ts b/triggers/channel_created.ts new file mode 100644 index 0000000..33d195a --- /dev/null +++ b/triggers/channel_created.ts @@ -0,0 +1,16 @@ +import { Trigger } from "deno-slack-api/types.ts"; +import { TriggerEventTypes, TriggerTypes } from "deno-slack-api/mod.ts"; +import { RenameChannelWorkflow } from "../workflows/rename_channel.ts"; + +const trigger: Trigger = { + type: TriggerTypes.Event, + name: "Channel Created", + description: "responds to a channel creation", + workflow: `#/workflows/${RenameChannelWorkflow.definition.callback_id}`, + event: { + event_type: TriggerEventTypes.ChannelCreated, + }, + inputs: {}, +}; + +export default trigger; diff --git a/triggers/channel_renamed.ts b/triggers/channel_renamed.ts new file mode 100644 index 0000000..a3f8f9f --- /dev/null +++ b/triggers/channel_renamed.ts @@ -0,0 +1,16 @@ +import { Trigger } from "deno-slack-api/types.ts"; +import { TriggerEventTypes, TriggerTypes } from "deno-slack-api/mod.ts"; +import { RenameChannelWorkflow } from "../workflows/rename_channel.ts"; + +const trigger: Trigger = { + type: TriggerTypes.Event, + name: "Channel Created", + description: "Responds to a channel renaming", + workflow: `#/workflows/${RenameChannelWorkflow.definition.callback_id}`, + event: { + event_type: TriggerEventTypes.ChannelRenamed, + }, + inputs: {}, +}; + +export default trigger; diff --git a/triggers/channel_unarchived.ts b/triggers/channel_unarchived.ts new file mode 100644 index 0000000..cc85ada --- /dev/null +++ b/triggers/channel_unarchived.ts @@ -0,0 +1,16 @@ +import { Trigger } from "deno-slack-api/types.ts"; +import { TriggerEventTypes, TriggerTypes } from "deno-slack-api/mod.ts"; +import { RenameChannelWorkflow } from "../workflows/rename_channel.ts"; + +const trigger: Trigger = { + type: TriggerTypes.Event, + name: "Channel Created", + description: "Responds to a channel unarchive", + workflow: `#/workflows/${RenameChannelWorkflow.definition.callback_id}`, + event: { + event_type: TriggerEventTypes.ChannelUnarchived, + }, + inputs: {}, +}; + +export default trigger; diff --git a/triggers/google_groups_sync.ts b/triggers/google_groups_sync.ts new file mode 100644 index 0000000..317973e --- /dev/null +++ b/triggers/google_groups_sync.ts @@ -0,0 +1,24 @@ +// triggers/daily_maintenance_job.ts +import { Trigger } from "deno-slack-sdk/types.ts"; +import { SyncGoogleGroupsWorkflow } from "../workflows/sync_google_groups.ts"; +import { TriggerTypes } from "deno-slack-api/mod.ts"; + +const msSchedule = 100 + +const trigger: Trigger = { + type: TriggerTypes.Scheduled, + name: "Periodical Google Groups Synce", + workflow: `#/workflows/${SyncGoogleGroupsWorkflow.definition.callback_id}`, + inputs: {}, + schedule: { + start_time: new Date(new Date().getTime() + msSchedule).toISOString(), + end_time: "2037-12-31T23:59:59Z", + timezone: "asia/jerusalem", + frequency: { + type: "hourly", + repeats_every: 1, + }, + }, +}; + +export default trigger; diff --git a/workflows/archive_create_channel.ts b/workflows/archive_create_channel.ts new file mode 100644 index 0000000..b9befc0 --- /dev/null +++ b/workflows/archive_create_channel.ts @@ -0,0 +1,45 @@ +import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts"; + +// Define a workflow that can pass the parameters for the Slack function +const ArchiveCreateWorkflow = DefineWorkflow({ + callback_id: "archive_create_channel", + title: "Channel Archiver and Creator", + input_parameters: { + properties: { + channel_to_archive_id: { type: Schema.types.string }, + channel_to_create_name: { type: Schema.types.string }, + creator: { type: Schema.types.string }, + is_private: { type: Schema.types.boolean }, + }, + required: [ + "channel_to_archive_id", + "channel_to_create_name", + "creator", + ], + }, +}); + +ArchiveCreateWorkflow.addStep( + Schema.slack.functions.ArchiveChannel, + { + channel_id: ArchiveCreateWorkflow.inputs.channel_to_archive_id, + }, +); + +const createChannel = ArchiveCreateWorkflow.addStep( + Schema.slack.functions.CreateChannel, + { + channel_name: ArchiveCreateWorkflow.inputs.channel_to_create_name, + is_private: ArchiveCreateWorkflow.inputs.is_private, + }, +); + +ArchiveCreateWorkflow.addStep( + Schema.slack.functions.InviteUserToChannel, + { + channel_ids: [createChannel.outputs.channel_id], + user_ids: [ArchiveCreateWorkflow.inputs.creator], + }, +); + +export default ArchiveCreateWorkflow; diff --git a/workflows/rename_channel.ts b/workflows/rename_channel.ts new file mode 100644 index 0000000..c503161 --- /dev/null +++ b/workflows/rename_channel.ts @@ -0,0 +1,14 @@ +import { DefineWorkflow } from "deno-slack-sdk/mod.ts"; +import { RenameChannelFunction } from "../functions/rename_channel.ts"; + +export const RenameChannelWorkflow = DefineWorkflow({ + callback_id: "rename_channel", + title: "Renames a channel", + description: "Renames a channel according to Tikal's naming conventions", + input_parameters: { + properties: {}, + required: [], + }, +}); + +RenameChannelWorkflow.addStep(RenameChannelFunction, {}); diff --git a/workflows/sync_google_groups.ts b/workflows/sync_google_groups.ts new file mode 100644 index 0000000..9659317 --- /dev/null +++ b/workflows/sync_google_groups.ts @@ -0,0 +1,14 @@ +import { DefineWorkflow } from "deno-slack-sdk/mod.ts"; +import { SyncGoogleGroupsFunction } from "../functions/sync_google_groups.ts"; + +export const SyncGoogleGroupsWorkflow = DefineWorkflow({ + callback_id: "sync_google_groups", + title: "Sync Google Groups", + description: "Sync google groups with slack groups", + input_parameters: { + properties: {}, + required: [], + }, +}); + +SyncGoogleGroupsWorkflow.addStep(SyncGoogleGroupsFunction, {});