├── github ├── icon.png ├── i18n │ └── en.json ├── enum │ ├── Processors.ts │ ├── GitHubURL.ts │ ├── App.ts │ ├── Subcommands.ts │ └── OcticonIcons.ts ├── definitions │ ├── Reminder.ts │ ├── Userinfo.ts │ ├── subscription.ts │ ├── PRdetails.ts │ ├── githubIssueData.ts │ ├── repositorySubscriptions.ts │ ├── searchResultData.ts │ ├── githubReactions.ts │ ├── searchResult.ts │ ├── ExecutorProps.ts │ └── githubIssue.ts ├── package.json ├── .vscode │ └── extensions.json ├── .editorconfig ├── tsconfig.json ├── tslint.json ├── .rcappsconfig ├── .gitignore ├── app.json ├── helpers │ ├── dateTime.ts │ ├── getWebhookURL.ts │ ├── checkLinks.ts │ ├── githubSDKclass.ts │ └── basicQueryMessage.ts ├── lib │ ├── blocks.ts │ ├── CreateReactionsBar.ts │ ├── CreateIssueStatsBar.ts │ ├── pullReqeustListMessage.ts │ ├── contributorListMessage.ts │ ├── notifications.ts │ ├── issuesListMessage.ts │ ├── helperMessage.ts │ ├── repoDataMessage.ts │ ├── initiatorMessage.ts │ └── message.ts ├── oath2 │ └── authentication.ts ├── handlers │ ├── CreateReminder.ts │ ├── GithubPRlinkHandler.ts │ ├── MainModalHandler.ts │ ├── SearchHandler.ts │ ├── UserProfileHandler.ts │ ├── HandleInvalidRepoName.ts │ ├── AuthenticationHandler.ts │ ├── HandleIssues.ts │ ├── GitHubCodeSegmentHandler.ts │ ├── HandleRemider.ts │ └── ExecuteViewClosedHandler.ts ├── settings │ └── settings.ts ├── processors │ ├── deleteOAthToken.ts │ └── bodyMarkdowmRenderer.ts ├── commands │ ├── GithubCommand.ts │ └── GhCommand.ts ├── persistance │ ├── roomInteraction.ts │ ├── githubIssues.ts │ ├── searchResults.ts │ ├── auth.ts │ └── remind.ts ├── modals │ ├── githubSearchErrorModal.ts │ ├── messageModal.ts │ ├── fileCodeModal.ts │ ├── newreminderModal.ts │ ├── getIssuesStarterModal.ts │ ├── remindersModal.ts │ ├── newIssueStarterModal.ts │ ├── profileShareModal.ts │ ├── githubSearchResultsShareModal.ts │ ├── issueTemplateSelectionModal.ts │ ├── githubIssuesShareModal.ts │ ├── UserProfileModal.ts │ ├── IssueDisplayModal.ts │ ├── MainModal.ts │ ├── addIssueCommentModal.ts │ ├── deleteSubscriptions.ts │ ├── addPullRequestCommentsModal.ts │ ├── issueCommentsModal.ts │ ├── subscriptionsModal.ts │ ├── addSubscriptionsModal.ts │ ├── pullRequestCommentsModal.ts │ ├── addIssueAssigneeModal.ts │ └── pullDetailsModal.ts └── endpoints │ └── githubEndpoints.ts ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .github ├── ISSUE_TEMPLATE │ ├── feature.md │ └── bug.md └── pull_request_template.md └── .gitignore /github/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketChat/Apps.Github22/HEAD/github/icon.png -------------------------------------------------------------------------------- /github/i18n/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmd_description": "Search, Share, Review, Merge, Subscribe GitHub Resources and do much more from Rocket.Chat." 3 | } -------------------------------------------------------------------------------- /github/enum/Processors.ts: -------------------------------------------------------------------------------- 1 | export enum ProcessorsEnum { 2 | REMOVE_GITHUB_LOGIN = 'remove_github_token', 3 | PR_REMINDER = 'pr_reminder', 4 | } -------------------------------------------------------------------------------- /github/enum/GitHubURL.ts: -------------------------------------------------------------------------------- 1 | export enum GitHubURLEnum { 2 | PREFIX = "blob/", 3 | HOST = "github.com", 4 | RAW_HOST = "raw.githubusercontent.com", 5 | } 6 | -------------------------------------------------------------------------------- /github/definitions/Reminder.ts: -------------------------------------------------------------------------------- 1 | export interface IReminder { 2 | userid:string, 3 | username:string, 4 | repos: string[]; 5 | unsubscribedPR:{repo:string,prnum:number[]}[] 6 | } 7 | -------------------------------------------------------------------------------- /github/definitions/Userinfo.ts: -------------------------------------------------------------------------------- 1 | export interface UserInformation { 2 | username: string; 3 | name: string; 4 | email: string; 5 | bio: string; 6 | followers: string; 7 | following: string; 8 | avatar: string; 9 | } 10 | -------------------------------------------------------------------------------- /github/definitions/subscription.ts: -------------------------------------------------------------------------------- 1 | //subscriptions which will be saved in the apps local storage 2 | export interface ISubscription{ 3 | webhookId : string, 4 | user: string, 5 | repoName : string, 6 | room : string, 7 | event: string 8 | } -------------------------------------------------------------------------------- /github/definitions/PRdetails.ts: -------------------------------------------------------------------------------- 1 | export interface IPRdetail{ 2 | title: string; 3 | number:string; 4 | url: string; 5 | id: string; 6 | createdAt: Date; 7 | ageInDays?:number; 8 | author: { avatar: string; username: string; };repo:string; 9 | } -------------------------------------------------------------------------------- /github/enum/App.ts: -------------------------------------------------------------------------------- 1 | export enum AppEnum { 2 | DEFAULT_TITLE = 'GitHub', 3 | USERNAME_ALIAS = 'GitHub', 4 | EMOJI_AVATAR = '', 5 | USER_MESSAGED_BOT = 'You have messaged the bot user. This has no effect.', 6 | APP_ID='826f0d95-9e25-48a6-a781-a32f147230a5' 7 | } -------------------------------------------------------------------------------- /github/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@rocket.chat/apps-engine": "^1.36.0", 4 | "@rocket.chat/ui-kit": "^0.31.22", 5 | "@types/node": "14.14.6", 6 | "tslint": "^5.10.0", 7 | "typescript": "^4.0.5" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-base:latest 2 | 3 | USER root 4 | 5 | RUN wget -O - -c https://github.com/henit-chobisa/Thrust.RC/releases/download/v2.0.3/thrust.RC_2.0.3_Linux_amd64.tar.gz | tar -xz -C /usr/local/bin 6 | 7 | RUN mv /usr/local/bin/thrust.RC /usr/local/bin/thrust -------------------------------------------------------------------------------- /github/definitions/githubIssueData.ts: -------------------------------------------------------------------------------- 1 | import { IGitHubIssue } from "./githubIssue" 2 | 3 | //search results for a user 4 | export interface IGitHubIssueData{ 5 | user_id: string , 6 | room_id: string, 7 | repository: string, 8 | push_rights: boolean, 9 | issue_list: Array 10 | } -------------------------------------------------------------------------------- /github/definitions/repositorySubscriptions.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 2 | 3 | //subscriptions which will be saved in the apps local storage 4 | export interface IRepositorySubscriptions{ 5 | webhookId : string, 6 | repoName : string, 7 | user : IUser , 8 | events : Array 9 | } -------------------------------------------------------------------------------- /github/definitions/searchResultData.ts: -------------------------------------------------------------------------------- 1 | import { IGitHubSearchResult } from "./searchResult"; 2 | 3 | //search results for a user 4 | export interface IGitHubSearchResultData{ 5 | user_id : string , 6 | room_id : string, 7 | search_query : string, 8 | total_count? : string|number, 9 | search_results : Array 10 | } -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - name: Thrust Off Github App 3 | command: thrust start ./github 4 | 5 | ports: 6 | - port: 3000 7 | visibility: public 8 | 9 | vscode: 10 | extensions: 11 | - "ms-vscode.vscode-typescript-next" 12 | - "esbenp.prettier-vscode" 13 | - "github.vscode-pull-request-github" 14 | 15 | image: 16 | file: .gitpod.Dockerfile -------------------------------------------------------------------------------- /github/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.editorconfig", 4 | "eamodio.gitlens", 5 | "eg2.vscode-npm-script", 6 | "wayou.vscode-todo-highlight", 7 | "minhthai.vscode-todo-parser", 8 | "ms-vscode.vscode-typescript-tslint-plugin", 9 | "rbbit.typescript-hero" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /github/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /github/definitions/githubReactions.ts: -------------------------------------------------------------------------------- 1 | // reactions object for issues, comments and repos 2 | 3 | export interface IGithubReactions { 4 | total_count : number, 5 | plus_one : number, 6 | minus_one : number, 7 | laugh : number, 8 | hooray : number, 9 | confused : number, 10 | heart : number, 11 | rocket : number, 12 | eyes : number 13 | } 14 | -------------------------------------------------------------------------------- /github/enum/Subcommands.ts: -------------------------------------------------------------------------------- 1 | export enum SubcommandEnum { 2 | LOGIN = 'login', 3 | LOGOUT = 'logout', 4 | HELP = 'help', 5 | SUBSCRIBE = 'subscribe', 6 | UNSUBSCRIBE = 'unsubscribe', 7 | TEST = 'test', 8 | SEARCH = 'search', 9 | NEW_ISSUE = 'issue', 10 | ISSUES = 'issues', 11 | PROFILE = 'me', 12 | REMIND = 'remind', 13 | CREATE= 'create', 14 | LIST ='list', 15 | } 16 | -------------------------------------------------------------------------------- /github/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "declaration": false, 7 | "noImplicitAny": false, 8 | "removeComments": true, 9 | "strictNullChecks": true, 10 | "noImplicitReturns": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | }, 14 | "include": [ 15 | "**/*.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /github/definitions/searchResult.ts: -------------------------------------------------------------------------------- 1 | //inidividual search result 2 | export interface IGitHubSearchResult{ 3 | result_id: string|number, 4 | title?: string, 5 | html_url?: string, 6 | number?: string|number 7 | labels?: string, 8 | pull_request?: boolean, 9 | pull_request_url?: string, 10 | user_login?:string, 11 | state?:string, 12 | share?:boolean,//true if seacrh result is to be shareed 13 | result: string, 14 | } -------------------------------------------------------------------------------- /github/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "array-type": [true, "generic"], 5 | "member-access": true, 6 | "no-console": [false], 7 | "no-duplicate-variable": true, 8 | "object-literal-sort-keys": false, 9 | "quotemark": [true, "single"], 10 | "max-line-length": [true, { 11 | "limit": 160, 12 | "ignore-pattern": "^import | *export .*? {" 13 | }] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /github/.rcappsconfig: -------------------------------------------------------------------------------- 1 | { 2 | "url": "http://localhost:3000", 3 | "username": "", 4 | "password": "", 5 | "ignoredFiles": [ 6 | "**/README.md", 7 | "**/package-lock.json", 8 | "**/package.json", 9 | "**/tslint.json", 10 | "**/tsconfig.json", 11 | "**/*.js", 12 | "**/*.js.map", 13 | "**/*.d.ts", 14 | "**/*.spec.ts", 15 | "**/*.test.ts", 16 | "**/dist/**", 17 | "**/.*" 18 | ] 19 | } -------------------------------------------------------------------------------- /github/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore modules pulled in from npm 2 | node_modules/ 3 | 4 | # rc-apps package output 5 | dist/ 6 | 7 | # JetBrains IDEs 8 | out/ 9 | .idea/ 10 | .idea_modules/ 11 | 12 | # macOS 13 | .DS_Store 14 | .AppleDouble 15 | .LSOverride 16 | ._* 17 | .DocumentRevisions-V100 18 | .fseventsd 19 | .Spotlight-V100 20 | .TemporaryItems 21 | .Trashes 22 | .VolumeIcon.icns 23 | .com.apple.timemachine.donotpresent 24 | .AppleDB 25 | .AppleDesktop 26 | Network Trash Folder 27 | Temporary Items 28 | .apdisk 29 | -------------------------------------------------------------------------------- /github/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "826f0d95-9e25-48a6-a781-a32f147230a5", 3 | "version": "0.0.1", 4 | "requiredApiVersion": "^1.36.0", 5 | "iconFile": "icon.png", 6 | "author": { 7 | "name": "Samad Yar Khan", 8 | "homepage": "https://github.com/samad-yar-khan", 9 | "support": "https://github.com/samad-yar-khan" 10 | }, 11 | "name": "Github", 12 | "nameSlug": "github", 13 | "classFile": "GithubApp.ts", 14 | "description": "The ultimate app extending Rocket.Chat for all developers collaborating on Github", 15 | "implements": [ 16 | "IPreMessageSentExtend" 17 | ] 18 | } -------------------------------------------------------------------------------- /github/definitions/ExecutorProps.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, IModify, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 5 | import { GithubApp } from "../GithubApp"; 6 | 7 | export interface ExecutorProps { 8 | sender: IUser; 9 | room: IRoom; 10 | command: string[]; 11 | context: SlashCommandContext; 12 | read: IRead; 13 | modify: IModify; 14 | http: IHttp; 15 | persistence: IPersistence; 16 | app:GithubApp; 17 | } 18 | -------------------------------------------------------------------------------- /github/helpers/dateTime.ts: -------------------------------------------------------------------------------- 1 | export const parseTime = (myDate) => { 2 | const time = new Date(myDate).toLocaleTimeString("en", { 3 | timeStyle: "short", 4 | hour12: true, 5 | timeZone: "UTC", 6 | }); 7 | return time; 8 | }; 9 | 10 | export const parseDate = (myDate) => { 11 | let date = new Date(myDate); 12 | let year: string = `${date.getFullYear()}`; 13 | let month: string = `${date.getMonth() + 1}`; 14 | let dt: string = `${date.getDate()}`; 15 | 16 | if (dt.length < 2) { 17 | dt = "0" + dt; 18 | } 19 | if (month.length < 2) { 20 | month = "0" + month; 21 | } 22 | 23 | return dt + "/" + month + "/" + year; 24 | }; 25 | -------------------------------------------------------------------------------- /github/helpers/getWebhookURL.ts: -------------------------------------------------------------------------------- 1 | import { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; 2 | import { GithubApp } from '../GithubApp'; 3 | 4 | export async function getWebhookUrl(app: GithubApp): Promise { 5 | const accessors = app.getAccessors(); 6 | const webhookEndpoint = accessors.providedApiEndpoints.find((endpoint) => endpoint.path === 'githubwebhook') as IApiEndpointMetadata; 7 | let siteUrl: string = await accessors.environmentReader.getServerSettings().getValueById('Site_Url') as string; 8 | if (siteUrl.charAt(siteUrl.length - 1) === '/') { 9 | siteUrl = siteUrl.substring(0, siteUrl.length - 1); 10 | } 11 | return siteUrl + webhookEndpoint.computedPath; 12 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature 3 | about: Create a feature request 4 | labels: enhancement 5 | --- 6 | 7 | ## Why do we need this ? 8 | 9 | 10 | 11 | As a GitHub App developer 12 | 13 | **I need to**: 14 | 15 | **So That**: 16 | 17 | ## Acceptance Criteria 18 | 19 | 20 | 21 | - [ ] TODO 1 22 | - [ ] TODO 2 23 | - [ ] TODO 3 24 | 25 | ## Further Comments / References 26 | 27 | 28 | -------------------------------------------------------------------------------- /github/definitions/githubIssue.ts: -------------------------------------------------------------------------------- 1 | import { IGithubReactions } from "./githubReactions" 2 | 3 | //inidividual issue 4 | export interface IGitHubIssue{ 5 | issue_id: string|number, 6 | title?: string, 7 | html_url?: string, 8 | number?: string|number 9 | labels?: Array, 10 | user_login?:string, 11 | user_avatar?:string, 12 | last_updated_at?: string, 13 | comments?:string|number, 14 | state?: string, 15 | share?: boolean,//true if seacrh result is to be shareed 16 | assignees?: Array,//user ids seperated by " " 17 | issue_compact: string,//compact string to share issues in rooms 18 | repo_url?: string, 19 | body?: string, 20 | reactions? : IGithubReactions 21 | } 22 | -------------------------------------------------------------------------------- /github/enum/OcticonIcons.ts: -------------------------------------------------------------------------------- 1 | export enum OcticonIcons { 2 | ISSUE_OPEN = "https://raw.githubusercontent.com/primer/octicons/main/icons/issue-opened-24.svg", 3 | ISSUE_CLOSED = "https://raw.githubusercontent.com/primer/octicons/main/icons/issue-closed-24.svg", 4 | COMMENTS = "https://raw.githubusercontent.com/primer/octicons/main/icons/comment-24.svg", 5 | COMMIT = "https://raw.githubusercontent.com/primer/octicons/main/icons/commit-24.svg", 6 | PERSON = "https://raw.githubusercontent.com/primer/octicons/main/icons/person-24.svg", 7 | REPOSITORY = "https://raw.githubusercontent.com/primer/octicons/main/icons/repo-24.svg", 8 | PENCIL = "https://raw.githubusercontent.com/primer/octicons/main/icons/pencil-24.svg" 9 | } 10 | -------------------------------------------------------------------------------- /github/helpers/checkLinks.ts: -------------------------------------------------------------------------------- 1 | import { IMessage } from "@rocket.chat/apps-engine/definition/messages"; 2 | 3 | export async function hasGitHubCodeSegmentLink(message: IMessage): Promise { 4 | let lineNo: RegExp = 5 | /https?:\/\/github\.com\/[A-Za-z0-9_-]+\/[A-Za-z0-9_.-]+\/blob\/[A-Za-z0-9_-]+\/.+/; 6 | 7 | if (lineNo.test(message.text!)) { 8 | return true; 9 | } 10 | return false; 11 | } 12 | export async function hasGithubPRLink(message: IMessage): Promise { 13 | let prLink: RegExp = /https?:\/\/github\.com\/[A-Za-z0-9_-]+\/[A-Za-z0-9_.-]+\/pull\/[0-9]+/; 14 | if (prLink.test(message.text!)) { 15 | return true; 16 | } 17 | return false; 18 | } 19 | export async function isGithubLink(message: IMessage) { 20 | let githubLink: RegExp = /(?:https?:\/\/)?(?:www\.)?github\.com\//; 21 | if (githubLink.test(message.text!)) { 22 | return true; 23 | } 24 | return false; 25 | } -------------------------------------------------------------------------------- /github/lib/blocks.ts: -------------------------------------------------------------------------------- 1 | import { IModify } from '@rocket.chat/apps-engine/definition/accessors'; 2 | import { AccessoryElements, BlockBuilder } from '@rocket.chat/apps-engine/definition/uikit'; 3 | 4 | export interface IButton { 5 | text: string; 6 | url?: string; 7 | actionId?: string; 8 | } 9 | 10 | export async function createSectionBlock(modify: IModify, sectionText: string, button?: IButton): Promise { 11 | const blocks = modify.getCreator().getBlockBuilder(); 12 | 13 | blocks.addSectionBlock({ 14 | text: blocks.newMarkdownTextObject(sectionText), 15 | }); 16 | 17 | if (button) { 18 | blocks.addActionsBlock({ 19 | elements: [ 20 | blocks.newButtonElement({ 21 | actionId: button.actionId, 22 | text: blocks.newPlainTextObject(button.text), 23 | url: button.url, 24 | }), 25 | ], 26 | }); 27 | } 28 | 29 | return blocks; 30 | } -------------------------------------------------------------------------------- /github/lib/CreateReactionsBar.ts: -------------------------------------------------------------------------------- 1 | import { BlockBuilder } from "@rocket.chat/apps-engine/definition/uikit"; 2 | import { IGithubReactions } from "../definitions/githubReactions"; 3 | 4 | export async function CreateReactionsBar( 5 | reactions : IGithubReactions, 6 | block : BlockBuilder 7 | ){ 8 | block.addContextBlock({ 9 | elements : [ 10 | block.newPlainTextObject(`Total Reactions ${reactions?.total_count}`, true), 11 | block.newPlainTextObject(`➕ ${reactions?.plus_one} `, true), 12 | block.newPlainTextObject(`➖ ${reactions?.minus_one}`, true), 13 | block.newPlainTextObject(`😄 ${reactions?.laugh}`, true), 14 | block.newPlainTextObject(`🎉 ${reactions?.hooray}`, true), 15 | block.newPlainTextObject(`😕 ${reactions?.confused}`, true), 16 | block.newPlainTextObject(`♥️ ${reactions?.heart}`, true), 17 | block.newPlainTextObject(`🚀 ${reactions?.rocket}`, true), 18 | block.newPlainTextObject(`👀 ${reactions?.eyes}`, true), 19 | ] 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Issue(s) 2 | 3 | 4 | 5 | ## Acceptance Criteria fulfillment 6 | 7 | 8 | 9 | - [ ] Task 1 10 | - [ ] Task 2 11 | - [ ] Task 3 12 | 13 | ## Proposed changes (including videos or screenshots) 14 | 15 | 16 | 21 | 22 | 23 | 24 | 25 | ## Further comments 26 | 27 | -------------------------------------------------------------------------------- /github/oath2/authentication.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IModify, 3 | IPersistence, 4 | IRead, 5 | } from "@rocket.chat/apps-engine/definition/accessors"; 6 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 7 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 8 | import { GithubApp } from "../GithubApp"; 9 | import { IButton, createSectionBlock } from "../lib/blocks"; 10 | import { sendNotification } from "../lib/message"; 11 | import { ModalsEnum } from "../enum/Modals"; 12 | 13 | export async function authorize( 14 | app: GithubApp, 15 | read: IRead, 16 | modify: IModify, 17 | user: IUser, 18 | room: IRoom, 19 | persistence: IPersistence 20 | ): Promise { 21 | const url = await app 22 | .getOauth2ClientInstance() 23 | .getUserAuthorizationUrl(user); 24 | 25 | const button: IButton = { 26 | text: "GitHub Login", 27 | url: url.toString(), 28 | actionId: ModalsEnum.GITHUB_LOGIN_ACTION 29 | }; 30 | const message = `Login to GitHub`; 31 | const block = await createSectionBlock(modify, message, button); 32 | await sendNotification(read, modify, user, room, message, block); 33 | } 34 | -------------------------------------------------------------------------------- /github/handlers/CreateReminder.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, IModify, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 5 | import { GithubApp } from "../GithubApp"; 6 | import { HandleInvalidRepoName } from "./HandleInvalidRepoName"; 7 | import { CreateReminder } from "../persistance/remind"; 8 | import { sendNotification } from "../lib/message"; 9 | 10 | export async function createReminder( 11 | repository: string, 12 | room: IRoom, 13 | read: IRead, 14 | app: GithubApp, 15 | persistence: IPersistence, 16 | modify: IModify, 17 | http: IHttp, 18 | user: IUser 19 | ) { 20 | 21 | const repoName = repository; 22 | 23 | const isValidRepo = await HandleInvalidRepoName( 24 | repoName, 25 | http, 26 | app, 27 | modify, 28 | user, 29 | read, 30 | room 31 | ) 32 | 33 | if (!isValidRepo) { 34 | return; 35 | } else { 36 | await CreateReminder(read, persistence, user, repoName); 37 | } 38 | 39 | await sendNotification(read, modify, user, room, `Pull Request Reminder Set for [${repoName}](https://github.com/${repoName}) 👍`) 40 | } 41 | -------------------------------------------------------------------------------- /github/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import { ISetting, SettingType } from '@rocket.chat/apps-engine/definition/settings'; 2 | 3 | export enum AppSettingsEnum { 4 | ReminderCRONjobID = 'reminder_cron_job_id', 5 | ReminderCRONjobLabel = 'cron_job_string_for_pr_reminders_label', 6 | ReminderCRONjobPackageValue = '0 9 * * *', 7 | BaseHostID = "base_host_id", 8 | BaseHostLabel = "base_host_label", 9 | BaseHostPackageValue = "https://github.com/", 10 | BaseApiHostID = "base_api_host_id", 11 | BaseApiHostLabel = "base_api_host_label", 12 | BaseApiHostPackageValue = "https://api.github.com/" 13 | } 14 | export const settings: ISetting[] = [ 15 | { 16 | id: AppSettingsEnum.ReminderCRONjobID, 17 | i18nLabel: AppSettingsEnum.ReminderCRONjobLabel, 18 | type: SettingType.STRING, 19 | required: true, 20 | public: false, 21 | packageValue: AppSettingsEnum.ReminderCRONjobPackageValue, 22 | }, 23 | { 24 | id: AppSettingsEnum.BaseHostID, 25 | i18nLabel: AppSettingsEnum.BaseHostLabel, 26 | type: SettingType.STRING, 27 | required: true, 28 | public: false, 29 | packageValue: AppSettingsEnum.BaseHostPackageValue, 30 | }, 31 | { 32 | id: AppSettingsEnum.BaseApiHostID, 33 | i18nLabel: AppSettingsEnum.BaseApiHostLabel, 34 | type: SettingType.STRING, 35 | required: true, 36 | public: false, 37 | packageValue: AppSettingsEnum.BaseApiHostPackageValue, 38 | }, 39 | ]; 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: Create a report to help us improve 4 | labels: bug 5 | --- 6 | 7 | ### Description: 8 | 9 | 10 | 11 | ### Steps to reproduce: 12 | 13 | 1. 14 | 2. 15 | 3. 16 | 17 | ### Expected behavior: 18 | 19 | 20 | 21 | ### Actual behavior: 22 | 23 | 24 | 25 | ### Rocket.Chat Server Setup Information: 26 | 27 | 28 | 29 | 30 | - Version of Rocket.Chat Server: 31 | - Operating System: 32 | - Deployment Method: 33 | 34 | ### Rocket.Chat Client Setup Information 35 | 36 | 37 | 38 | 39 | 40 | - Desktop App Version: 41 | - Browser Version: 42 | - Operating System: 43 | - Android App Version: 44 | - IOS Application Version: 45 | 46 | ### Additional context 47 | 48 | 49 | -------------------------------------------------------------------------------- /github/processors/deleteOAthToken.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | 8 | import { sendDirectMessage } from "../lib/message"; 9 | import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler"; 10 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 11 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 12 | import { 13 | getAccessTokenForUser, 14 | revokeUserAccessToken, 15 | } from "../persistance/auth"; 16 | import { IOAuth2ClientOptions } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 17 | 18 | export async function deleteOathToken({ 19 | user, 20 | config, 21 | read, 22 | modify, 23 | http, 24 | persis, 25 | }: { 26 | user: IUser; 27 | config: IOAuth2ClientOptions; 28 | read: IRead; 29 | modify: IModify; 30 | http: IHttp; 31 | persis: IPersistence; 32 | }) { 33 | try { 34 | let token = await getAccessTokenForUser(read, user, config); 35 | if (token?.token) { 36 | await revokeUserAccessToken(read, user, persis, http, config); 37 | } 38 | token = await getAccessTokenForUser(read, user, config); 39 | await sendDirectMessage( 40 | read, 41 | modify, 42 | user, 43 | "GitHub Token Expired, Login to GitHub Again ! `/github login`", 44 | persis 45 | ); 46 | } catch (error) { 47 | console.log("deleteOathToken error : ", error); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /github/commands/GithubCommand.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ISlashCommand, 3 | SlashCommandContext, 4 | } from "@rocket.chat/apps-engine/definition/slashcommands"; 5 | import { 6 | IHttp, 7 | IModify, 8 | IPersistence, 9 | IRead, 10 | } from "@rocket.chat/apps-engine/definition/accessors"; 11 | import { GithubApp } from "../GithubApp"; 12 | import { CommandUtility } from "../lib/commandUtility"; 13 | 14 | 15 | export class GithubCommand implements ISlashCommand { 16 | public constructor(private readonly app: GithubApp) {} 17 | public command = "github"; 18 | public i18nDescription = "cmd_description"; 19 | public providesPreview = false; 20 | public i18nParamsExample = ""; 21 | 22 | public async executor( 23 | context: SlashCommandContext, 24 | read: IRead, 25 | modify: IModify, 26 | http: IHttp, 27 | persistence: IPersistence 28 | ): Promise { 29 | 30 | const command = context.getArguments(); 31 | const sender = context.getSender(); 32 | const room = context.getRoom(); 33 | 34 | if(!Array.isArray(command)){ 35 | return; 36 | } 37 | 38 | const commandUtility = new CommandUtility( 39 | { 40 | sender: sender, 41 | room: room, 42 | command: command, 43 | context: context, 44 | read: read, 45 | modify: modify, 46 | http: http, 47 | persistence: persistence, 48 | app: this.app 49 | } 50 | ); 51 | 52 | commandUtility.resolveCommand(); 53 | }} -------------------------------------------------------------------------------- /github/commands/GhCommand.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IRead, 3 | IModify, 4 | IHttp, 5 | IPersistence, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { 8 | ISlashCommand, 9 | SlashCommandContext, 10 | } from "@rocket.chat/apps-engine/definition/slashcommands"; 11 | import { GithubApp } from "../GithubApp"; 12 | import { CommandUtility } from "../lib/commandUtility"; 13 | 14 | export class GHCommand implements ISlashCommand { 15 | public constructor(private readonly app: GithubApp) {} 16 | public command: string = "gh"; 17 | public i18nParamsExample: string = ""; 18 | public i18nDescription: string = "cmd_description"; 19 | public providesPreview: boolean = false; 20 | 21 | public async executor( 22 | context: SlashCommandContext, 23 | read: IRead, 24 | modify: IModify, 25 | http: IHttp, 26 | persistence: IPersistence 27 | ): Promise { 28 | const command = context.getArguments(); 29 | const sender = context.getSender(); 30 | const room = context.getRoom(); 31 | 32 | if (!Array.isArray(command)) { 33 | return; 34 | } 35 | 36 | const commandUtility = new CommandUtility( 37 | { 38 | sender: sender, 39 | room: room, 40 | command: command, 41 | context: context, 42 | read: read, 43 | modify: modify, 44 | http: http, 45 | persistence: persistence, 46 | app: this.app 47 | } 48 | ); 49 | 50 | commandUtility.resolveCommand(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /github/persistance/roomInteraction.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPersistence, 3 | IPersistenceRead, 4 | } from "@rocket.chat/apps-engine/definition/accessors"; 5 | import { 6 | RocketChatAssociationModel, 7 | RocketChatAssociationRecord, 8 | } from "@rocket.chat/apps-engine/definition/metadata"; 9 | 10 | //functions needed ro persist room data while modal and other UI interactions 11 | 12 | export const storeInteractionRoomData = async ( 13 | persistence: IPersistence, 14 | userId: string, 15 | roomId: string 16 | ): Promise => { 17 | const association = new RocketChatAssociationRecord( 18 | RocketChatAssociationModel.USER, 19 | `${userId}#RoomId` 20 | ); 21 | await persistence.updateByAssociation( 22 | association, 23 | { roomId: roomId }, 24 | true 25 | ); 26 | }; 27 | 28 | export const getInteractionRoomData = async ( 29 | persistenceRead: IPersistenceRead, 30 | userId: string 31 | ): Promise => { 32 | const association = new RocketChatAssociationRecord( 33 | RocketChatAssociationModel.USER, 34 | `${userId}#RoomId` 35 | ); 36 | const result = (await persistenceRead.readByAssociation( 37 | association 38 | )) as Array; 39 | return result && result.length ? result[0] : null; 40 | }; 41 | 42 | export const clearInteractionRoomData = async ( 43 | persistence: IPersistence, 44 | userId: string 45 | ): Promise => { 46 | const association = new RocketChatAssociationRecord( 47 | RocketChatAssociationModel.USER, 48 | `${userId}#RoomId` 49 | ); 50 | await persistence.removeByAssociation(association); 51 | }; 52 | -------------------------------------------------------------------------------- /github/lib/CreateIssueStatsBar.ts: -------------------------------------------------------------------------------- 1 | import { BlockBuilder } from "@rocket.chat/apps-engine/definition/uikit"; 2 | import { IGitHubIssue } from "../definitions/githubIssue"; 3 | import { OcticonIcons } from "../enum/OcticonIcons"; 4 | 5 | export async function CreateIssueStatsBar( 6 | issueInfo : IGitHubIssue, 7 | block : BlockBuilder 8 | ){ 9 | block.addContextBlock({ 10 | elements: [ 11 | block.newImageElement({ 12 | imageUrl: OcticonIcons.COMMENTS, 13 | altText: "Comments", 14 | }), 15 | block.newPlainTextObject( 16 | `${issueInfo.comments}`, 17 | false 18 | ), 19 | block.newImageElement({ 20 | imageUrl: OcticonIcons.ISSUE_OPEN, 21 | altText: "Assignees Icon", 22 | }), 23 | block.newPlainTextObject( 24 | issueInfo.assignees ? (issueInfo.assignees.length == 0 25 | ? "No Assignees" 26 | : `${issueInfo.assignees.length} Assignees`) : "" 27 | ), 28 | block.newImageElement({ 29 | imageUrl: 30 | issueInfo.state == "open" 31 | ? OcticonIcons.ISSUE_OPEN 32 | : OcticonIcons.ISSUE_CLOSED, 33 | altText: "State", 34 | }), 35 | block.newPlainTextObject(`${issueInfo.state}`), 36 | block.newImageElement({ 37 | imageUrl: issueInfo.user_avatar ?? "", 38 | altText: "User Image", 39 | }), 40 | block.newPlainTextObject(issueInfo.user_login ? `Created by ${issueInfo.user_login}` : "Failed to Load Creator"), 41 | ], 42 | }); 43 | 44 | } 45 | -------------------------------------------------------------------------------- /github/handlers/GithubPRlinkHandler.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 2 | import { IHttp, IMessageBuilder, IMessageExtender, IModify, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; 3 | import { IMessage, IMessageAttachment, MessageActionButtonsAlignment, MessageActionType } from "@rocket.chat/apps-engine/definition/messages"; 4 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 5 | 6 | export async function handleGithubPRLinks( 7 | message: IMessage, 8 | read: IRead, 9 | http: IHttp, 10 | user: IUser, 11 | room: IRoom, 12 | extend: IMessageExtender 13 | ) { 14 | const githubPRLinkRegex = /https?:\/\/github\.com\/(\S+)\/(\S+)\/pull\/(\d+)/g; 15 | const text = message.text!; 16 | let prLinkMatches: RegExpExecArray | null; 17 | const matches: RegExpExecArray[] = []; 18 | 19 | while ((prLinkMatches = githubPRLinkRegex.exec(text)) !== null) { 20 | matches.push(prLinkMatches); 21 | } 22 | 23 | if (matches.length > 3) { 24 | return; 25 | } 26 | 27 | for (const match of matches) { 28 | const username = match[1]; 29 | const repositoryName = match[2]; 30 | const pullNumber = match[3]; 31 | 32 | const attachment: IMessageAttachment = { 33 | actionButtonsAlignment: MessageActionButtonsAlignment.VERTICAL, 34 | actions: [ 35 | { 36 | type: MessageActionType.BUTTON, 37 | text: `PR Actions in ${repositoryName} #${pullNumber}`, 38 | msg: `/github ${username}/${repositoryName} pulls ${pullNumber}`, 39 | msg_in_chat_window: true, 40 | }, 41 | ], 42 | }; 43 | extend.addAttachment(attachment); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /github/handlers/MainModalHandler.ts: -------------------------------------------------------------------------------- 1 | import { IRead, IPersistence, IHttp, IModify } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { GithubApp } from "../GithubApp"; 5 | import { MainModal } from "../modals/MainModal"; 6 | import { getAccessTokenForUser } from "../persistance/auth"; 7 | import { sendNotification } from "../lib/message"; 8 | 9 | export async function handleMainModal( 10 | read: IRead, 11 | context: SlashCommandContext, 12 | app: GithubApp, 13 | persistence: IPersistence, 14 | http: IHttp, 15 | room: IRoom, 16 | modify: IModify 17 | ) { 18 | let accessToken = await getAccessTokenForUser( 19 | read, 20 | context.getSender(), 21 | app.oauth2Config 22 | ); 23 | if (accessToken && accessToken.token) { 24 | const triggerId = context.getTriggerId(); 25 | if (triggerId) { 26 | const modal = await MainModal({ 27 | modify: modify, 28 | read: read, 29 | persistence: persistence, 30 | http: http, 31 | slashcommandcontext: context, 32 | }); 33 | await modify 34 | .getUiController() 35 | .openModalView( 36 | modal, 37 | { triggerId }, 38 | context.getSender() 39 | ); 40 | } else { 41 | console.log("invalid Trigger ID !"); 42 | } 43 | } else { 44 | await sendNotification( 45 | read, 46 | modify, 47 | context.getSender(), 48 | room, 49 | "Login to go in the Main Modal ! `/github login`" 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /github/helpers/githubSDKclass.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, HttpStatusCode, IRead } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { UserInfo } from "os"; 3 | import { UserInformation } from "../definitions/Userinfo"; 4 | import { ModalsEnum } from "../enum/Modals"; 5 | 6 | class GitHubApi { 7 | private http: IHttp; 8 | private BaseHost: string; 9 | private BaseApiHost: string; 10 | private accessToken: String; 11 | 12 | constructor(http: IHttp, accessToken: String, BaseHost: string, BaseApiHost: string) { 13 | this.http = http; 14 | this.accessToken = accessToken; 15 | this.BaseApiHost = BaseApiHost; 16 | this.BaseHost = BaseHost; 17 | } 18 | 19 | private async getRequest(url: string): Promise { 20 | const response = await this.http.get(url, { 21 | headers: { 22 | Authorization: `token ${this.accessToken}`, 23 | "Content-Type": "application/json", 24 | }, 25 | }); 26 | 27 | if (!response.statusCode.toString().startsWith("2")) { 28 | throw response; 29 | } 30 | 31 | return JSON.parse(response.content || "{}"); 32 | } 33 | 34 | public async getBasicUserInfo(): Promise { 35 | try { 36 | const response = await this.getRequest( 37 | this.BaseApiHost + 'user' 38 | ); 39 | return { 40 | username: response.login, 41 | name: response.name, 42 | email: response.email, 43 | bio: response.bio, 44 | followers: response.followers, 45 | following: response.following, 46 | avatar: response.avatar_url 47 | } 48 | } catch (error) { 49 | throw error; 50 | } 51 | } 52 | } 53 | 54 | export { GitHubApi }; 55 | -------------------------------------------------------------------------------- /github/handlers/SearchHandler.ts: -------------------------------------------------------------------------------- 1 | import { IRead, IPersistence, IHttp, IModify } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { GithubApp } from "../GithubApp"; 5 | import { sendNotification } from "../lib/message"; 6 | import { githubSearchModal } from "../modals/githubSearchModal"; 7 | import { getAccessTokenForUser } from "../persistance/auth"; 8 | 9 | export async function handleSearch( 10 | read: IRead, 11 | context: SlashCommandContext, 12 | app: GithubApp, 13 | persistence: IPersistence, 14 | http: IHttp, 15 | room: IRoom, 16 | modify: IModify 17 | ){ 18 | let accessToken = await getAccessTokenForUser( 19 | read, 20 | context.getSender(), 21 | app.oauth2Config 22 | ); 23 | if (accessToken && accessToken.token) { 24 | const triggerId = context.getTriggerId(); 25 | if (triggerId) { 26 | const modal = await githubSearchModal({ 27 | modify: modify, 28 | read: read, 29 | persistence: persistence, 30 | http: http, 31 | slashcommandcontext: context, 32 | }); 33 | await modify 34 | .getUiController() 35 | .openModalView( 36 | modal, 37 | { triggerId }, 38 | context.getSender() 39 | ); 40 | } else { 41 | console.log("invalid Trigger ID !"); 42 | } 43 | } else { 44 | await sendNotification( 45 | read, 46 | modify, 47 | context.getSender(), 48 | room, 49 | "Login to subscribe to repository events ! `/github login`" 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /github/handlers/UserProfileHandler.ts: -------------------------------------------------------------------------------- 1 | import { IRead, IPersistence, IHttp, IModify } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; 5 | import { GithubApp } from "../GithubApp"; 6 | import { sendNotification } from "../lib/message"; 7 | import { userProfileModal } from "../modals/UserProfileModal"; 8 | import { getAccessTokenForUser } from "../persistance/auth"; 9 | 10 | export async function handleUserProfileRequest( 11 | read: IRead, 12 | context: SlashCommandContext, 13 | app: GithubApp, 14 | persistence: IPersistence, 15 | http: IHttp, 16 | room: IRoom, 17 | modify: IModify, 18 | uikitcontext?: UIKitInteractionContext 19 | ){ 20 | let access_token = await getAccessTokenForUser( 21 | read, 22 | context.getSender(), 23 | app.oauth2Config 24 | ); 25 | if (access_token?.token){ 26 | const triggerId = context.getTriggerId(); 27 | if (triggerId){ 28 | const modal = await userProfileModal({ 29 | access_token: access_token.token, 30 | modify: modify, 31 | read: read, 32 | persistence: persistence, 33 | http: http, 34 | slashcommandcontext: context 35 | }); 36 | await modify.getUiController().openModalView( 37 | modal, 38 | {triggerId}, 39 | context.getSender() 40 | ); 41 | } 42 | }else { 43 | await sendNotification( 44 | read, 45 | modify, 46 | context.getSender(), 47 | room, 48 | "Login is Mandatory for getting User Info ! `/github login`" 49 | ) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /github/modals/githubSearchErrorModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | 19 | export async function githubSearchErrorModal({ 20 | errorMessage, 21 | modify, 22 | read, 23 | slashcommandcontext, 24 | uikitcontext, 25 | }: { 26 | errorMessage?: string; 27 | modify: IModify; 28 | read: IRead; 29 | slashcommandcontext?: SlashCommandContext; 30 | uikitcontext?: UIKitInteractionContext; 31 | }): Promise { 32 | const viewId = ModalsEnum.GITHUB_SEARCH_ERROR_VIEW; 33 | 34 | const block = modify.getCreator().getBlockBuilder(); 35 | 36 | block.addSectionBlock({ 37 | text: { 38 | text: `🤖 GitHub Search Error : ${errorMessage}`, 39 | type: TextObjectType.MARKDOWN, 40 | }, 41 | }); 42 | 43 | 44 | return { 45 | id: viewId, 46 | title: { 47 | type: TextObjectType.PLAINTEXT, 48 | text: AppEnum.DEFAULT_TITLE, 49 | }, 50 | close: block.newButtonElement({ 51 | text: { 52 | type: TextObjectType.PLAINTEXT, 53 | text: "Close", 54 | }, 55 | }), 56 | blocks: block.getBlocks(), 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /github/handlers/HandleInvalidRepoName.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IRead, 5 | } from "@rocket.chat/apps-engine/definition/accessors"; 6 | import { GithubApp } from "../GithubApp"; 7 | import { isRepositoryExist } from "../helpers/githubSDK"; 8 | import { getAccessTokenForUser } from "../persistance/auth"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 11 | import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 12 | 13 | export async function HandleInvalidRepoName( 14 | repoName: string, 15 | http: IHttp, 16 | app: GithubApp, 17 | modify: IModify, 18 | sender: IUser, 19 | read: IRead, 20 | room: IRoom 21 | ): Promise { 22 | const accessToken = (await getAccessTokenForUser( 23 | read, 24 | sender, 25 | app.oauth2Config 26 | )) as IAuthData; 27 | 28 | const isValidRepository: boolean = await isRepositoryExist( 29 | http, 30 | repoName, 31 | accessToken?.token 32 | ); 33 | 34 | if (!isValidRepository) { 35 | const warningBuilder = await modify 36 | .getCreator() 37 | .startMessage() 38 | .setRoom(room); 39 | 40 | if (accessToken) { 41 | warningBuilder.setText( 42 | `Hey ${sender.username}! Provided repository doesn't exist.` 43 | ); 44 | } else { 45 | warningBuilder.setText( 46 | `Hey ${sender.username}! Provided repository doesn't exist or you need to login to access private repo.` 47 | ); 48 | } 49 | 50 | if (room.type !== "l") { 51 | await modify 52 | .getNotifier() 53 | .notifyUser(sender, warningBuilder.getMessage()); 54 | } else { 55 | await modify.getCreator().finish(warningBuilder); 56 | } 57 | } 58 | 59 | return isValidRepository; 60 | } 61 | -------------------------------------------------------------------------------- /github/helpers/basicQueryMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 8 | import { issueListMessage } from "../lib/issuesListMessage"; 9 | import { contributorListMessage } from "../lib/contributorListMessage"; 10 | import { pullRequestListMessage } from "../lib/pullReqeustListMessage"; 11 | import { repoDataMessage } from "../lib/repoDataMessage"; 12 | import { helperMessage } from "../lib/helperMessage"; 13 | import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 14 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 15 | 16 | export async function basicQueryMessage({ 17 | query, 18 | repository, 19 | room, 20 | read, 21 | persistence, 22 | modify, 23 | http, 24 | accessToken, 25 | user 26 | }: { 27 | query: String; 28 | repository: String; 29 | room: IRoom; 30 | read: IRead; 31 | persistence: IPersistence; 32 | modify: IModify; 33 | http: IHttp; 34 | accessToken?: IAuthData; 35 | user?: IUser; 36 | }) { 37 | 38 | switch (query) { 39 | case "issues": { 40 | await issueListMessage({ repository, room, read, persistence, modify, http, accessToken }); 41 | break; 42 | } 43 | case "contributors": { 44 | await contributorListMessage({ repository, room, read, persistence, modify, http, accessToken }); 45 | break; 46 | } 47 | case "pulls": { 48 | await pullRequestListMessage({ repository, room, read, persistence, modify, http, accessToken }); 49 | break; 50 | } 51 | case "repo": { 52 | await repoDataMessage({ repository, room, read, persistence, modify, http, accessToken }) 53 | break; 54 | } 55 | default: 56 | await helperMessage({ room, read, persistence, modify, http, user }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /github/modals/messageModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 13 | import { 14 | UIKitBlockInteractionContext, 15 | UIKitInteractionContext, 16 | } from "@rocket.chat/apps-engine/definition/uikit"; 17 | import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; 18 | 19 | export async function messageModal({ 20 | message, 21 | modify, 22 | read, 23 | persistence, 24 | http, 25 | slashcommandcontext, 26 | uikitcontext, 27 | }: { 28 | message:string; 29 | modify: IModify; 30 | read: IRead; 31 | persistence: IPersistence; 32 | http: IHttp; 33 | slashcommandcontext?: SlashCommandContext; 34 | uikitcontext?: UIKitInteractionContext; 35 | }): Promise { 36 | const viewId = ModalsEnum.MESSAGE_MODAL_VIEW; 37 | 38 | const block = modify.getCreator().getBlockBuilder(); 39 | 40 | block.addSectionBlock({ 41 | text: { 42 | text: `*${message}*`, 43 | type: TextObjectType.MARKDOWN, 44 | emoji:true, 45 | }, 46 | }); 47 | 48 | return { 49 | id: viewId, 50 | title: { 51 | type: TextObjectType.PLAINTEXT, 52 | text: AppEnum.DEFAULT_TITLE, 53 | }, 54 | close: block.newButtonElement({ 55 | text: { 56 | type: TextObjectType.PLAINTEXT, 57 | text: "Close", 58 | }, 59 | }), 60 | blocks: block.getBlocks(), 61 | }; 62 | } 63 | -------------------------------------------------------------------------------- /github/handlers/AuthenticationHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 8 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { GithubApp } from "../GithubApp"; 11 | import { sendNotification } from "../lib/message"; 12 | import { authorize } from "../oath2/authentication"; 13 | import { 14 | getAccessTokenForUser, 15 | revokeUserAccessToken, 16 | } from "../persistance/auth"; 17 | 18 | export async function handleLogin( 19 | app: GithubApp, 20 | read: IRead, 21 | modify: IModify, 22 | context: SlashCommandContext, 23 | room: IRoom, 24 | persistence: IPersistence 25 | ) { 26 | await authorize(app, read, modify, context.getSender(), room, persistence); 27 | } 28 | 29 | export async function handleLogout( 30 | app: GithubApp, 31 | read: IRead, 32 | modify: IModify, 33 | context: SlashCommandContext, 34 | room: IRoom, 35 | persistence: IPersistence, 36 | sender: IUser, 37 | http: IHttp 38 | ) { 39 | let accessToken = await getAccessTokenForUser( 40 | read, 41 | context.getSender(), 42 | app.oauth2Config 43 | ); 44 | if (accessToken && accessToken?.token) { 45 | await revokeUserAccessToken( 46 | read, 47 | sender, 48 | persistence, 49 | http, 50 | app.oauth2Config 51 | ); 52 | await sendNotification( 53 | read, 54 | modify, 55 | context.getSender(), 56 | room, 57 | "Logged out successfully !" 58 | ); 59 | } else { 60 | await sendNotification( 61 | read, 62 | modify, 63 | context.getSender(), 64 | room, 65 | "You are not logged in !" 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /github/lib/pullReqeustListMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IMessageBuilder, 4 | IModify, 5 | IModifyCreator, 6 | IPersistence, 7 | IRead, 8 | } from "@rocket.chat/apps-engine/definition/accessors"; 9 | import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 10 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 11 | 12 | export async function pullRequestListMessage({ 13 | repository, 14 | room, 15 | read, 16 | persistence, 17 | modify, 18 | http, 19 | accessToken, 20 | }: { 21 | repository : String, 22 | room: IRoom; 23 | read: IRead; 24 | persistence: IPersistence; 25 | modify: IModify; 26 | http: IHttp; 27 | accessToken?: IAuthData; 28 | }) { 29 | 30 | let gitResponse:any; 31 | if(accessToken?.token){ 32 | gitResponse = await http.get(`https://api.github.com/repos/${repository}/pulls`, { 33 | headers: { 34 | Authorization: `token ${accessToken?.token}`, 35 | "Content-Type": "application/json", 36 | }, 37 | }); 38 | } else { 39 | gitResponse = await http.get( 40 | `https://api.github.com/repos/${repository}/pulls` 41 | ); 42 | } 43 | const resData = gitResponse.data; 44 | const textSender = await modify 45 | .getCreator() 46 | .startMessage() 47 | .setText(`*PULL REQUESTS*`); 48 | 49 | if (room) { 50 | textSender.setRoom(room); 51 | } 52 | 53 | await modify.getCreator().finish(textSender); 54 | resData.forEach(async (pull, ind) => { 55 | if (ind < 10) { 56 | const url = pull.html_url; 57 | const textSender = await modify 58 | .getCreator() 59 | .startMessage() 60 | .setText(`[ #${pull.number} ](${url}) *${pull.title}*`); 61 | 62 | if (room) { 63 | textSender.setRoom(room); 64 | } 65 | await modify.getCreator().finish(textSender); 66 | } 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /github/lib/contributorListMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 8 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 9 | 10 | export async function contributorListMessage({ 11 | repository, 12 | room, 13 | read, 14 | persistence, 15 | modify, 16 | http, 17 | accessToken, 18 | }: { 19 | repository : String, 20 | room: IRoom; 21 | read: IRead; 22 | persistence: IPersistence; 23 | modify: IModify; 24 | http: IHttp; 25 | accessToken?: IAuthData; 26 | }) { 27 | let gitResponse: any; 28 | if(accessToken?.token){ 29 | gitResponse = await http.get(`https://api.github.com/repos/${repository}/contributors`, { 30 | headers: { 31 | Authorization: `token ${accessToken?.token}`, 32 | "Content-Type": "application/json", 33 | }, 34 | }); 35 | } else { 36 | gitResponse = await http.get( 37 | `https://api.github.com/repos/${repository}/contributors` 38 | ); 39 | } 40 | const resData = gitResponse.data; 41 | const textSender = await modify 42 | .getCreator() 43 | .startMessage() 44 | .setText(`*CONTRIBUTOR LIST*`); 45 | 46 | if (room) { 47 | textSender.setRoom(room); 48 | } 49 | 50 | await modify.getCreator().finish(textSender); 51 | resData.forEach(async (contributor, ind) => { 52 | if (ind < 20) { 53 | const login = contributor.login; 54 | const html_url = contributor.html_url; 55 | 56 | const textSender = await modify 57 | .getCreator() 58 | .startMessage() 59 | .setText(`[ ${login} ](${html_url})`); 60 | 61 | if (room) { 62 | textSender.setRoom(room); 63 | } 64 | 65 | await modify.getCreator().finish(textSender); 66 | } 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .rc.yaml 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | 107 | #RC Server and APP Testing 108 | RC_SERVER 109 | TESTING_APPS -------------------------------------------------------------------------------- /github/lib/notifications.ts: -------------------------------------------------------------------------------- 1 | import { IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; 2 | import { RocketChatAssociationModel, RocketChatAssociationRecord } from '@rocket.chat/apps-engine/definition/metadata/RocketChatAssociations'; 3 | import { IUser } from '@rocket.chat/apps-engine/definition/users/IUser'; 4 | 5 | interface INotificationsStatus { 6 | status: boolean; 7 | } 8 | 9 | export class NotificationsController { 10 | private read: IRead; 11 | private persistence: IPersistence; 12 | private association: RocketChatAssociationRecord; 13 | private userAssociation: RocketChatAssociationRecord; 14 | 15 | constructor(read: IRead, persistence: IPersistence, user: IUser) { 16 | this.read = read; 17 | this.persistence = persistence; 18 | this.association = new RocketChatAssociationRecord( 19 | RocketChatAssociationModel.MISC, 20 | `github-notifications`, 21 | ); 22 | 23 | this.userAssociation = new RocketChatAssociationRecord( 24 | RocketChatAssociationModel.USER, 25 | user.id, 26 | ); 27 | } 28 | 29 | public async getNotificationsStatus(): Promise { 30 | 31 | const [record] = await this.read 32 | .getPersistenceReader() 33 | .readByAssociations([this.association, this.userAssociation]); 34 | 35 | return record as INotificationsStatus; 36 | } 37 | 38 | public async setNotificationsStatus(status: boolean): Promise { 39 | await this.persistence.createWithAssociations({ status } , [this.association, this.userAssociation]); 40 | return status; 41 | } 42 | 43 | public async updateNotificationsStatus(status: boolean) { 44 | const notificationsStatus = await this.getNotificationsStatus(); 45 | 46 | if (!notificationsStatus) { 47 | return await this.setNotificationsStatus(status); 48 | } 49 | 50 | await this.persistence.updateByAssociations([this.association, this.userAssociation], { status }); 51 | 52 | return status; 53 | } 54 | 55 | public async deleteNotifications(): Promise { 56 | await this.persistence.removeByAssociations([this.association, this.userAssociation]); 57 | } 58 | } -------------------------------------------------------------------------------- /github/lib/issuesListMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IMessageBuilder, 4 | IModify, 5 | IModifyCreator, 6 | IPersistence, 7 | IRead, 8 | } from "@rocket.chat/apps-engine/definition/accessors"; 9 | import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 10 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 11 | import { 12 | ISlashCommand, 13 | SlashCommandContext, 14 | } from "@rocket.chat/apps-engine/definition/slashcommands"; 15 | 16 | export async function issueListMessage({ 17 | repository, 18 | room, 19 | read, 20 | persistence, 21 | modify, 22 | http, 23 | accessToken, 24 | }: { 25 | repository : String, 26 | room: IRoom; 27 | read: IRead; 28 | persistence: IPersistence; 29 | modify: IModify; 30 | http: IHttp; 31 | accessToken?: IAuthData; 32 | }) { 33 | let gitResponse:any; 34 | if(accessToken?.token){ 35 | gitResponse = await http.get(`https://api.github.com/repos/${repository}/issues`, { 36 | headers: { 37 | Authorization: `token ${accessToken?.token}`, 38 | "Content-Type": "application/json", 39 | }, 40 | }); 41 | } else { 42 | gitResponse = await http.get( 43 | `https://api.github.com/repos/${repository}/issues` 44 | ); 45 | } 46 | const resData = gitResponse.data; 47 | const textSender = await modify 48 | .getCreator() 49 | .startMessage() 50 | .setText(`*ISSUES LIST*`); 51 | 52 | if (room) { 53 | textSender.setRoom(room); 54 | } 55 | let ind = 0; 56 | await modify.getCreator().finish(textSender); 57 | resData.forEach(async (issue) => { 58 | if (typeof issue.pull_request === "undefined" && ind < 10) { 59 | issue.title = issue.title.replace(/[\[\]()`]/g, ""); 60 | const textSender = await modify 61 | .getCreator() 62 | .startMessage() 63 | .setText( 64 | `[ #${issue.number} ](${issue.html_url}) *[${issue.title}](${issue.html_url})*` 65 | ); 66 | if (room) { 67 | textSender.setRoom(room); 68 | } 69 | await modify.getCreator().finish(textSender); 70 | ind++; 71 | } 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /github/lib/helperMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 8 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 9 | 10 | export async function helperMessage({ 11 | room, 12 | read, 13 | persistence, 14 | modify, 15 | http, 16 | user 17 | }: { 18 | room: IRoom; 19 | read: IRead; 20 | persistence: IPersistence; 21 | modify: IModify; 22 | http: IHttp; 23 | user?: IUser; 24 | }) { 25 | let helperMessageString = `### Github App 26 | *The app can be accessed with any of the slash commands /gh or /github* 27 | 1. See Interactive Button interface to fetch repository data -> \`/github Username/RepositoryName\` 28 | 2. Get details of a Repository -> \`/github Username/RepositoryName repo\` 29 | 3. Get Issues of a Repository -> \`/github Username/RepositoryName issues\` 30 | 4. Get Contributors of a Repository -> \`/github Username/RepositoryName contributors\` 31 | 5. Get Recent Pull Request of a Repository -> \`/github Username/RepositoryName pulls\` 32 | 6. Review a Pull Request -> \`/github Username/RepositoryName pulls pullNumber\` 33 | 7. Login to GitHub -> \`/github login\` 34 | 8. Logout from GitHub -> \`/github logout\` 35 | 9. View your GitHub Profile and Issues -> \`/github me\` 36 | 10. View/Add/Delete/Update Repository Subscriptions -> \`/github subscribe\` 37 | 11. Subscribe to all repository events -> \`/github Username/RepositoryName subscribe\` 38 | 12. Unsubscribe to all repository events -> \`/github Username/RepositoryName unsubscribe\` 39 | 13. Add New Issues to GitHub Repository -> \`/github issue\` 40 | 14. Search Issues and Pull Requests -> \`/github search\` 41 | 15. Assign and Share Issues -> \`/github issues\` 42 | 16. Add a new repository for pull request review reminders -> \`/github reminder create\` 43 | 17. Get a list of repositories for which you've set up pull request review reminders -> \`/github reminder list\` 44 | `; 45 | 46 | const textSender = await modify 47 | .getCreator() 48 | .startMessage() 49 | .setText(`${helperMessageString}`); 50 | 51 | if (room) { 52 | textSender.setRoom(room); 53 | } 54 | 55 | await modify.getNotifier().notifyUser(user as IUser, textSender.getMessage()); 56 | } 57 | -------------------------------------------------------------------------------- /github/lib/repoDataMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 8 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 9 | 10 | 11 | export async function repoDataMessage({ 12 | repository, 13 | room, 14 | read, 15 | persistence, 16 | modify, 17 | http, 18 | accessToken, 19 | }: { 20 | repository : String, 21 | room: IRoom; 22 | read: IRead; 23 | persistence: IPersistence; 24 | modify: IModify; 25 | http: IHttp; 26 | accessToken?: IAuthData; 27 | }){ 28 | let gitResponse:any; 29 | if(accessToken?.token){ 30 | gitResponse = await http.get( `https://api.github.com/repos/${repository}`, { 31 | headers: { 32 | Authorization: `token ${accessToken?.token}`, 33 | "Content-Type": "application/json", 34 | }, 35 | }); 36 | } else { 37 | gitResponse = await http.get( 38 | `https://api.github.com/repos/${repository}` 39 | ); 40 | } 41 | const resData = gitResponse.data; 42 | const fullName = 43 | "[" + 44 | resData.full_name + 45 | "](" + 46 | resData.html_url + 47 | ")" + 48 | " ▫️ "; 49 | const stars = 50 | "` ⭐ Stars " + resData.stargazers_count + " ` "; 51 | const issues = "` ❗ Issues " + resData.open_issues + " ` "; 52 | const forks = "` 🍴 Forks " + resData.forks_count + " ` "; 53 | let tags = ""; 54 | if ( 55 | resData && 56 | resData.topics && 57 | Array.isArray(resData.topics) 58 | ) { 59 | resData.topics.forEach((topic: string) => { 60 | let tempTopic = " ` "; 61 | tempTopic += topic; 62 | tempTopic += " ` "; 63 | tags += tempTopic; 64 | }); 65 | } 66 | 67 | const description = resData.description || 'No description provided.'; 68 | 69 | const textSender = await modify 70 | .getCreator() 71 | .startMessage() 72 | .setText( 73 | fullName + 74 | stars + 75 | issues + 76 | forks + 77 | "`" + 78 | description + 79 | "`" + 80 | tags 81 | ); 82 | if (room) { 83 | textSender.setRoom(room); 84 | } 85 | await modify.getCreator().finish(textSender); 86 | } -------------------------------------------------------------------------------- /github/handlers/HandleIssues.ts: -------------------------------------------------------------------------------- 1 | import { IRead, IPersistence, IHttp, IModify } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { GithubApp } from "../GithubApp"; 5 | import { sendNotification } from "../lib/message"; 6 | import { NewIssueStarterModal } from "../modals/newIssueStarterModal"; 7 | import { getAccessTokenForUser } from "../persistance/auth"; 8 | import { GitHubIssuesStarterModal } from "../modals/getIssuesStarterModal"; 9 | 10 | export async function handleNewIssue( 11 | read: IRead, 12 | context: SlashCommandContext, 13 | app: GithubApp, 14 | persistence: IPersistence, 15 | http: IHttp, 16 | room: IRoom, 17 | modify: IModify 18 | ){ 19 | let accessToken = await getAccessTokenForUser( 20 | read, 21 | context.getSender(), 22 | app.oauth2Config 23 | ); 24 | if (accessToken && accessToken.token) { 25 | const triggerId = context.getTriggerId(); 26 | if (triggerId) { 27 | const modal = await NewIssueStarterModal({ 28 | modify: modify, 29 | read: read, 30 | persistence: persistence, 31 | http: http, 32 | slashcommandcontext: context, 33 | }); 34 | await modify 35 | .getUiController() 36 | .openModalView( 37 | modal, 38 | { triggerId }, 39 | context.getSender() 40 | ); 41 | } else { 42 | console.log("invalid Trigger ID !"); 43 | } 44 | } else { 45 | await sendNotification( 46 | read, 47 | modify, 48 | context.getSender(), 49 | room, 50 | "Login to subscribe to repository events ! `/github login`" 51 | ); 52 | } 53 | } 54 | 55 | export async function handleIssues( 56 | read: IRead, 57 | context: SlashCommandContext, 58 | app: GithubApp, 59 | persistence: IPersistence, 60 | http: IHttp, 61 | room: IRoom, 62 | modify: IModify 63 | ){ 64 | const triggerId= context.getTriggerId(); 65 | if(triggerId){ 66 | const modal = await GitHubIssuesStarterModal({modify,read,persistence,http,slashcommandcontext:context}); 67 | await modify.getUiController().openModalView(modal,{triggerId},context.getSender()); 68 | }else{ 69 | console.log("invalid Trigger ID !"); 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /github/lib/initiatorMessage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IMessageBuilder, 4 | IModify, 5 | IModifyCreator, 6 | IPersistence, 7 | IRead, 8 | } from "@rocket.chat/apps-engine/definition/accessors"; 9 | import { ButtonStyle } from "@rocket.chat/apps-engine/definition/uikit"; 10 | 11 | export async function initiatorMessage({ 12 | data, 13 | read, 14 | persistence, 15 | modify, 16 | http, 17 | }: { 18 | data; 19 | read: IRead; 20 | persistence: IPersistence; 21 | modify: IModify; 22 | http: IHttp; 23 | }) { 24 | const greetBuilder = await modify 25 | .getCreator() 26 | .startMessage() 27 | .setRoom(data.room) 28 | .setText(`Hey ${data.sender.username} !`); 29 | 30 | if (data.room.type !== "l") { 31 | await modify 32 | .getNotifier() 33 | .notifyUser(data.sender, greetBuilder.getMessage()); 34 | } else { 35 | await modify.getCreator().finish(greetBuilder); 36 | } 37 | 38 | const builder = await modify.getCreator().startMessage().setRoom(data.room); 39 | 40 | const block = modify.getCreator().getBlockBuilder(); 41 | 42 | block.addSectionBlock({ 43 | text: block.newPlainTextObject( 44 | `Choose Action for ${data.arguments[0]} 👇 ?` 45 | ), 46 | }); 47 | 48 | block.addActionsBlock({ 49 | blockId: "githubdata", 50 | elements: [ 51 | block.newButtonElement({ 52 | actionId: "githubDataSelect", 53 | text: block.newPlainTextObject("Overview"), 54 | value: `${data.arguments[0]}/repo`, 55 | style: ButtonStyle.PRIMARY, 56 | }), 57 | block.newButtonElement({ 58 | actionId: "githubDataSelect", 59 | text: block.newPlainTextObject("Issues"), 60 | value: `${data.arguments[0]}/issues`, 61 | style: ButtonStyle.DANGER, 62 | }), 63 | block.newButtonElement({ 64 | actionId: "githubDataSelect", 65 | text: block.newPlainTextObject("Contributors"), 66 | value: `${data.arguments[0]}/contributors`, 67 | style: ButtonStyle.PRIMARY, 68 | }), 69 | block.newButtonElement({ 70 | actionId: "githubDataSelect", 71 | text: block.newPlainTextObject("Pull Requests"), 72 | value: `${data.arguments[0]}/pulls`, 73 | style: ButtonStyle.PRIMARY, 74 | }), 75 | ], 76 | }); 77 | 78 | builder.setBlocks(block); 79 | 80 | await modify.getNotifier().notifyUser(data.sender, builder.getMessage()); 81 | } 82 | -------------------------------------------------------------------------------- /github/modals/fileCodeModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | 19 | 20 | export async function fileCodeModal({ 21 | data, 22 | modify, 23 | read, 24 | persistence, 25 | http, 26 | slashcommandcontext, 27 | uikitcontext, 28 | }: { 29 | data; 30 | modify: IModify; 31 | read: IRead; 32 | persistence: IPersistence; 33 | http: IHttp; 34 | slashcommandcontext?: SlashCommandContext; 35 | uikitcontext?: UIKitInteractionContext; 36 | }): Promise { 37 | const viewId = ModalsEnum.CODE_VIEW; 38 | 39 | const block = modify.getCreator().getBlockBuilder(); 40 | 41 | const room = 42 | slashcommandcontext?.getRoom() || 43 | uikitcontext?.getInteractionData().room; 44 | const user = 45 | slashcommandcontext?.getSender() || 46 | uikitcontext?.getInteractionData().user; 47 | 48 | if (user?.id) { 49 | let roomId; 50 | const pullRawData = await http.get(data.value); 51 | const pullData = pullRawData.content; 52 | block.addSectionBlock({ 53 | text: { text: `${pullData}`, type: TextObjectType.MARKDOWN }, 54 | }); 55 | 56 | // shows indentations in input blocks but not inn section block 57 | // block.addInputBlock({ 58 | // blockId: ModalsEnum.CODE_VIEW, 59 | // label: { text: ModalsEnum.CODE_VIEW_LABEL, type: TextObjectType.PLAINTEXT }, 60 | // element: block.newPlainTextInputElement({ 61 | // initialValue : `${pullData}`, 62 | // multiline:true, 63 | // actionId: ModalsEnum.CODE_INPUT, 64 | // }) 65 | // }); 66 | } 67 | 68 | block.addDividerBlock(); 69 | 70 | return { 71 | id: viewId, 72 | title: { 73 | type: TextObjectType.PLAINTEXT, 74 | text: AppEnum.DEFAULT_TITLE, 75 | }, 76 | close: block.newButtonElement({ 77 | text: { 78 | type: TextObjectType.PLAINTEXT, 79 | text: "Close", 80 | }, 81 | }), 82 | blocks: block.getBlocks(), 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /github/handlers/GitHubCodeSegmentHandler.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 2 | import { IHttp, IRead } from "@rocket.chat/apps-engine/definition/accessors"; 3 | import { IMessage, IMessageAttachment } from "@rocket.chat/apps-engine/definition/messages"; 4 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 5 | import { IMessageExtender } from "@rocket.chat/apps-engine/definition/accessors"; 6 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit"; 7 | import { GitHubURLEnum } from "../enum/GitHubURL"; 8 | 9 | async function extractCodeSnippetFromURL(content: string, url: string): Promise { 10 | const lineRangeRegex: RegExp = /(?:L(\d+)+-L(\d+)|L(\d+))/; 11 | const lineRangeMatch: RegExpMatchArray | null = url.match(lineRangeRegex); 12 | 13 | if (lineRangeMatch) { 14 | return extractCodeSnippetByLineRange(content, lineRangeMatch); 15 | } 16 | 17 | return ""; 18 | } 19 | 20 | function extractCodeSnippetByLineRange(content: string, lineRangeMatch: RegExpMatchArray): string { 21 | const [_, startLine, endLine, singleLine] = lineRangeMatch; 22 | 23 | const lineOffset = singleLine ? parseInt(singleLine) : parseInt(startLine) - 1; 24 | const lineCount = singleLine ? 1 : parseInt(endLine) - parseInt(startLine) + 1; 25 | 26 | const linesRegex = `(?:.*\n){${lineOffset}}(.*(?:\n.*){${lineCount}})`; 27 | const lines = new RegExp(linesRegex); 28 | const match = content.match(lines); 29 | 30 | return match?.[1] ?? ""; 31 | } 32 | 33 | async function fetchGitHubContent(http: IHttp, modifiedUrl: string): Promise { 34 | const response: any = await http.get(modifiedUrl); 35 | const { content } = response; 36 | return content; 37 | } 38 | 39 | function buildCodeSnippetAttachment(codeSnippet: string, url: string): IMessageAttachment { 40 | const attachment: IMessageAttachment = { 41 | text: `\`\`\`\n${codeSnippet}\n\`\`\` \n[Show more...](${url})`, 42 | type: TextObjectType.MARKDOWN, 43 | }; 44 | return attachment; 45 | } 46 | 47 | export async function handleGitHubCodeSegmentLink( 48 | message: IMessage, 49 | read: IRead, 50 | http: IHttp, 51 | user: IUser, 52 | room: IRoom, 53 | extend: IMessageExtender 54 | ) { 55 | const urlRegex: RegExp = /\bhttps?:\/\/github\.com\/\S+\b/; 56 | const messageText: string = message.text!; 57 | const urlMatch: RegExpMatchArray | null = messageText.match(urlRegex); 58 | const url: string | undefined = urlMatch?.[0]; 59 | let modifiedUrl: string = url?.replace(GitHubURLEnum.PREFIX, "")!; 60 | modifiedUrl = modifiedUrl.replace(GitHubURLEnum.HOST, GitHubURLEnum.RAW_HOST); 61 | 62 | const content: string = await fetchGitHubContent(http, modifiedUrl); 63 | const codeSnippet = await extractCodeSnippetFromURL(content, modifiedUrl); 64 | 65 | if (codeSnippet) { 66 | const attachment: IMessageAttachment = buildCodeSnippetAttachment(codeSnippet, url!); 67 | extend.addAttachment(attachment); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /github/modals/newreminderModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { ModalsEnum } from "../enum/Modals"; 10 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 11 | import { 12 | UIKitInteractionContext, 13 | } from "@rocket.chat/apps-engine/definition/uikit"; 14 | import { 15 | storeInteractionRoomData, 16 | getInteractionRoomData, 17 | } from "../persistance/roomInteraction"; 18 | 19 | export async function NewReminderStarterModal({ 20 | modify, 21 | read, 22 | persistence, 23 | http, 24 | slashcommandcontext, 25 | uikitcontext, 26 | }: { 27 | modify: IModify; 28 | read: IRead; 29 | persistence: IPersistence; 30 | http: IHttp; 31 | slashcommandcontext?: SlashCommandContext; 32 | uikitcontext?: UIKitInteractionContext; 33 | }): Promise { 34 | const viewId = ModalsEnum.NEW_REMINDER_VIEW; 35 | const block = modify.getCreator().getBlockBuilder(); 36 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 37 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 38 | 39 | if (user?.id) { 40 | let roomId; 41 | 42 | if (room?.id) { 43 | roomId = room.id; 44 | await storeInteractionRoomData(persistence, user.id, roomId); 45 | } else { 46 | roomId = ( 47 | await getInteractionRoomData( 48 | read.getPersistenceReader(), 49 | user.id 50 | ) 51 | ).roomId; 52 | } 53 | 54 | block.addInputBlock({ 55 | blockId: ModalsEnum.REPO_NAME_INPUT, 56 | label: { 57 | text: ModalsEnum.REPO_NAME_LABEL, 58 | type: TextObjectType.PLAINTEXT, 59 | }, 60 | element: block.newPlainTextInputElement({ 61 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 62 | placeholder: { 63 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 64 | type: TextObjectType.PLAINTEXT, 65 | }, 66 | }), 67 | }); 68 | 69 | } 70 | 71 | block.addDividerBlock(); 72 | 73 | return { 74 | id: viewId, 75 | title: { 76 | type: TextObjectType.PLAINTEXT, 77 | text: 'Create New reminder', 78 | }, 79 | close: block.newButtonElement({ 80 | text: { 81 | type: TextObjectType.PLAINTEXT, 82 | text: "Close", 83 | }, 84 | }), 85 | submit: block.newButtonElement({ 86 | actionId: "new-reminder-action", 87 | text: { 88 | type: TextObjectType.PLAINTEXT, 89 | emoji:true, 90 | text: "Create", 91 | }, 92 | }), 93 | blocks: block.getBlocks(), 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /github/modals/getIssuesStarterModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { ModalsEnum } from "../enum/Modals"; 10 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 11 | import { 12 | UIKitInteractionContext, 13 | } from "@rocket.chat/apps-engine/definition/uikit"; 14 | import { 15 | storeInteractionRoomData, 16 | getInteractionRoomData, 17 | } from "../persistance/roomInteraction"; 18 | 19 | export async function GitHubIssuesStarterModal({ 20 | modify, 21 | read, 22 | persistence, 23 | http, 24 | slashcommandcontext, 25 | uikitcontext, 26 | }: { 27 | modify: IModify; 28 | read: IRead; 29 | persistence: IPersistence; 30 | http: IHttp; 31 | slashcommandcontext?: SlashCommandContext; 32 | uikitcontext?: UIKitInteractionContext; 33 | }): Promise { 34 | const viewId = ModalsEnum.GITHUB_ISSUES_STARTER_VIEW; 35 | const block = modify.getCreator().getBlockBuilder(); 36 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 37 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 38 | 39 | if (user?.id) { 40 | let roomId; 41 | 42 | if (room?.id) { 43 | roomId = room.id; 44 | await storeInteractionRoomData(persistence, user.id, roomId); 45 | } else { 46 | roomId = ( 47 | await getInteractionRoomData( 48 | read.getPersistenceReader(), 49 | user.id 50 | ) 51 | ).roomId; 52 | } 53 | 54 | block.addInputBlock({ 55 | blockId: ModalsEnum.REPO_NAME_INPUT, 56 | label: { 57 | text: ModalsEnum.REPO_NAME_LABEL, 58 | type: TextObjectType.PLAINTEXT, 59 | }, 60 | element: block.newPlainTextInputElement({ 61 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 62 | placeholder: { 63 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 64 | type: TextObjectType.PLAINTEXT, 65 | }, 66 | }), 67 | }); 68 | } 69 | 70 | block.addDividerBlock(); 71 | 72 | return { 73 | id: viewId, 74 | title: { 75 | type: TextObjectType.PLAINTEXT, 76 | text: ModalsEnum.GITHUB_ISSUES_TITLE, 77 | }, 78 | close: block.newButtonElement({ 79 | text: { 80 | type: TextObjectType.PLAINTEXT, 81 | text: "Close", 82 | }, 83 | }), 84 | submit: block.newButtonElement({ 85 | actionId: ModalsEnum.NEW_ISSUE_STARTER__ACTION, 86 | text: { 87 | type: TextObjectType.PLAINTEXT, 88 | emoji:true, 89 | text: "Next 🚀", 90 | }, 91 | }), 92 | blocks: block.getBlocks(), 93 | }; 94 | } 95 | -------------------------------------------------------------------------------- /github/modals/remindersModal.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; 2 | import { ButtonStyle, ITextObject, TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; 3 | import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; 4 | import { ModalsEnum } from '../enum/Modals'; 5 | import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; 6 | import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; 7 | import { getUserReminder } from '../persistance/remind'; 8 | import { IReminder } from '../definitions/Reminder'; 9 | 10 | export async function reminderModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp, slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { 11 | const viewId = ModalsEnum.REMINDER_LIST_MODAL_VIEW; 12 | 13 | const block = modify.getCreator().getBlockBuilder(); 14 | 15 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 16 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 17 | 18 | block.addDividerBlock(); 19 | 20 | if (user?.id) { 21 | const reminders: IReminder = await getUserReminder(read, user); 22 | 23 | if (reminders && reminders.repos.length > 0) { 24 | for (let repository of reminders.repos) { 25 | block.addSectionBlock({ 26 | text: { text: `${repository}`, type: TextObjectType.PLAINTEXT }, 27 | }); 28 | 29 | block.addActionsBlock({ 30 | blockId:ModalsEnum.REMINDER_LIST_MODAL, 31 | 32 | elements:[ 33 | block.newButtonElement({ 34 | actionId: ModalsEnum.OPEN_REPO_ACTION, 35 | text: block.newPlainTextObject("Open"), 36 | url:`https://github.com/${repository}`, 37 | style: ButtonStyle.PRIMARY, 38 | 39 | }), 40 | block.newButtonElement({ 41 | actionId: ModalsEnum.REMINDER_REMOVE_REPO_ACTION, 42 | text: block.newPlainTextObject("Remove"), 43 | value: `${repository}`, 44 | style: ButtonStyle.DANGER, 45 | })] 46 | }) 47 | 48 | block.addDividerBlock(); 49 | } 50 | } else { 51 | block.addSectionBlock({ 52 | text: { text: "You have no reminders.", type: TextObjectType.PLAINTEXT } 53 | }); 54 | } 55 | } 56 | 57 | return { 58 | id: viewId, 59 | title: { 60 | type: TextObjectType.PLAINTEXT, 61 | text: "Reminder List", 62 | }, 63 | close: block.newButtonElement({ 64 | text: { 65 | type: TextObjectType.PLAINTEXT, 66 | text: 'Close', 67 | }, 68 | }), 69 | blocks: block.getBlocks(), 70 | }; 71 | } -------------------------------------------------------------------------------- /github/modals/newIssueStarterModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { ModalsEnum } from "../enum/Modals"; 10 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 11 | import { 12 | UIKitInteractionContext, 13 | } from "@rocket.chat/apps-engine/definition/uikit"; 14 | import { 15 | storeInteractionRoomData, 16 | getInteractionRoomData, 17 | } from "../persistance/roomInteraction"; 18 | 19 | export async function NewIssueStarterModal({ 20 | modify, 21 | read, 22 | persistence, 23 | http, 24 | slashcommandcontext, 25 | uikitcontext, 26 | }: { 27 | modify: IModify; 28 | read: IRead; 29 | persistence: IPersistence; 30 | http: IHttp; 31 | slashcommandcontext?: SlashCommandContext; 32 | uikitcontext?: UIKitInteractionContext; 33 | }): Promise { 34 | const viewId = ModalsEnum.NEW_ISSUE_STARTER_VIEW; 35 | const block = modify.getCreator().getBlockBuilder(); 36 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 37 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 38 | 39 | if (user?.id) { 40 | let roomId; 41 | 42 | if (room?.id) { 43 | roomId = room.id; 44 | await storeInteractionRoomData(persistence, user.id, roomId); 45 | } else { 46 | roomId = ( 47 | await getInteractionRoomData( 48 | read.getPersistenceReader(), 49 | user.id 50 | ) 51 | ).roomId; 52 | } 53 | 54 | // shows indentations in input blocks but not inn section block 55 | block.addInputBlock({ 56 | blockId: ModalsEnum.REPO_NAME_INPUT, 57 | label: { 58 | text: ModalsEnum.REPO_NAME_LABEL, 59 | type: TextObjectType.PLAINTEXT, 60 | }, 61 | element: block.newPlainTextInputElement({ 62 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 63 | placeholder: { 64 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 65 | type: TextObjectType.PLAINTEXT, 66 | }, 67 | }), 68 | }); 69 | 70 | } 71 | 72 | block.addDividerBlock(); 73 | 74 | return { 75 | id: viewId, 76 | title: { 77 | type: TextObjectType.PLAINTEXT, 78 | text: ModalsEnum.NEW_ISSUE_TITLE, 79 | }, 80 | close: block.newButtonElement({ 81 | text: { 82 | type: TextObjectType.PLAINTEXT, 83 | text: "Close", 84 | }, 85 | }), 86 | submit: block.newButtonElement({ 87 | actionId: ModalsEnum.NEW_ISSUE_STARTER__ACTION, 88 | text: { 89 | type: TextObjectType.PLAINTEXT, 90 | emoji:true, 91 | text: "Next 🚀", 92 | }, 93 | }), 94 | blocks: block.getBlocks(), 95 | }; 96 | } 97 | -------------------------------------------------------------------------------- /github/handlers/HandleRemider.ts: -------------------------------------------------------------------------------- 1 | import { IRead, IPersistence, IHttp, IModify } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 3 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 4 | import { GithubApp } from "../GithubApp"; 5 | import { sendNotification } from "../lib/message"; 6 | import { NewIssueStarterModal } from "../modals/newIssueStarterModal"; 7 | import { getAccessTokenForUser } from "../persistance/auth"; 8 | import { GitHubIssuesStarterModal } from "../modals/getIssuesStarterModal"; 9 | import { NewReminderStarterModal } from "../modals/newreminderModal"; 10 | import { reminderModal } from "../modals/remindersModal"; 11 | 12 | export async function handleReminder( 13 | read: IRead, 14 | context: SlashCommandContext, 15 | app: GithubApp, 16 | persistence: IPersistence, 17 | http: IHttp, 18 | room: IRoom, 19 | modify: IModify 20 | ){ 21 | let accessToken = await getAccessTokenForUser( 22 | read, 23 | context.getSender(), 24 | app.oauth2Config 25 | ); 26 | if (accessToken && accessToken.token) { 27 | const triggerId = context.getTriggerId(); 28 | if (triggerId) { 29 | const modal = await NewReminderStarterModal({ 30 | modify: modify, 31 | read: read, 32 | persistence: persistence, 33 | http: http, 34 | slashcommandcontext: context, 35 | }); 36 | await modify 37 | .getUiController() 38 | .openModalView( 39 | modal, 40 | { triggerId }, 41 | context.getSender() 42 | ); 43 | } else { 44 | console.log("invalid Trigger ID !"); 45 | } 46 | } else { 47 | await sendNotification( 48 | read, 49 | modify, 50 | context.getSender(), 51 | room, 52 | "Login to add Pull Request reminder! `/github login`" 53 | ); 54 | } 55 | } 56 | 57 | export async function ManageReminders( 58 | read:IRead, 59 | context:SlashCommandContext, 60 | app:GithubApp, 61 | persistence:IPersistence, 62 | http:IHttp, 63 | room:IRoom, 64 | modify:IModify 65 | ){ 66 | let accessToken = await getAccessTokenForUser( 67 | read, 68 | context.getSender(), 69 | app.oauth2Config 70 | ); 71 | if (accessToken && accessToken.token) { 72 | const triggerId = context.getTriggerId(); 73 | if (triggerId) { 74 | const modal = await reminderModal({ 75 | modify: modify, 76 | read: read, 77 | persistence: persistence, 78 | http: http, 79 | slashcommandcontext: context, 80 | }); 81 | await modify 82 | .getUiController() 83 | .openModalView(modal, { triggerId }, context.getSender()); 84 | } else { 85 | console.log("Invalid Trigger ID !"); 86 | } 87 | } else { 88 | await sendNotification( 89 | read, 90 | modify, 91 | context.getSender(), 92 | room, 93 | "Login to see to pull request reminders! `/github login`" 94 | ); 95 | } 96 | } -------------------------------------------------------------------------------- /github/processors/bodyMarkdowmRenderer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BlockBuilder, 3 | TextObjectType, 4 | } from "@rocket.chat/apps-engine/definition/uikit"; 5 | 6 | 7 | function cleanHeadingSyntax(text: string) : string{ 8 | try { 9 | text = text.replace(/(#{3}\s)(.*)/g, (val) => `*${val.substring(3, val.length).trim()}*`); 10 | text = text.replace(/(#{2}\s)(.*)/g, (val) => `*${val.substring(3, val.length).trim()}*`); 11 | text = text.replace(/(#{1}\s)(.*)/g, (val) => `*${val.substring(2, val.length).trim()}*`); 12 | text = text.replace(/\[ ] (?!\~|\[\^\d+])/g, (val) => `⭕ ${val.substring(3, val.length)}*`); 13 | text = text.replace(/\[X] (?!\~|\[\^\d+])/g, (val) => `✅ ${val.substring(3, val.length)}*`); 14 | } 15 | catch(e){ 16 | console.log(e); 17 | } 18 | return text; 19 | } 20 | 21 | 22 | export async function BodyMarkdownRenderer({ 23 | body, 24 | block, 25 | }: { 26 | body: string; 27 | block: BlockBuilder; 28 | }) { 29 | const imagePatterns: { type: string; pattern: RegExp }[] = [ 30 | { 31 | type: "ImageTag", 32 | pattern: RegExp(/()/g, "g"), 33 | }, 34 | { 35 | type: "ImageMarkdownLink", 36 | pattern: RegExp(/[!]\[([^\]]+)\]\(([^)]+(.png|jpg|svg|gif))\)/gim), 37 | }, 38 | ]; 39 | 40 | let matches: { beginningIndex: number; match: string; type: string }[] = []; 41 | var match; 42 | 43 | imagePatterns.forEach((patObj) => { 44 | while ((match = patObj.pattern.exec(body)) != null) { 45 | matches.push({ 46 | beginningIndex: match.index, 47 | match: match[0], 48 | type: patObj.type, 49 | }); 50 | } 51 | }); 52 | 53 | if (matches.length == 0) { 54 | block.addSectionBlock({ 55 | text: { 56 | text: cleanHeadingSyntax(body), 57 | type: TextObjectType.MARKDOWN, 58 | }, 59 | }); 60 | } else { 61 | matches.sort((a, b) => a.beginningIndex - b.beginningIndex); 62 | matches.map((value, index) => { 63 | let start = 64 | index == 0 65 | ? 0 66 | : matches[index - 1].beginningIndex + 67 | matches[index - 1].match.length; 68 | 69 | const rawURL = value.match.match( 70 | /(https:\/\/.*\.(png|jpg|gif|svg))/gim 71 | ); 72 | const url = rawURL ? rawURL[0] : ""; 73 | 74 | block.addContextBlock({ 75 | elements: [ 76 | block.newMarkdownTextObject( 77 | cleanHeadingSyntax(body.substring(start, value.beginningIndex - 1) ?? "") 78 | ), 79 | ], 80 | }); 81 | 82 | block.addImageBlock({ 83 | imageUrl: url, 84 | altText: "ImageURL", 85 | }); 86 | 87 | if ( 88 | index == matches.length - 1 && 89 | value.beginningIndex + value.match.length < body.length 90 | ) { 91 | block.addContextBlock({ 92 | elements: [ 93 | block.newMarkdownTextObject( 94 | cleanHeadingSyntax(body.substring( 95 | value.beginningIndex + value.match.length, 96 | body.length 97 | )) 98 | ), 99 | ], 100 | }); 101 | } 102 | }); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /github/modals/profileShareModal.ts: -------------------------------------------------------------------------------- 1 | import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 3 | import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; 4 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 5 | import { ModalsEnum } from "../enum/Modals"; 6 | import { getBasicUserInfo } from "../helpers/githubSDK"; 7 | import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; 8 | 9 | export async function shareProfileModal({ 10 | modify, 11 | read, 12 | persistence, 13 | http, 14 | slashcommandcontext, 15 | uikitcontext 16 | } : { 17 | modify : IModify, 18 | read: IRead, 19 | persistence: IPersistence, 20 | http: IHttp, 21 | slashcommandcontext?: SlashCommandContext, 22 | uikitcontext?: UIKitInteractionContext 23 | }) : Promise { 24 | 25 | const viewId = ModalsEnum.SHARE_PROFILE_EXEC; 26 | const block = modify.getCreator().getBlockBuilder(); 27 | 28 | block.addActionsBlock({ 29 | 30 | elements : [ 31 | block.newMultiStaticElement({ 32 | actionId: ModalsEnum.SHARE_PROFILE_PARAMS, 33 | initialValue: ['username', 'avatar', 'email', 'bio', 'followers', 'following'], 34 | options: [ 35 | { 36 | value: 'followers', 37 | text: { 38 | type: TextObjectType.PLAINTEXT, 39 | text: 'Followers', 40 | emoji: true, 41 | } 42 | }, 43 | { 44 | value: 'following', 45 | text: { 46 | type: TextObjectType.PLAINTEXT, 47 | text: 'Following', 48 | emoji: true, 49 | } 50 | }, 51 | { 52 | value : 'avatar', 53 | text : { 54 | text: "Avatar", 55 | type: TextObjectType.PLAINTEXT 56 | } 57 | }, 58 | { 59 | value : 'username', 60 | text : { 61 | text : "Github ID", 62 | type : TextObjectType.PLAINTEXT 63 | } 64 | }, 65 | { 66 | value: 'email', 67 | text: { 68 | type: TextObjectType.PLAINTEXT, 69 | text: 'Email', 70 | emoji: true, 71 | } 72 | }, 73 | { 74 | value : 'bio', 75 | text : { 76 | type: TextObjectType.PLAINTEXT, 77 | text: 'bio' 78 | } 79 | }, 80 | ], 81 | placeholder: { 82 | type: TextObjectType.PLAINTEXT, 83 | text: 'Select Property to Share', 84 | }, 85 | }), 86 | ] 87 | }) 88 | 89 | return { 90 | id: viewId, 91 | title: { 92 | type: TextObjectType.PLAINTEXT, 93 | text: "Share Profile" 94 | }, 95 | blocks: block.getBlocks(), 96 | submit:block.newButtonElement({ 97 | text : { 98 | text : "Share to Chat", 99 | type : TextObjectType.PLAINTEXT 100 | }, 101 | value : "shareChat" 102 | }) 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /github/modals/githubSearchResultsShareModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | import { IGitHubSearchResultData } from "../definitions/searchResultData"; 19 | import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; 20 | 21 | export async function githubSearchResultShareModal({ 22 | data, 23 | modify, 24 | read, 25 | persistence, 26 | http, 27 | slashcommandcontext, 28 | uikitcontext, 29 | }: { 30 | data: IGitHubSearchResultData; 31 | modify: IModify; 32 | read: IRead; 33 | persistence: IPersistence; 34 | http: IHttp; 35 | slashcommandcontext?: SlashCommandContext; 36 | uikitcontext?: UIKitInteractionContext; 37 | }): Promise { 38 | const viewId = ModalsEnum.SEARCH_RESULT_SHARE_VIEW; 39 | 40 | const block = modify.getCreator().getBlockBuilder(); 41 | 42 | const room = 43 | slashcommandcontext?.getRoom() || 44 | uikitcontext?.getInteractionData().room; 45 | const user = 46 | slashcommandcontext?.getSender() || 47 | uikitcontext?.getInteractionData().user; 48 | 49 | if (user?.id) { 50 | let roomId; 51 | 52 | if (room?.id) { 53 | roomId = room.id; 54 | await storeInteractionRoomData(persistence, user.id, roomId); 55 | } else { 56 | roomId = ( 57 | await getInteractionRoomData( 58 | read.getPersistenceReader(), 59 | user.id 60 | ) 61 | ).roomId; 62 | } 63 | let finalString = `${data.search_query} \n`; 64 | if(data.search_results?.length){ 65 | for(let searchResult of data.search_results){ 66 | if(searchResult.share){ 67 | let searchResultString = `${searchResult.result} `; 68 | finalString =`${finalString} \n${searchResultString}`; 69 | } 70 | } 71 | } 72 | 73 | block.addInputBlock({ 74 | blockId: ModalsEnum.MULTI_SHARE_SEARCH_INPUT, 75 | label: { 76 | text: ModalsEnum.MULTI_SHARE_SEARCH_INPUT_LABEL, 77 | type: TextObjectType.MARKDOWN 78 | }, 79 | element: block.newPlainTextInputElement({ 80 | initialValue : `${finalString}`, 81 | multiline:true, 82 | actionId: ModalsEnum.MULTI_SHARE_SEARCH_INPUT_ACTION, 83 | }) 84 | }); 85 | } 86 | 87 | block.addDividerBlock(); 88 | return { 89 | id: viewId, 90 | title: { 91 | type: TextObjectType.PLAINTEXT, 92 | text: ModalsEnum.SEARCH_RESULT_SHARE_VIEW_TITLE, 93 | }, 94 | submit: block.newButtonElement({ 95 | text: { 96 | type: TextObjectType.PLAINTEXT, 97 | text: "Send", 98 | }, 99 | }), 100 | close: block.newButtonElement({ 101 | text: { 102 | type: TextObjectType.PLAINTEXT, 103 | text: "Close", 104 | }, 105 | }), 106 | blocks: block.getBlocks(), 107 | }; 108 | } 109 | -------------------------------------------------------------------------------- /github/modals/issueTemplateSelectionModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | 19 | export async function issueTemplateSelectionModal({ 20 | data, 21 | modify, 22 | read, 23 | persistence, 24 | http, 25 | slashcommandcontext, 26 | uikitcontext, 27 | }: { 28 | data?; 29 | modify: IModify; 30 | read: IRead; 31 | persistence: IPersistence; 32 | http: IHttp; 33 | slashcommandcontext?: SlashCommandContext; 34 | uikitcontext?: UIKitInteractionContext; 35 | }): Promise { 36 | const viewId = ModalsEnum.ISSUE_TEMPLATE_SELECTION_VIEW; 37 | 38 | const block = modify.getCreator().getBlockBuilder(); 39 | 40 | const room = 41 | slashcommandcontext?.getRoom() || 42 | uikitcontext?.getInteractionData().room; 43 | const user = 44 | slashcommandcontext?.getSender() || 45 | uikitcontext?.getInteractionData().user; 46 | 47 | if (user?.id && data?.repository && data?.templates?.length) { 48 | 49 | let repositoryName = data.repository as string; 50 | let templates = data.templates as Array; 51 | block.addSectionBlock({ 52 | text: { 53 | text: `Choose Issue Template for ${repositoryName}`, 54 | type: TextObjectType.PLAINTEXT, 55 | }, 56 | }); 57 | 58 | block.addDividerBlock(); 59 | 60 | let index = 1; 61 | 62 | for (let template of templates) { 63 | block.addSectionBlock({ 64 | text: { 65 | text: `${template.name}`, 66 | type: TextObjectType.PLAINTEXT, 67 | }, 68 | accessory: block.newButtonElement({ 69 | actionId: ModalsEnum.ISSUE_TEMPLATE_SELECTION_ACTION, 70 | text: { 71 | text: ModalsEnum.ISSUE_TEMPLATE_SELECTION_LABEL, 72 | type: TextObjectType.PLAINTEXT, 73 | }, 74 | value: `${repositoryName} ${template.download_url}`, 75 | }), 76 | }); 77 | index++; 78 | } 79 | block.addSectionBlock({ 80 | text: { 81 | text: `Blank Template`, 82 | type: TextObjectType.PLAINTEXT, 83 | }, 84 | accessory: block.newButtonElement({ 85 | actionId: ModalsEnum.ISSUE_TEMPLATE_SELECTION_ACTION, 86 | text: { 87 | text: ModalsEnum.ISSUE_TEMPLATE_SELECTION_LABEL, 88 | type: TextObjectType.PLAINTEXT, 89 | }, 90 | value: `${repositoryName} ${ModalsEnum.BLANK_GITHUB_TEMPLATE}`, 91 | }), 92 | }); 93 | } 94 | 95 | return { 96 | id: viewId, 97 | title: { 98 | type: TextObjectType.PLAINTEXT, 99 | text: ModalsEnum.NEW_ISSUE_TITLE, 100 | }, 101 | close: block.newButtonElement({ 102 | text: { 103 | type: TextObjectType.PLAINTEXT, 104 | text: "Close", 105 | }, 106 | }), 107 | blocks: block.getBlocks(), 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /github/modals/githubIssuesShareModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | import { IGitHubSearchResultData } from "../definitions/searchResultData"; 19 | import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; 20 | import { IGitHubIssueData } from "../definitions/githubIssueData"; 21 | 22 | export async function githubIssuesShareModal({ 23 | data, 24 | modify, 25 | read, 26 | persistence, 27 | http, 28 | slashcommandcontext, 29 | uikitcontext, 30 | }: { 31 | data: IGitHubIssueData; 32 | modify: IModify; 33 | read: IRead; 34 | persistence: IPersistence; 35 | http: IHttp; 36 | slashcommandcontext?: SlashCommandContext; 37 | uikitcontext?: UIKitInteractionContext; 38 | }): Promise { 39 | const viewId = ModalsEnum.GITHUB_ISSUES_SHARE_VIEW; 40 | 41 | const block = modify.getCreator().getBlockBuilder(); 42 | 43 | const room = 44 | slashcommandcontext?.getRoom() || 45 | uikitcontext?.getInteractionData().room; 46 | const user = 47 | slashcommandcontext?.getSender() || 48 | uikitcontext?.getInteractionData().user; 49 | 50 | if (user?.id) { 51 | let roomId; 52 | 53 | if (room?.id) { 54 | roomId = room.id; 55 | await storeInteractionRoomData(persistence, user.id, roomId); 56 | } else { 57 | roomId = ( 58 | await getInteractionRoomData( 59 | read.getPersistenceReader(), 60 | user.id 61 | ) 62 | ).roomId; 63 | } 64 | let finalString = `\n`; 65 | if(data.issue_list?.length){ 66 | for(let searchResult of data.issue_list){ 67 | if(searchResult.share){ 68 | let searchResultString = `${searchResult.issue_compact} `; 69 | finalString =`${finalString} \n${searchResultString}`; 70 | } 71 | } 72 | } 73 | 74 | block.addInputBlock({ 75 | blockId: ModalsEnum.MULTI_SHARE_GITHUB_ISSUES_INPUT, 76 | label: { 77 | text: ModalsEnum.MULTI_SHARE_GITHUB_ISSUES_INPUT_LABEL, 78 | type: TextObjectType.MARKDOWN 79 | }, 80 | element: block.newPlainTextInputElement({ 81 | initialValue : `${finalString}`, 82 | multiline:true, 83 | actionId: ModalsEnum.MULTI_SHARE_GITHUB_ISSUES_INPUT_ACTION, 84 | }) 85 | }); 86 | } 87 | 88 | block.addDividerBlock(); 89 | return { 90 | id: viewId, 91 | title: { 92 | type: TextObjectType.PLAINTEXT, 93 | text: ModalsEnum.GITHUB_ISSUES_TITLE, 94 | }, 95 | submit: block.newButtonElement({ 96 | text: { 97 | type: TextObjectType.PLAINTEXT, 98 | text: "Send", 99 | }, 100 | }), 101 | close: block.newButtonElement({ 102 | text: { 103 | type: TextObjectType.PLAINTEXT, 104 | text: "Close", 105 | }, 106 | }), 107 | blocks: block.getBlocks(), 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /github/handlers/ExecuteViewClosedHandler.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { IApp } from "@rocket.chat/apps-engine/definition/IApp"; 8 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 9 | import { UIKitViewCloseInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { pullDetailsModal } from "../modals/pullDetailsModal"; 12 | import { storeInteractionRoomData, clearInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; 13 | import { GithubSearchResultStorage } from "../persistance/searchResults"; 14 | import { GithubRepoIssuesStorage } from "../persistance/githubIssues"; 15 | 16 | export class ExecuteViewClosedHandler { 17 | constructor( 18 | private readonly app: IApp, 19 | private readonly read: IRead, 20 | private readonly http: IHttp, 21 | private readonly modify: IModify, 22 | private readonly persistence: IPersistence 23 | ) {} 24 | 25 | public async run(context: UIKitViewCloseInteractionContext) { 26 | const { view } = context.getInteractionData(); 27 | switch (view.id) { 28 | case ModalsEnum.PULL_VIEW || 29 | ModalsEnum.CODE_VIEW: 30 | const modal = await pullDetailsModal({ 31 | modify: this.modify, 32 | read: this.read, 33 | persistence: this.persistence, 34 | http: this.http, 35 | uikitcontext: context, 36 | }); 37 | await this.modify.getUiController().updateModalView( 38 | modal, 39 | { 40 | triggerId: context.getInteractionData() 41 | .triggerId as string, 42 | }, 43 | context.getInteractionData().user 44 | ); 45 | break; 46 | case ModalsEnum.SEARCH_RESULT_VIEW:{ 47 | const room = context.getInteractionData().room; 48 | const user = context.getInteractionData().user; 49 | 50 | if (user?.id) { 51 | let roomId; 52 | 53 | if (room?.id) { 54 | roomId = room.id; 55 | await storeInteractionRoomData(this.persistence, user.id, roomId); 56 | } else { 57 | roomId = ( 58 | await getInteractionRoomData( 59 | this.read.getPersistenceReader(), 60 | user.id 61 | ) 62 | ).roomId; 63 | } 64 | let githubSearchStorage = new GithubSearchResultStorage(this.persistence,this.read.getPersistenceReader()); 65 | await githubSearchStorage.deleteSearchResults(roomId,user); 66 | } 67 | break; 68 | } 69 | case ModalsEnum.ISSUE_LIST_VIEW: { 70 | const room = context.getInteractionData().room; 71 | const user = context.getInteractionData().user; 72 | 73 | if (user?.id) { 74 | let roomId; 75 | if (room?.id) { 76 | roomId = room.id; 77 | await storeInteractionRoomData(this.persistence, user.id, roomId); 78 | } else { 79 | roomId = ( 80 | await getInteractionRoomData( 81 | this.read.getPersistenceReader(), 82 | user.id 83 | ) 84 | ).roomId; 85 | } 86 | let githubIssuesStorage = new GithubRepoIssuesStorage(this.persistence,this.read.getPersistenceReader()); 87 | await githubIssuesStorage.deleteIssueData(roomId,user); 88 | } 89 | break; 90 | } 91 | } 92 | return { success: true } as any; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /github/persistance/githubIssues.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPersistence, 3 | IPersistenceRead, 4 | } from "@rocket.chat/apps-engine/definition/accessors"; 5 | import { 6 | RocketChatAssociationModel, 7 | RocketChatAssociationRecord, 8 | } from "@rocket.chat/apps-engine/definition/metadata"; 9 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 10 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 11 | import { IGitHubIssueData } from "../definitions/githubIssueData"; 12 | 13 | export class GithubRepoIssuesStorage { 14 | constructor( 15 | private readonly persistence: IPersistence, 16 | private readonly persistenceRead: IPersistenceRead 17 | ) {} 18 | 19 | public async updateIssueData( 20 | room: IRoom, 21 | user: IUser, 22 | repositoryIssues: IGitHubIssueData 23 | ): Promise { 24 | try { 25 | const associations: Array = [ 26 | new RocketChatAssociationRecord( 27 | RocketChatAssociationModel.MISC, 28 | `githubRepoIssues` 29 | ), 30 | new RocketChatAssociationRecord( 31 | RocketChatAssociationModel.ROOM, 32 | room.id 33 | ), 34 | new RocketChatAssociationRecord( 35 | RocketChatAssociationModel.USER, 36 | `${user.id}` 37 | ), 38 | ]; 39 | await this.persistence.updateByAssociations( 40 | associations, 41 | repositoryIssues, 42 | true 43 | ); 44 | } catch (error) { 45 | console.warn("Add Repository Issues Error :", error); 46 | return false; 47 | } 48 | return true; 49 | } 50 | 51 | public async getIssueData( 52 | roomId: string, 53 | user: IUser, 54 | ): Promise { 55 | try { 56 | const associations: Array = [ 57 | new RocketChatAssociationRecord( 58 | RocketChatAssociationModel.MISC, 59 | `githubRepoIssues` 60 | ), 61 | new RocketChatAssociationRecord( 62 | RocketChatAssociationModel.ROOM, 63 | roomId 64 | ), 65 | new RocketChatAssociationRecord( 66 | RocketChatAssociationModel.USER, 67 | `${user.id}` 68 | ), 69 | ]; 70 | let searchResults: Array = 71 | (await this.persistenceRead.readByAssociations( 72 | associations 73 | )) as Array; 74 | 75 | if(searchResults?.length<1){ 76 | console.warn("No Repo Issues Found ",searchResults ); 77 | throw new Error("No Repo Issues Found"); 78 | } 79 | return searchResults[0]; 80 | } catch (error) { 81 | console.warn("No Repo Issues Found " ); 82 | throw new Error("No Repo Issues Found"); 83 | } 84 | } 85 | 86 | public async deleteIssueData( 87 | roomId: string, 88 | user: IUser, 89 | ): Promise { 90 | try { 91 | const associations: Array = [ 92 | new RocketChatAssociationRecord( 93 | RocketChatAssociationModel.MISC, 94 | `githubRepoIssues` 95 | ), 96 | new RocketChatAssociationRecord( 97 | RocketChatAssociationModel.ROOM, 98 | roomId 99 | ), 100 | new RocketChatAssociationRecord( 101 | RocketChatAssociationModel.USER, 102 | `${user.id}` 103 | ), 104 | ]; 105 | await this.persistence.removeByAssociations(associations); 106 | } catch (error) { 107 | console.warn("Delete Repo Issues Error :", error); 108 | return false; 109 | } 110 | return true; 111 | } 112 | }; -------------------------------------------------------------------------------- /github/persistance/searchResults.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPersistence, 3 | IPersistenceRead, 4 | } from "@rocket.chat/apps-engine/definition/accessors"; 5 | import { 6 | RocketChatAssociationModel, 7 | RocketChatAssociationRecord, 8 | } from "@rocket.chat/apps-engine/definition/metadata"; 9 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 10 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 11 | import { IGitHubSearchResultData } from "../definitions/searchResultData"; 12 | 13 | export class GithubSearchResultStorage { 14 | constructor( 15 | private readonly persistence: IPersistence, 16 | private readonly persistenceRead: IPersistenceRead 17 | ) {} 18 | 19 | public async updateSearchResult( 20 | room: IRoom, 21 | user: IUser, 22 | searchResult: IGitHubSearchResultData 23 | ): Promise { 24 | try { 25 | const associations: Array = [ 26 | new RocketChatAssociationRecord( 27 | RocketChatAssociationModel.MISC, 28 | `githubSearchResult` 29 | ), 30 | new RocketChatAssociationRecord( 31 | RocketChatAssociationModel.ROOM, 32 | room.id 33 | ), 34 | new RocketChatAssociationRecord( 35 | RocketChatAssociationModel.USER, 36 | `${user.id}` 37 | ), 38 | ]; 39 | await this.persistence.updateByAssociations( 40 | associations, 41 | searchResult, 42 | true 43 | ); 44 | } catch (error) { 45 | console.warn("Add Search Result Error :", error); 46 | return false; 47 | } 48 | return true; 49 | } 50 | 51 | public async getSearchResults( 52 | roomId: string, 53 | user: IUser, 54 | ): Promise { 55 | try { 56 | const associations: Array = [ 57 | new RocketChatAssociationRecord( 58 | RocketChatAssociationModel.MISC, 59 | `githubSearchResult` 60 | ), 61 | new RocketChatAssociationRecord( 62 | RocketChatAssociationModel.ROOM, 63 | roomId 64 | ), 65 | new RocketChatAssociationRecord( 66 | RocketChatAssociationModel.USER, 67 | `${user.id}` 68 | ), 69 | ]; 70 | let searchResults: Array = 71 | (await this.persistenceRead.readByAssociations( 72 | associations 73 | )) as Array; 74 | 75 | if(searchResults?.length<1){ 76 | console.warn("No Search Result Found ",searchResults ); 77 | throw new Error("No Search Result Found"); 78 | } 79 | return searchResults[0]; 80 | } catch (error) { 81 | console.warn("No Search Result Found " ); 82 | throw new Error("No Search Result Found"); 83 | } 84 | } 85 | 86 | public async deleteSearchResults( 87 | roomId: string, 88 | user: IUser, 89 | ): Promise { 90 | try { 91 | const associations: Array = [ 92 | new RocketChatAssociationRecord( 93 | RocketChatAssociationModel.MISC, 94 | `githubSearchResult` 95 | ), 96 | new RocketChatAssociationRecord( 97 | RocketChatAssociationModel.ROOM, 98 | roomId 99 | ), 100 | new RocketChatAssociationRecord( 101 | RocketChatAssociationModel.USER, 102 | `${user.id}` 103 | ), 104 | ]; 105 | await this.persistence.removeByAssociations(associations); 106 | } catch (error) { 107 | console.warn("Delete Search Result Error :", error); 108 | return false; 109 | } 110 | return true; 111 | } 112 | }; -------------------------------------------------------------------------------- /github/persistance/auth.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IHttpRequest, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { 8 | RocketChatAssociationModel, 9 | RocketChatAssociationRecord, 10 | } from "@rocket.chat/apps-engine/definition/metadata"; 11 | import { 12 | IAuthData, 13 | IOAuth2ClientOptions, 14 | } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; 15 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 16 | import { URL } from "url"; 17 | 18 | // const assoc = new RocketChatAssociationRecord(RocketChatAssociationModel.MISC, 'users'); 19 | 20 | // export async function create(read: IRead, persistence: IPersistence, user: IUser): Promise { 21 | // const users = await getAllUsers(read); 22 | 23 | // if (!users) { 24 | // await persistence.createWithAssociation([user], assoc); 25 | // return; 26 | // } 27 | 28 | // if (!isUserPresent(users, user)) { 29 | // users.push(user); 30 | // await persistence.updateByAssociation(assoc, users); 31 | // } 32 | // } 33 | 34 | // export async function remove(read: IRead, persistence: IPersistence, user: IUser): Promise { 35 | // const users = await getAllUsers(read); 36 | 37 | // if (!users || !isUserPresent(users, user)) { 38 | // // @NOTE do nothing 39 | // return; 40 | // } 41 | 42 | // const idx = users.findIndex((u: IUser) => u.id === user.id); 43 | // users.splice(idx, 1); 44 | // await persistence.updateByAssociation(assoc, users); 45 | // } 46 | 47 | // export async function getAllUsers(read: IRead): Promise { 48 | // const data = await read.getPersistenceReader().readByAssociation(assoc); 49 | // return (data.length ? data[0] as IUser[] : []); 50 | // } 51 | 52 | // function isUserPresent(users: IUser[], targetUser: IUser): boolean { 53 | // return users.some((user) => user.id === targetUser.id); 54 | // } 55 | 56 | /** 57 | * This function needed to be copied from the apps engine due to difficulties trying to 58 | * get access to the auth client from inside a job processor. 59 | * @NOTE It relies on hardcoded information (config alias's suffix) to work and it might break if 60 | * the value changes 61 | */ 62 | 63 | export async function getAccessTokenForUser( 64 | read: IRead, 65 | user: IUser, 66 | config: IOAuth2ClientOptions 67 | ): Promise { 68 | const associations = [ 69 | new RocketChatAssociationRecord( 70 | RocketChatAssociationModel.USER, 71 | user.id 72 | ), 73 | new RocketChatAssociationRecord( 74 | RocketChatAssociationModel.MISC, 75 | `${config.alias}-oauth-connection` 76 | ), 77 | ]; 78 | 79 | const [result] = (await read 80 | .getPersistenceReader() 81 | .readByAssociations(associations)) as unknown as Array< 82 | IAuthData | undefined 83 | >; 84 | return result; 85 | } 86 | 87 | export async function removeToken({ 88 | userId, 89 | persis, 90 | config, 91 | }: { 92 | userId: string; 93 | persis: IPersistence; 94 | config: IOAuth2ClientOptions; 95 | }): Promise { 96 | const [result] = (await persis.removeByAssociations([ 97 | new RocketChatAssociationRecord( 98 | RocketChatAssociationModel.USER, 99 | userId 100 | ), 101 | new RocketChatAssociationRecord( 102 | RocketChatAssociationModel.MISC, 103 | `${config.alias}-oauth-connection` 104 | ), 105 | ])) as unknown as Array; 106 | 107 | return result; 108 | } 109 | 110 | export async function revokeUserAccessToken( 111 | read: IRead, 112 | user: IUser, 113 | persis: IPersistence, 114 | http: IHttp, 115 | config: IOAuth2ClientOptions 116 | ): Promise { 117 | try { 118 | const tokenInfo = await getAccessTokenForUser(read, user, config); 119 | if (!tokenInfo?.token) { 120 | throw new Error("No access token available for this user."); 121 | } 122 | await removeToken({ userId: user.id, persis, config }); 123 | return true; 124 | } catch (error) { 125 | console.log("revokeTokenError : ", error); 126 | return false; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /github/modals/UserProfileModal.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, IModify, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 3 | import { ButtonStyle, TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; 4 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 5 | import { ModalsEnum } from "../enum/Modals"; 6 | import { GitHubApi } from "../helpers/githubSDKclass"; 7 | import { UserInformation } from "../definitions/Userinfo"; 8 | import { 9 | getInteractionRoomData, 10 | storeInteractionRoomData, 11 | } from "../persistance/roomInteraction"; 12 | import { AppSettingsEnum } from "../settings/settings"; 13 | 14 | export async function userProfileModal({ 15 | access_token, 16 | modify, 17 | read, 18 | persistence, 19 | http, 20 | slashcommandcontext, 21 | uikitcontext 22 | } : { 23 | access_token: String, 24 | modify : IModify, 25 | read: IRead, 26 | persistence: IPersistence, 27 | http: IHttp, 28 | slashcommandcontext: SlashCommandContext, 29 | uikitcontext?: UIKitInteractionContext 30 | }) : Promise { 31 | 32 | const viewId = ModalsEnum.USER_PROFILE_VIEW; 33 | const block = modify.getCreator().getBlockBuilder(); 34 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 35 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 36 | 37 | if (user?.id){ 38 | let roomId; 39 | if (room?.id){ 40 | roomId = room.id; 41 | await storeInteractionRoomData(persistence, user.id, roomId); 42 | } 43 | else { 44 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 45 | } 46 | } 47 | let userInfo: UserInformation | undefined; 48 | try { 49 | let BaseHost = await read.getEnvironmentReader().getSettings().getValueById(AppSettingsEnum.BaseHostID); 50 | let BaseApiHost = await read.getEnvironmentReader().getSettings().getValueById(AppSettingsEnum.BaseApiHostID); 51 | const gitHubApiClient = new GitHubApi( 52 | http, 53 | access_token, 54 | BaseHost, 55 | BaseApiHost 56 | ); 57 | userInfo = await gitHubApiClient.getBasicUserInfo(); 58 | } catch (error) { 59 | console.log("Error occurred while fetching user info:", error); 60 | } 61 | 62 | if (userInfo) { 63 | block.addContextBlock({ 64 | elements: [block.newPlainTextObject(userInfo.email, true)], 65 | }); 66 | 67 | block.addSectionBlock({ 68 | text: block.newPlainTextObject(userInfo.bio), 69 | accessory : block.newImageElement({ 70 | imageUrl: userInfo.avatar, 71 | altText: userInfo.name 72 | }) 73 | }) 74 | 75 | block.addContextBlock({ 76 | elements: [ 77 | block.newPlainTextObject(`followers: ${userInfo.followers}`), 78 | block.newPlainTextObject(`following: ${userInfo.following}`) 79 | ] 80 | }); 81 | 82 | block.addDividerBlock(); 83 | 84 | block.addSectionBlock({ 85 | text: block.newPlainTextObject("Select from the following options.") 86 | }) 87 | 88 | block.addActionsBlock({ 89 | elements : [ 90 | block.newButtonElement({ 91 | text : { 92 | text : "Share Profile", 93 | type : TextObjectType.PLAINTEXT 94 | }, 95 | actionId: ModalsEnum.SHARE_PROFILE, 96 | style : ButtonStyle.PRIMARY 97 | }), 98 | block.newButtonElement( 99 | { 100 | actionId: ModalsEnum.TRIGGER_ISSUES_MODAL, 101 | value: "Trigger Issues Modal", 102 | text: { 103 | type: TextObjectType.PLAINTEXT, 104 | text: "Issues" 105 | }, 106 | style: ButtonStyle.PRIMARY 107 | }, 108 | ) 109 | ] 110 | }) 111 | } 112 | return { 113 | id: viewId, 114 | title: { 115 | type: TextObjectType.PLAINTEXT, 116 | text: userInfo ? userInfo.name : "User Profile", 117 | }, 118 | blocks: block.getBlocks() 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /github/modals/IssueDisplayModal.ts: -------------------------------------------------------------------------------- 1 | import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 3 | import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; 4 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 5 | import { IGitHubIssue } from "../definitions/githubIssue"; 6 | import { IGithubReactions } from "../definitions/githubReactions"; 7 | import { ModalsEnum } from "../enum/Modals"; 8 | import { OcticonIcons } from "../enum/OcticonIcons"; 9 | import { getIssueData, getUserAssignedIssues } from "../helpers/githubSDK"; 10 | import { CreateIssueStatsBar } from "../lib/CreateIssueStatsBar"; 11 | import { CreateReactionsBar } from "../lib/CreateReactionsBar"; 12 | import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; 13 | import { BodyMarkdownRenderer } from "../processors/bodyMarkdowmRenderer"; 14 | 15 | export async function IssueDisplayModal ({ 16 | repoName, 17 | issueNumber, 18 | access_token, 19 | modify, 20 | read, 21 | persistence, 22 | http, 23 | slashcommandcontext, 24 | uikitcontext 25 | } : { 26 | repoName : String, 27 | issueNumber : String, 28 | access_token: String, 29 | modify : IModify, 30 | read: IRead, 31 | persistence: IPersistence, 32 | http: IHttp, 33 | slashcommandcontext?: SlashCommandContext, 34 | uikitcontext?: UIKitInteractionContext 35 | }) : Promise { 36 | const viewId = ModalsEnum.USER_ISSUE_VIEW; 37 | const block = modify.getCreator().getBlockBuilder(); 38 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 39 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 40 | 41 | if (user?.id){ 42 | let roomId; 43 | if (room?.id){ 44 | roomId = room.id; 45 | await storeInteractionRoomData(persistence, user.id, roomId); 46 | } 47 | else { 48 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 49 | } 50 | } 51 | 52 | const issueInfo : IGitHubIssue = await getIssueData(repoName, issueNumber, access_token, http); 53 | 54 | if (issueInfo.issue_id == 0){ 55 | block.addSectionBlock({ 56 | text : { 57 | text : "Sorry there is some issue fetching this issue, try again later", 58 | type : TextObjectType.PLAINTEXT 59 | }, 60 | }) 61 | return { 62 | id : viewId, 63 | title : { 64 | text : "Error", 65 | type : TextObjectType.PLAINTEXT 66 | }, 67 | blocks : block.getBlocks() 68 | } 69 | } 70 | 71 | const lastUpdated = new Date(issueInfo.last_updated_at ?? ""); 72 | 73 | block.addContextBlock({ 74 | elements : [ 75 | block.newImageElement({ 76 | imageUrl: OcticonIcons.PENCIL, 77 | altText: "Last Update At", 78 | }), 79 | block.newPlainTextObject( 80 | `Last Updated at ${ lastUpdated.toISOString() }` 81 | ), 82 | ] 83 | }) 84 | 85 | CreateIssueStatsBar(issueInfo, block); 86 | 87 | block.addSectionBlock({ 88 | text : { 89 | text : `*${issueInfo.title}*` ?? "", 90 | type : TextObjectType.MARKDOWN 91 | } 92 | }) 93 | block.addDividerBlock(); 94 | 95 | issueInfo.reactions && CreateReactionsBar(issueInfo.reactions, block); 96 | 97 | issueInfo.body && BodyMarkdownRenderer({body : issueInfo.body, block : block}) 98 | 99 | block.addActionsBlock({ 100 | elements : [ 101 | block.newButtonElement({ 102 | actionId : ModalsEnum.SHARE_ISSUE_ACTION, 103 | value : `${repoName}, ${issueNumber}`, 104 | text : { 105 | text : "Share Issue", 106 | type : TextObjectType.PLAINTEXT 107 | }, 108 | }), 109 | block.newButtonElement({ 110 | actionId : ModalsEnum.ADD_GITHUB_ISSUE_ASSIGNEE, 111 | value : `${repoName}, ${issueNumber}`, 112 | text : { 113 | text : "Assign Issue", 114 | type : TextObjectType.PLAINTEXT 115 | }, 116 | }) 117 | ] 118 | }) 119 | 120 | return { 121 | id : viewId, 122 | title : { 123 | text : `${repoName} \`#${issueNumber}\``, 124 | type : TextObjectType.MARKDOWN 125 | }, 126 | blocks : block.getBlocks() 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /github/persistance/remind.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IPersistence, 3 | IRead 4 | } from '@rocket.chat/apps-engine/definition/accessors'; 5 | import { 6 | RocketChatAssociationModel, 7 | RocketChatAssociationRecord 8 | } from '@rocket.chat/apps-engine/definition/metadata'; 9 | import { IUser } from '@rocket.chat/apps-engine/definition/users'; 10 | import { IReminder } from '../definitions/Reminder'; 11 | 12 | const assoc = new RocketChatAssociationRecord( 13 | RocketChatAssociationModel.MISC, 14 | 'reminder' 15 | ); 16 | 17 | export async function CreateReminder( 18 | read: IRead, 19 | persistence: IPersistence, 20 | user: IUser, 21 | repo: string, 22 | ): Promise { 23 | const reminders = await getAllReminders(read); 24 | 25 | if (reminders.length === 0) { 26 | await persistence.createWithAssociation( 27 | [ 28 | { 29 | userid: user.id, 30 | username: user.username, 31 | repos: [repo], 32 | unsubscribedPR: [], 33 | } 34 | ], 35 | assoc 36 | ); 37 | return; 38 | } 39 | if ( 40 | !isReminderExist(reminders, { 41 | userid: user.id, 42 | username: user.username, 43 | repos: [repo], 44 | unsubscribedPR: [] 45 | }) 46 | ) { 47 | reminders.push({ 48 | userid: user.id, 49 | username: user.name, 50 | repos: [repo], 51 | unsubscribedPR: [] 52 | }); 53 | await persistence.updateByAssociation(assoc, reminders); 54 | } else { 55 | const idx = reminders.findIndex((u: IReminder) => u.userid === user.id) 56 | 57 | if (!reminders[idx].repos.includes(repo)) { 58 | reminders[idx].repos.push(repo); 59 | } 60 | 61 | await persistence.updateByAssociation(assoc, reminders) 62 | } 63 | } 64 | 65 | export async function unsubscribedPR(read: IRead, persistence: IPersistence, repo: string, Prnum: number, user: IUser): Promise { 66 | const reminders = await getAllReminders(read); 67 | const repository = repo.trim(); 68 | const PullRequestNumber = Prnum; 69 | const index = reminders.findIndex((reminder: IReminder) => reminder.userid === user.id); 70 | const reminder = reminders[index] as IReminder 71 | const unsubscribPR = reminder.unsubscribedPR.find((value) => value.repo === repository); 72 | 73 | if (unsubscribPR) { 74 | const idx = reminder.unsubscribedPR.findIndex((value) => value.repo === repository); 75 | const unsubscribPRnums = reminder.unsubscribedPR[idx].prnum; 76 | if (!unsubscribPRnums.includes(PullRequestNumber)) { 77 | unsubscribPRnums.push(PullRequestNumber); 78 | } 79 | } else { 80 | reminder.unsubscribedPR.push({ 81 | repo: repository, 82 | prnum: [PullRequestNumber] 83 | }) 84 | } 85 | 86 | await persistence.updateByAssociation(assoc, reminders) 87 | } 88 | 89 | 90 | export async function RemoveReminder( 91 | read: IRead, 92 | persistence: IPersistence, 93 | user: IReminder 94 | ): Promise { 95 | const reminders = await getAllReminders(read); 96 | 97 | if (!reminders || !isReminderExist(reminders, user)) { 98 | return; 99 | } 100 | 101 | const idx = reminders.findIndex((u: IReminder) => u.userid === user.userid); 102 | reminders.splice(idx, 1); 103 | await persistence.updateByAssociation(assoc, reminders); 104 | } 105 | 106 | export async function getAllReminders(read: IRead): Promise { 107 | const data = await read.getPersistenceReader().readByAssociation(assoc); 108 | return data.length ? (data[0] as IReminder[]) : []; 109 | } 110 | 111 | function isReminderExist(reminders: IReminder[], targetUser: IReminder): boolean { 112 | return reminders.some((user) => user.userid === targetUser.userid); 113 | } 114 | 115 | export async function getUserReminder(read:IRead,User:IUser):Promise{ 116 | const reminders = await getAllReminders(read); 117 | const index = reminders.findIndex((reminder)=>reminder.userid === User.id); 118 | return reminders[index]; 119 | } 120 | 121 | export async function removeRepoReminder(read:IRead,persistence:IPersistence,repository:string,User:IUser){ 122 | const reminders = await getAllReminders(read); 123 | const idx = reminders.findIndex((u: IReminder) => u.userid === User.id); 124 | 125 | if (idx === -1) { 126 | return; 127 | } 128 | 129 | const repoindex = reminders[idx].repos.findIndex((repo)=>repo == repository); 130 | 131 | if (repoindex === -1) { 132 | return; 133 | } 134 | 135 | reminders[idx].repos.splice(repoindex,1); 136 | await persistence.updateByAssociation(assoc, reminders); 137 | } 138 | 139 | 140 | -------------------------------------------------------------------------------- /github/modals/MainModal.ts: -------------------------------------------------------------------------------- 1 | import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; 2 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 3 | import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; 4 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 5 | import { ModalsEnum } from "../enum/Modals"; 6 | 7 | export async function MainModal( 8 | { 9 | modify, 10 | read, 11 | persistence, 12 | http, 13 | slashcommandcontext, 14 | uikitcontext, 15 | }: { 16 | modify: IModify; 17 | read: IRead; 18 | persistence: IPersistence; 19 | http: IHttp; 20 | slashcommandcontext?: SlashCommandContext; 21 | uikitcontext?: UIKitInteractionContext; 22 | }): Promise { 23 | const viewId = ModalsEnum.MAIN_MODAL_VIEW; 24 | const block = modify.getCreator().getBlockBuilder(); 25 | 26 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 27 | 28 | block.addContextBlock( 29 | { 30 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 31 | elements: [ 32 | block.newMarkdownTextObject(`Hey 👋 Welcome ${user?.username}`), 33 | ], 34 | } 35 | ); 36 | 37 | block.addDividerBlock(); 38 | 39 | block.addSectionBlock({ 40 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 41 | text: block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_GITHUB_SEARCH_LABLE), 42 | accessory: block.newButtonElement({ 43 | text: { 44 | type: TextObjectType.PLAINTEXT, 45 | text: ModalsEnum.MAIN_MODAL_OPEN_LABLE, 46 | }, 47 | actionId: ModalsEnum.TRIGGER_SEARCH_MODAL, 48 | }), 49 | }) 50 | 51 | block.addContextBlock({ 52 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 53 | elements: [ 54 | block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_GITHUB_SEARCH_DESCRIPTION), 55 | ], 56 | }); 57 | 58 | block.addDividerBlock(); 59 | 60 | block.addSectionBlock({ 61 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 62 | text: block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_NEW_ISSUE_LABLE), 63 | accessory: block.newButtonElement({ 64 | text: { 65 | type: TextObjectType.PLAINTEXT, 66 | text: ModalsEnum.MAIN_MODAL_OPEN_LABLE, 67 | }, 68 | actionId: ModalsEnum.TRIGGER_NEW_ISSUE_MODAL, 69 | }), 70 | }); 71 | 72 | block.addContextBlock({ 73 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 74 | elements: [ 75 | block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_NEW_ISSUE_DESCRIPTION), 76 | ], 77 | }); 78 | 79 | block.addDividerBlock(); 80 | 81 | block.addSectionBlock({ 82 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 83 | text: block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_REPOSITORY_SUBSCRIPTIONS_LABLE), 84 | accessory: block.newButtonElement({ 85 | text: { 86 | type: TextObjectType.PLAINTEXT, 87 | text: ModalsEnum.MAIN_MODAL_OPEN_LABLE, 88 | }, 89 | actionId: ModalsEnum.TRIGGER_SUBSCRIPTIONS_MODAL, 90 | }), 91 | }) 92 | 93 | block.addContextBlock({ 94 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 95 | elements: [ 96 | block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_REPOSITORY_SUBSCRIPTIONS_DESCRIPTION), 97 | ], 98 | }); 99 | 100 | block.addDividerBlock(); 101 | 102 | block.addSectionBlock({ 103 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 104 | text: block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_ASSIGN_ISSUES_LABLE), 105 | accessory: block.newButtonElement({ 106 | text: { 107 | type: TextObjectType.PLAINTEXT, 108 | text: ModalsEnum.MAIN_MODAL_OPEN_LABLE, 109 | }, 110 | actionId: ModalsEnum.TRIGGER_ASSIGN_ISSUES_MODAL, 111 | }), 112 | }) 113 | 114 | block.addContextBlock({ 115 | blockId: ModalsEnum.MAIN_MODAL_VIEW, 116 | elements: [ 117 | block.newMarkdownTextObject(ModalsEnum.MAIN_MODAL_ASSIGN_ISSUES_DESCRIPTION), 118 | ], 119 | }); 120 | 121 | block.addDividerBlock(); 122 | 123 | return { 124 | id: viewId, 125 | title: { 126 | type: TextObjectType.PLAINTEXT, 127 | text: ModalsEnum.MAIN_MODAL_TITLE 128 | }, 129 | close: block.newButtonElement({ 130 | text: { 131 | type: TextObjectType.PLAINTEXT, 132 | text: "Close", 133 | } 134 | }), 135 | blocks: block.getBlocks(), 136 | }; 137 | } -------------------------------------------------------------------------------- /github/modals/addIssueCommentModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 13 | import { 14 | UIKitBlockInteractionContext, 15 | UIKitInteractionContext, 16 | } from "@rocket.chat/apps-engine/definition/uikit"; 17 | import { 18 | storeInteractionRoomData, 19 | getInteractionRoomData, 20 | } from "../persistance/roomInteraction"; 21 | import { Subscription } from "../persistance/subscriptions"; 22 | import { ISubscription } from "../definitions/subscription"; 23 | 24 | export async function addIssueCommentsModal({ 25 | data, 26 | modify, 27 | read, 28 | persistence, 29 | http, 30 | slashcommandcontext, 31 | uikitcontext, 32 | }: { 33 | data?:any 34 | modify: IModify; 35 | read: IRead; 36 | persistence: IPersistence; 37 | http: IHttp; 38 | slashcommandcontext?: SlashCommandContext; 39 | uikitcontext?: UIKitInteractionContext; 40 | }): Promise { 41 | const viewId = ModalsEnum.ADD_ISSUE_COMMENT_VIEW; 42 | const block = modify.getCreator().getBlockBuilder(); 43 | const room = 44 | slashcommandcontext?.getRoom() || 45 | uikitcontext?.getInteractionData().room; 46 | const user = 47 | slashcommandcontext?.getSender() || 48 | uikitcontext?.getInteractionData().user; 49 | 50 | if (user?.id) { 51 | let roomId; 52 | 53 | if (room?.id) { 54 | roomId = room.id; 55 | await storeInteractionRoomData(persistence, user.id, roomId); 56 | } else { 57 | roomId = ( 58 | await getInteractionRoomData( 59 | read.getPersistenceReader(), 60 | user.id 61 | ) 62 | ).roomId; 63 | } 64 | 65 | let repoName = ""; 66 | let issueNumber = ""; 67 | if(data?.repo?.length){ 68 | repoName = data?.repo; 69 | } 70 | if(data?.issueNumber?.length){ 71 | issueNumber = data?.issueNumber; 72 | } 73 | // shows indentations in input blocks but not inn section block 74 | block.addInputBlock({ 75 | blockId: ModalsEnum.REPO_NAME_INPUT, 76 | label: { 77 | text: ModalsEnum.REPO_NAME_LABEL, 78 | type: TextObjectType.PLAINTEXT, 79 | }, 80 | element: block.newPlainTextInputElement({ 81 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 82 | placeholder: { 83 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 84 | type: TextObjectType.PLAINTEXT, 85 | }, 86 | initialValue:repoName 87 | }), 88 | }); 89 | 90 | block.addInputBlock({ 91 | blockId: ModalsEnum.ISSUE_NUMBER_INPUT, 92 | label: { 93 | text: ModalsEnum.ISSUE_NUMBER_LABEL, 94 | type: TextObjectType.PLAINTEXT, 95 | }, 96 | element: block.newPlainTextInputElement({ 97 | actionId: ModalsEnum.ISSUE_NUMBER_INPUT_ACTION, 98 | placeholder: { 99 | text: ModalsEnum.ISSUE_NUMBER_INPUT_PLACEHOLDER, 100 | type: TextObjectType.PLAINTEXT, 101 | }, 102 | initialValue:issueNumber 103 | }), 104 | }); 105 | 106 | block.addInputBlock({ 107 | blockId: ModalsEnum.ISSUE_COMMENT_INPUT, 108 | label: { 109 | text: ModalsEnum.ISSUE_COMMENT_INPUT_LABEL, 110 | type: TextObjectType.PLAINTEXT, 111 | }, 112 | element: block.newPlainTextInputElement({ 113 | actionId: ModalsEnum.ISSUE_COMMENT_INPUT_ACTION, 114 | placeholder: { 115 | text: ModalsEnum.ISSUE_COMMENT_INPUT_PLACEHOLDER, 116 | type: TextObjectType.PLAINTEXT, 117 | }, 118 | multiline: true 119 | }), 120 | }); 121 | } 122 | 123 | block.addDividerBlock(); 124 | 125 | return { 126 | id: viewId, 127 | title: { 128 | type: TextObjectType.PLAINTEXT, 129 | text: ModalsEnum.ADD_ISSUE_COMMENT_VIEW_TITLE, 130 | }, 131 | close: block.newButtonElement({ 132 | text: { 133 | type: TextObjectType.PLAINTEXT, 134 | text: "Close", 135 | }, 136 | }), 137 | submit: block.newButtonElement({ 138 | actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, 139 | text: { 140 | type: TextObjectType.PLAINTEXT, 141 | text: "Comment", 142 | }, 143 | }), 144 | blocks: block.getBlocks(), 145 | }; 146 | } 147 | -------------------------------------------------------------------------------- /github/modals/deleteSubscriptions.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; 2 | import { ITextObject, TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; 3 | import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; 4 | import { IUser } from '@rocket.chat/apps-engine/definition/users'; 5 | import { ModalsEnum } from '../enum/Modals'; 6 | import { AppEnum } from '../enum/App'; 7 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 8 | import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; 9 | import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; 10 | import { getInteractionRoomData, storeInteractionRoomData } from '../persistance/roomInteraction'; 11 | import { Subscription } from '../persistance/subscriptions'; 12 | import { ISubscription } from '../definitions/subscription'; 13 | import { IRepositorySubscriptions } from '../definitions/repositorySubscriptions'; 14 | 15 | export async function deleteSubscriptionsModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { 16 | const viewId = ModalsEnum.DELETE_SUBSCRIPTION_VIEW; 17 | 18 | const block = modify.getCreator().getBlockBuilder(); 19 | 20 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 21 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 22 | 23 | if (user?.id) { 24 | let roomId; 25 | if (room?.id) { 26 | roomId = room.id; 27 | await storeInteractionRoomData(persistence, user.id, roomId); 28 | } else { 29 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 30 | } 31 | 32 | let subscriptionStorage = new Subscription(persistence,read.getPersistenceReader()); 33 | let roomSubscriptions: Array = await subscriptionStorage.getSubscriptions(roomId); 34 | 35 | block.addDividerBlock(); 36 | 37 | let repositoryData = new Map; 38 | for (let subscription of roomSubscriptions) { 39 | 40 | let repoName = subscription.repoName; 41 | let userId = subscription.user; 42 | let event = subscription.event; 43 | let user = await read.getUserReader().getById(userId); 44 | 45 | if(repositoryData.has(repoName)){ 46 | let repoData = repositoryData.get(repoName) as IRepositorySubscriptions; 47 | repoData.events.push(event); 48 | repoData.user=user; 49 | repositoryData.set(repoName,repoData); 50 | }else{ 51 | let events:Array = []; 52 | events.push(event); 53 | let repoData:IRepositorySubscriptions={ 54 | webhookId:subscription.webhookId, 55 | events:events, 56 | user:user, 57 | repoName:repoName 58 | }; 59 | repositoryData.set(repoName,repoData); 60 | } 61 | 62 | } 63 | let index=1; 64 | for (let repository of repositoryData.values()) { 65 | 66 | let repoName = repository.repoName; 67 | let repoUser = repository.user; 68 | let events = repository.events; 69 | if(repoUser.id == user.id){ 70 | block.addSectionBlock({ 71 | text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, 72 | accessory: block.newButtonElement({ 73 | actionId: ModalsEnum.DELETE_SUBSCRIPTION_ACTION, 74 | text: { 75 | text: ModalsEnum.DELETE_SUBSCRIPTION_LABEL, 76 | type: TextObjectType.PLAINTEXT 77 | }, 78 | value: repoName + "," + repository.webhookId, 79 | }) 80 | }); 81 | 82 | }else{ 83 | block.addSectionBlock({ 84 | text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, 85 | }); 86 | 87 | } 88 | 89 | let eventList : Array=[]; 90 | eventList.push(block.newPlainTextObject("Events : ")); 91 | for(let event of events){ 92 | eventList.push(block.newPlainTextObject(`${event} `)); 93 | } 94 | block.addContextBlock({ elements: eventList}); 95 | index++; 96 | } 97 | } 98 | 99 | block.addDividerBlock(); 100 | 101 | return { 102 | id: viewId, 103 | title: { 104 | type: TextObjectType.PLAINTEXT, 105 | text: ModalsEnum.DELETE_SUBSCIPTIONS_TITLE, 106 | }, 107 | close: block.newButtonElement({ 108 | text: { 109 | type: TextObjectType.PLAINTEXT, 110 | text: 'Close', 111 | }, 112 | }), 113 | blocks: block.getBlocks(), 114 | }; 115 | } -------------------------------------------------------------------------------- /github/modals/addPullRequestCommentsModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | import { 19 | storeInteractionRoomData, 20 | getInteractionRoomData, 21 | } from "../persistance/roomInteraction"; 22 | import { Subscription } from "../persistance/subscriptions"; 23 | import { ISubscription } from "../definitions/subscription"; 24 | 25 | export async function addPullRequestCommentsModal({ 26 | data, 27 | modify, 28 | read, 29 | persistence, 30 | http, 31 | slashcommandcontext, 32 | uikitcontext, 33 | }: { 34 | data?:any 35 | modify: IModify; 36 | read: IRead; 37 | persistence: IPersistence; 38 | http: IHttp; 39 | slashcommandcontext?: SlashCommandContext; 40 | uikitcontext?: UIKitInteractionContext; 41 | }): Promise { 42 | const viewId = ModalsEnum.ADD_PULL_REQUEST_COMMENT_VIEW; 43 | const block = modify.getCreator().getBlockBuilder(); 44 | const room = 45 | slashcommandcontext?.getRoom() || 46 | uikitcontext?.getInteractionData().room; 47 | const user = 48 | slashcommandcontext?.getSender() || 49 | uikitcontext?.getInteractionData().user; 50 | 51 | if (user?.id) { 52 | let roomId; 53 | 54 | if (room?.id) { 55 | roomId = room.id; 56 | await storeInteractionRoomData(persistence, user.id, roomId); 57 | } else { 58 | roomId = ( 59 | await getInteractionRoomData( 60 | read.getPersistenceReader(), 61 | user.id 62 | ) 63 | ).roomId; 64 | } 65 | 66 | let repoName = ""; 67 | let pullRequestNumber = ""; 68 | if(data?.repo?.length){ 69 | repoName = data?.repo; 70 | } 71 | if(data?.pullNumber?.length){ 72 | pullRequestNumber = data?.pullNumber; 73 | } 74 | // shows indentations in input blocks but not inn section block 75 | block.addInputBlock({ 76 | blockId: ModalsEnum.REPO_NAME_INPUT, 77 | label: { 78 | text: ModalsEnum.REPO_NAME_LABEL, 79 | type: TextObjectType.PLAINTEXT, 80 | }, 81 | element: block.newPlainTextInputElement({ 82 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 83 | placeholder: { 84 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 85 | type: TextObjectType.PLAINTEXT, 86 | }, 87 | initialValue:repoName 88 | }), 89 | }); 90 | 91 | block.addInputBlock({ 92 | blockId: ModalsEnum.PULL_REQUEST_NUMBER_INPUT, 93 | label: { 94 | text: ModalsEnum.PULL_REQUEST_NUMBER_LABEL, 95 | type: TextObjectType.PLAINTEXT, 96 | }, 97 | element: block.newPlainTextInputElement({ 98 | actionId: ModalsEnum.PULL_REQUEST_NUMBER_INPUT_ACTION, 99 | placeholder: { 100 | text: ModalsEnum.PULL_REQUEST_NUMBER_INPUT_PLACEHOLDER, 101 | type: TextObjectType.PLAINTEXT, 102 | }, 103 | initialValue:pullRequestNumber 104 | }), 105 | }); 106 | 107 | block.addInputBlock({ 108 | blockId: ModalsEnum.PULL_REQUEST_COMMENT_INPUT, 109 | label: { 110 | text: ModalsEnum.PULL_REQUEST_COMMENT_INPUT_LABEL, 111 | type: TextObjectType.PLAINTEXT, 112 | }, 113 | element: block.newPlainTextInputElement({ 114 | actionId: ModalsEnum.PULL_REQUEST_COMMENT_INPUT_ACTION, 115 | placeholder: { 116 | text: ModalsEnum.PULL_REQUEST_COMMENT_INPUT_PLACEHOLDER, 117 | type: TextObjectType.PLAINTEXT, 118 | }, 119 | multiline: true 120 | }), 121 | }); 122 | } 123 | 124 | block.addDividerBlock(); 125 | 126 | return { 127 | id: viewId, 128 | title: { 129 | type: TextObjectType.PLAINTEXT, 130 | text: ModalsEnum.ADD_PULL_REQUEST_COMMENT_VIEW_TITLE, 131 | }, 132 | close: block.newButtonElement({ 133 | text: { 134 | type: TextObjectType.PLAINTEXT, 135 | text: "Close", 136 | }, 137 | }), 138 | submit: block.newButtonElement({ 139 | actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, 140 | text: { 141 | type: TextObjectType.PLAINTEXT, 142 | text: "Comment", 143 | }, 144 | }), 145 | blocks: block.getBlocks(), 146 | }; 147 | } 148 | -------------------------------------------------------------------------------- /github/endpoints/githubEndpoints.ts: -------------------------------------------------------------------------------- 1 | import { ApiEndpoint } from "@rocket.chat/apps-engine/definition/api"; 2 | import { 3 | IRead, 4 | IHttp, 5 | IModify, 6 | IPersistence, 7 | } from "@rocket.chat/apps-engine/definition/accessors"; 8 | import { 9 | IApiEndpointInfo, 10 | IApiEndpoint, 11 | IApiRequest, 12 | IApiResponse, 13 | } from "@rocket.chat/apps-engine/definition/api"; 14 | import { Subscription } from "../persistance/subscriptions"; 15 | import { ISubscription } from "../definitions/subscription"; 16 | import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; 17 | export class githubWebHooks extends ApiEndpoint { 18 | public path = "githubwebhook"; 19 | 20 | public async post( 21 | request: IApiRequest, 22 | endpoint: IApiEndpointInfo, 23 | read: IRead, 24 | modify: IModify, 25 | http: IHttp, 26 | persis: IPersistence 27 | ): Promise { 28 | let event: string = request.headers["x-github-event"] as string; 29 | let payload: any; 30 | if ( 31 | request.headers["content-type"] === 32 | "application/x-www-form-urlencoded" 33 | ) { 34 | payload = JSON.parse(request.content.payload); 35 | } else { 36 | payload = request.content; 37 | } 38 | 39 | let subscriptionStorage = new Subscription( 40 | persis, 41 | read.getPersistenceReader() 42 | ); 43 | 44 | const subscriptions: Array = 45 | await subscriptionStorage.getSubscribedRooms( 46 | payload.repository.full_name, 47 | event 48 | ); 49 | if (!subscriptions || subscriptions.length == 0) { 50 | return this.success(); 51 | } 52 | const eventCaps = event.toUpperCase(); 53 | let messageText = "newEvent !"; 54 | 55 | if (event == "push") { 56 | messageText = `*New Commits to* *[${payload.repository.full_name}](${payload.repository.html_url}) by ${payload.pusher.name}*`; 57 | } else if (event == "pull_request") { 58 | if(payload.action == "opened"){ 59 | messageText = `*[New Pull Request](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.pull_request.user.login}](${payload.pull_request.user.html_url})* *|* *[${payload.repository.full_name}]*`; 60 | }else if(payload.action == "closed" && payload.pull_request.merged ){ 61 | messageText = `*[Merged Pull Request](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.pull_request.user.login}](${payload.pull_request.user.html_url})* *|* *[${payload.repository.full_name}]*`; 62 | }else if(payload.action == "closed" && !payload.pull_request.merged){ 63 | messageText = `*[Closed Pull Request](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.pull_request.user.login}](${payload.pull_request.user.html_url})* *|* *[${payload.repository.full_name}]*`; 64 | }else if(payload.action =="reopened"){ 65 | messageText = `*[ReOpened Pull Request](${payload.pull_request.html_url})* *|* *#${payload.pull_request.number} ${payload.pull_request.title}* by *[${payload.pull_request.user.login}](${payload.pull_request.user.html_url})* *|* *[${payload.repository.full_name}]*`; 66 | }else{ 67 | return this.success(); 68 | } 69 | } else if (event == "issues") { 70 | if(payload.action == "opened"){ 71 | messageText = `*[New Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; 72 | }else if(payload.action == "closed"){ 73 | messageText = `*[Issue Closed](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; 74 | }else if(payload.action == "reopened"){ 75 | messageText = `*[ReOpened Issue](${payload.issue.html_url})* *|* *#${payload.issue.number}* *${payload.issue.title}* *|* *[${payload.repository.full_name}](${payload.repository.html_url})* `; 76 | }else{ 77 | return this.success(); 78 | } 79 | } else if (event == "deployment_status") { 80 | messageText = `*Deployment Status ${payload.deployment_status.state}* *|* *${payload.repository.full_name}*`; 81 | } else if (event == "star"){ 82 | if(payload?.action == "created"){ 83 | messageText = `*New Stars on* *${payload.repository.full_name}* *|* *${payload.repository.stargazers_count}* ⭐`; 84 | }else{ 85 | return this.success(); 86 | } 87 | } 88 | for (let subscription of subscriptions) { 89 | let roomId = subscription.room; 90 | if (!roomId) { 91 | continue; 92 | } 93 | const room: IRoom = (await read 94 | .getRoomReader() 95 | .getById(roomId)) as IRoom; 96 | const textSender = await modify 97 | .getCreator() 98 | .startMessage() 99 | .setText(messageText); 100 | if (room) { 101 | textSender.setRoom(room); 102 | } 103 | await modify.getCreator().finish(textSender); 104 | } 105 | 106 | return this.success(); 107 | } 108 | } -------------------------------------------------------------------------------- /github/modals/issueCommentsModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 13 | import { 14 | UIKitBlockInteractionContext, 15 | UIKitInteractionContext, 16 | } from "@rocket.chat/apps-engine/definition/uikit"; 17 | import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; 18 | import { parseDate, parseTime } from "../helpers/dateTime"; 19 | 20 | export async function issueCommentsModal({ 21 | data, 22 | modify, 23 | read, 24 | persistence, 25 | http, 26 | slashcommandcontext, 27 | uikitcontext, 28 | }: { 29 | data?; 30 | modify: IModify; 31 | read: IRead; 32 | persistence: IPersistence; 33 | http: IHttp; 34 | slashcommandcontext?: SlashCommandContext; 35 | uikitcontext?: UIKitInteractionContext; 36 | }): Promise { 37 | const viewId = ModalsEnum.ISSUE_COMMENTS_MODAL_VIEW; 38 | 39 | const block = modify.getCreator().getBlockBuilder(); 40 | 41 | const room = 42 | slashcommandcontext?.getRoom() || 43 | uikitcontext?.getInteractionData().room; 44 | const user = 45 | slashcommandcontext?.getSender() || 46 | uikitcontext?.getInteractionData().user; 47 | 48 | if (user?.id) { 49 | let roomId; 50 | if (room?.id) { 51 | roomId = room.id; 52 | await storeInteractionRoomData(persistence, user.id, roomId); 53 | } else { 54 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 55 | } 56 | let issueData = data?.issueData; 57 | let issueComments = data?.issueComments; 58 | 59 | block.addSectionBlock({ 60 | text: { 61 | text: `*${issueData?.title}*`, 62 | type: TextObjectType.MARKDOWN, 63 | } 64 | }); 65 | block.addContextBlock({ 66 | elements: [ 67 | block.newPlainTextObject(`Author: ${issueData?.user_login} | `), 68 | block.newPlainTextObject(`State : ${issueData?.state} | `), 69 | block.newPlainTextObject(`Total Comments : ${issueComments?.length} |` ), 70 | ], 71 | }); 72 | 73 | block.addActionsBlock({ 74 | elements: [ 75 | block.newButtonElement({ 76 | actionId: ModalsEnum.COMMENT_ISSUE_ACTION, 77 | text: { 78 | text: ModalsEnum.COMMENT_ISSUE_LABEL, 79 | type: TextObjectType.PLAINTEXT, 80 | }, 81 | value: `${data?.repo} ${data?.issueNumber}`, 82 | }), 83 | block.newButtonElement({ 84 | text: { 85 | text: AppEnum.DEFAULT_TITLE, 86 | type: TextObjectType.PLAINTEXT, 87 | }, 88 | url: issueData?.html_url 89 | }), 90 | ], 91 | }); 92 | 93 | block.addDividerBlock(); 94 | 95 | if(issueComments?.length === 0){ 96 | block.addSectionBlock({ 97 | text: { 98 | text: `📝 No comments so far !`, 99 | type: TextObjectType.MARKDOWN, 100 | }, 101 | }); 102 | } 103 | 104 | let index = 1; 105 | 106 | for (let comment of issueComments) { 107 | let username = comment?.user?.login; 108 | let avatarUrl = comment?.user?.avatar_url; 109 | let commentBody = comment?.body; 110 | let userProfileUrl = comment?.user?.html_url 111 | 112 | block.addSectionBlock({ 113 | text: { 114 | text: `*@${username}*`, 115 | type: TextObjectType.MARKDOWN, 116 | }, 117 | accessory: block.newButtonElement({ 118 | actionId: ModalsEnum.VIEW_USER_ACTION, 119 | text: { 120 | text: ModalsEnum.VIEW_USER_LABEL, 121 | type: TextObjectType.PLAINTEXT, 122 | }, 123 | url:userProfileUrl 124 | }), 125 | }); 126 | block.addSectionBlock({ 127 | text: { 128 | text: `${commentBody}`, 129 | type: TextObjectType.MARKDOWN, 130 | }, 131 | }); 132 | let date = parseDate(comment?.created_at); 133 | let time = parseTime(comment?.created_at); 134 | block.addContextBlock({ 135 | elements: [ 136 | block.newPlainTextObject(`Created at : ${date} ${time} UTC`), 137 | ], 138 | }); 139 | block.addDividerBlock(); 140 | index++; 141 | } 142 | } 143 | 144 | return { 145 | id: viewId, 146 | title: { 147 | type: TextObjectType.PLAINTEXT, 148 | text: ModalsEnum.ISSUE_COMMENT_VIEW_TITLE, 149 | }, 150 | close: block.newButtonElement({ 151 | text: { 152 | type: TextObjectType.PLAINTEXT, 153 | text: "Close", 154 | }, 155 | }), 156 | blocks: block.getBlocks(), 157 | }; 158 | } 159 | -------------------------------------------------------------------------------- /github/lib/message.ts: -------------------------------------------------------------------------------- 1 | import { IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; 2 | import { IRoom, RoomType } from '@rocket.chat/apps-engine/definition/rooms'; 3 | import { BlockBuilder, IBlock } from '@rocket.chat/apps-engine/definition/uikit'; 4 | import { IUser } from '@rocket.chat/apps-engine/definition/users'; 5 | import { NotificationsController } from './notifications'; 6 | 7 | export async function getDirect(read: IRead, modify: IModify, appUser: IUser, username: string): Promise { 8 | const usernames = [appUser.username, username]; 9 | let room: IRoom; 10 | try { 11 | room = await read.getRoomReader().getDirectByUsernames(usernames); 12 | } catch (error) { 13 | console.log(error); 14 | return; 15 | } 16 | 17 | if (room) { 18 | return room; 19 | } else { 20 | let roomId: string; 21 | 22 | // Create direct room between botUser and username 23 | const newRoom = modify.getCreator().startRoom() 24 | .setType(RoomType.DIRECT_MESSAGE) 25 | .setCreator(appUser) 26 | .setMembersToBeAddedByUsernames(usernames); 27 | roomId = await modify.getCreator().finish(newRoom); 28 | return await read.getRoomReader().getById(roomId); 29 | } 30 | } 31 | 32 | export async function sendMessage( 33 | modify: IModify, 34 | room: IRoom, 35 | sender: IUser, 36 | message: string, 37 | blocks?: BlockBuilder | [IBlock], 38 | ): Promise { 39 | 40 | const msg = modify.getCreator().startMessage() 41 | .setSender(sender) 42 | .setRoom(room) 43 | .setGroupable(false) 44 | .setParseUrls(false) 45 | .setText(message); 46 | 47 | if (blocks !== undefined) { 48 | msg.setBlocks(blocks); 49 | } 50 | 51 | return await modify.getCreator().finish(msg); 52 | } 53 | 54 | export async function shouldSendMessage(read: IRead, persistence: IPersistence, user: IUser): Promise { 55 | const notificationsController = new NotificationsController(read, persistence, user); 56 | const notificationStatus = await notificationsController.getNotificationsStatus(); 57 | 58 | return notificationStatus ? notificationStatus.status : true; 59 | } 60 | 61 | export async function sendNotification(read: IRead, modify: IModify, user: IUser, room: IRoom, message: string, blocks?: BlockBuilder): Promise { 62 | const appUser = await read.getUserReader().getAppUser() as IUser; 63 | 64 | const msg = modify.getCreator().startMessage() 65 | .setSender(appUser) 66 | .setRoom(room) 67 | .setText(message); 68 | 69 | if (blocks) { 70 | msg.setBlocks(blocks); 71 | } 72 | 73 | return read.getNotifier().notifyUser(user, msg.getMessage()); 74 | } 75 | 76 | export async function sendDirectMessage( 77 | read: IRead, 78 | modify: IModify, 79 | user: IUser, 80 | message: string, 81 | persistence: IPersistence, 82 | blocks?: BlockBuilder | [IBlock], 83 | ): Promise { 84 | const appUser = await read.getUserReader().getAppUser() as IUser; 85 | const targetRoom = await getDirect(read, modify, appUser, user.username) as IRoom; 86 | 87 | const shouldSend = await shouldSendMessage(read, persistence, user); 88 | 89 | if (!shouldSend) { return ''; } 90 | 91 | return await sendMessage(modify, targetRoom, appUser, message, blocks); 92 | } 93 | 94 | export function isUserHighHierarchy(user: IUser): boolean { 95 | const clearanceList = ['admin', 'owner', 'moderator']; 96 | return user.roles.some((role) => clearanceList.includes(role)); 97 | } 98 | 99 | export async function sendDirectMessageOnInstall( 100 | read: IRead, 101 | modify: IModify, 102 | user: IUser, 103 | persistence: IPersistence 104 | ) { 105 | if (user.roles.includes("admin")) { 106 | const message = ` 107 | Hello **${user.name}!** Thank you for installing the **GitHub Rocket.Chat App**. 108 | 109 | Here's some important information to get you started: 110 | 111 | \xa0\xa0• **Quick and Easy Setup**: You can log in to GitHub with just one click using the built-in OAuth2 mechanism. 112 | \xa0\xa0• **Stay Updated**: Subscribe to Repository Events to receive notifications about new issues, pull requests, and more. 113 | \xa0\xa0• **Review and Merge Pull Requests**: You can conveniently review and merge pull requests directly from RocketChat Channels. 114 | \xa0\xa0• **Create and Manage Issues**: Create new issues from RocketChat and easily search and share existing issues and pull requests using extensive filters. 115 | \xa0\xa0• **Slash Commands**: Access the app using the slash commands \`/gh\` or \`/github\`. 116 | 117 | To assist you further, use the power of \`/github help\` to unlock a world of enhanced teamwork 118 | 119 | To unlock the full potential of the GitHub App, you need to set up a **GitHub OAuth App**. Follow these steps: 120 | \xa0\xa0• Set up a GitHub OAuth2 App by following the instructions provided. 121 | \xa0\xa0• Ensure the callback URL is set to your server's URL (Note: Remove any trailing '/' at the end of the hosted URL if authentication issues occur). 122 | \xa0\xa0• Once the GitHub OAuth app is set up, access the GitHub Application Settings and enter the GitHub App OAuth Client Id and Client Secret. 123 | 124 | We hope it enhances your collaboration and workflow. We would love to hear your feedback on your experience with the app. 125 | Your feedback helps us improve and provide a better user experience. Please visit the link to [**provide your feedback**](https://github.com/RocketChat/Apps.Github22/issues) 126 | 127 | Happy collaborating with \`GitHub\` and \`RocketChat\` :rocket: 128 | `; 129 | await sendDirectMessage(read, modify, user, message, persistence); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /github/modals/subscriptionsModal.ts: -------------------------------------------------------------------------------- 1 | import { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors'; 2 | import { ITextObject, TextObjectType } from '@rocket.chat/apps-engine/definition/uikit/blocks'; 3 | import { IUIKitModalViewParam } from '@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder'; 4 | import { IUser } from '@rocket.chat/apps-engine/definition/users'; 5 | import { ModalsEnum } from '../enum/Modals'; 6 | import { AppEnum } from '../enum/App'; 7 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 8 | import { SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands'; 9 | import { UIKitBlockInteractionContext, UIKitInteractionContext } from '@rocket.chat/apps-engine/definition/uikit'; 10 | import { getInteractionRoomData, storeInteractionRoomData } from '../persistance/roomInteraction'; 11 | import { Subscription } from '../persistance/subscriptions'; 12 | import { ISubscription } from '../definitions/subscription'; 13 | import { IRepositorySubscriptions } from '../definitions/repositorySubscriptions'; 14 | 15 | export async function subscriptionsModal({ modify, read, persistence, http, slashcommandcontext, uikitcontext }: { modify: IModify, read: IRead, persistence: IPersistence, http: IHttp ,slashcommandcontext?: SlashCommandContext, uikitcontext?: UIKitInteractionContext }): Promise { 16 | const viewId = ModalsEnum.SUBSCRIPTION_VIEW; 17 | 18 | const block = modify.getCreator().getBlockBuilder(); 19 | 20 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 21 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 22 | 23 | if (user?.id) { 24 | let roomId; 25 | if (room?.id) { 26 | roomId = room.id; 27 | await storeInteractionRoomData(persistence, user.id, roomId); 28 | } else { 29 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 30 | } 31 | 32 | let subscriptionStorage = new Subscription(persistence,read.getPersistenceReader()); 33 | let roomSubscriptions: Array = await subscriptionStorage.getSubscriptions(roomId); 34 | 35 | block.addDividerBlock(); 36 | 37 | 38 | let repositoryData = new Map; 39 | for (let subscription of roomSubscriptions) { 40 | 41 | let repoName = subscription.repoName; 42 | let userId = subscription.user; 43 | let event = subscription.event; 44 | let user = await read.getUserReader().getById(userId); 45 | 46 | if(repositoryData.has(repoName)){ 47 | let repoData = repositoryData.get(repoName) as IRepositorySubscriptions; 48 | repoData.events.push(event); 49 | repoData.user=user; 50 | repositoryData.set(repoName,repoData); 51 | }else{ 52 | let events:Array = []; 53 | events.push(event); 54 | let repoData:IRepositorySubscriptions={ 55 | webhookId:subscription.webhookId, 56 | events:events, 57 | user:user, 58 | repoName:repoName 59 | }; 60 | repositoryData.set(repoName,repoData); 61 | } 62 | 63 | } 64 | let index=1; 65 | for (let repository of repositoryData.values()) { 66 | let repoName = repository.repoName; 67 | let repoUser = repository.user; 68 | let events = repository.events; 69 | block.addSectionBlock({ 70 | text: { text: `${index}) ${repoName}`, type: TextObjectType.PLAINTEXT}, 71 | accessory: block.newButtonElement({ 72 | actionId: ModalsEnum.OPEN_REPO_ACTION, 73 | text: { 74 | text: ModalsEnum.OPEN_REPO_LABEL, 75 | type: TextObjectType.PLAINTEXT 76 | }, 77 | value: repository.webhookId, 78 | url:`https://github.com/${repoName}` 79 | }) 80 | }); 81 | let eventList : Array=[]; 82 | eventList.push(block.newPlainTextObject("Events : ")); 83 | for(let event of events){ 84 | eventList.push(block.newPlainTextObject(`${event} `)); 85 | } 86 | block.addContextBlock({ elements: eventList}); 87 | index++; 88 | } 89 | } 90 | 91 | block.addDividerBlock(); 92 | 93 | block.addActionsBlock({ 94 | elements: [ 95 | block.newButtonElement({ 96 | actionId: ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_MODAL, 97 | text: { text: ModalsEnum.OPEN_ADD_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, 98 | value: room?.id 99 | }), 100 | block.newButtonElement({ 101 | actionId: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_MODAL, 102 | text: { text: ModalsEnum.OPEN_DELETE_SUBSCRIPTIONS_LABEL, type: TextObjectType.PLAINTEXT }, 103 | value: room?.id 104 | }), 105 | block.newButtonElement({ 106 | actionId: ModalsEnum.SUBSCRIPTION_REFRESH_ACTION, 107 | text: { text: ModalsEnum.SUBSCRIPTION_REFRESH_LABEL, type: TextObjectType.PLAINTEXT }, 108 | value: room?.id 109 | }), 110 | ] 111 | }); 112 | 113 | return { 114 | id: viewId, 115 | title: { 116 | type: TextObjectType.PLAINTEXT, 117 | text: ModalsEnum.SUBSCRIPTION_TITLE, 118 | }, 119 | close: block.newButtonElement({ 120 | text: { 121 | type: TextObjectType.PLAINTEXT, 122 | text: 'Close', 123 | }, 124 | }), 125 | blocks: block.getBlocks(), 126 | }; 127 | } -------------------------------------------------------------------------------- /github/modals/addSubscriptionsModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | // import { getRoomTasks, getUIData, persistUIData } from '../lib/persistence'; 13 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 14 | import { 15 | UIKitBlockInteractionContext, 16 | UIKitInteractionContext, 17 | } from "@rocket.chat/apps-engine/definition/uikit"; 18 | import { 19 | storeInteractionRoomData, 20 | getInteractionRoomData, 21 | } from "../persistance/roomInteraction"; 22 | import { Subscription } from "../persistance/subscriptions"; 23 | import { ISubscription } from "../definitions/subscription"; 24 | 25 | export async function AddSubscriptionModal({ 26 | modify, 27 | read, 28 | persistence, 29 | http, 30 | slashcommandcontext, 31 | uikitcontext, 32 | }: { 33 | modify: IModify; 34 | read: IRead; 35 | persistence: IPersistence; 36 | http: IHttp; 37 | slashcommandcontext?: SlashCommandContext; 38 | uikitcontext?: UIKitInteractionContext; 39 | }): Promise { 40 | const viewId = ModalsEnum.ADD_SUBSCRIPTION_VIEW; 41 | const block = modify.getCreator().getBlockBuilder(); 42 | const room = 43 | slashcommandcontext?.getRoom() || 44 | uikitcontext?.getInteractionData().room; 45 | const user = 46 | slashcommandcontext?.getSender() || 47 | uikitcontext?.getInteractionData().user; 48 | 49 | if (user?.id) { 50 | let roomId; 51 | 52 | if (room?.id) { 53 | roomId = room.id; 54 | await storeInteractionRoomData(persistence, user.id, roomId); 55 | } else { 56 | roomId = ( 57 | await getInteractionRoomData( 58 | read.getPersistenceReader(), 59 | user.id 60 | ) 61 | ).roomId; 62 | } 63 | 64 | let subscriptionStorage = new Subscription( 65 | persistence, 66 | read.getPersistenceReader() 67 | ); 68 | let roomSubscriptions: Array = 69 | await subscriptionStorage.getSubscriptions(roomId); 70 | 71 | // shows indentations in input blocks but not inn section block 72 | block.addInputBlock({ 73 | blockId: ModalsEnum.REPO_NAME_INPUT, 74 | label: { 75 | text: ModalsEnum.REPO_NAME_LABEL, 76 | type: TextObjectType.PLAINTEXT, 77 | }, 78 | element: block.newPlainTextInputElement({ 79 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 80 | placeholder: { 81 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 82 | type: TextObjectType.PLAINTEXT, 83 | }, 84 | }), 85 | }); 86 | 87 | let newMultiStaticElemnt = block.newMultiStaticElement({ 88 | actionId: ModalsEnum.ADD_SUBSCRIPTION_EVENT_OPTIONS, 89 | options: [ 90 | { 91 | value: "issues", 92 | text: { 93 | type: TextObjectType.PLAINTEXT, 94 | text: "New Issues", 95 | emoji: true, 96 | }, 97 | }, 98 | { 99 | value: "pull_request", 100 | text: { 101 | type: TextObjectType.PLAINTEXT, 102 | text: "New Pull Request", 103 | emoji: true, 104 | }, 105 | }, 106 | { 107 | value: "push", 108 | text: { 109 | type: TextObjectType.PLAINTEXT, 110 | text: "New Commits", 111 | emoji: true, 112 | }, 113 | }, 114 | { 115 | value: "deployment_status", 116 | text: { 117 | type: TextObjectType.PLAINTEXT, 118 | text: "Deployment", 119 | emoji: true, 120 | }, 121 | }, 122 | { 123 | value: "star", 124 | text: { 125 | type: TextObjectType.PLAINTEXT, 126 | text: "New Stars", 127 | emoji: true, 128 | }, 129 | }, 130 | ], 131 | placeholder: { 132 | type: TextObjectType.PLAINTEXT, 133 | text: "Select Events", 134 | }, 135 | }); 136 | 137 | block.addInputBlock({ 138 | label: { 139 | text: ModalsEnum.ADD_SUBSCRIPTION_EVENT_LABEL, 140 | type: TextObjectType.PLAINTEXT, 141 | }, 142 | element: newMultiStaticElemnt, 143 | blockId: ModalsEnum.ADD_SUBSCRIPTION_EVENT_INPUT, 144 | }); 145 | } 146 | 147 | block.addDividerBlock(); 148 | 149 | return { 150 | id: viewId, 151 | title: { 152 | type: TextObjectType.PLAINTEXT, 153 | text: ModalsEnum.ADD_SUBSCIPTIONS_TITLE, 154 | }, 155 | close: block.newButtonElement({ 156 | text: { 157 | type: TextObjectType.PLAINTEXT, 158 | text: "Close", 159 | }, 160 | }), 161 | submit: block.newButtonElement({ 162 | actionId: ModalsEnum.ADD_SUBSCRIPTION_ACTION, 163 | text: { 164 | type: TextObjectType.PLAINTEXT, 165 | text: "Subscribe", 166 | }, 167 | }), 168 | blocks: block.getBlocks(), 169 | }; 170 | } 171 | -------------------------------------------------------------------------------- /github/modals/pullRequestCommentsModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 13 | import { 14 | UIKitBlockInteractionContext, 15 | UIKitInteractionContext, 16 | } from "@rocket.chat/apps-engine/definition/uikit"; 17 | import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; 18 | import { parseDate, parseTime } from "../helpers/dateTime"; 19 | 20 | export async function pullRequestCommentsModal({ 21 | data, 22 | modify, 23 | read, 24 | persistence, 25 | http, 26 | slashcommandcontext, 27 | uikitcontext, 28 | }: { 29 | data?; 30 | modify: IModify; 31 | read: IRead; 32 | persistence: IPersistence; 33 | http: IHttp; 34 | slashcommandcontext?: SlashCommandContext; 35 | uikitcontext?: UIKitInteractionContext; 36 | }): Promise { 37 | const viewId = ModalsEnum.PULL_REQUEST_COMMENTS_MODAL_VIEW; 38 | 39 | const block = modify.getCreator().getBlockBuilder(); 40 | 41 | const room = 42 | slashcommandcontext?.getRoom() || 43 | uikitcontext?.getInteractionData().room; 44 | const user = 45 | slashcommandcontext?.getSender() || 46 | uikitcontext?.getInteractionData().user; 47 | 48 | if (user?.id) { 49 | let roomId; 50 | if (room?.id) { 51 | roomId = room.id; 52 | await storeInteractionRoomData(persistence, user.id, roomId); 53 | } else { 54 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 55 | } 56 | let pullData = data?.pullData; 57 | let pullRequestComments = data?.pullRequestComments; 58 | 59 | block.addSectionBlock({ 60 | text: { 61 | text: `*${pullData?.title}*`, 62 | type: TextObjectType.MARKDOWN, 63 | }, 64 | accessory: block.newButtonElement({ 65 | actionId: ModalsEnum.VIEW_FILE_ACTION, 66 | text: { 67 | text: ModalsEnum.VIEW_DIFFS_ACTION_LABEL, 68 | type: TextObjectType.PLAINTEXT, 69 | }, 70 | value: pullData?.diff_url, 71 | }), 72 | }); 73 | block.addContextBlock({ 74 | elements: [ 75 | block.newPlainTextObject(`Author: ${pullData?.user?.login} | `), 76 | block.newPlainTextObject(`State : ${pullData?.state} | `), 77 | block.newPlainTextObject(`Mergeable : ${pullData?.mergeable} |` ), 78 | block.newPlainTextObject(`Total Comments : ${pullRequestComments?.length} |` ), 79 | ], 80 | }); 81 | 82 | block.addActionsBlock({ 83 | elements: [ 84 | block.newButtonElement({ 85 | actionId: ModalsEnum.COMMENT_PR_ACTION, 86 | text: { 87 | text: ModalsEnum.COMMENT_PR_LABEL, 88 | type: TextObjectType.PLAINTEXT, 89 | }, 90 | value: `${data?.repo} ${data?.pullNumber}`, 91 | }), 92 | block.newButtonElement({ 93 | text: { 94 | text: AppEnum.DEFAULT_TITLE, 95 | type: TextObjectType.PLAINTEXT, 96 | }, 97 | url: pullData ?.html_url 98 | }), 99 | ], 100 | }); 101 | 102 | block.addDividerBlock(); 103 | 104 | if(pullRequestComments?.length === 0){ 105 | block.addSectionBlock({ 106 | text: { 107 | text: `📝 No comments so far !`, 108 | type: TextObjectType.MARKDOWN, 109 | }, 110 | }); 111 | } 112 | 113 | let index = 1; 114 | 115 | for (let comment of pullRequestComments) { 116 | let username = comment?.user?.login; 117 | let avatarUrl = comment?.user?.avatar_url; 118 | let commentBody = comment?.body; 119 | let userProfileUrl = comment?.user?.html_url 120 | 121 | block.addSectionBlock({ 122 | text: { 123 | text: `*@${username}*`, 124 | type: TextObjectType.MARKDOWN, 125 | }, 126 | accessory: block.newButtonElement({ 127 | actionId: ModalsEnum.VIEW_USER_ACTION, 128 | text: { 129 | text: ModalsEnum.VIEW_USER_LABEL, 130 | type: TextObjectType.PLAINTEXT, 131 | }, 132 | url:userProfileUrl 133 | }), 134 | }); 135 | block.addSectionBlock({ 136 | text: { 137 | text: `${commentBody}`, 138 | type: TextObjectType.MARKDOWN, 139 | }, 140 | }); 141 | let date = parseDate(comment?.created_at); 142 | let time = parseTime(comment?.created_at); 143 | block.addContextBlock({ 144 | elements: [ 145 | block.newPlainTextObject(`Created at : ${date} ${time} UTC`), 146 | ], 147 | }); 148 | block.addDividerBlock(); 149 | index++; 150 | } 151 | } 152 | 153 | return { 154 | id: viewId, 155 | title: { 156 | type: TextObjectType.PLAINTEXT, 157 | text: ModalsEnum.PULL_REQUEST_COMMENT_VIEW_TITLE, 158 | }, 159 | close: block.newButtonElement({ 160 | text: { 161 | type: TextObjectType.PLAINTEXT, 162 | text: "Close", 163 | }, 164 | }), 165 | blocks: block.getBlocks(), 166 | }; 167 | } 168 | -------------------------------------------------------------------------------- /github/modals/addIssueAssigneeModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 13 | import { 14 | UIKitBlockInteractionContext, 15 | UIKitInteractionContext, 16 | } from "@rocket.chat/apps-engine/definition/uikit"; 17 | import { 18 | storeInteractionRoomData, 19 | getInteractionRoomData, 20 | } from "../persistance/roomInteraction"; 21 | 22 | export async function addIssueAssigneeModal({ 23 | data, 24 | modify, 25 | read, 26 | persistence, 27 | http, 28 | slashcommandcontext, 29 | uikitcontext, 30 | }: { 31 | data: any, 32 | modify: IModify; 33 | read: IRead; 34 | persistence: IPersistence; 35 | http: IHttp; 36 | slashcommandcontext?: SlashCommandContext; 37 | uikitcontext?: UIKitInteractionContext; 38 | }): Promise { 39 | const viewId = ModalsEnum.ADD_ISSUE_ASSIGNEE_VIEW; 40 | const block = modify.getCreator().getBlockBuilder(); 41 | const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; 42 | const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; 43 | 44 | if (user?.id) { 45 | let roomId; 46 | if (room?.id) { 47 | roomId = room.id; 48 | await storeInteractionRoomData(persistence, user.id, roomId); 49 | } else { 50 | roomId = ( 51 | await getInteractionRoomData( 52 | read.getPersistenceReader(), 53 | user.id 54 | ) 55 | ).roomId; 56 | } 57 | 58 | if(data?.repository != undefined){ 59 | block.addInputBlock({ 60 | blockId: ModalsEnum.REPO_NAME_INPUT, 61 | label: { 62 | text: ModalsEnum.REPO_NAME_LABEL, 63 | type: TextObjectType.PLAINTEXT, 64 | }, 65 | element: block.newPlainTextInputElement({ 66 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 67 | placeholder: { 68 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 69 | type: TextObjectType.PLAINTEXT, 70 | }, 71 | initialValue: data?.repository 72 | }), 73 | }); 74 | }else{ 75 | block.addInputBlock({ 76 | blockId: ModalsEnum.REPO_NAME_INPUT, 77 | label: { 78 | text: ModalsEnum.REPO_NAME_LABEL, 79 | type: TextObjectType.PLAINTEXT, 80 | }, 81 | element: block.newPlainTextInputElement({ 82 | actionId: ModalsEnum.REPO_NAME_INPUT_ACTION, 83 | placeholder: { 84 | text: ModalsEnum.REPO_NAME_PLACEHOLDER, 85 | type: TextObjectType.PLAINTEXT, 86 | }, 87 | }), 88 | }); 89 | } 90 | 91 | if(data?.issueNumber){ 92 | block.addInputBlock({ 93 | blockId: ModalsEnum.ISSUE_NUMBER_INPUT, 94 | label: { 95 | text: ModalsEnum.ISSUE_NUMBER_INPUT_LABEL, 96 | type: TextObjectType.PLAINTEXT, 97 | }, 98 | element: block.newPlainTextInputElement({ 99 | actionId: ModalsEnum.ISSUE_NUMBER_INPUT_ACTION, 100 | placeholder: { 101 | text: ModalsEnum.ISSUE_NUMBER_INPUT_PLACEHOLDER, 102 | type: TextObjectType.PLAINTEXT, 103 | }, 104 | initialValue: data.issueNumber 105 | }), 106 | }); 107 | }else{ 108 | block.addInputBlock({ 109 | blockId: ModalsEnum.ISSUE_NUMBER_INPUT, 110 | label: { 111 | text: ModalsEnum.ISSUE_NUMBER_INPUT_LABEL, 112 | type: TextObjectType.PLAINTEXT, 113 | }, 114 | element: block.newPlainTextInputElement({ 115 | actionId: ModalsEnum.ISSUE_NUMBER_INPUT_ACTION, 116 | placeholder: { 117 | text: ModalsEnum.ISSUE_NUMBER_INPUT_PLACEHOLDER, 118 | type: TextObjectType.PLAINTEXT, 119 | }, 120 | }), 121 | }); 122 | } 123 | 124 | if(data?.assignees){ 125 | block.addInputBlock({ 126 | blockId: ModalsEnum.ISSUE_ASSIGNEE_INPUT, 127 | label: { 128 | text: ModalsEnum.ISSUE_ASSIGNEE_LABEL, 129 | type: TextObjectType.PLAINTEXT, 130 | }, 131 | element: block.newPlainTextInputElement({ 132 | actionId: ModalsEnum.ISSUE_ASSIGNEE_INPUT_ACTION, 133 | placeholder: { 134 | text: ModalsEnum.ISSUE_ASSIGNEE_PLACEHOLDER, 135 | type: TextObjectType.PLAINTEXT, 136 | }, 137 | initialValue: data.assignees 138 | }), 139 | }); 140 | }else{ 141 | block.addInputBlock({ 142 | blockId: ModalsEnum.ISSUE_ASSIGNEE_INPUT, 143 | label: { 144 | text: ModalsEnum.ISSUE_ASSIGNEE_LABEL, 145 | type: TextObjectType.PLAINTEXT, 146 | }, 147 | element: block.newPlainTextInputElement({ 148 | actionId: ModalsEnum.ISSUE_ASSIGNEE_INPUT_ACTION, 149 | placeholder: { 150 | text: ModalsEnum.ISSUE_ASSIGNEE_PLACEHOLDER, 151 | type: TextObjectType.PLAINTEXT, 152 | }, 153 | }), 154 | }); 155 | } 156 | } 157 | 158 | block.addDividerBlock(); 159 | 160 | return { 161 | id: viewId, 162 | title: { 163 | type: TextObjectType.PLAINTEXT, 164 | text: ModalsEnum.GITHUB_ISSUES_TITLE, 165 | }, 166 | close: block.newButtonElement({ 167 | text: { 168 | type: TextObjectType.PLAINTEXT, 169 | text: "Close", 170 | }, 171 | }), 172 | submit: block.newButtonElement({ 173 | actionId: ModalsEnum.NEW_ISSUE_ACTION, 174 | text: { 175 | type: TextObjectType.PLAINTEXT, 176 | text: "Assign", 177 | }, 178 | }), 179 | blocks: block.getBlocks(), 180 | }; 181 | } 182 | -------------------------------------------------------------------------------- /github/modals/pullDetailsModal.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IHttp, 3 | IModify, 4 | IPersistence, 5 | IRead, 6 | } from "@rocket.chat/apps-engine/definition/accessors"; 7 | import { TextObjectType } from "@rocket.chat/apps-engine/definition/uikit/blocks"; 8 | import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; 9 | import { IUser } from "@rocket.chat/apps-engine/definition/users"; 10 | import { ModalsEnum } from "../enum/Modals"; 11 | import { AppEnum } from "../enum/App"; 12 | import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; 13 | import { 14 | UIKitBlockInteractionContext, 15 | UIKitInteractionContext, 16 | } from "@rocket.chat/apps-engine/definition/uikit"; 17 | import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; 18 | 19 | export async function pullDetailsModal({ 20 | data, 21 | modify, 22 | read, 23 | persistence, 24 | http, 25 | slashcommandcontext, 26 | uikitcontext, 27 | }: { 28 | data?; 29 | modify: IModify; 30 | read: IRead; 31 | persistence: IPersistence; 32 | http: IHttp; 33 | slashcommandcontext?: SlashCommandContext; 34 | uikitcontext?: UIKitInteractionContext; 35 | }): Promise { 36 | const viewId = ModalsEnum.PULL_VIEW; 37 | 38 | const block = modify.getCreator().getBlockBuilder(); 39 | 40 | const room = 41 | slashcommandcontext?.getRoom() || 42 | uikitcontext?.getInteractionData().room; 43 | const user = 44 | slashcommandcontext?.getSender() || 45 | uikitcontext?.getInteractionData().user; 46 | 47 | if (user?.id) { 48 | let roomId; 49 | if (room?.id) { 50 | roomId = room.id; 51 | await storeInteractionRoomData(persistence, user.id, roomId); 52 | } else { 53 | roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; 54 | } 55 | 56 | const pullRawData = await http.get( 57 | `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}` 58 | ); 59 | 60 | // If pullsNumber doesn't exist, notify the user 61 | if (pullRawData.statusCode === 404) { 62 | block.addSectionBlock({ 63 | text: { 64 | text: `Pull request #${data?.number} doesn't exist.`, 65 | type: TextObjectType.PLAINTEXT, 66 | }, 67 | }); 68 | 69 | return { 70 | title: { 71 | type: TextObjectType.PLAINTEXT, 72 | text: AppEnum.DEFAULT_TITLE, 73 | }, 74 | close: block.newButtonElement({ 75 | text: { 76 | type: TextObjectType.PLAINTEXT, 77 | text: "Close", 78 | }, 79 | }), 80 | blocks: block.getBlocks(), 81 | }; 82 | } 83 | 84 | const pullData = pullRawData.data; 85 | 86 | const pullRequestFilesRaw = await http.get( 87 | `https://api.github.com/repos/${data?.repository}/pulls/${data?.number}/files` 88 | ); 89 | 90 | const pullRequestFiles = pullRequestFilesRaw.data; 91 | 92 | block.addSectionBlock({ 93 | text: { 94 | text: `*${pullData?.title}*`, 95 | type: TextObjectType.MARKDOWN, 96 | }, 97 | accessory: block.newButtonElement({ 98 | actionId: ModalsEnum.VIEW_FILE_ACTION, 99 | text: { 100 | text: ModalsEnum.VIEW_DIFFS_ACTION_LABEL, 101 | type: TextObjectType.PLAINTEXT, 102 | }, 103 | value: pullData["diff_url"], 104 | }), 105 | }); 106 | block.addContextBlock({ 107 | elements: [ 108 | block.newPlainTextObject(`Author: ${pullData?.user?.login} | `), 109 | block.newPlainTextObject(`State : ${pullData?.state} | `), 110 | block.newPlainTextObject(`Mergeable : ${pullData?.mergeable}`), 111 | ], 112 | }); 113 | 114 | block.addDividerBlock(); 115 | 116 | let index = 1; 117 | 118 | for (let file of pullRequestFiles) { 119 | let fileName = file["filename"]; 120 | let rawUrl = file["raw_url"]; 121 | let status = file["status"]; 122 | let addition = file["additions"]; 123 | let deletions = file["deletions"]; 124 | block.addSectionBlock({ 125 | text: { 126 | text: `${index} ${fileName}`, 127 | type: TextObjectType.PLAINTEXT, 128 | }, 129 | accessory: block.newButtonElement({ 130 | actionId: ModalsEnum.VIEW_FILE_ACTION, 131 | text: { 132 | text: ModalsEnum.VIEW_FILE_ACTION_LABEL, 133 | type: TextObjectType.PLAINTEXT, 134 | }, 135 | value: rawUrl, 136 | }), 137 | }); 138 | block.addContextBlock({ 139 | elements: [ 140 | block.newPlainTextObject(`Status: ${status} | `), 141 | block.newPlainTextObject(`Additions : ${addition} | `), 142 | block.newPlainTextObject(`Deletions : ${deletions}`), 143 | ], 144 | }); 145 | 146 | index++; 147 | } 148 | } 149 | 150 | block.addActionsBlock({ 151 | elements: [ 152 | block.newButtonElement({ 153 | actionId: ModalsEnum.MERGE_PULL_REQUEST_ACTION, 154 | text: { 155 | text: ModalsEnum.MERGE_PULL_REQUEST_LABEL, 156 | type: TextObjectType.PLAINTEXT, 157 | }, 158 | value: `${data?.repository} ${data?.number}`, 159 | }), 160 | block.newButtonElement({ 161 | actionId: ModalsEnum.PR_COMMENT_LIST_ACTION, 162 | text: { 163 | text: ModalsEnum.PR_COMMENT_LIST_LABEL, 164 | type: TextObjectType.PLAINTEXT, 165 | }, 166 | value: `${data?.repository} ${data?.number}`, 167 | }), 168 | block.newButtonElement({ 169 | actionId: ModalsEnum.APPROVE_PULL_REQUEST_ACTION, 170 | text: { 171 | text: ModalsEnum.APPROVE_PULL_REQUEST_LABEL, 172 | type: TextObjectType.PLAINTEXT, 173 | }, 174 | value: `${data?.repository} ${data?.number}`, 175 | }), 176 | ], 177 | }); 178 | 179 | return { 180 | id: viewId, 181 | title: { 182 | type: TextObjectType.PLAINTEXT, 183 | text: AppEnum.DEFAULT_TITLE, 184 | }, 185 | close: block.newButtonElement({ 186 | text: { 187 | type: TextObjectType.PLAINTEXT, 188 | text: "Close", 189 | }, 190 | }), 191 | blocks: block.getBlocks(), 192 | }; 193 | } 194 | --------------------------------------------------------------------------------