diff --git a/github/definitions/Gist.ts b/github/definitions/Gist.ts new file mode 100644 index 0000000..254665a --- /dev/null +++ b/github/definitions/Gist.ts @@ -0,0 +1,11 @@ +import { IGistFile } from "./GistFile"; + +export interface IGist { + id : string, + html_url : string, + updated_at : string, + description : string, + owner_login : string, + owner_avatar : string, + files : any +} diff --git a/github/definitions/GistFile.ts b/github/definitions/GistFile.ts new file mode 100644 index 0000000..7313fe5 --- /dev/null +++ b/github/definitions/GistFile.ts @@ -0,0 +1,6 @@ +export interface IGistFile { + filename : string, + language : string, + raw_url : string, + size : number +} diff --git a/github/definitions/githubIssue.ts b/github/definitions/githubIssue.ts index 597517b..b32438c 100644 --- a/github/definitions/githubIssue.ts +++ b/github/definitions/githubIssue.ts @@ -1,13 +1,21 @@ +import { IGithubReactions } from "./githubReactions" + //inidividual issue export interface IGitHubIssue{ issue_id: string|number, title?: string, - html_url?: string, + html_url?: string, number?: string|number labels?: Array, user_login?:string, + user_avatar?:string, + last_updated_at?: string, + comments?:string|number, state?: string, share?: boolean,//true if seacrh result is to be shareed assignees?: Array,//user ids seperated by " " issue_compact: string,//compact string to share issues in rooms -} \ No newline at end of file + repo_url?: string, + body?: string, + reactions? : IGithubReactions +} diff --git a/github/definitions/githubReactions.ts b/github/definitions/githubReactions.ts new file mode 100644 index 0000000..04624d6 --- /dev/null +++ b/github/definitions/githubReactions.ts @@ -0,0 +1,13 @@ +// reactions object for issues, comments and repos + +export interface IGithubReactions { + total_count : number, + plus_one : number, + minus_one : number, + laugh : number, + hooray : number, + confused : number, + heart : number, + rocket : number, + eyes : number +} diff --git a/github/enum/Modals.ts b/github/enum/Modals.ts index ab8ceec..58052e1 100644 --- a/github/enum/Modals.ts +++ b/github/enum/Modals.ts @@ -1,4 +1,29 @@ export enum ModalsEnum { + SHARE_GIST_ACTION = 'share-gist-action', + SHARE_ISSUE_ACTION = 'share-issue-action', + TRIGGER_ISSUE_DISPLAY_MODAL = 'display-issue', + SWITCH_ISSUE_ORDER = 'switch-issue-order', + ISSUES_ASCENDING = 'asc', + ISSUES_DESCENDING = 'desc', + SWITCH_ISSUE_STATE = 'switch-issue-state', + SWITCH_ISSUE_SORT = 'switch-issue-sort', + ISSUE_SORT_CREATED = 'sort_created', + ISSUE_SORT_UPDATED = 'sort_updated', + ISSUE_SORT_COMMENTS = 'sort_comments', + ISSUE_STATE_OPEN = 'open', + ISSUE_STATE_CLOSED = 'closed', + ASSIGNED_ISSUE_FILTER = 'assigned', + CREATED_ISSUE_FILTER = 'created', + MENTIONED_ISSUE_FILTER = 'mentioned', + SWITCH_ISSUE_FILTER = 'switch-issue-filter', + USER_ISSUE_VIEW = 'user-issue-view', + TRIGGER_ISSUES_MODAL = 'trigger-issue-modal', + TRIGGER_REPOS_MODAL = 'trigger-repos-modal', + TRIGGER_ACTIVITY_MODAL = 'trigger-activity-modal', + TRIGGER_NOTIFICATIONS_MODAL = 'trigger-notifications-modal', + SHARE_PROFILE_EXEC = 'share-profile-exec', + SHARE_PROFILE_PARAMS = 'share-profile-params', + SHARE_PROFILE = 'share-profile', PULL_VIEW = 'pull-view', CODE_VIEW = 'code-view', CODE_VIEW_LABEL = 'Code Changes', @@ -15,6 +40,7 @@ export enum ModalsEnum { SUBSCRIPTION_VIEW = 'subscriptions-view', ADD_SUBSCRIPTION_VIEW = 'add-subscription-view', DELETE_SUBSCRIPTION_VIEW ='delete-subscription-view', + USER_PROFILE_VIEW = 'user-profile-view', OPEN_ADD_SUBSCRIPTIONS_MODAL='open-add-subscriptions', OPEN_UPDATE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', OPEN_DELETE_SUBSCRIPTIONS_MODAL='open-update-subscriptions', @@ -134,7 +160,7 @@ export enum ModalsEnum { ISSUE_TEMPLATE_SELECTION_LABEL = "Select", BLANK_GITHUB_TEMPLATE = "blank-github-app-template", GITHUB_ISSUES_STARTER_VIEW = "github-issues-starter-view", - GITHUB_ISSUES_TITLE = "GitHub Issues", + GITHUB_ISSUES_TITLE = "GitHub Issues", ISSUE_LIST_VIEW = "github-issue-list-view", ADD_GITHUB_ISSUE_ASSIGNEE = "update-github-issue", ADD_GITHUB_ISSUE_ASSIGNEE_LABEL = "Assign", @@ -157,4 +183,4 @@ export enum ModalsEnum { MULTI_SHARE_GITHUB_ISSUES_INPUT = "multishare-github-issues-input", MULTI_SHARE_GITHUB_ISSUES_INPUT_LABEL = "Issues", MULTI_SHARE_GITHUB_ISSUES_INPUT_ACTION = "multishare-github-issues-input-action", -} \ No newline at end of file +} diff --git a/github/enum/OcticonIcons.ts b/github/enum/OcticonIcons.ts new file mode 100644 index 0000000..0174b80 --- /dev/null +++ b/github/enum/OcticonIcons.ts @@ -0,0 +1,9 @@ +export enum OcticonIcons { + ISSUE_OPEN = "https://raw.githubusercontent.com/primer/octicons/main/icons/issue-opened-24.svg", + ISSUE_CLOSED = "https://raw.githubusercontent.com/primer/octicons/main/icons/issue-closed-24.svg", + COMMENTS = "https://raw.githubusercontent.com/primer/octicons/main/icons/comment-24.svg", + COMMIT = "https://raw.githubusercontent.com/primer/octicons/main/icons/commit-24.svg", + PERSON = "https://raw.githubusercontent.com/primer/octicons/main/icons/person-24.svg", + REPOSITORY = "https://raw.githubusercontent.com/primer/octicons/main/icons/repo-24.svg", + PENCIL = "https://raw.githubusercontent.com/primer/octicons/main/icons/pencil-24.svg" +} diff --git a/github/enum/Subcommands.ts b/github/enum/Subcommands.ts index 61d4fab..b5c689b 100644 --- a/github/enum/Subcommands.ts +++ b/github/enum/Subcommands.ts @@ -7,5 +7,7 @@ export enum SubcommandEnum { TEST = 'test', SEARCH = 'search', NEW_ISSUE = 'issue', - ISSUES = 'issues' -} \ No newline at end of file + ISSUES = 'issues', + PROFILE = 'me', + GIST = 'gist' +} diff --git a/github/handlers/ExecuteBlockActionHandler.ts b/github/handlers/ExecuteBlockActionHandler.ts index 6cbe820..2db94de 100644 --- a/github/handlers/ExecuteBlockActionHandler.ts +++ b/github/handlers/ExecuteBlockActionHandler.ts @@ -11,17 +11,32 @@ import { basicQueryMessage } from "../helpers/basicQueryMessage"; import { ModalsEnum } from "../enum/Modals"; import { fileCodeModal } from "../modals/fileCodeModal"; import { + ButtonStyle, IUIKitResponse, + TextObjectType, UIKitBlockInteractionContext, } from "@rocket.chat/apps-engine/definition/uikit"; import { AddSubscriptionModal } from "../modals/addSubscriptionsModal"; import { deleteSubsciptionsModal } from "../modals/deleteSubscriptions"; -import { deleteSubscription, updateSubscription, getIssueTemplateCode, getPullRequestComments, getPullRequestData, getRepositoryIssues } from "../helpers/githubSDK"; +import { + deleteSubscription, + updateSubscription, + getIssueTemplateCode, + getPullRequestComments, + getPullRequestData, + getRepositoryIssues, + getBasicUserInfo, + getIssueData, + loadGist, +} from "../helpers/githubSDK"; import { Subscription } from "../persistance/subscriptions"; import { getAccessTokenForUser } from "../persistance/auth"; import { GithubApp } from "../GithubApp"; import { IAuthData } from "@rocket.chat/apps-engine/definition/oauth2/IOAuth2"; -import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; +import { + storeInteractionRoomData, + getInteractionRoomData, +} from "../persistance/roomInteraction"; import { sendMessage, sendNotification } from "../lib/message"; import { subsciptionsModal } from "../modals/subscriptionsModal"; import { mergePullRequestModal } from "../modals/mergePullReqeustModal"; @@ -39,6 +54,18 @@ import { addIssueAssigneeModal } from "../modals/addIssueAssigneeModal"; import { githubIssuesListModal } from "../modals/githubIssuesListModal"; import { GithubRepoIssuesStorage } from "../persistance/githubIssues"; import { IGitHubIssueData } from "../definitions/githubIssueData"; +import { shareProfileModal } from "../modals/profileShareModal"; +import { + RocketChatAssociationModel, + RocketChatAssociationRecord, +} from "@rocket.chat/apps-engine/definition/metadata"; +import { userIssuesModal } from "../modals/UserIssuesModal"; +import { IssueDisplayModal } from "../modals/IssueDisplayModal"; +import { IGitHubIssue } from "../definitions/githubIssue"; +import { BodyMarkdownRenderer } from "../processors/bodyMarkdowmRenderer"; +import { CreateIssueStatsBar } from "../lib/CreateIssueStatsBar"; +import { OcticonIcons } from "../enum/OcticonIcons"; +import { IGistFile } from "../definitions/GistFile"; export class ExecuteBlockActionHandler { constructor( @@ -47,7 +74,7 @@ export class ExecuteBlockActionHandler { private readonly http: IHttp, private readonly modify: IModify, private readonly persistence: IPersistence - ) { } + ) {} public async run( context: UIKitBlockInteractionContext @@ -78,8 +105,12 @@ export class ExecuteBlockActionHandler { lengthOfRepoString ) as String; let { user, room } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; - if(room && user){ + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + if (room && user) { await basicQueryMessage({ query, repository, @@ -100,6 +131,448 @@ export class ExecuteBlockActionHandler { success: false, }; } + } + case ModalsEnum.SHARE_GIST_ACTION: { + let { user, value, room } = context.getInteractionData(); + const access_token = await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + ); + if (value != undefined && access_token != undefined) { + const val = JSON.parse(value); + const block = this.modify + .getCreator() + .getBlockBuilder(); + + block.addContextBlock({ + elements: [ + block.newImageElement({ + imageUrl: val["owner"]["avatar_url"], + altText: "Created by", + }), + block.newPlainTextObject(val["owner"]["login"]), + block.newImageElement({ + imageUrl: OcticonIcons.PENCIL, + altText: "Updated at", + }), + block.newPlainTextObject( + new Date(val.updated_at).toISOString() + ), + ], + }); + + const files = new Map( + Object.entries(val.files) + ); + for (const [_key, value] of files) { + const gistContent = await loadGist( + this.http, + value.raw_url, + access_token.token + ); + block.addSectionBlock({ + text: { + text: value.filename, + type: TextObjectType.PLAINTEXT, + }, + }); + block.addSectionBlock({ + text: { + text: `\`\`\`${value.language}\n ${gistContent} \n\`\`\``, + type: TextObjectType.MARKDOWN, + }, + }); + } + + if (user?.id) { + if (room?.id) { + await sendMessage( + this.modify, + room!, + user, + `Gist`, + block + ); + } else { + let roomId = ( + await getInteractionRoomData( + this.read.getPersistenceReader(), + user.id + ) + ).roomId; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; + await sendMessage( + this.modify, + room, + user, + `Gist`, + block + ); + } + } + break; + } + } + case ModalsEnum.SHARE_ISSUE_ACTION: { + let { user, value, room } = context.getInteractionData(); + const access_token = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + + const repoName = value?.split(",")[0] ?? ""; + const issueNumber = value?.split(",")[1] ?? ""; + + const issueInfo: IGitHubIssue = await getIssueData( + repoName, + issueNumber, + access_token.token, + this.http + ); + + const block = this.modify.getCreator().getBlockBuilder(); + + CreateIssueStatsBar(issueInfo, block); + + block.addSectionBlock({ + text: { + text: `*${issueInfo.title}*` ?? "", + type: TextObjectType.MARKDOWN, + }, + }), + block.addActionsBlock({ + elements: [ + block.newButtonElement({ + text: { + text: "Open Issue in Browser", + type: TextObjectType.PLAINTEXT, + }, + url: issueInfo.html_url, + style: ButtonStyle.PRIMARY, + }), + ], + }); + issueInfo.body && + BodyMarkdownRenderer({ + body: issueInfo.body, + block: block, + }); + + if (user?.id) { + if (room?.id) { + await sendMessage( + this.modify, + room!, + user, + `Issue`, + block + ); + } else { + let roomId = ( + await getInteractionRoomData( + this.read.getPersistenceReader(), + user.id + ) + ).roomId; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; + await sendMessage( + this.modify, + room, + user, + `Issue`, + block + ); + } + } + break; + } + case ModalsEnum.TRIGGER_ISSUE_DISPLAY_MODAL: { + const { user, value } = context.getInteractionData(); + const access_token = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + + const repoInfo = value?.split(",")[0] ?? ""; + const issueNumber = value?.split(",")[1] ?? ""; + + const issueDisplayModal = await IssueDisplayModal({ + repoName: repoInfo, + issueNumber: issueNumber, + access_token: access_token.token, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + + return context + .getInteractionResponder() + .updateModalViewResponse(issueDisplayModal); + } + case ModalsEnum.SWITCH_ISSUE_SORT: + case ModalsEnum.SWITCH_ISSUE_STATE: + case ModalsEnum.SWITCH_ISSUE_FILTER: { + const record = new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + "ISSUE_MAIN_FILTER" + ); + + const issueFilterArray = (await this.read + .getPersistenceReader() + .readByAssociation(record)) as { + filter: string; + state: string; + sort: string; + order: string; + }[]; + + const { user, value } = context.getInteractionData(); + + let filter: + | { + filter: string; + state: string; + sort: string; + order: string; + } + | undefined; + + const prev_sort = + issueFilterArray.length == 0 + ? ModalsEnum.ISSUE_SORT_CREATED + : issueFilterArray[0].sort; + const prev_filter = + issueFilterArray.length == 0 + ? ModalsEnum.ASSIGNED_ISSUE_FILTER + : issueFilterArray[0].filter; + const prev_state = + issueFilterArray.length == 0 + ? ModalsEnum.ISSUE_STATE_OPEN + : issueFilterArray[0].state; + + switch (value as string) { + case ModalsEnum.ASSIGNED_ISSUE_FILTER: + case ModalsEnum.MENTIONED_ISSUE_FILTER: + case ModalsEnum.CREATED_ISSUE_FILTER: + filter = { + filter: value as string, + sort: prev_sort, + state: prev_state, + order: ModalsEnum.ISSUES_DESCENDING, + }; + break; + case ModalsEnum.ISSUE_SORT_CREATED: + case ModalsEnum.ISSUE_SORT_COMMENTS: + case ModalsEnum.ISSUE_SORT_UPDATED: + filter = { + filter: prev_filter, + sort: value as string, + state: prev_state, + order: ModalsEnum.ISSUES_DESCENDING, + }; + break; + case ModalsEnum.ISSUE_STATE_OPEN: + case ModalsEnum.ISSUE_STATE_CLOSED: + filter = { + filter: prev_filter, + sort: prev_sort, + state: value as string, + order: ModalsEnum.ISSUES_DESCENDING, + }; + break; + default: + filter = { + filter: ModalsEnum.ASSIGNED_ISSUE_FILTER, + sort: ModalsEnum.ISSUE_SORT_CREATED, + state: ModalsEnum.ISSUE_STATE_OPEN, + order: ModalsEnum.ISSUES_DESCENDING, + }; + } + + let access_token = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + const issueModal = await userIssuesModal({ + access_token: access_token.token, + filter: filter, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + }); + + await this.persistence.updateByAssociation(record, filter); + + return context + .getInteractionResponder() + .updateModalViewResponse(issueModal); + } + case ModalsEnum.TRIGGER_ISSUES_MODAL: { + const { user } = context.getInteractionData(); + + let access_token = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + + const filter = { + filter: ModalsEnum.ASSIGNED_ISSUE_FILTER, + state: ModalsEnum.ISSUE_STATE_OPEN, + sort: ModalsEnum.ISSUE_SORT_CREATED, + }; + + const issuesModal = await userIssuesModal({ + filter: filter, + access_token: access_token.token, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + + return context + .getInteractionResponder() + .openModalViewResponse(issuesModal); + } + case ModalsEnum.TRIGGER_REPOS_MODAL: { + break; + } + case ModalsEnum.TRIGGER_ACTIVITY_MODAL: { + break; + } + case ModalsEnum.SHARE_PROFILE_PARAMS: { + const profileInteractionData = + context.getInteractionData().value; + const datAny = profileInteractionData as any; + const storeData = { + profileParams: datAny as string[], + }; + + await this.persistence.updateByAssociation( + new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + "ProfileShareParam" + ), + storeData + ); + break; + } + case ModalsEnum.SHARE_PROFILE_EXEC: { + let { user, room } = context.getInteractionData(); + const block = this.modify.getCreator().getBlockBuilder(); + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + const userProfile = await getBasicUserInfo( + this.http, + accessToken.token + ); + + const idRecord = new RocketChatAssociationRecord( + RocketChatAssociationModel.MISC, + "ProfileShareParam" + ); + + const profileData = await this.read + .getPersistenceReader() + .readByAssociation(idRecord); + + let profileShareParams: string[] = []; + + if (profileData.length == 0) { + profileShareParams = [ + "username", + "avatar", + "email", + "bio", + "followers", + "following", + "contributionGraph", + ]; + } else { + const dat = profileData[0] as { + profileParams: string[]; + }; + profileShareParams = dat.profileParams; + } + + if (profileShareParams.includes("avatar")) { + block.addImageBlock({ + imageUrl: userProfile.avatar, + altText: "User Info", + }); + } + + profileShareParams.map((value) => { + if (value != "contributionGraph" && value != "avatar") { + block.addSectionBlock({ + text: block.newPlainTextObject(value), + }); + block.addContextBlock({ + elements: [ + block.newPlainTextObject( + userProfile[value], + true + ), + ], + }); + block.addDividerBlock(); + } + }); + + if (profileShareParams.includes("contributionGraph")) { + block.addImageBlock({ + imageUrl: `https://activity-graph.herokuapp.com/graph?username=${userProfile.username}&bg_color=ffffff&color=708090&line=24292e&point=24292e`, + altText: "Github Contribution Graph", + }); + } + + if (user?.id) { + if (room?.id) { + await sendMessage( + this.modify, + room!, + user, + `${userProfile.name}'s Github Profile`, + block + ); + } else { + let roomId = ( + await getInteractionRoomData( + this.read.getPersistenceReader(), + user.id + ) + ).roomId; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; + await sendMessage( + this.modify, + room, + user, + `${userProfile.name}'s Github Profile`, + block + ); + } + } + + this.persistence.removeByAssociation(idRecord); + break; } case ModalsEnum.VIEW_FILE_ACTION: { @@ -121,8 +594,8 @@ export class ExecuteBlockActionHandler { read: this.read, persistence: this.persistence, http: this.http, - uikitcontext: context - }) + uikitcontext: context, + }); return context .getInteractionResponder() .openModalViewResponse(addSubscriptionModal); @@ -133,40 +606,73 @@ export class ExecuteBlockActionHandler { read: this.read, persistence: this.persistence, http: this.http, - uikitcontext: context - }) + uikitcontext: context, + }); return context .getInteractionResponder() .openModalViewResponse(addSubscriptionModal); } case ModalsEnum.DELETE_SUBSCRIPTION_ACTION: { - let { user, room } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; - let value: string = context.getInteractionData().value as string; - let splitted = value.split(','); + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + let value: string = context.getInteractionData() + .value as string; + let splitted = value.split(","); if (splitted.length == 2 && accessToken.token) { let repoName = splitted[0]; let hookId = splitted[1]; let roomId; if (room?.id) { roomId = room.id; - await storeInteractionRoomData(this.persistence, user.id, roomId); + await storeInteractionRoomData( + this.persistence, + user.id, + roomId + ); } else { - roomId = (await getInteractionRoomData(this.read.getPersistenceReader(), user.id)).roomId; + roomId = ( + await getInteractionRoomData( + this.read.getPersistenceReader(), + user.id + ) + ).roomId; } - //delete the susbscriptions for persistance - let subscriptionStorage = new Subscription(this.persistence, this.read.getPersistenceReader()); - let oldSubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName, user.id); - await subscriptionStorage.deleteSubscriptionsByRepoUser(repoName, roomId, user.id); + //delete the susbscriptions for persistance + let subscriptionStorage = new Subscription( + this.persistence, + this.read.getPersistenceReader() + ); + let oldSubscriptions = + await subscriptionStorage.getSubscriptionsByRepo( + repoName, + user.id + ); + await subscriptionStorage.deleteSubscriptionsByRepoUser( + repoName, + roomId, + user.id + ); //check if any subscription events of the repo is left in any other room - let eventSubscriptions = new Map; + let eventSubscriptions = new Map(); for (let subsciption of oldSubscriptions) { eventSubscriptions.set(subsciption.event, false); } - let updatedsubscriptions = await subscriptionStorage.getSubscriptionsByRepo(repoName, user.id); + let updatedsubscriptions = + await subscriptionStorage.getSubscriptionsByRepo( + repoName, + user.id + ); if (updatedsubscriptions.length == 0) { - await deleteSubscription(this.http, repoName, accessToken.token, hookId); + await deleteSubscription( + this.http, + repoName, + accessToken.token, + hookId + ); } else { for (let subsciption of updatedsubscriptions) { eventSubscriptions.set(subsciption.event, true); @@ -180,43 +686,90 @@ export class ExecuteBlockActionHandler { } } if (updatedEvents.length && !sameEvents) { - let response = await updateSubscription(this.http, repoName, accessToken.token, hookId, updatedEvents); + let response = await updateSubscription( + this.http, + repoName, + accessToken.token, + hookId, + updatedEvents + ); } } - let userRoom = await this.read.getRoomReader().getById(roomId) as IRoom; - await sendNotification(this.read, this.modify, user, userRoom, `Unsubscribed to ${repoName} 🔕`); + let userRoom = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; + await sendNotification( + this.read, + this.modify, + user, + userRoom, + `Unsubscribed to ${repoName} 🔕` + ); } - const modal = await deleteSubsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context }); - await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + const modal = await deleteSubsciptionsModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + await this.modify.getUiController().updateModalView( + modal, + { + triggerId: context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); break; } - case ModalsEnum.SUBSCRIPTION_REFRESH_ACTION:{ - const modal = await subsciptionsModal({ modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context }); - await this.modify.getUiController().updateModalView(modal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + case ModalsEnum.SUBSCRIPTION_REFRESH_ACTION: { + const modal = await subsciptionsModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + await this.modify.getUiController().updateModalView( + modal, + { + triggerId: context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); break; } - case ModalsEnum.ISSUE_TEMPLATE_SELECTION_ACTION:{ + case ModalsEnum.ISSUE_TEMPLATE_SELECTION_ACTION: { let { user } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; - let value: string = context.getInteractionData().value as string; + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + let value: string = context.getInteractionData() + .value as string; let actionDetailsArray = value?.trim()?.split(" "); - if(accessToken && actionDetailsArray?.length == 2){ - - if(actionDetailsArray[1] !== ModalsEnum.BLANK_GITHUB_TEMPLATE){ - - let templateResponse = await getIssueTemplateCode(this.http,actionDetailsArray[1],accessToken.token); - // console.log(templateResponse); + if (accessToken && actionDetailsArray?.length == 2) { + if ( + actionDetailsArray[1] !== + ModalsEnum.BLANK_GITHUB_TEMPLATE + ) { + let templateResponse = await getIssueTemplateCode( + this.http, + actionDetailsArray[1], + accessToken.token + ); let data = {}; - if(templateResponse?.template){ + if (templateResponse?.template) { data = { - template : templateResponse.template, - repository:actionDetailsArray[0] + template: templateResponse.template, + repository: actionDetailsArray[0], }; - }else{ + } else { data = { - template : "", - repository:actionDetailsArray[0] + template: "", + repository: actionDetailsArray[0], }; } const newIssueModal = await NewIssueModal({ @@ -230,10 +783,9 @@ export class ExecuteBlockActionHandler { return context .getInteractionResponder() .openModalViewResponse(newIssueModal); - - }else{ + } else { let data = { - repository:actionDetailsArray[0] + repository: actionDetailsArray[0], }; const newIssueModal = await NewIssueModal({ data, @@ -250,295 +802,429 @@ export class ExecuteBlockActionHandler { } break; } - case ModalsEnum.SHARE_SEARCH_RESULT_ACTION:{ + case ModalsEnum.SHARE_SEARCH_RESULT_ACTION: { let { user, room } = await context.getInteractionData(); - let value: string = context.getInteractionData().value as string; - if(user?.id){ - if(room?.id){ - await sendMessage(this.modify,room,user,`${value}`); - }else{ + let value: string = context.getInteractionData() + .value as string; + if (user?.id) { + if (room?.id) { + await sendMessage( + this.modify, + room, + user, + `${value}` + ); + } else { let roomId = ( await getInteractionRoomData( this.read.getPersistenceReader(), user.id ) ).roomId; - room = await this.read.getRoomReader().getById(roomId) as IRoom; - await sendMessage(this.modify,room,user,`${value}`); + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; + await sendMessage( + this.modify, + room, + user, + `${value}` + ); } } break; } - case ModalsEnum.VIEW_GITHUB_SEARCH_RESULT_PR_CHANGES:{ + case ModalsEnum.VIEW_GITHUB_SEARCH_RESULT_PR_CHANGES: { let { user, room } = await context.getInteractionData(); - let value: string = context.getInteractionData().value as string; + let value: string = context.getInteractionData() + .value as string; let PullRequestDetails = value.split(" "); - if(PullRequestDetails.length==2){ - const triggerId= context.getInteractionData().triggerId; + if (PullRequestDetails.length == 2) { + const triggerId = + context.getInteractionData().triggerId; const data = { - repository:PullRequestDetails[0], - query:"pulls", - number:PullRequestDetails[1] - } - if(triggerId && data){ + repository: PullRequestDetails[0], + query: "pulls", + number: PullRequestDetails[1], + }; + if (triggerId && data) { const resultsModal = await pullDetailsModal({ data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, - uikitcontext: context + uikitcontext: context, }); return context - .getInteractionResponder() - .openModalViewResponse(resultsModal); - }else{ + .getInteractionResponder() + .openModalViewResponse(resultsModal); + } else { console.log("Inavlid Trigger ID !"); } } - if(user?.id){ - if(room?.id){ - await sendMessage(this.modify,room,user,`${value}`); - }else{ + if (user?.id) { + if (room?.id) { + await sendMessage( + this.modify, + room, + user, + `${value}` + ); + } else { let roomId = ( await getInteractionRoomData( this.read.getPersistenceReader(), user.id ) ).roomId; - room = await this.read.getRoomReader().getById(roomId) as IRoom; - await sendMessage(this.modify,room,user,`${value}`); + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; + await sendMessage( + this.modify, + room, + user, + `${value}` + ); } } break; } - case ModalsEnum.MULTI_SHARE_ADD_SEARCH_RESULT_ACTION:{ + case ModalsEnum.MULTI_SHARE_ADD_SEARCH_RESULT_ACTION: { let { user, room } = await context.getInteractionData(); - let searchResultId: string = context.getInteractionData().value as string; - let roomId:string=""; - if(user?.id){ - if(room?.id){ + let searchResultId: string = context.getInteractionData() + .value as string; + let roomId: string = ""; + if (user?.id) { + if (room?.id) { roomId = room.id; - }else{ + } else { roomId = ( await getInteractionRoomData( this.read.getPersistenceReader(), user.id ) ).roomId; - room = await this.read.getRoomReader().getById(roomId) as IRoom; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; } - let githubSearchStorage = new GithubSearchResultStorage(this.persistence,this.read.getPersistenceReader()); - let searchResultData: IGitHubSearchResultData = await githubSearchStorage.getSearchResults(room?.id as string,user); - if(searchResultData?.search_results?.length){ - let index = -1; - let currentIndex = 0; - for(let searchResult of searchResultData.search_results){ - if(searchResult.result_id == searchResultId ){ - index=currentIndex; - break; - } - currentIndex++; - } - if(index !== -1){ - searchResultData.search_results[index].share=true; - await githubSearchStorage.updateSearchResult(room as IRoom,user,searchResultData); + let githubSearchStorage = new GithubSearchResultStorage( + this.persistence, + this.read.getPersistenceReader() + ); + let searchResultData: IGitHubSearchResultData = + await githubSearchStorage.getSearchResults( + room?.id as string, + user + ); + if (searchResultData?.search_results?.length) { + let index = -1; + let currentIndex = 0; + for (let searchResult of searchResultData.search_results) { + if (searchResult.result_id == searchResultId) { + index = currentIndex; + break; } - const resultsModal = await githubSearchResultModal({ - data: searchResultData, - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - }) - await this.modify.getUiController().updateModalView(resultsModal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + currentIndex++; + } + if (index !== -1) { + searchResultData.search_results[index].share = + true; + await githubSearchStorage.updateSearchResult( + room as IRoom, + user, + searchResultData + ); } + const resultsModal = await githubSearchResultModal({ + data: searchResultData, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + }); + await this.modify.getUiController().updateModalView( + resultsModal, + { + triggerId: + context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); + } } break; } - case ModalsEnum.MULTI_SHARE_REMOVE_SEARCH_RESULT_ACTION:{ + case ModalsEnum.MULTI_SHARE_REMOVE_SEARCH_RESULT_ACTION: { let { user, room } = await context.getInteractionData(); - let searchResultId: string = context.getInteractionData().value as string; - let roomId=""; - if(user?.id && searchResultId){ - if(room?.id){ + let searchResultId: string = context.getInteractionData() + .value as string; + let roomId = ""; + if (user?.id && searchResultId) { + if (room?.id) { roomId = room.id; - }else{ + } else { roomId = ( await getInteractionRoomData( this.read.getPersistenceReader(), user.id ) ).roomId; - room = await this.read.getRoomReader().getById(roomId) as IRoom; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; } - let githubSearchStorage = new GithubSearchResultStorage(this.persistence,this.read.getPersistenceReader()); - let searchResultData: IGitHubSearchResultData = await githubSearchStorage.getSearchResults(room?.id as string,user); - if(searchResultData?.search_results?.length){ - let index = -1; - let currentIndex = 0; - for(let searchResult of searchResultData.search_results){ - if(searchResult.result_id == searchResultId){ - index=currentIndex; - break; - } - currentIndex++; + let githubSearchStorage = new GithubSearchResultStorage( + this.persistence, + this.read.getPersistenceReader() + ); + let searchResultData: IGitHubSearchResultData = + await githubSearchStorage.getSearchResults( + room?.id as string, + user + ); + if (searchResultData?.search_results?.length) { + let index = -1; + let currentIndex = 0; + for (let searchResult of searchResultData.search_results) { + if (searchResult.result_id == searchResultId) { + index = currentIndex; + break; } - if(index !== -1){ - searchResultData.search_results[index].share=false; - await githubSearchStorage.updateSearchResult(room as IRoom,user,searchResultData); - } - const resultsModal = await githubSearchResultModal({ - data: searchResultData, - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - }) - await this.modify.getUiController().updateModalView(resultsModal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + currentIndex++; } + if (index !== -1) { + searchResultData.search_results[index].share = + false; + await githubSearchStorage.updateSearchResult( + room as IRoom, + user, + searchResultData + ); + } + const resultsModal = await githubSearchResultModal({ + data: searchResultData, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + }); + await this.modify.getUiController().updateModalView( + resultsModal, + { + triggerId: + context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); + } } break; } - case ModalsEnum.MERGE_PULL_REQUEST_ACTION:{ - let value: string = context.getInteractionData().value as string; + case ModalsEnum.MERGE_PULL_REQUEST_ACTION: { + let value: string = context.getInteractionData() + .value as string; let splittedValues = value?.split(" "); let { user } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; - if(splittedValues.length==2 && accessToken?.token){ - let data={ - "repo" : splittedValues[0], - "pullNumber": splittedValues[1] - } - let repoDetails = await getRepoData(this.http,splittedValues[0],accessToken.token); + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + if (splittedValues.length == 2 && accessToken?.token) { + let data = { + repo: splittedValues[0], + pullNumber: splittedValues[1], + }; + let repoDetails = await getRepoData( + this.http, + splittedValues[0], + accessToken.token + ); - if(repoDetails?.permissions?.admin || repoDetails?.permissions?.push || repoDetails?.permissions?.maintain ){ + if ( + repoDetails?.permissions?.admin || + repoDetails?.permissions?.push || + repoDetails?.permissions?.maintain + ) { const mergePRModal = await mergePullRequestModal({ - data:data, + data: data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, - uikitcontext: context - }) + uikitcontext: context, + }); return context .getInteractionResponder() .openModalViewResponse(mergePRModal); - }else{ - const unauthorizedMessageModal = await messageModal({ - message:"Unauthorized Action 🤖 You dont have push rights ⚠️", - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) + } else { + const unauthorizedMessageModal = await messageModal( + { + message: + "Unauthorized Action 🤖 You dont have push rights ⚠️", + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + } + ); return context .getInteractionResponder() - .openModalViewResponse(unauthorizedMessageModal); + .openModalViewResponse( + unauthorizedMessageModal + ); } - } break; } - case ModalsEnum.COMMENT_PR_ACTION:{ - let value: string = context.getInteractionData().value as string; + case ModalsEnum.COMMENT_PR_ACTION: { + let value: string = context.getInteractionData() + .value as string; let splittedValues = value?.split(" "); let { user } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; - if(splittedValues.length==2 && accessToken?.token){ - let data={ - "repo" : splittedValues[0], - "pullNumber": splittedValues[1] - } - const addPRCommentModal = await addPullRequestCommentsModal({ - data:data, - modify:this.modify, - read:this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + if (splittedValues.length == 2 && accessToken?.token) { + let data = { + repo: splittedValues[0], + pullNumber: splittedValues[1], + }; + const addPRCommentModal = + await addPullRequestCommentsModal({ + data: data, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); return context - .getInteractionResponder() - .openModalViewResponse(addPRCommentModal); + .getInteractionResponder() + .openModalViewResponse(addPRCommentModal); } break; } - case ModalsEnum.PR_COMMENT_LIST_ACTION:{ - let value: string = context.getInteractionData().value as string; + case ModalsEnum.SHARE_PROFILE: { + const shareProfileMod = await shareProfileModal({ + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + + return context + .getInteractionResponder() + .openModalViewResponse(shareProfileMod); + } + + case ModalsEnum.PR_COMMENT_LIST_ACTION: { + let value: string = context.getInteractionData() + .value as string; let splittedValues = value?.split(" "); let { user } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config) as IAuthData; - if(splittedValues.length==2 && accessToken?.token){ + let accessToken = (await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + )) as IAuthData; + if (splittedValues.length == 2 && accessToken?.token) { let repoName = splittedValues[0]; let pullNumber = splittedValues[1]; - let pullRequestComments = await getPullRequestComments(this.http,repoName,accessToken.token,pullNumber); - let pullRequestData = await getPullRequestData(this.http,repoName,accessToken.token,pullNumber); - if(pullRequestData?.serverError || pullRequestComments?.pullRequestData){ - if(pullRequestData?.serverError){ - const unauthorizedMessageModal = await messageModal({ - message:`🤖 Error Fetching Repository Data: ⚠️ ${pullRequestData?.message}`, - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) + let pullRequestComments = await getPullRequestComments( + this.http, + repoName, + accessToken.token, + pullNumber + ); + let pullRequestData = await getPullRequestData( + this.http, + repoName, + accessToken.token, + pullNumber + ); + if ( + pullRequestData?.serverError || + pullRequestComments?.pullRequestData + ) { + if (pullRequestData?.serverError) { + const unauthorizedMessageModal = + await messageModal({ + message: `🤖 Error Fetching Repository Data: ⚠️ ${pullRequestData?.message}`, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); return context .getInteractionResponder() - .openModalViewResponse(unauthorizedMessageModal); + .openModalViewResponse( + unauthorizedMessageModal + ); } - if(pullRequestComments?.serverError){ - const unauthorizedMessageModal = await messageModal({ - message:`🤖 Error Fetching Comments: ⚠️ ${pullRequestData?.message}`, - modify: this.modify, - read: this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) + if (pullRequestComments?.serverError) { + const unauthorizedMessageModal = + await messageModal({ + message: `🤖 Error Fetching Comments: ⚠️ ${pullRequestData?.message}`, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); return context .getInteractionResponder() - .openModalViewResponse(unauthorizedMessageModal); + .openModalViewResponse( + unauthorizedMessageModal + ); } } - let data={ + let data = { repo: repoName, pullNumber: pullNumber, pullData: pullRequestData, - pullRequestComments: pullRequestComments?.data - } - const addPRCommentModal = await pullRequestCommentsModal({ - data:data, - modify:this.modify, - read:this.read, - persistence: this.persistence, - http: this.http, - uikitcontext: context - }) + pullRequestComments: pullRequestComments?.data, + }; + const addPRCommentModal = + await pullRequestCommentsModal({ + data: data, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); return context - .getInteractionResponder() - .openModalViewResponse(addPRCommentModal); + .getInteractionResponder() + .openModalViewResponse(addPRCommentModal); } break; } case ModalsEnum.ADD_GITHUB_ISSUE_ASSIGNEE: { - let value: string = context.getInteractionData().value as string; + let value: string = context.getInteractionData() + .value as string; let splittedValues = value?.split(" "); - if(splittedValues?.length>=3){ + if (splittedValues?.length >= 2) { let repository = splittedValues[0]; let issueNumber = splittedValues[1]; let assignees: string = ""; - for(let i = 2;i0){ + for (let i = 2; i < splittedValues.length; i++) { + if (splittedValues[i].length > 0) { assignees += `${splittedValues[i]} `; } } let data = { repository, issueNumber, - assignees + assignees, }; const addIssueAssignee = await addIssueAssigneeModal({ data, @@ -546,8 +1232,8 @@ export class ExecuteBlockActionHandler { read: this.read, persistence: this.persistence, http: this.http, - uikitcontext: context - }) + uikitcontext: context, + }); return context .getInteractionResponder() .openModalViewResponse(addIssueAssignee); @@ -555,129 +1241,218 @@ export class ExecuteBlockActionHandler { break; } case ModalsEnum.REFRESH_GITHUB_ISSUES_ACTION: { - let repository: string = context.getInteractionData().value as string; - repository=repository?.trim(); + let repository: string = context.getInteractionData() + .value as string; + repository = repository?.trim(); let { user } = await context.getInteractionData(); - let accessToken = await getAccessTokenForUser(this.read, user, this.app.oauth2Config); + let accessToken = await getAccessTokenForUser( + this.read, + user, + this.app.oauth2Config + ); if (!accessToken) { - let response = await getRepositoryIssues(this.http,repository); + let response = await getRepositoryIssues( + this.http, + repository + ); let data = { issues: response.issues, - pushRights : false, //no access token, so user has no pushRights to the repo, - repo: repository - } - const issuesListModal = await githubIssuesListModal( {data: data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context} ); - await this.modify.getUiController().updateModalView(issuesListModal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); - }else{ - let repoDetails = await getRepoData(this.http,repository,accessToken.token); - let response = await getRepositoryIssues(this.http,repository); + pushRights: false, //no access token, so user has no pushRights to the repo, + repo: repository, + }; + const issuesListModal = await githubIssuesListModal({ + data: data, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + await this.modify.getUiController().updateModalView( + issuesListModal, + { + triggerId: + context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); + } else { + let repoDetails = await getRepoData( + this.http, + repository, + accessToken.token + ); + let response = await getRepositoryIssues( + this.http, + repository + ); let data = { issues: response.issues, - pushRights : repoDetails?.permissions?.push || repoDetails?.permissions?.admin, - repo: repository - } - const issuesListModal = await githubIssuesListModal( {data: data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, uikitcontext: context} ); - await this.modify.getUiController().updateModalView(issuesListModal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); + pushRights: + repoDetails?.permissions?.push || + repoDetails?.permissions?.admin, + repo: repository, + }; + const issuesListModal = await githubIssuesListModal({ + data: data, + modify: this.modify, + read: this.read, + persistence: this.persistence, + http: this.http, + uikitcontext: context, + }); + await this.modify.getUiController().updateModalView( + issuesListModal, + { + triggerId: + context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); } break; } - case ModalsEnum.MULTI_SHARE_ADD_GITHUB_ISSUE_ACTION:{ + case ModalsEnum.MULTI_SHARE_ADD_GITHUB_ISSUE_ACTION: { let { user, room } = await context.getInteractionData(); - let issueId: string = context.getInteractionData().value as string; - let roomId:string=""; - if(user?.id){ - if(room?.id){ + let issueId: string = context.getInteractionData() + .value as string; + let roomId: string = ""; + if (user?.id) { + if (room?.id) { roomId = room.id; - }else{ + } else { roomId = ( await getInteractionRoomData( this.read.getPersistenceReader(), user.id ) ).roomId; - room = await this.read.getRoomReader().getById(roomId) as IRoom; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; } - let githubissueStorage = new GithubRepoIssuesStorage(this.persistence,this.read.getPersistenceReader()); - let repoIssuesData: IGitHubIssueData = await githubissueStorage.getIssueData(room?.id as string,user); - if(repoIssuesData?.issue_list?.length){ - let index = -1; - let currentIndex = 0; - for(let issue of repoIssuesData.issue_list){ - if(issue.issue_id == issueId ){ - index=currentIndex; - break; - } - currentIndex++; - } - if(index !== -1){ - repoIssuesData.issue_list[index].share=true; - await githubissueStorage.updateIssueData(room as IRoom,user,repoIssuesData); + let githubissueStorage = new GithubRepoIssuesStorage( + this.persistence, + this.read.getPersistenceReader() + ); + let repoIssuesData: IGitHubIssueData = + await githubissueStorage.getIssueData( + room?.id as string, + user + ); + if (repoIssuesData?.issue_list?.length) { + let index = -1; + let currentIndex = 0; + for (let issue of repoIssuesData.issue_list) { + if (issue.issue_id == issueId) { + index = currentIndex; + break; } - let data = { - issues: repoIssuesData.issue_list, - pushRights : repoIssuesData.push_rights, //no access token, so user has no pushRights to the repo, - repo: repoIssuesData.repository, - user_id: user.id - } - const githubIssuesModal = await githubIssuesListModal({ + currentIndex++; + } + if (index !== -1) { + repoIssuesData.issue_list[index].share = true; + await githubissueStorage.updateIssueData( + room as IRoom, + user, + repoIssuesData + ); + } + let data = { + issues: repoIssuesData.issue_list, + pushRights: repoIssuesData.push_rights, //no access token, so user has no pushRights to the repo, + repo: repoIssuesData.repository, + user_id: user.id, + }; + const githubIssuesModal = + await githubIssuesListModal({ data: data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, - }) - await this.modify.getUiController().updateModalView(githubIssuesModal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); - } + }); + await this.modify.getUiController().updateModalView( + githubIssuesModal, + { + triggerId: + context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); + } } break; } - case ModalsEnum.MULTI_SHARE_REMOVE_GITHUB_ISSUE_ACTION:{ + case ModalsEnum.MULTI_SHARE_REMOVE_GITHUB_ISSUE_ACTION: { let { user, room } = await context.getInteractionData(); - let issueId: string = context.getInteractionData().value as string; - let roomId:string=""; - if(user?.id){ - if(room?.id){ + let issueId: string = context.getInteractionData() + .value as string; + let roomId: string = ""; + if (user?.id) { + if (room?.id) { roomId = room.id; - }else{ + } else { roomId = ( await getInteractionRoomData( this.read.getPersistenceReader(), user.id ) ).roomId; - room = await this.read.getRoomReader().getById(roomId) as IRoom; + room = (await this.read + .getRoomReader() + .getById(roomId)) as IRoom; } - let githubissueStorage = new GithubRepoIssuesStorage(this.persistence,this.read.getPersistenceReader()); - let repoIssuesData: IGitHubIssueData = await githubissueStorage.getIssueData(room?.id as string,user); - if(repoIssuesData?.issue_list?.length){ - let index = -1; - let currentIndex = 0; - for(let issue of repoIssuesData.issue_list){ - if(issue.issue_id == issueId ){ - index=currentIndex; - break; - } - currentIndex++; + let githubissueStorage = new GithubRepoIssuesStorage( + this.persistence, + this.read.getPersistenceReader() + ); + let repoIssuesData: IGitHubIssueData = + await githubissueStorage.getIssueData( + room?.id as string, + user + ); + if (repoIssuesData?.issue_list?.length) { + let index = -1; + let currentIndex = 0; + for (let issue of repoIssuesData.issue_list) { + if (issue.issue_id == issueId) { + index = currentIndex; + break; } - if(index !== -1){ - repoIssuesData.issue_list[index].share=false; - await githubissueStorage.updateIssueData(room as IRoom,user,repoIssuesData); - } - let data = { - issues: repoIssuesData.issue_list, - pushRights : repoIssuesData.push_rights, //no access token, so user has no pushRights to the repo, - repo: repoIssuesData.repository, - user_id: user.id - } - const githubIssuesModal = await githubIssuesListModal({ + currentIndex++; + } + if (index !== -1) { + repoIssuesData.issue_list[index].share = false; + await githubissueStorage.updateIssueData( + room as IRoom, + user, + repoIssuesData + ); + } + let data = { + issues: repoIssuesData.issue_list, + pushRights: repoIssuesData.push_rights, //no access token, so user has no pushRights to the repo, + repo: repoIssuesData.repository, + user_id: user.id, + }; + const githubIssuesModal = + await githubIssuesListModal({ data: data, modify: this.modify, read: this.read, persistence: this.persistence, http: this.http, - }) - await this.modify.getUiController().updateModalView(githubIssuesModal, { triggerId: context.getInteractionData().triggerId }, context.getInteractionData().user); - } + }); + await this.modify.getUiController().updateModalView( + githubIssuesModal, + { + triggerId: + context.getInteractionData().triggerId, + }, + context.getInteractionData().user + ); + } } break; } diff --git a/github/handlers/UserProfileHandler.ts b/github/handlers/UserProfileHandler.ts new file mode 100644 index 0000000..b8eff52 --- /dev/null +++ b/github/handlers/UserProfileHandler.ts @@ -0,0 +1,52 @@ +import { IRead, IPersistence, IHttp, IModify } from "@rocket.chat/apps-engine/definition/accessors"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { GithubApp } from "../GithubApp"; +import { sendNotification } from "../lib/message"; +import { userProfileModal } from "../modals/UserProfileModal"; +import { getAccessTokenForUser } from "../persistance/auth"; + +export async function handleUserProfileRequest( + read: IRead, + context: SlashCommandContext, + app: GithubApp, + persistence: IPersistence, + http: IHttp, + room: IRoom, + modify: IModify, + uikitcontext?: UIKitInteractionContext +){ + let access_token = await getAccessTokenForUser( + read, + context.getSender(), + app.oauth2Config + ); + if (access_token && access_token.token){ + const triggerId = context.getTriggerId(); + if (triggerId){ + const modal = await userProfileModal({ + access_token: access_token.token, + modify: modify, + read: read, + persistence: persistence, + http: http, + slashcommandcontext: context + }); + await modify.getUiController().openModalView( + modal, + {triggerId}, + context.getSender() + ); + } + }else { + await sendNotification( + read, + modify, + context.getSender(), + room, + "Login is Mandatory for getting User Info ! `/github login`" + ) + } + +} diff --git a/github/handlers/handleGist.ts b/github/handlers/handleGist.ts new file mode 100644 index 0000000..91b0a04 --- /dev/null +++ b/github/handlers/handleGist.ts @@ -0,0 +1,164 @@ +import { + IRead, + IPersistence, + IHttp, + IModify, + IMessageBuilder +} from "@rocket.chat/apps-engine/definition/accessors"; +import { IRoom } from "@rocket.chat/apps-engine/definition/rooms"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { ButtonStyle, TextObjectType } from "@rocket.chat/apps-engine/definition/uikit"; +import { IGist } from "../definitions/Gist"; +import { IGistFile } from "../definitions/GistFile"; +import { OcticonIcons } from "../enum/OcticonIcons"; +import { GithubApp } from "../GithubApp"; +import { getBasicUserInfo, getUserGist, loadGist } from "../helpers/githubSDK"; +import { + sendMessage, + sendNotification, +} from "../lib/message"; +import { userGistModal } from "../modals/UserGistModal"; +import { getAccessTokenForUser } from "../persistance/auth"; + +export async function handleGist( + read: IRead, + context: SlashCommandContext, + app: GithubApp, + persistence: IPersistence, + http: IHttp, + room: IRoom, + modify: IModify +) { + let access_token = await getAccessTokenForUser( + read, + context.getSender(), + app.oauth2Config + ); + const loggedIn = access_token && access_token.token; + if (!loggedIn) { + await sendNotification( + read, + modify, + context.getSender(), + room, + "Login is Mandatory for getting User Info ! `/github login`" + ); + return; + } + + const triggerId = context.getTriggerId(); + if (triggerId) { + const modal = await userGistModal({ + access_token: access_token!.token, + modify: modify, + read: read, + persistence: persistence, + http: http, + slashcommandcontext: context, + }); + await modify + .getUiController() + .openModalView(modal, { triggerId }, context.getSender()); + } +} + +export async function sendGistWithNumber( + gistNumber: number, + read: IRead, + context: SlashCommandContext, + app: GithubApp, + persistence: IPersistence, + http: IHttp, + room: IRoom, + modify: IModify, + gistFileNumber?: number +) { + let access_token = await getAccessTokenForUser( + read, + context.getSender(), + app.oauth2Config + ); + const loggedIn = access_token && access_token.token; + if (!loggedIn) { + await sendNotification( + read, + modify, + context.getSender(), + room, + "Login is Mandatory for getting User Info ! `/github login`" + ); + return; + } + + const userInfo = await getBasicUserInfo(http, access_token!.token); + const userGist: IGist[] = await getUserGist( + http, + userInfo.username, + access_token!.token + ); + + if (userGist.length < gistNumber || gistNumber <= 0) { + await sendNotification( + read, + modify, + context.getSender(), + room, + "Sorry, not enough gist available, kindly reconfirm the gist number." + ); + + return; + } + + try { + const block = modify.getCreator().getBlockBuilder(); + + const targetGist = userGist[gistNumber - 1]; + + const files = new Map( + Object.entries(targetGist.files) + ); + + block.addContextBlock({ + elements : [ + block.newImageElement({ + imageUrl : targetGist['owner']['avatar_url'], + altText : "Created by" + }), + block.newPlainTextObject(targetGist['owner']['login']), + block.newImageElement({ + imageUrl : OcticonIcons.PENCIL, + altText : "Updated at", + }), + block.newPlainTextObject(new Date(targetGist.updated_at).toISOString()) + ] + }) + + for (const [_, value] of files) { + const gistContent = await loadGist( + http, + value.raw_url, + access_token!.token + ); + + block.addSectionBlock({ + text: { + text: `*${value.filename}*`, + type: TextObjectType.MARKDOWN, + }, + }); + + block.addSectionBlock({ + text: { + text: `\`\`\` ${value.language} \n ${gistContent} \n\`\`\``, + type: TextObjectType.MARKDOWN, + }, + }); + block.addDividerBlock(); + } + + await sendMessage(modify, room, context.getSender(), "Gist", block); + } catch (e) { + console.log(e); + } + return; +} diff --git a/github/helpers/githubSDK.ts b/github/helpers/githubSDK.ts index eea6ab5..079d8ad 100644 --- a/github/helpers/githubSDK.ts +++ b/github/helpers/githubSDK.ts @@ -1,4 +1,7 @@ import { IHttp } from "@rocket.chat/apps-engine/definition/accessors"; +import { IGist } from "../definitions/Gist"; +import { IGitHubIssue } from "../definitions/githubIssue"; +import { ModalsEnum } from "../enum/Modals"; const BaseHost = "https://github.com/"; const BaseApiHost = "https://api.github.com/"; @@ -364,6 +367,22 @@ export async function githubSearchIssuesPulls( return resultResponse; } + +export async function loadGist(http: IHttp,url : string, access_token : string){ + try { + const response = await http.get(url, { + headers : { + "Authorization" : `Bearer ${access_token}` + } + }); + return response.content; + } catch (error) { + console.error("ERROR WHILE LOADING GIST-------------------------------"); + console.error(error); + return ""; + } +} + export async function getRepoData( http: IHttp, repoName: string, @@ -430,6 +449,157 @@ export async function mergePullRequest( return JSONResponse; } +export async function getBasicUserInfo( + http: IHttp, + access_token: String, +){ + try { + const response = await getRequest( + http, + access_token, + BaseApiHost + 'user' + ); + return { + username: response.login, + name : response.name, + email : response.email, + bio: response.bio, + followers : response.followers, + following : response.following, + avatar : response.avatar_url + } + }catch(e){ + return { + name : "", + email : "", + bio: "", + followers : "", + following : "", + avatar : "" + }; + } +} + +export async function getUserGist( + http : IHttp, + username : string, + access_token: string +){ + try { + const response = await getRequest(http, access_token, BaseApiHost + 'users/' + username + '/gists'); + const modRes = response as IGist[]; + return modRes; + } catch (e) { + console.log(e); + return []; + } +} + +export async function getUserAssignedIssues( + http: IHttp, + username: String, + access_token : String, + filter : { + filter : String, + state : String, + sort : String + }, +) : Promise{ + let url; + + switch (filter.filter) { + case ModalsEnum.CREATED_ISSUE_FILTER: + url = `https://api.github.com/search/issues?q=is:${filter.state}+is:issue+sort:${filter.sort.substring(5)}-desc+author:${username}` + break; + case ModalsEnum.ASSIGNED_ISSUE_FILTER: + url = `https://api.github.com/search/issues?q=is:${filter.state}+is:issue+sort:${filter.sort.substring(5)}-desc+assignee:${username}` + break; + case ModalsEnum.MENTIONED_ISSUE_FILTER: + url = `https://api.github.com/search/issues?q=is:${filter.state}+is:issue+sort:${filter.sort.substring(5)}-desc+mentions:${username}` + default: + break; + } + try { + const response = await getRequest( + http, + access_token, + url, + ); + + const getAssignees = (assignees : any[]) : string[] => assignees.map((val): string => { + return val.login as string; + }) + + const modifiedResponse : Array = response.items.map((value) : IGitHubIssue => { + return { + issue_id : value.id as string, + issue_compact : value.body as string, + repo_url : value.repository_url as string, + user_login : value.user.login as string, + user_avatar : value.user.avatar_url as string, + number : value.number as number, + title : value.title as string, + body : value.body as string, + assignees : getAssignees(value.assignees), + state : value.state as string, + last_updated_at : value.updated_at as string, + comments : value.comments as number, + } + }) + + return modifiedResponse; + } + catch(e){ + return []; + } +} + +export async function getIssueData( + repoInfo:String, + issueNumber:String, + access_token:String, + http:IHttp +) : Promise { + try { + const response = await getRequest(http, access_token, BaseRepoApiHost + repoInfo + '/issues/' + issueNumber); + const getAssignees = (assignees : any[]) : string[] => assignees.map((val): string => { + return val.login as string; + }) + + return { + issue_id : response.id as string, + issue_compact : response.body as string, + html_url : response.html_url as string, + repo_url : response.repository_url as string, + user_login : response.user.login as string, + user_avatar : response.user.avatar_url as string, + number : response.number as number, + title : response.title as string, + body : response.body as string, + assignees : getAssignees(response.assignees), + state : response.state as string, + last_updated_at : response.updated_at as string, + comments : response.comments as number, + reactions : { + total_count : response.reactions["total_count"], + plus_one : response.reactions["+1"], + minus_one : response.reactions["-1"], + laugh : response.reactions["laugh"], + hooray : response.reactions["hooray"], + confused : response.reactions["confused"], + heart : response.reactions["heart"], + rocket : response.reactions["rocket"], + eyes : response.reactions["eyes"] + } + } + }catch(e) { + return { + issue_compact : "Error Fetching Issue", + issue_id : 0 + } + } +} + export async function addNewPullRequestComment( http: IHttp, repoName: string, diff --git a/github/lib/CreateIssueStatsBar.ts b/github/lib/CreateIssueStatsBar.ts new file mode 100644 index 0000000..3682dec --- /dev/null +++ b/github/lib/CreateIssueStatsBar.ts @@ -0,0 +1,44 @@ +import { BlockBuilder } from "@rocket.chat/apps-engine/definition/uikit"; +import { IGitHubIssue } from "../definitions/githubIssue"; +import { OcticonIcons } from "../enum/OcticonIcons"; + +export async function CreateIssueStatsBar( + issueInfo : IGitHubIssue, + block : BlockBuilder +){ + block.addContextBlock({ + elements: [ + block.newImageElement({ + imageUrl: OcticonIcons.COMMENTS, + altText: "Comments", + }), + block.newPlainTextObject( + `${issueInfo.comments}`, + false + ), + block.newImageElement({ + imageUrl: OcticonIcons.ISSUE_OPEN, + altText: "Assignees Icon", + }), + block.newPlainTextObject( + issueInfo.assignees ? (issueInfo.assignees.length == 0 + ? "No Assignees" + : `${issueInfo.assignees.length} Assignees`) : "" + ), + block.newImageElement({ + imageUrl: + issueInfo.state == "open" + ? OcticonIcons.ISSUE_OPEN + : OcticonIcons.ISSUE_CLOSED, + altText: "State", + }), + block.newPlainTextObject(`${issueInfo.state}`), + block.newImageElement({ + imageUrl: issueInfo.user_avatar ?? "", + altText: "User Image", + }), + block.newPlainTextObject(`Created by ${issueInfo.user_login}` ?? ""), + ], + }); + +} diff --git a/github/lib/CreateReactionsBar.ts b/github/lib/CreateReactionsBar.ts new file mode 100644 index 0000000..ab470c5 --- /dev/null +++ b/github/lib/CreateReactionsBar.ts @@ -0,0 +1,21 @@ +import { BlockBuilder } from "@rocket.chat/apps-engine/definition/uikit"; +import { IGithubReactions } from "../definitions/githubReactions"; + +export async function CreateReactionsBar( + reactions : IGithubReactions, + block : BlockBuilder +){ + block.addContextBlock({ + elements : [ + block.newPlainTextObject(`Total Reactions ${reactions?.total_count}`, true), + block.newPlainTextObject(`➕ ${reactions?.plus_one} `, true), + block.newPlainTextObject(`➖ ${reactions?.minus_one}`, true), + block.newPlainTextObject(`😄 ${reactions?.laugh}`, true), + block.newPlainTextObject(`🎉 ${reactions?.hooray}`, true), + block.newPlainTextObject(`😕 ${reactions?.confused}`, true), + block.newPlainTextObject(`♥️ ${reactions?.heart}`, true), + block.newPlainTextObject(`🚀 ${reactions?.rocket}`, true), + block.newPlainTextObject(`👀 ${reactions?.eyes}`, true), + ] + }) +} diff --git a/github/lib/commandUtility.ts b/github/lib/commandUtility.ts index a1cdf48..d669654 100644 --- a/github/lib/commandUtility.ts +++ b/github/lib/commandUtility.ts @@ -22,6 +22,9 @@ import { } from "../handlers/EventHandler"; import { handleSearch } from "../handlers/SearchHandler"; import { handleNewIssue } from "../handlers/HandleNewIssue"; +import { handleUserProfileRequest } from "../handlers/UserProfileHandler"; +import { handleGist, sendGistWithNumber } from "../handlers/handleGist"; +import { getUserGist } from "../helpers/githubSDK"; export class CommandUtility implements ExecutorProps { sender: IUser; @@ -62,6 +65,18 @@ export class CommandUtility implements ExecutorProps { }); } else { switch (this.command[0]) { + case SubcommandEnum.GIST : { + await handleGist( + this.read, + this.context, + this.app, + this.persistence, + this.http, + this.room, + this.modify + ) + break; + } case SubcommandEnum.LOGIN: { await handleLogin( this.app, @@ -125,6 +140,18 @@ export class CommandUtility implements ExecutorProps { ); break; } + case SubcommandEnum.PROFILE: { + await handleUserProfileRequest( + this.read, + this.context, + this.app, + this.persistence, + this.http, + this.room, + this.modify + ); + break; + } default: { await helperMessage({ room: this.room, @@ -141,7 +168,23 @@ export class CommandUtility implements ExecutorProps { private async handleDualParamCommands() { const query = this.command[1]; - const repository = this.command[0]; + const command = this.command[0]; + + + if (command == "gist" && command != undefined){ + await sendGistWithNumber( + +query, + this.read, + this.context, + this.app, + this.persistence, + this.http, + this.room, + this.modify + ) + return; + } + switch (query) { case SubcommandEnum.SUBSCRIBE: { SubscribeAllEvents( @@ -172,7 +215,7 @@ export class CommandUtility implements ExecutorProps { default: { await basicQueryMessage({ query, - repository, + repository: command, room: this.room, read: this.read, persistence: this.persistence, diff --git a/github/lib/helperMessage.ts b/github/lib/helperMessage.ts index d43858b..0518a54 100644 --- a/github/lib/helperMessage.ts +++ b/github/lib/helperMessage.ts @@ -21,7 +21,7 @@ export async function helperMessage({ }) { let helperMessageString = ` Github App - + - Both /gh and /github commands can be used. 1) See Interactive Button interface to fetch repository data -> /github Username/RepositoryName 2) Get details of a Repository -> /github Username/RepositoryName repo 3) Get Issues of a Repository -> /github Username/RepositoryName issues @@ -30,13 +30,14 @@ export async function helperMessage({ 6) Review a Pull Request -> /github Username/RepositoryName pulls pullNumber 7) Login to GitHub -> /github login 8) Logout from GitHub -> /github logout - 9) View/Add/Delete/Update Repository Subscriptions -> /github subscribe + 9) View/Add/Delete/Update Repository Subscriptions -> /github subscribe 10) Subscribe to all repository events -> /github Username/RepositoryName subscribe 11) Unsubscribe to all repository events -> /github Username/RepositoryName unsubscribe 12) Add New Issues to GitHub Repository -> /github issue 13) Search Issues and Pull Requests -> /github search 14) Assign and Share Issues -> /github issues - + 15) Review and Share Gist -> /github gist + 16) Get the latest Gist for the Authorized User -> /github gist 1 `; const textSender = await modify diff --git a/github/modals/IssueDisplayModal.ts b/github/modals/IssueDisplayModal.ts new file mode 100644 index 0000000..2cee65b --- /dev/null +++ b/github/modals/IssueDisplayModal.ts @@ -0,0 +1,128 @@ +import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { IGitHubIssue } from "../definitions/githubIssue"; +import { IGithubReactions } from "../definitions/githubReactions"; +import { ModalsEnum } from "../enum/Modals"; +import { OcticonIcons } from "../enum/OcticonIcons"; +import { getIssueData, getUserAssignedIssues } from "../helpers/githubSDK"; +import { CreateIssueStatsBar } from "../lib/CreateIssueStatsBar"; +import { CreateReactionsBar } from "../lib/CreateReactionsBar"; +import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; +import { BodyMarkdownRenderer } from "../processors/bodyMarkdowmRenderer"; + +export async function IssueDisplayModal ({ + repoName, + issueNumber, + access_token, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext +} : { + repoName : String, + issueNumber : String, + access_token: String, + modify : IModify, + read: IRead, + persistence: IPersistence, + http: IHttp, + slashcommandcontext?: SlashCommandContext, + uikitcontext?: UIKitInteractionContext +}) : Promise { + const viewId = ModalsEnum.USER_ISSUE_VIEW; + const block = modify.getCreator().getBlockBuilder(); + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id){ + let roomId; + if (room?.id){ + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } + else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + } + + const issueInfo : IGitHubIssue = await getIssueData(repoName, issueNumber, access_token, http); + + if (issueInfo.issue_id == 0){ + block.addSectionBlock({ + text : { + text : "Sorry there is some issue fetching this issue, try again later", + type : TextObjectType.PLAINTEXT + }, + }) + return { + id : viewId, + title : { + text : "Error", + type : TextObjectType.PLAINTEXT + }, + blocks : block.getBlocks() + } + } + + const lastUpdated = new Date(issueInfo.last_updated_at ?? ""); + + block.addContextBlock({ + elements : [ + block.newImageElement({ + imageUrl: OcticonIcons.PENCIL, + altText: "Last Update At", + }), + block.newPlainTextObject( + `Last Updated at ${ lastUpdated.toISOString() }` + ), + ] + }) + + CreateIssueStatsBar(issueInfo, block); + + block.addSectionBlock({ + text : { + text : `*${issueInfo.title}*` ?? "", + type : TextObjectType.MARKDOWN + } + }) + block.addDividerBlock(); + + issueInfo.reactions && CreateReactionsBar(issueInfo.reactions, block); + + issueInfo.body && BodyMarkdownRenderer({body : issueInfo.body, block : block}) + + block.addActionsBlock({ + elements : [ + block.newButtonElement({ + actionId : ModalsEnum.SHARE_ISSUE_ACTION, + value : `${repoName}, ${issueNumber}`, + text : { + text : "Share Issue", + type : TextObjectType.PLAINTEXT + }, + }), + block.newButtonElement({ + actionId : ModalsEnum.SHARE_ISSUE_ACTION, + value : `${repoName}, ${issueNumber}`, + text : { + text : "Assign Issue", + type : TextObjectType.PLAINTEXT + }, + }) + ] + }) + + return { + id : viewId, + title : { + text : `${repoName} \`#${issueNumber}\``, + type : TextObjectType.MARKDOWN + }, + blocks : block.getBlocks() + } +} diff --git a/github/modals/UserGistModal.ts b/github/modals/UserGistModal.ts new file mode 100644 index 0000000..d0f6bb1 --- /dev/null +++ b/github/modals/UserGistModal.ts @@ -0,0 +1,146 @@ +import { + IModify, + IRead, + IPersistence, + IHttp, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { + ButtonStyle, + TextObjectType, + UIKitInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { IGist } from "../definitions/Gist"; +import { IGistFile } from "../definitions/GistFile"; +import { ModalsEnum } from "../enum/Modals"; +import { OcticonIcons } from "../enum/OcticonIcons"; +import { + getBasicUserInfo, + getUserAssignedIssues, + getUserGist, + loadGist, +} from "../helpers/githubSDK"; +import { + getInteractionRoomData, + storeInteractionRoomData, +} from "../persistance/roomInteraction"; + +export async function userGistModal({ + access_token, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext, +}: { + access_token: string; + modify: IModify; + read: IRead; + persistence: IPersistence; + http: IHttp; + slashcommandcontext: SlashCommandContext; + uikitcontext?: UIKitInteractionContext; +}): Promise { + const viewId = ModalsEnum.USER_ISSUE_VIEW; + const block = modify.getCreator().getBlockBuilder(); + const room = + slashcommandcontext?.getRoom() || + uikitcontext?.getInteractionData().room; + const user = + slashcommandcontext?.getSender() || + uikitcontext?.getInteractionData().user; + + if (user?.id) { + let roomId; + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } else { + roomId = ( + await getInteractionRoomData( + read.getPersistenceReader(), + user.id + ) + ).roomId; + } + } + + const userInfo = await getBasicUserInfo(http, access_token); + + const usersGist: IGist[] = await getUserGist( + http, + userInfo.username ?? "", + access_token + ); + + block.addContextBlock({ + elements : [ + block.newMarkdownTextObject("*Tip* : You can directly enter `/gh gist 1` to send the most recent gist created by you.") + ] + }) + for(const val of usersGist){ + + block.addContextBlock({ + elements : [ + block.newImageElement({ + imageUrl : val['owner']['avatar_url'], + altText : "Created by" + }), + block.newPlainTextObject(val['owner']['login']), + block.newImageElement({ + imageUrl : OcticonIcons.PENCIL, + altText : "Updated at", + }), + block.newPlainTextObject(new Date(val.updated_at).toISOString()) + ] + }) + + block.addActionsBlock({ + elements : [ + block.newButtonElement({ + value : JSON.stringify(val), + text : { + text : "Share in chat 🚀", + type : TextObjectType.PLAINTEXT + }, + style : ButtonStyle.PRIMARY, + actionId : ModalsEnum.SHARE_GIST_ACTION + }) + ] + }) + + const files = new Map(Object.entries(val.files)); + for(const [_key, value] of files){ + const gistContent = await loadGist( + http, + value.raw_url, + access_token + ); + block.addSectionBlock({ + text: { + text: value.filename, + type: TextObjectType.PLAINTEXT, + }, + }); + block.addSectionBlock({ + text: { + text: `\`\`\`${value.language}\n ${gistContent} \n\`\`\``, + type: TextObjectType.MARKDOWN, + }, + }); + } + + block.addDividerBlock(); + } + + return { + id: viewId, + title: { + text: "Your Gists", + type: TextObjectType.PLAINTEXT, + }, + blocks: block.getBlocks(), + }; +} diff --git a/github/modals/UserIssuesModal.ts b/github/modals/UserIssuesModal.ts new file mode 100644 index 0000000..e220c2b --- /dev/null +++ b/github/modals/UserIssuesModal.ts @@ -0,0 +1,290 @@ +import { + IModify, + IRead, + IPersistence, + IHttp, +} from "@rocket.chat/apps-engine/definition/accessors"; +import { RocketChatAssociationModel, RocketChatAssociationRecord } from "@rocket.chat/apps-engine/definition/metadata"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { + ButtonStyle, + TextObjectType, + UIKitInteractionContext, +} from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { ModalsEnum } from "../enum/Modals"; +import { OcticonIcons } from "../enum/OcticonIcons"; +import { getBasicUserInfo, getUserAssignedIssues } from "../helpers/githubSDK"; +import { + getInteractionRoomData, + storeInteractionRoomData, +} from "../persistance/roomInteraction"; + +export async function userIssuesModal({ + filter, + access_token, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext, +}: { + filter: { + filter: string; + state: string; + sort: string; + }; + access_token: String; + modify: IModify; + read: IRead; + persistence: IPersistence; + http: IHttp; + slashcommandcontext?: SlashCommandContext; + uikitcontext?: UIKitInteractionContext; +}): Promise { + const viewId = ModalsEnum.USER_ISSUE_VIEW; + const block = modify.getCreator().getBlockBuilder(); + const room = + slashcommandcontext?.getRoom() || + uikitcontext?.getInteractionData().room; + const user = + slashcommandcontext?.getSender() || + uikitcontext?.getInteractionData().user; + + if (user?.id) { + let roomId; + if (room?.id) { + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } else { + roomId = ( + await getInteractionRoomData( + read.getPersistenceReader(), + user.id + ) + ).roomId; + } + } + + const userInfo = await getBasicUserInfo(http, access_token); + + const repoInfo = await getUserAssignedIssues( + http, + userInfo.username, + access_token, + filter + ); + + block.addActionsBlock({ + elements: [ + block.newStaticSelectElement({ + placeholder: { + text: "Select an Issue Filter", + type: TextObjectType.PLAINTEXT, + }, + initialValue: filter.filter, + actionId: ModalsEnum.SWITCH_ISSUE_FILTER, + options: [ + { + value: ModalsEnum.ASSIGNED_ISSUE_FILTER, + text: { + text: "Assigned", + type: TextObjectType.PLAINTEXT, + }, + }, + { + value: ModalsEnum.CREATED_ISSUE_FILTER, + text: { + text: "Created", + type: TextObjectType.PLAINTEXT, + }, + }, + { + value: ModalsEnum.MENTIONED_ISSUE_FILTER, + text: { + text: "Mentioned", + type: TextObjectType.PLAINTEXT, + }, + }, + ], + }), + ], + }); + + block.addActionsBlock({ + elements: [ + block.newStaticSelectElement({ + placeholder: { + text: "Select Issues State", + type: TextObjectType.PLAINTEXT, + }, + initialValue: filter.state, + actionId: ModalsEnum.SWITCH_ISSUE_STATE, + options: [ + { + value: ModalsEnum.ISSUE_STATE_OPEN, + text: { + text: "Open Issues", + type: TextObjectType.PLAINTEXT, + }, + + }, + { + value: ModalsEnum.ISSUE_STATE_CLOSED, + text: { + text: "Closed Issues", + type: TextObjectType.PLAINTEXT, + }, + }, + ], + }), + block.newStaticSelectElement({ + actionId: ModalsEnum.SWITCH_ISSUE_SORT, + placeholder: { + text: "Sort Issues By...", + type: TextObjectType.PLAINTEXT, + }, + initialValue: filter.sort, + options: [ + { + value: ModalsEnum.ISSUE_SORT_CREATED, + text: { + text: "Created", + type: TextObjectType.PLAINTEXT, + }, + }, + { + value: ModalsEnum.ISSUE_SORT_UPDATED, + text: { + text: "Updated", + type: TextObjectType.PLAINTEXT, + }, + }, + { + value: ModalsEnum.ISSUE_SORT_COMMENTS, + text: { + text: "Comments", + type: TextObjectType.PLAINTEXT, + }, + }, + ], + }), + ], + }); + + if (repoInfo.length == 0) { + block.addContextBlock({ + elements: [ + block.newPlainTextObject( + "Sorry, there are no issues to display" + ), + ], + }); + } else { + repoInfo.map( + (value) => { + const repoURL = value.repo_url ?? ""; + const repoName = repoURL.substring(29, repoURL.length); + block.addContextBlock({ + elements: [ + block.newImageElement({ + imageUrl: OcticonIcons.REPOSITORY, + altText: "REPO_ICON", + }), + block.newPlainTextObject(repoName, false), + block.newImageElement({ + imageUrl: value.user_avatar ?? "", + altText: "User Image", + }), + block.newPlainTextObject(value.user_login ?? ""), + ], + }); + block.addSectionBlock({ + text: { + text: `\`#${value.number}\` ${value.title}` ?? "None", + type: TextObjectType.MARKDOWN, + }, + }); + const lastUpdated = new Date(value.last_updated_at ?? ""); + block.addContextBlock({ + elements: [ + block.newImageElement({ + imageUrl: OcticonIcons.COMMENTS, + altText: "Comments", + }), + block.newPlainTextObject( + `${value.comments}`, + false + ), + block.newImageElement({ + imageUrl: OcticonIcons.ISSUE_OPEN, + altText: "Assignees Icon", + }), + block.newPlainTextObject( + value.assignees ? (value.assignees.length == 0 + ? "No Assignees" + : `${value.assignees.length} Assignees`) : "" + ), + block.newImageElement({ + imageUrl: + value.state == "open" + ? OcticonIcons.ISSUE_OPEN + : OcticonIcons.ISSUE_CLOSED, + altText: "State", + }), + block.newPlainTextObject(`${value.state}`), + block.newImageElement({ + imageUrl: OcticonIcons.PENCIL, + altText: "Last Update At", + }), + block.newPlainTextObject( + `Last Updated at ${lastUpdated.toUTCString()}` + ), + ], + }); + + block.addActionsBlock({ + elements: [ + block.newButtonElement({ + value: ``, + text: { + text: "Share Issue", + type: TextObjectType.PLAINTEXT, + }, + style: ButtonStyle.PRIMARY, + }), + block.newButtonElement({ + actionId: ModalsEnum.TRIGGER_ISSUE_DISPLAY_MODAL, + value: `${repoName}, ${value.number}`, + text: { + text: "Open Issue", + type: TextObjectType.PLAINTEXT, + }, + style: ButtonStyle.PRIMARY, + }), + block.newButtonElement({ + actionId: ModalsEnum.ADD_GITHUB_ISSUE_ASSIGNEE, + value: `${repoName} ${value.number} henit-chobisa`, + text: { + text: "Assign Issue", + type: TextObjectType.PLAINTEXT, + }, + style: ButtonStyle.PRIMARY, + }), + ], + }); + block.addDividerBlock(); + } + ); + } + + return { + id: viewId, + title: { + text: "Your Issues", + type: TextObjectType.PLAINTEXT, + }, + blocks: block.getBlocks(), + }; +} diff --git a/github/modals/UserNotificationsModal.ts b/github/modals/UserNotificationsModal.ts new file mode 100644 index 0000000..b646828 --- /dev/null +++ b/github/modals/UserNotificationsModal.ts @@ -0,0 +1,53 @@ +import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { ModalsEnum } from "../enum/Modals"; +import { getUserAssignedIssues } from "../helpers/githubSDK"; +import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; + +export async function userNotificationsModal ({ + access_token, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext +} : { + access_token: String, + modify : IModify, + read: IRead, + persistence: IPersistence, + http: IHttp, + slashcommandcontext: SlashCommandContext, + uikitcontext?: UIKitInteractionContext +}) : Promise { + const viewId = ModalsEnum.USER_ISSUE_VIEW; + const block = modify.getCreator().getBlockBuilder(); + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id){ + let roomId; + if (room?.id){ + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } + else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + } + + // const repoInfo = await getUserAssignedIssues(http, access_token); + + return { + id : viewId, + title : { + text : "Your Issues", + type : TextObjectType.PLAINTEXT + }, + blocks : block.getBlocks() + } + +} diff --git a/github/modals/UserProfileModal.ts b/github/modals/UserProfileModal.ts new file mode 100644 index 0000000..91e287f --- /dev/null +++ b/github/modals/UserProfileModal.ts @@ -0,0 +1,148 @@ +import { IHttp, IModify, IPersistence, IRead } from "@rocket.chat/apps-engine/definition/accessors"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { ButtonStyle, TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { AppEnum } from "../enum/App"; +import { ModalsEnum } from "../enum/Modals"; +import { getBasicUserInfo } from "../helpers/githubSDK"; +import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; +import {} from "@rocket.chat/apps-engine/definition/uikit/" + +export async function userProfileModal({ + access_token, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext +} : { + access_token: String, + modify : IModify, + read: IRead, + persistence: IPersistence, + http: IHttp, + slashcommandcontext: SlashCommandContext, + uikitcontext?: UIKitInteractionContext +}) : Promise { + + const viewId = ModalsEnum.USER_PROFILE_VIEW; + const block = modify.getCreator().getBlockBuilder(); + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id){ + let roomId; + if (room?.id){ + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } + else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + } + + const userInfo = await getBasicUserInfo(http, access_token); + + + block.addContextBlock({ + elements: [ + block.newPlainTextObject(userInfo.email, true), + ] + }) + + block.addSectionBlock({ + text: block.newPlainTextObject(userInfo.bio), + accessory : block.newImageElement({ + imageUrl: userInfo.avatar, + altText: userInfo.name + }) + }) + + block.addContextBlock({ + elements: [ + block.newPlainTextObject(`followers: ${userInfo.followers}`), + block.newPlainTextObject(`following: ${userInfo.following}`) + ] + }); + + block.addDividerBlock(); + + block.addImageBlock({imageUrl : `https://activity-graph.herokuapp.com/graph?username=${userInfo.username}&bg_color=ffffff&color=708090&line=24292e&point=24292e`, altText: "Github Contribution Graph"}); + + + + block.addDividerBlock(); + + block.addSectionBlock({ + text: block.newPlainTextObject("Where should we teleport ?") + }) + + block.addActionsBlock({ + elements : [ + block.newButtonElement({ + text : { + text : "Share Profile", + type : TextObjectType.PLAINTEXT + }, + actionId: ModalsEnum.SHARE_PROFILE, + style : ButtonStyle.PRIMARY + }), + block.newButtonElement( + { + actionId: ModalsEnum.TRIGGER_REPOS_MODAL, + value: "Trigger Repos Modal", + text: { + type: TextObjectType.PLAINTEXT, + text: "Repositories" + }, + style: ButtonStyle.PRIMARY + }, + ), + block.newButtonElement( + { + actionId: ModalsEnum.TRIGGER_ISSUES_MODAL, + value: "Trigger Issues Modal", + text: { + type: TextObjectType.PLAINTEXT, + text: "Issues" + }, + style: ButtonStyle.PRIMARY + }, + ), + block.newButtonElement( + { + actionId: ModalsEnum.TRIGGER_NOTIFICATIONS_MODAL, + value: "repo", + text: { + type: TextObjectType.PLAINTEXT, + text: "Notifications" + }, + style: ButtonStyle.PRIMARY + }, + ), + block.newButtonElement( + { + actionId: ModalsEnum.TRIGGER_ACTIVITY_MODAL, + value: "repo", + text: { + type: TextObjectType.PLAINTEXT, + text: "Your Feed" + }, + style: ButtonStyle.PRIMARY + }, + ) + ] + }) + + + return { + id: viewId, + title: { + type: TextObjectType.PLAINTEXT, + text: userInfo.name + }, + blocks: block.getBlocks() + } + +} diff --git a/github/modals/UserReposModal.ts b/github/modals/UserReposModal.ts new file mode 100644 index 0000000..b95023a --- /dev/null +++ b/github/modals/UserReposModal.ts @@ -0,0 +1,53 @@ +import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { ModalsEnum } from "../enum/Modals"; +import { getUserAssignedIssues } from "../helpers/githubSDK"; +import { getInteractionRoomData, storeInteractionRoomData } from "../persistance/roomInteraction"; + +export async function userReposModal ({ + access_token, + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext +} : { + access_token: String, + modify : IModify, + read: IRead, + persistence: IPersistence, + http: IHttp, + slashcommandcontext: SlashCommandContext, + uikitcontext?: UIKitInteractionContext +}) : Promise { + const viewId = ModalsEnum.USER_ISSUE_VIEW; + const block = modify.getCreator().getBlockBuilder(); + const room = slashcommandcontext?.getRoom() || uikitcontext?.getInteractionData().room; + const user = slashcommandcontext?.getSender() || uikitcontext?.getInteractionData().user; + + if (user?.id){ + let roomId; + if (room?.id){ + roomId = room.id; + await storeInteractionRoomData(persistence, user.id, roomId); + } + else { + roomId = (await getInteractionRoomData(read.getPersistenceReader(), user.id)).roomId; + } + } + + // const repoInfo = await getUserAssignedIssues(http, access_token); + + return { + id : viewId, + title : { + text : "Your Repositories", + type : TextObjectType.PLAINTEXT + }, + blocks : block.getBlocks() + } + +} diff --git a/github/modals/profileShareModal.ts b/github/modals/profileShareModal.ts new file mode 100644 index 0000000..5efc163 --- /dev/null +++ b/github/modals/profileShareModal.ts @@ -0,0 +1,114 @@ +import { IModify, IRead, IPersistence, IHttp } from "@rocket.chat/apps-engine/definition/accessors"; +import { SlashCommandContext } from "@rocket.chat/apps-engine/definition/slashcommands"; +import { TextObjectType, UIKitInteractionContext } from "@rocket.chat/apps-engine/definition/uikit"; +import { IUIKitModalViewParam } from "@rocket.chat/apps-engine/definition/uikit/UIKitInteractionResponder"; +import { ModalsEnum } from "../enum/Modals"; +import { getBasicUserInfo } from "../helpers/githubSDK"; +import { storeInteractionRoomData, getInteractionRoomData } from "../persistance/roomInteraction"; + +export async function shareProfileModal({ + modify, + read, + persistence, + http, + slashcommandcontext, + uikitcontext +} : { + modify : IModify, + read: IRead, + persistence: IPersistence, + http: IHttp, + slashcommandcontext?: SlashCommandContext, + uikitcontext?: UIKitInteractionContext +}) : Promise { + + const viewId = "ProfileShareView"; + const block = modify.getCreator().getBlockBuilder(); + + block.addActionsBlock({ + + elements : [ + block.newMultiStaticElement({ + actionId: ModalsEnum.SHARE_PROFILE_PARAMS, + initialValue: ['username', 'avatar', 'email', 'bio', 'followers', 'following' , 'contributionGraph'], + options: [ + { + value: 'followers', + text: { + type: TextObjectType.PLAINTEXT, + text: 'Followers', + emoji: true, + } + }, + { + value: 'following', + text: { + type: TextObjectType.PLAINTEXT, + text: 'Following', + emoji: true, + } + }, + { + value : 'avatar', + text : { + text: "Avatar", + type: TextObjectType.PLAINTEXT + } + }, + { + value : 'username', + text : { + text : "Github ID", + type : TextObjectType.PLAINTEXT + } + }, + { + value: 'email', + text: { + type: TextObjectType.PLAINTEXT, + text: 'Email', + emoji: true, + } + }, + { + value : 'bio', + text : { + type: TextObjectType.PLAINTEXT, + text: 'bio' + } + }, + { + value : 'contributionGraph', + text : { + text: 'Contribution', + type : TextObjectType.PLAINTEXT + } + } + ], + placeholder: { + type: TextObjectType.PLAINTEXT, + text: 'Select Property to Share', + }, + }), + block.newButtonElement({ + actionId : ModalsEnum.SHARE_PROFILE_EXEC, + text : { + text : "Share to Chat", + type : TextObjectType.PLAINTEXT + }, + value : "shareChat" + + }) + ] + }) + + return { + id: viewId, + title: { + type: TextObjectType.PLAINTEXT, + text: "Share Profile" + }, + blocks: block.getBlocks() + } + +} diff --git a/github/processors/bodyMarkdowmRenderer.ts b/github/processors/bodyMarkdowmRenderer.ts new file mode 100644 index 0000000..1de7385 --- /dev/null +++ b/github/processors/bodyMarkdowmRenderer.ts @@ -0,0 +1,104 @@ +import { + BlockBuilder, + TextObjectType, +} from "@rocket.chat/apps-engine/definition/uikit"; + + +function cleanHeadingSyntax(text: string) : string{ + try { + text = text.replace(/(#{3}\s)(.*)/g, (val) => `*${val.substring(3, val.length).trim()}*`); + text = text.replace(/(#{2}\s)(.*)/g, (val) => `*${val.substring(3, val.length).trim()}*`); + text = text.replace(/(#{1}\s)(.*)/g, (val) => `*${val.substring(2, val.length).trim()}*`); + text = text.replace(/\[ ] (?!\~|\[\^\d+])/g, (val) => `⭕ ${val.substring(3, val.length)}*`); + text = text.replace(/\[X] (?!\~|\[\^\d+])/g, (val) => `✅ ${val.substring(3, val.length)}*`); + } + catch(e){ + console.log(e); + } + return text; +} + + +export async function BodyMarkdownRenderer({ + body, + block, +}: { + body: string; + block: BlockBuilder; +}) { + const imagePatterns: { type: string; pattern: RegExp }[] = [ + { + type: "ImageTag", + pattern: RegExp(/()/g, "g"), + }, + { + type: "ImageMarkdownLink", + pattern: RegExp(/[!]\[([^\]]+)\]\(([^)]+(.png|jpg|svg|gif))\)/gim), + }, + ]; + + let matches: { beginningIndex: number; match: string; type: string }[] = []; + var match; + + imagePatterns.forEach((patObj) => { + while ((match = patObj.pattern.exec(body)) != null) { + matches.push({ + beginningIndex: match.index, + match: match[0], + type: patObj.type, + }); + } + }); + + if (matches.length == 0) { + block.addSectionBlock({ + text: { + text: cleanHeadingSyntax(body), + type: TextObjectType.MARKDOWN, + }, + }); + } else { + matches.sort((a, b) => a.beginningIndex - b.beginningIndex); + matches.map((value, index) => { + let start = + index == 0 + ? 0 + : matches[index - 1].beginningIndex + + matches[index - 1].match.length; + + const rawURL = value.match.match( + /(https:\/\/.*\.(png|jpg|gif|svg))/gim + ); + const url = rawURL ? rawURL[0] : ""; + + block.addContextBlock({ + elements: [ + block.newMarkdownTextObject( + cleanHeadingSyntax(body.substring(start, value.beginningIndex - 1) ?? "") + ), + ], + }); + + block.addImageBlock({ + imageUrl: url, + altText: "ImageURL", + }); + + if ( + index == matches.length - 1 && + value.beginningIndex + value.match.length < body.length + ) { + block.addContextBlock({ + elements: [ + block.newMarkdownTextObject( + cleanHeadingSyntax(body.substring( + value.beginningIndex + value.match.length, + body.length + )) + ), + ], + }); + } + }); + } +}