Files
2026-07-13 13:34:48 +08:00

409 lines
17 KiB
TypeScript

import { z, IntegrationDefinition } from '@botpress/sdk'
export default new IntegrationDefinition({
name: 'canny',
version: '0.2.3',
title: 'Canny',
description: 'Connect your Botpress bot to Canny for feature request management and customer feedback collection',
readme: 'hub.md',
icon: 'icon.svg',
configuration: {
schema: z.object({
apiKey: z.string().title('API Key').describe('Your Canny API key'),
}),
},
channels: {
posts: {
title: 'Posts',
description: 'Channel for Canny posts and comments',
messages: {
text: {
schema: z.object({
text: z.string(),
}),
},
image: {
schema: z.object({
imageUrl: z.string(),
caption: z.string().optional(),
}),
},
},
conversation: {
tags: {
cannyPostId: {
title: 'Post ID',
description: 'The Canny post ID',
},
cannyBoardId: {
title: 'Board ID',
description: 'The Canny board ID',
},
},
},
message: {
tags: {
cannyType: {
title: 'Type',
description: 'Whether this is a post or comment',
},
cannyPostId: {
title: 'Post ID',
description: 'The associated post ID',
},
cannyCommentId: {
title: 'Comment ID',
description: 'The comment ID (if applicable)',
},
},
},
},
},
actions: {
// Post actions
createPost: {
title: 'Create Post',
description: 'Create a new post in Canny',
input: {
schema: z.object({
authorId: z
.string()
.optional()
.title('Author Id')
.describe('The author Id (defaults to Botpress user if not provided)'),
boardId: z.string().title('Board Id').describe('The board Id'),
title: z.string().title('Post Title').describe('Post title'),
details: z.string().title('Post Details').describe('Post details'),
byId: z.string().optional().title('By Id').describe('Admin Id who created the post'),
categoryId: z.string().optional().title('Category Id').describe('Category Id'),
ownerId: z.string().optional().title('Owner Id').describe('Owner Id'),
imageURLs: z.array(z.string()).optional().title('Image URLs').describe('Image URLs'),
eta: z.string().optional().title('ETA').describe('ETA (MM/YYYY)'),
etaPublic: z.boolean().optional().title('ETA Public').describe('Make ETA public'),
customFields: z.record(z.any()).optional().title('Custom Fields').describe('Custom fields'),
}),
},
output: {
schema: z.object({
postId: z.string().title('Post ID').describe('The ID of the created post'),
}),
},
},
getPost: {
title: 'Get Post',
description: 'Retrieve a post by ID',
input: {
schema: z.object({
postId: z.string().title('Post Id').describe('The post Id'),
boardId: z.string().optional().title('Board Id').describe('The board Id'),
}),
},
output: {
schema: z.object({
post: z
.object({
id: z.string().title('ID').describe('Post ID'),
title: z.string().title('Title').describe('Post title'),
details: z.string().optional().title('Details').describe('Post details'),
authorName: z.string().optional().title('Author Name').describe('Name of the post author'),
authorEmail: z.string().optional().title('Author Email').describe('Email of the post author'),
boardName: z.string().title('Board Name').describe('Name of the board'),
status: z.string().title('Status').describe('Post status'),
score: z.number().title('Score').describe('Post score'),
commentCount: z.number().title('Comment Count').describe('Number of comments'),
created: z.string().title('Created').describe('Creation timestamp'),
url: z.string().title('URL').describe('Post URL'),
})
.title('Post')
.describe('The retrieved post object'),
}),
},
},
listPosts: {
title: 'List Posts',
description: 'List posts with optional filters',
input: {
schema: z.object({
boardId: z.string().optional().title('Board Id').describe('Filter posts by board Id'),
authorId: z.string().optional().title('Author Id').describe('Filter posts by author Id'),
companyId: z.string().optional().title('Company Id').describe('Filter posts by company Id'),
tagIds: z.array(z.string()).optional().title('Tag Ids').describe('Filter posts by tag Ids'),
limit: z.number().optional().title('Limit').describe('Number of posts to return'),
nextToken: z.number().optional().title('Next Token').describe('Token for pagination - skip this many posts'),
search: z.string().optional().title('Search').describe('Search term to filter posts'),
sort: z
.enum(['newest', 'oldest', 'relevance', 'score', 'statusChanged', 'trending'])
.optional()
.title('Sort')
.describe('Sort order for posts'),
status: z.string().optional().title('Status').describe('Filter posts by status'),
}),
},
output: {
schema: z.object({
posts: z
.array(
z.object({
id: z.string().title('ID').describe('Post ID'),
title: z.string().title('Title').describe('Post title'),
details: z.string().optional().title('Details').describe('Post details'),
authorName: z.string().optional().title('Author Name').describe('Name of the post author'),
authorEmail: z.string().optional().title('Author Email').describe('Email of the post author'),
boardName: z.string().title('Board Name').describe('Name of the board'),
status: z.string().title('Status').describe('Post status'),
score: z.number().title('Score').describe('Post score'),
commentCount: z.number().title('Comment Count').describe('Number of comments'),
created: z.string().title('Created').describe('Creation timestamp'),
url: z.string().title('URL').describe('Post URL'),
})
)
.title('Posts')
.describe('Array of posts'),
nextToken: z.number().optional().title('Next Token').describe('Token for next page if more posts available'),
}),
},
},
updatePost: {
title: 'Update Post',
description: 'Update an existing post',
input: {
schema: z.object({
postId: z.string().title('Post Id').describe('The post Id'),
title: z.string().optional().title('Title').describe('Updated post title'),
details: z.string().optional().title('Details').describe('Updated post details'),
eta: z.string().optional().title('ETA').describe('Updated ETA (MM/YYYY)'),
etaPublic: z.boolean().optional().title('ETA Public').describe('Make ETA public'),
imageURLs: z.array(z.string()).optional().title('Image URLs').describe('Updated image URLs'),
customFields: z.record(z.any()).optional().title('Custom Fields').describe('Updated custom fields'),
}),
},
output: {
schema: z.object({}),
},
},
deletePost: {
title: 'Delete Post',
description: 'Delete a post',
input: {
schema: z.object({
postId: z.string().title('Post Id').describe('The post Id to delete'),
}),
},
output: {
schema: z.object({}),
},
},
// Comment actions
createComment: {
title: 'Create Comment',
description: 'Create a new comment on a post',
input: {
schema: z.object({
authorId: z
.string()
.optional()
.title('Author Id')
.describe('The author Id (defaults to Botpress user if not provided)'),
postId: z.string().title('Post Id').describe('The post Id'),
value: z.string().title('Comment Text').describe('Comment text'),
parentId: z.string().optional().title('Parent Id').describe('Parent comment Id for replies'),
imageURLs: z.array(z.string()).optional().title('Image URLs').describe('Image URLs'),
internal: z.boolean().optional().title('Internal').describe('Whether this is an internal comment'),
shouldNotifyVoters: z.boolean().optional().title('Notify Voters').describe('Notify voters'),
createdAt: z.string().optional().title('Created At').describe('Created timestamp'),
}),
},
output: {
schema: z.object({
commentId: z.string().title('Comment ID').describe('The ID of the created comment'),
}),
},
},
getComment: {
title: 'Get Comment',
description: 'Retrieve a comment by ID',
input: {
schema: z.object({
commentId: z.string().title('Comment Id').describe('The comment Id'),
}),
},
output: {
schema: z.object({
comment: z
.object({
id: z.string().title('ID').describe('Comment ID'),
value: z.string().title('Value').describe('Comment text'),
authorName: z.string().title('Author Name').describe('Name of the comment author'),
authorEmail: z.string().title('Author Email').describe('Email of the comment author'),
postTitle: z.string().title('Post Title').describe('Title of the associated post'),
postId: z.string().title('Post ID').describe('ID of the associated post'),
created: z.string().title('Created').describe('Creation timestamp'),
internal: z.boolean().title('Internal').describe('Whether this is an internal comment'),
likeCount: z.number().title('Like Count').describe('Number of likes'),
})
.title('Comment')
.describe('The retrieved comment object'),
}),
},
},
listComments: {
title: 'List Comments',
description: 'List comments with optional filters',
input: {
schema: z.object({
postId: z.string().optional().title('Post Id').describe('Filter comments by post Id'),
authorId: z.string().optional().title('Author Id').describe('Filter comments by author Id'),
boardId: z.string().optional().title('Board Id').describe('Filter comments by board Id'),
companyId: z.string().optional().title('Company Id').describe('Filter comments by company Id'),
limit: z.number().optional().title('Limit').describe('Number of comments to return'),
nextToken: z
.number()
.optional()
.title('Next Token')
.describe('Token for pagination - skip this many comments'),
}),
},
output: {
schema: z.object({
comments: z
.array(
z.object({
id: z.string().title('ID').describe('Comment ID'),
value: z.string().title('Value').describe('Comment text'),
authorName: z.string().title('Author Name').describe('Name of the comment author'),
authorEmail: z.string().title('Author Email').describe('Email of the comment author'),
postTitle: z.string().title('Post Title').describe('Title of the associated post'),
postId: z.string().title('Post ID').describe('ID of the associated post'),
created: z.string().title('Created').describe('Creation timestamp'),
internal: z.boolean().title('Internal').describe('Whether this is an internal comment'),
likeCount: z.number().title('Like Count').describe('Number of likes'),
})
)
.title('Comments')
.describe('Array of comments'),
nextToken: z
.number()
.optional()
.title('Next Token')
.describe('Token for next page if more comments available'),
}),
},
},
deleteComment: {
title: 'Delete Comment',
description: 'Delete a comment',
input: {
schema: z.object({
commentId: z.string().title('Comment Id').describe('The comment Id to delete'),
}),
},
output: {
schema: z.object({}),
},
},
// User management actions
createOrUpdateUser: {
title: 'Create or Update User',
description: 'Create a new user or update an existing one in Canny',
input: {
schema: z.object({
name: z.string().min(1).max(50).title('Name').describe('User display name (required, 1-50 characters)'),
userId: z.string().optional().title('User Id').describe('Your internal user Id'),
email: z.string().optional().title('Email').describe('User email address'),
avatarURL: z.string().optional().title('Avatar URL').describe('User avatar URL'),
alias: z.string().optional().title('Alias').describe('User alias'),
created: z.string().optional().title('Created').describe('User creation timestamp'),
customFields: z.record(z.any()).optional().title('Custom Fields').describe('Custom fields'),
}),
},
output: {
schema: z.object({
user: z
.object({
id: z.string().title('ID').describe('User ID'),
name: z.string().title('Name').describe('User display name'),
email: z.string().title('Email').describe('User email address'),
avatarURL: z.string().optional().title('Avatar URL').describe('User avatar URL'),
userId: z.string().title('User ID').describe('Your internal user ID'),
isAdmin: z.boolean().title('Is Admin').describe('Whether the user is an admin'),
created: z.string().title('Created').describe('User creation timestamp'),
})
.title('User')
.describe('The created or updated user object'),
}),
},
},
listUsers: {
title: 'List Users',
description: 'List all users in your Canny workspace with pagination',
input: {
schema: z.object({
limit: z
.number()
.min(1)
.max(100)
.optional()
.title('Limit')
.describe('Number of users to fetch (1-100, defaults to 10)'),
nextToken: z.string().optional().title('Next Token').describe('Token for pagination from previous request'),
}),
},
output: {
schema: z.object({
users: z
.array(
z.object({
id: z.string().title('ID').describe('User ID'),
name: z.string().title('Name').describe('User display name'),
email: z.string().title('Email').describe('User email address'),
avatarURL: z.string().optional().title('Avatar URL').describe('User avatar URL'),
userId: z.string().title('User ID').describe('Your internal user ID'), // Can be empty string if user has no userId
isAdmin: z.boolean().title('Is Admin').describe('Whether the user is an admin'),
created: z.string().title('Created').describe('User creation timestamp'),
})
)
.title('Users')
.describe('Array of users'),
nextToken: z.string().optional().title('Next Token').describe('Token for next page if more users available'),
}),
},
},
// Board management actions
listBoards: {
title: 'List Boards',
description: 'List all boards in your Canny workspace',
input: {
schema: z.object({
limit: z.number().optional().title('Limit').describe('Number of boards to fetch'),
nextToken: z.number().optional().title('Next Token').describe('Token for pagination - skip this many boards'),
}),
},
output: {
schema: z.object({
boards: z
.array(
z.object({
id: z.string().title('ID').describe('Board ID'),
name: z.string().title('Name').describe('Board name'),
postCount: z.number().title('Post Count').describe('Number of posts in the board'),
url: z.string().title('URL').describe('Board URL'),
created: z.string().title('Created').describe('Board creation timestamp'),
})
)
.title('Boards')
.describe('Array of boards'),
nextToken: z.number().optional().title('Next Token').describe('Token for next page if more boards available'),
}),
},
},
},
attributes: {
category: 'Project Management',
guideSlug: 'canny',
repo: 'botpress',
},
})