chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:48 +08:00
commit 77bb5bf71f
3762 changed files with 353249 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
import rootConfig from '../../eslint.config.mjs'
export default [
...rootConfig,
{
languageOptions: {
parserOptions: {
project: ['./tsconfig.json'],
tsconfigRootDir: import.meta.dirname,
},
},
},
]
+36
View File
@@ -0,0 +1,36 @@
# Monday Integration
`@botpresshub/monday` is a Botpress integration that integrates with Monday.com, enabling your bots to create Monday.com items.
## Configuration
This integration connects to Monday.com with OAuth by default. You can also select manual configuration and provide a personal access token if you prefer to configure access manually.
### Monday
You will need to identify the Board IDs of the Monday.com boards you would like your bot to interact with.
#### OAuth
Use the authorization button in Botpress to connect your Monday.com account. During the OAuth flow, Monday will ask you to approve access for this integration.
#### Personal access token
If you prefer manual configuration, refer to the [Authentication Guide](https://developer.monday.com/api-reference/docs/authentication#get-your-token) in the Monday documentation to learn how to acquire your token.
#### Board ID
In order to find your Board ID, log into Monday and navigate to your Board. Then, inspect the URL in your address bar. See the following example:
```
https://<your-workspace-id>.monday.com/boards/9012345678
```
In the URL provided above, the Board ID would be `9012345678`. Keep this (and any other relevant board IDs) around for when configure actions using the Botpress integration.
### Botpress
1. Install the Monday integration in your Botpress bot.
2. Use the default OAuth configuration and click the authorization button to complete the OAuth flow.
3. To configure manually, select **Manual Configuration** and paste your Monday.com personal access token.
4. Save configuration.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="120" viewBox="0 0 120 120" version="1.1"><path d="M 87.183 66.829 C 85.439 68.458, 83.872 71.163, 83.489 73.207 C 83.032 75.643, 83.337 77.750, 84.460 79.923 C 85.360 81.664, 87.538 83.992, 89.299 85.097 C 91.630 86.559, 93.587 86.972, 96.499 86.614 C 99.475 86.249, 101.361 85.182, 103.867 82.448 C 106.922 79.114, 107.186 78.347, 106.716 74.169 C 106.319 70.653, 105.460 68.907, 103.083 66.782 C 100.578 64.545, 99.013 64, 95.090 64 C 91.115 64, 89.650 64.524, 87.183 66.829" stroke="none" fill="#00d647" fill-rule="evenodd"/><path d="M 38.301 32.772 C 37.091 33.723, 30.904 42.792, 24.551 52.926 C 15.023 68.126, 13 72.024, 13 75.191 C 13 78.128, 13.764 79.863, 16.251 82.576 C 18.642 85.186, 20.558 86.253, 23.501 86.614 C 26.385 86.968, 28.367 86.561, 30.609 85.153 C 32.641 83.877, 37.438 77.266, 44.459 66.065 C 50.367 56.640, 55.672 47.681, 56.247 46.156 C 56.823 44.630, 56.988 41.748, 56.613 39.751 C 56.189 37.490, 54.784 35.155, 52.889 33.560 C 50.561 31.601, 48.749 31.005, 45.173 31.022 C 42.290 31.036, 39.658 31.706, 38.301 32.772" stroke="none" fill="#fc3c54" fill-rule="evenodd"/><path d="M 74.301 32.198 C 73.092 32.782, 66.807 41.639, 60.335 51.880 C 49.562 68.925, 48.564 70.909, 48.533 75.332 C 48.504 79.540, 48.919 80.538, 51.742 83.061 C 53.526 84.654, 56.113 86.241, 57.492 86.587 C 58.872 86.934, 61.128 86.934, 62.508 86.587 C 63.887 86.241, 66.307 84.730, 67.886 83.229 C 69.465 81.728, 75.537 72.794, 81.379 63.374 C 90.495 48.676, 92 45.650, 92 42.024 C 92 38.554, 91.394 37.194, 88.600 34.400 C 85.739 31.539, 84.510 31.011, 80.850 31.068 C 78.457 31.105, 75.511 31.613, 74.301 32.198" stroke="none" fill="#fccc04" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,54 @@
import { IntegrationDefinition, z } from '@botpress/sdk'
import { configurationSchema, createItemSchema, manualConfigurationSchema } from 'src/misc/custom-schemas'
export default new IntegrationDefinition({
name: 'monday',
title: 'Monday',
description: 'Manage items in Monday boards.',
version: '1.1.5',
readme: 'hub.md',
icon: 'icon.svg',
states: {
oAuthCredentials: {
type: 'integration',
schema: z.object({
accessToken: z.string().secret().title('Access Token').describe('The Monday OAuth access token.'),
}),
},
},
actions: {
createItem: {
title: 'Create Item',
description: 'Create a new item.',
input: { schema: createItemSchema },
output: {
schema: z.object({}),
},
},
},
configuration: {
schema: configurationSchema,
identifier: {
linkTemplateScript: 'linkTemplate.vrl',
},
},
configurations: {
manual: {
title: 'Manual Configuration',
description: 'Configure with your Personal Access Token',
schema: manualConfigurationSchema,
},
},
secrets: {
CLIENT_ID: {
description: 'The client ID of the OAuth app.',
},
CLIENT_SECRET: {
description: 'The client secret of the OAuth app.',
},
},
attributes: {
category: 'Project Management',
repo: 'botpress',
},
})
+4
View File
@@ -0,0 +1,4 @@
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)
"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}"
+20
View File
@@ -0,0 +1,20 @@
{
"name": "@botpresshub/monday",
"scripts": {
"check:type": "tsc --noEmit",
"check:bplint": "bp lint",
"build": "bp add -y && bp build",
"test": "vitest --run"
},
"private": true,
"devDependencies": {
"@botpress/cli": "workspace:*",
"@botpress/sdk": "workspace:*"
},
"dependencies": {
"@botpress/client": "workspace:*",
"@botpress/common": "workspace:*",
"@botpress/sdk": "workspace:*",
"axios": "^1.4.0"
}
}
+14
View File
@@ -0,0 +1,14 @@
import { getMondayClient } from 'src/misc/auth'
import * as bp from '.botpress'
type CreateItem = bp.IntegrationProps['actions']['createItem']
export const createItem: CreateItem = async ({ input, ctx, client }) => {
const mondayClient = await getMondayClient({ client, ctx })
await mondayClient.createItem(input.boardId, {
name: input.itemName,
})
return {}
}
+34
View File
@@ -0,0 +1,34 @@
import { RuntimeError } from '@botpress/sdk'
import * as actions from 'src/actions'
import { getMondayClient } from 'src/misc/auth'
import { isOAuthWizardUrl, oauthWizardHandler } from './oauth-wizard'
import * as bp from '.botpress'
export default new bp.Integration({
register: async ({ client, ctx }) => {
try {
const mondayClient = await getMondayClient({ client, ctx })
await mondayClient.validateAccessToken()
await client.configureIntegration({
identifier: ctx.webhookId,
})
} catch (thrown) {
if (thrown instanceof RuntimeError) {
throw thrown
}
const message = thrown instanceof Error ? thrown.message : String(thrown)
throw new RuntimeError(`Failed to configure Monday integration. Please reconnect your account. (${message})`)
}
},
unregister: async () => {},
actions,
channels: {},
handler: async (props) => {
if (isOAuthWizardUrl(props.req.path)) {
return await oauthWizardHandler(props)
}
return { status: 404, body: 'Invalid endpoint' }
},
})
+57
View File
@@ -0,0 +1,57 @@
import { RuntimeError, isApiError } from '@botpress/sdk'
import { MondayClient } from './monday-client'
import * as bp from '.botpress'
type AuthProps = {
client: bp.Client
ctx: bp.Context
}
export const getOAuthAccessToken = async ({ client, ctx }: AuthProps) => {
try {
const { state } = await client.getState({
type: 'integration',
name: 'oAuthCredentials',
id: ctx.integrationId,
})
return state?.payload.accessToken || undefined
} catch (thrown) {
if (isApiError(thrown) && thrown.code === 404) {
return undefined
}
throw thrown
}
}
export const getMondayClient = async (props: AuthProps) => {
if (props.ctx.configurationType === 'manual') {
const { personalAccessToken } = props.ctx.configuration
if (!personalAccessToken) {
throw new RuntimeError('Monday credentials are missing. Please provide a personal access token.')
}
return MondayClient.create({ authorization: personalAccessToken })
}
let oAuthAccessToken: string | undefined
try {
oAuthAccessToken = await getOAuthAccessToken(props)
} catch (thrown) {
const message = thrown instanceof Error ? thrown.message : String(thrown)
throw new RuntimeError(`Failed to load Monday OAuth credentials. Please reconnect your account. (${message})`)
}
if (!oAuthAccessToken) {
throw new RuntimeError(
'Monday credentials are missing. Please connect your Monday account or provide a personal access token.'
)
}
return createOAuthMondayClient(oAuthAccessToken)
}
export const createOAuthMondayClient = (accessToken: string) =>
MondayClient.create({ authorization: `Bearer ${accessToken}` })
@@ -0,0 +1,20 @@
import { z } from '@botpress/sdk'
export const configurationSchema = z.object({})
export const manualConfigurationSchema = z.object({
personalAccessToken: z
.string()
.min(1)
.secret()
.title('Personal Access Token')
.describe('A Monday.com personal access token with sufficient access to manage items on your Monday.com boards.'),
})
export const createItemSchema = z.object({
boardId: z
.string()
.title('Board ID')
.describe("The board's unique identifier. The new item will be added to this board."),
itemName: z.string().min(1).title('Name').describe("The new item's name."),
})
@@ -0,0 +1,46 @@
const QUERY_INPUT = Symbol('graphqlInputType')
const QUERY_RESPONSE = Symbol('graphqlResponseType')
type GraphQLQuery<TInput, TResponse> = {
query: string
[QUERY_INPUT]: TInput
[QUERY_RESPONSE]: TResponse
}
export const GRAPHQL_QUERIES = {
validateAccessToken: {
query: `
query ValidateAccessToken {
boards(limit: 1) {
id
}
}`,
[QUERY_INPUT]: {} as Record<string, never>,
[QUERY_RESPONSE]: {} as {
boards: {
id: string
}[]
},
},
createItem: {
query: `
mutation CreateNewItem($boardId: ID!, $itemName: String!) {
create_item (board_id: $boardId, item_name: $itemName) {
id
}
}`,
[QUERY_INPUT]: {} as {
boardId: string
itemName: string
},
[QUERY_RESPONSE]: {} as {
create_item: {
id: string
}
},
},
} as const satisfies Record<string, GraphQLQuery<object, object>>
export type GRAPHQL_QUERIES = typeof GRAPHQL_QUERIES
export type QUERY_INPUT = typeof QUERY_INPUT
export type QUERY_RESPONSE = typeof QUERY_RESPONSE
@@ -0,0 +1,104 @@
import axios, { Axios } from 'axios'
import { GRAPHQL_QUERIES, QUERY_INPUT, QUERY_RESPONSE } from './graphql-queries'
export type MondayClientConfiguration = {
authorization: string
}
type MondayOAuthTokenResponse = {
access_token: string
}
export type MondayOAuthCredentials = {
accessToken: string
}
export type ExchangeCodeForTokensInput = {
clientId: string
clientSecret: string
redirectUri: string
code: string
}
export type CreateItemOptions = {
name: string
}
export type Item = {
id: string
name: string
}
export type ItemsPageResponse = {
items: Item[]
nextToken: string | undefined
}
export const exchangeCodeForTokens = async ({
clientId,
clientSecret,
redirectUri,
code,
}: ExchangeCodeForTokensInput): Promise<MondayOAuthCredentials> => {
const response = await axios.post<MondayOAuthTokenResponse>('https://auth.monday.com/oauth2/token', {
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
code,
})
return {
accessToken: response.data.access_token,
}
}
export class MondayClient {
private constructor(private readonly _client: Axios) {}
public static create(config: MondayClientConfiguration) {
const client = axios.create({
baseURL: 'https://api.monday.com/v2',
timeout: 10_000,
headers: {
Authorization: config.authorization,
'API-Version': '2023-07',
'Content-Type': 'application/json',
},
})
return new MondayClient(client)
}
private async _executeGraphqlQuery<K extends keyof GRAPHQL_QUERIES>(
queryName: K,
variables: GRAPHQL_QUERIES[K][QUERY_INPUT]
): Promise<GRAPHQL_QUERIES[K][QUERY_RESPONSE]> {
const response = await this._client.post('', {
query: GRAPHQL_QUERIES[queryName].query,
variables,
})
return response.data.data
}
public async validateAccessToken(): Promise<void> {
try {
const response = await this._executeGraphqlQuery('validateAccessToken', {})
if (!Array.isArray(response.boards)) {
throw new Error('Monday credentials validation returned an unexpected response.')
}
} catch (thrown) {
throw thrown instanceof Error ? thrown : new Error('Monday credentials validation failed.')
}
}
public async createItem(
boardId: string,
item: CreateItemOptions
): Promise<GRAPHQL_QUERIES['createItem'][QUERY_RESPONSE]> {
return await this._executeGraphqlQuery('createItem', {
boardId,
itemName: item.name,
})
}
}
@@ -0,0 +1,15 @@
import * as wizard from './wizard'
import * as bp from '.botpress'
export const oauthWizardHandler: bp.IntegrationProps['handler'] = async (props) => {
const { logger } = props
try {
return await wizard.handler(props)
} catch (thrown) {
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
logger.forBot().error(`OAuth wizard error: ${error.message}`)
return wizard.redirectToInterstitial(false, error.message)
}
}
export const isOAuthWizardUrl = wizard.isOAuthWizardUrl
@@ -0,0 +1,182 @@
import * as oauthWizard from '@botpress/common/src/oauth-wizard'
import { RuntimeError, type Response } from '@botpress/sdk'
import { createOAuthMondayClient } from 'src/misc/auth'
import { exchangeCodeForTokens } from 'src/misc/monday-client'
import * as bp from '.botpress'
const OAUTH_CONFIGURATION_ERROR_MESSAGE = 'Unable to complete the Monday OAuth setup. Please try again.'
const DISABLE_INTERSTITIAL_HEADER = { 'x-bp-disable-interstitial': 'true' } as const
const SCOPES = 'boards:read boards:write'
type WizardHandler = oauthWizard.WizardStepHandler<bp.HandlerProps>
const getMondayInstallUrl = () => {
const url = new URL('https://auth.monday.com/oauth2/authorize')
url.search = new URLSearchParams({
client_id: bp.secrets.CLIENT_ID,
response_type: 'install',
}).toString()
return url.toString()
}
export const handler = async (props: bp.HandlerProps) => {
const wizard = new oauthWizard.OAuthWizardBuilder(props)
.addStep({ id: 'start', handler: _startHandler })
.addStep({ id: 'confirm-install', handler: _confirmInstallHandler })
.addStep({ id: 'oauth-redirect', handler: _oauthRedirectHandler })
.addStep({ id: 'oauth-callback', handler: _oauthCallbackHandler })
.build()
return await wizard.handleRequest()
}
const _startHandler: WizardHandler = ({ ctx, responses }) => {
return responses.displayButtons({
pageTitle: 'Connect Monday.com',
htmlOrMarkdownPageContents:
'1. Click **Next step** to open the Monday.com install page in a new tab.\n' +
'2. Install the Botpress app in your Monday.com workspace.\n' +
'3. Return to this wizard and confirm the installation to start the OAuth connection.' +
`
<script>
function installMondayAppAndContinue() {
window.open(${JSON.stringify(getMondayInstallUrl())}, '_blank', 'noopener,noreferrer');
window.location.href = ${JSON.stringify(getWizardStepUrl('confirm-install', ctx).toString())};
}
</script>
`,
buttons: [
{
action: 'javascript',
label: 'Next step',
callFunction: 'installMondayAppAndContinue',
buttonType: 'primary',
},
],
})
}
const _confirmInstallHandler: WizardHandler = ({ responses }) => {
return responses.displayButtons({
pageTitle: 'Confirm Monday.com installation',
htmlOrMarkdownPageContents:
'Have you installed the Botpress app in your Monday.com workspace?\n\n' +
'Start OAuth only after the Monday app is installed in your workspace.',
buttons: [
{
action: 'navigate',
label: 'Yes, continue',
navigateToStep: 'oauth-redirect',
buttonType: 'primary',
},
{
action: 'navigate',
label: 'Go back',
navigateToStep: 'start',
buttonType: 'secondary',
},
],
})
}
const _oauthRedirectHandler: WizardHandler = async ({ ctx, responses }) => {
try {
const url = new URL('https://auth.monday.com/oauth2/authorize')
const params = new URLSearchParams({
client_id: bp.secrets.CLIENT_ID,
redirect_uri: getOAuthRedirectUri().toString(),
response_type: 'code',
scope: SCOPES,
state: ctx.webhookId,
force_install_if_needed: String(true),
})
url.search = params.toString()
return responses.redirectToExternalUrl(url.toString())
} catch (thrown) {
return responses.endWizard({
success: false,
errorMessage: _formatWizardError(thrown, OAUTH_CONFIGURATION_ERROR_MESSAGE),
})
}
}
const _oauthCallbackHandler: WizardHandler = async ({ ctx, client, query, responses, setIntegrationIdentifier }) => {
try {
const code = query.get('code')
const state = query.get('state')
if (!code) {
return responses.endWizard({ success: false, errorMessage: 'Missing OAuth code' })
}
if (state !== ctx.webhookId) {
return responses.endWizard({ success: false, errorMessage: 'Invalid OAuth state' })
}
const credentials = await _exchangeCodeForTokens({ code, redirectUri: getOAuthRedirectUri().toString() })
const mondayClient = createOAuthMondayClient(credentials.accessToken)
await mondayClient.validateAccessToken()
await client.setState({
type: 'integration',
name: 'oAuthCredentials',
id: ctx.integrationId,
payload: {
accessToken: credentials.accessToken,
},
})
setIntegrationIdentifier(ctx.webhookId)
return responses.endWizard({ success: true })
} catch (thrown) {
return responses.endWizard({
success: false,
errorMessage: _formatWizardError(thrown, OAUTH_CONFIGURATION_ERROR_MESSAGE),
})
}
}
const _formatWizardError = (thrown: unknown, fallbackMessage: string) => {
const message = thrown instanceof Error ? thrown.message : String(thrown)
return message ? `${fallbackMessage} (${message})` : fallbackMessage
}
const _exchangeCodeForTokens = async ({ code, redirectUri }: { code: string; redirectUri: string }) => {
try {
return await exchangeCodeForTokens({
clientId: bp.secrets.CLIENT_ID,
clientSecret: bp.secrets.CLIENT_SECRET,
redirectUri,
code,
})
} catch (thrown) {
const message = thrown instanceof Error ? thrown.message : String(thrown)
throw new RuntimeError(`Failed to exchange Monday OAuth code for tokens. ${message}`)
}
}
const getWizardStepUrl = (stepId: string, ctx?: { webhookId: string }) => oauthWizard.getWizardStepUrl(stepId, ctx)
const getOAuthRedirectUri = () => getWizardStepUrl('oauth-callback')
export const isOAuthWizardUrl = oauthWizard.isOAuthWizardUrl
const redirectToUrl = (url: URL): Response => ({
status: 303,
headers: {
...DISABLE_INTERSTITIAL_HEADER,
location: url.toString(),
},
})
export const redirectToInterstitial = (success: boolean, message?: string): Response => {
const url = new URL(
`${process.env.BP_WEBHOOK_URL?.replace('webhook', 'app')}/oauth/interstitial?success=${success}${
message ? `&errorMessage=${message}` : ''
}`
)
return redirectToUrl(url)
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"paths": { "*": ["./*"] },
"outDir": "dist"
},
"include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"]
}
+2
View File
@@ -0,0 +1,2 @@
import config from '../../vitest.config'
export default config