Skip to content
38 changes: 29 additions & 9 deletions github/GithubApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,18 @@ import {
import { githubWebHooks } from "./endpoints/githubEndpoints";
import { IJobContext } from "@rocket.chat/apps-engine/definition/scheduler";
import { IRoom } from "@rocket.chat/apps-engine/definition/rooms";
import { clearInteractionRoomData, getInteractionRoomData } from "./persistance/roomInteraction";
import {
clearInteractionRoomData,
getInteractionRoomData,
} from "./persistance/roomInteraction";
import { GHCommand } from "./commands/GhCommand";
import {
IMessageReactionContext,
IPostMessageReacted,
} from "@rocket.chat/apps-engine/definition/messages";
import { HandleIssueReaction } from "./handlers/HandleIssueReaction";

export class GithubApp extends App {
export class GithubApp extends App implements IPostMessageReacted {
constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
super(info, logger, accessors);
}
Expand All @@ -63,23 +71,25 @@ export class GithubApp extends App {
},
};
let text = `GitHub Authentication Succesfull 🚀`;
let interactionData = await getInteractionRoomData(read.getPersistenceReader(),user.id) ;
let interactionData = await getInteractionRoomData(
read.getPersistenceReader(),
user.id
);

if (token) {
// await registerAuthorizedUser(read, persistence, user);
await modify.getScheduler().scheduleOnce(deleteTokenTask);
} else {
text = `Authentication Failure 😔`;
}
if(interactionData && interactionData.roomId){
if (interactionData && interactionData.roomId) {
let roomId = interactionData.roomId as string;
let room = await read.getRoomReader().getById(roomId) as IRoom;
await clearInteractionRoomData(persistence,user.id);
await sendNotification(read,modify,user,room,text);
}else{
let room = (await read.getRoomReader().getById(roomId)) as IRoom;
await clearInteractionRoomData(persistence, user.id);
await sendNotification(read, modify, user, room, text);
} else {
await sendDirectMessage(read, modify, user, text, persistence);
}

}
public oauth2ClientInstance: IOAuth2Client;
public oauth2Config: IOAuth2ClientOptions = {
Expand Down Expand Up @@ -152,6 +162,16 @@ export class GithubApp extends App {
return await handler.run(context);
}

public async executePostMessageReacted(
context: IMessageReactionContext,
read: IRead,
http: IHttp,
persistence: IPersistence,
modify: IModify
): Promise<void> {
await HandleIssueReaction(this, context, read, http, persistence);
}

public async extendConfiguration(
configuration: IConfigurationExtend
): Promise<void> {
Expand Down
4 changes: 3 additions & 1 deletion github/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@
"nameSlug": "github",
"classFile": "GithubApp.ts",
"description": "The ultimate app extending Rocket.Chat for all developers collaborating on Github",
"implements": []
"implements": [
"IPostMessageReacted"
]
}
8 changes: 8 additions & 0 deletions github/definitions/githubIssueReaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// reactions results for issue (without s)
export interface IGithubIssueReaction {
reaction_id?: string;
repo_name: string;
user_id: string;
reaction: string;
issue_number: string;
}
20 changes: 19 additions & 1 deletion github/enum/OcticonIcons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,23 @@ export enum OcticonIcons {
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"
PENCIL = "https://raw.githubusercontent.com/primer/octicons/main/icons/pencil-24.svg",
}

export enum IssueReactions {
"PLUS_ONE" = "thumbsup",
"MINUS_ONE" = "thumbsdown",
"HEART" = "heart",
"CONFUSED" = "confused",
"ROCKET" = "rocket",
"EYES" = "eyes",
}

export enum IssueReactionsAliases {
thumbsup = "+1",
thumbsdown = "-1",
heart = "heart",
confused = "confused",
rocket = "rocket",
eyes = "eyes",
}
108 changes: 108 additions & 0 deletions github/handlers/HandleIssueReaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {
IHttp,
IPersistence,
IRead,
} from "@rocket.chat/apps-engine/definition/accessors";
import { IMessageReactionContext } from "@rocket.chat/apps-engine/definition/messages";
import { IGithubIssueReaction } from "../definitions/githubIssueReaction";
import { GithubApp } from "../GithubApp";
import { isAvailableReaction } from "../helpers/githubIssueReaction";
import { createIssueReaction, removeIssueReaction } from "../helpers/githubSDK";
import { getAccessTokenForUser } from "../persistance/auth";
import { GithubIssueReactionStorage } from "../persistance/githubIssueReaction";

export async function HandleIssueReaction(
app: GithubApp,
context: IMessageReactionContext,
read: IRead,
http: IHttp,
persistence: IPersistence
) {
if (context.message.customFields?.["issue"]) {
const { includes_emoji, emoji } = isAvailableReaction(context);
// when user reacts to those 6 emojis
if (includes_emoji) {
const user = context.user;
const auth = await getAccessTokenForUser(
read,
user,
app.oauth2Config
);

if (auth) {
const { issue_number, owner, repo_name } =
context.message?.customFields;
const token = auth.token;
const persistanceRead = await read.getPersistenceReader();
const githubIssueReactionStorage =
new GithubIssueReactionStorage(
persistence,
persistanceRead
);

// NOTE: isReacted field of context was retrieving undefined until RC-SERVER version 5.4.3 which is fixed in 6.0.0
if (context.isReacted) {
const response = await createIssueReaction(
repo_name,
owner,
issue_number,
http,
token,
emoji,
user.id
);

if (response.error == undefined) {
const issueReactionData =
response as IGithubIssueReaction;
await githubIssueReactionStorage.updateIssueReactionData(
user,
issueReactionData
);
}
} else {
// when user removes the reaction

const issueReactionData: IGithubIssueReaction = {
user_id: user.id,
reaction: emoji,
repo_name,
issue_number,
};

const issueReactionResponse =
await githubIssueReactionStorage.getIssueReactionData(
user,
issueReactionData
);
// meaning there are no reactions involved with this associations
if (issueReactionResponse === false) {
return;
}

const githubIssueReactionData =
issueReactionResponse as IGithubIssueReaction;

const reaction_id =
githubIssueReactionData.reaction_id as string;

const response = await removeIssueReaction(
repo_name,
owner,
issue_number,
http,
token,
reaction_id
);

if (response === true) {
await githubIssueReactionStorage.removeIssueReactionData(
user,
githubIssueReactionData
);
}
}
}
}
}
}
22 changes: 22 additions & 0 deletions github/helpers/githubIssueReaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// this file contains the logic for checking issue reactions which needs to update in github

import { IMessageReactionContext } from "@rocket.chat/apps-engine/definition/messages";
import { IssueReactions, IssueReactionsAliases } from "../enum/OcticonIcons";

export function isAvailableReaction(context: IMessageReactionContext) {
const content = context.reaction;
const reaction = content.substring(1, content.length - 1);
const includesEmoji = Object.values(IssueReactions).includes(
reaction as IssueReactions
);
if (includesEmoji) {
return {
includes_emoji: true,
emoji: IssueReactionsAliases[reaction],
};
}

return {
includes_emoji: false,
};
}
91 changes: 91 additions & 0 deletions github/helpers/githubSDK.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { IHttp } from "@rocket.chat/apps-engine/definition/accessors";
import { IGitHubIssue } from "../definitions/githubIssue";
import { IGithubIssueReaction } from "../definitions/githubIssueReaction";
import { ModalsEnum } from "../enum/Modals";

const BaseHost = "https://github.com/";
Expand Down Expand Up @@ -726,3 +727,93 @@ export async function updateGithubIssues(
}
return repsonseJSON;
}

export async function createIssueReaction(
repoName: string,
owner: string,
issueNumber: number,
http: IHttp,
access_token: string,
reaction: string,
user_id: string
): Promise<IGithubIssueReaction | any> {
try {
const request_data = {
content: reaction,
};

const response = await http.post(
`${BaseApiHost}repos/${repoName}/issues/${issueNumber}/reactions`,
{
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
data: request_data,
}
);

const { data } = response;
if (response.statusCode.toString().startsWith("2")) {
const { id } = data;

return {
reaction_id: id as string,
repo_name: repoName,
user_id,
reaction,
issue_number: issueNumber,
};
}

return {
message: response.content,
error: true,
};
} catch (e) {
return {
error: "Error While Creating Reaction to Issue",
repo_name: repoName,
issue_number: issueNumber,
user_id,
reaction,
};
}

// https://docs.github.com/en/rest/reactions?apiVersion=2022-11-28#create-reaction-for-an-issue
}

export async function removeIssueReaction(
repoName: string,
owner: string,
issueNumber: number,
http: IHttp,
access_token: string,
reactionId: string
) {
try {
const response = await http.del(
`${BaseApiHost}repos/${repoName}/issues/${issueNumber}/reactions/${reactionId}`,
{
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
}
);

if (response.statusCode.toString().startsWith("2")) {
return true;
}

return false;
} catch (e) {
return {
error: "Error While Removing Reaction to Issue",
repo_name: repoName,
issue_number: issueNumber,
};
}

// https://docs.github.com/en/rest/reactions?apiVersion=2022-11-28#delete-an-issue-reaction
}
Loading