Skip to content
This repository has been archived by the owner on Nov 1, 2024. It is now read-only.

Link Telegram Database #15

Merged
merged 17 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/cloudflare-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,19 @@ jobs:
- name: Publish to Cloudflare
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
WEBHOOK: "/endpoint"
SECRET: ${{ secrets.BOT_SECRET }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
DEFAULT_PRIORITY: "Priority: 0 (Normal)"
INSTALLATION_TOKEN: ${{ steps.get_installation_token.outputs.token }}
GITHUB_INSTALLATION_TOKEN: ${{ steps.get_installation_token.outputs.token }}
run: |
# Populate Cloudflare Worker secrets
echo "$TOKEN" | wrangler secret put TOKEN
echo "$WEBHOOK" | wrangler secret put WEBHOOK
echo "$SECRET" | wrangler secret put SECRET
echo "$OPENAI_API_KEY" | wrangler secret put OPENAI_API_KEY
echo "$DEFAULT_PRIORITY" | wrangler secret put DEFAULT_PRIORITY
echo "$INSTALLATION_TOKEN" | wrangler secret put INSTALLATION_TOKEN
echo "$GITHUB_INSTALLATION_TOKEN" | wrangler secret put GITHUB_INSTALLATION_TOKEN

yarn deploy
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,94 @@ yarn deploy
```

This command will deploy your Cloudflare Workers application and make it accessible.

# Telegram UbiquiBot

This project allows you to deploy a Telegram`X`ChatGPT auto bounty issue creator using Cloudflare Workers and wrangler.

## Getting Started

1. Fork/clone the repository:

2. Install dependencies:

If you are using `yarn`:

```bash
cd telegram-ubiquibot
yarn
```

3. Environment Setup:

- Copy the `environment.example.json` file and rename it to `environment.json`.

- Fill in the required data in the environment.json file.

```jsonc
{
"telegram_bot_token": "", // Telegram Bot Token, use @BotFather to create a bot an input the token here
"webhook": "/endpoint", // Path for telegram cloudflare communication (default preffered)
"secret": "QUEVEDO_BZRP_Random_String_52", // Random string for secure communication
"openai_api_key": "", // OpenAI ChatGPT API Key (Valid)
"github_pat": "", // Github Personal Access Token (Empty if you are using a bot)
"default_priority": "Priority: 0 (Normal)",
"github_installation_token": "", // Automatically generated by Github Action if you are using a bot
"client_id": "", // Github OAuth App Client ID
"client_secret": "", // Github OAuth App Client Secret
"supabase_key": "", // Supabase Service Role Key
"supabase_url": "" // Supabase Project URL
}
```

[Here's a guide to create Github App](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)

Note: Your Github App Callback URL should be `https://YOUR_CLOUDFLARE_WORKER_URL/endpoint` or `/` whatever you used in place of `"webhook": "/endpoint"`

[@BotFather for telegram bots](https://t.me/botfather)

- After filling in the data, run the setup key command:

If you are using yarn:

```bash
yarn setup-key
```

This command will set up the necessary secrets for your Cloudflare Workers application and await until the setup is complete.

4. Changing Keys:

If you need to change any key, you can use the following command:

```bash
wrangler secret delete <KEY>
```

Replace `<KEY>` with the name of the secret key you want to change. After deleting the key, you can run the setup key command again to set the new key.

5. Deploying the App:

To deploy the application, simply run:

```bash
yarn deploy
```

This command will deploy your Cloudflare Workers application and make it accessible.

## Testing Telegram Bot

1. Install Bot on your group
2. Edit Bot in [@BotFather](https://t.me/botfather) dashboard (On Telegram) - Add slash commands `/start`
3. Copy your Worker URL on Github and add `/registerWebhook` as suffix, ex: `https://WORKER_URL/registerWebhook` - it should return `Ok` if everything works then you can move to the next step
4. Use the private chat as an admin to trigger the `/start` command and link a Github Repo to any of the listed channels

![Screenshot 2023-08-18 at 8 15 28 PM](https://github.com/ubiquity/telegram-ubiquibot/assets/51956013/4e3d9313-af18-406e-9c91-3efcc372eb54)

4. Members of your group can use `/register` command (sent to the group) and a link will be sent to their inbox to bind their telegram with their Github accounts

### Troubleshooting Telegram

1. Bot cannot read messages - [Solution](https://www.teleme.io/articles/group_privacy_mode_of_telegram_bots?hl=en)
2. Every other errors can be debugged from the Cloudflare Worker dashboard (under Real-time Logs)
9 changes: 6 additions & 3 deletions environment.example.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"token": "",
"telegram_bot_token": "",
"webhook": "/endpoint",
"secret": "QUEVEDO_BZRP_Random_String_52",
"openai_api_key": "",
"github_pat": "",
"default_priority": "Priority: 0 (Normal)"
}
"default_priority": "Priority: 0 (Normal)",
"github_installation_token": "",
"supabase_key": "",
"supabase_url": ""
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"open-source"
],
"dependencies": {
"@supabase/supabase-js": "^2.32.0",
"dotenv": "^16.3.1",
"node-forge": "^1.3.1",
"octokit": "^3.1.0"
Expand All @@ -37,6 +38,7 @@
"husky": "^8.0.3",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"supabase": "^1.86.2",
"tsx": "^3.12.7",
"typescript": "^5.1.0",
"wrangler": "^3.0.0"
Expand Down
14 changes: 1 addition & 13 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,6 @@
import { RepoType } from "./types/Basic";
export const TIME_LABELS: string[] = ["Time: <1 Hour", "Time: <1 Day", "Time: <1 Week", "Time: <2 Weeks", "Time: <1 Month"];

export const repoMapping: RepoType[] = [
{
group: -1001738021587,
github: "Seprintour-Test/test",
},
{
group: -1001558587400,
github: "ubiquity/testing-telegram-ubiquibot",
},
];

export default {
TIME_LABELS,
repoMapping,
TIME_LABELS
};
28 changes: 19 additions & 9 deletions src/helpers/chatGPT.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { removeNewlinesAndExtractValues } from "./utils";
import { PROMPT } from "./prompt";
import { PROMPT, TRAINING } from "./prompt";
import { ErrorType } from "../types/Basic";

export const completeGPT3 = async (messageText: string) => {
try {
const apiKey = process.env.OPENAI_API_KEY;
const apiUrl = "https://api.openai.com/v1/completions";
const apiKey = OPENAI_API_KEY;
const apiUrl = "https://api.openai.com/v1/chat/completions";

const requestBody = {
model: "text-davinci-003",
prompt: PROMPT.replace(/{messageText}/g, messageText),
max_tokens: 1000,
temperature: 1, // Adjust temperature as needed
model: "gpt-3.5-turbo",
messages: [
{
role: "system",
content: TRAINING,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like you never renamed this to PROMPT_SYSTEM

},
{
role: "user",
content: PROMPT.replace(/{messageText}/g, messageText),
},
],
max_tokens: 1500,
temperature: 0,
stream: false,
};

const response = await fetch(apiUrl, {
Expand All @@ -32,8 +42,8 @@ export const completeGPT3 = async (messageText: string) => {
return;
}

return removeNewlinesAndExtractValues(data.choices[0].text);
} catch (e: unknown) {
return removeNewlinesAndExtractValues(data.choices[0].message.content);
} catch (e) {
console.log((e as ErrorType).message);
return {
issueTitle: null,
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const getGithubUserData = async (orgName: string, user: string) => {
const response = await fetch(apiUrl, {
method: "GET",
headers: {
Authorization: `token ${process.env.GITHUB_PAT}`,
Authorization: `token ${GITHUB_PAT}`,
"Content-Type": "application/json",
"User-Agent": "Telegram Cloudflare Worker",
},
Expand Down Expand Up @@ -56,7 +56,7 @@ export const createIssue = async (
const response = await fetch(apiUrl, {
method: "POST",
headers: {
Authorization: `token ${process.env.INSTALLATION_TOKEN || process.env.GITHUB_PAT}`,
Authorization: `token ${GITHUB_INSTALLATION_TOKEN || GITHUB_PAT}`,
"Content-Type": "application/json",
"User-Agent": "Telegram Cloudflare Worker",
},
Expand All @@ -71,7 +71,7 @@ export const createIssue = async (
return { data, assignees: assignees !== null && assignees.length > 0 };
} catch (error) {
console.log("Error creating issue:", error);
return { data: null, assignees: false };
return { data: null, assignees: false, error };
}
};

Expand Down
53 changes: 53 additions & 0 deletions src/helpers/navigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { CallbackQueryType } from "../types/Basic";

import { setUserSession } from "./session";
import { getGroupDetails, listGroupsWithBot } from "./telegram";
import { editBotMessage } from "./triggers";
import { parseCallData } from "./utils";

export const handleFirstMenu = async (value: string, chatId: number, messageId: number, groupData: string) => {
switch (value) {
case "link_github":
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recall asking about a /register command but I could be confused here.

await editBotMessage(chatId, messageId, `Please provide the URL of repository you want to link to this group.`);
setUserSession(chatId, { v: "link_github", c: groupData });
break;
default:
break;
}
};

/**
* Handle incoming callback_query (inline button press)
* https://core.telegram.org/bots/api#message
*/
export const onPrivateCallbackQuery = async (callbackQuery: CallbackQueryType) => {
const parsedData = parseCallData(callbackQuery.data);
const chatId = callbackQuery.message.chat.id;
const messageId = callbackQuery.message.message_id;
const fromId = callbackQuery.from.id;

const item = parsedData[parsedData.length - 1]; // select the last calldata

// Use the item.key and item.value to generate menu items
switch (item.key) {
case "group":
const name = await getGroupDetails(item.value as number);
await editBotMessage(chatId, messageId, `Here is your group: *${name}* \nWhat do you want to do?`, [
{
text: "Link Github Repo",
callback_data: `${callbackQuery.data},menu:link_github`,
},
]);
break;
case "menu":
const groupData = parsedData[0];
await handleFirstMenu(item.value as string, chatId, messageId, groupData.value as string);
break;
case "group_list":
await listGroupsWithBot(fromId, chatId, messageId);
break;
default:
console.log(`Unknown key: ${item.key}`);
break;
}
};
13 changes: 4 additions & 9 deletions src/helpers/prompt.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
export const PROMPT = `
You are a helpful bot that assists with GitHub issues.

{ "system": "You are a helpful bot that assists with GitHub issues. Please determine if the following user message is suitable for a GitHub issue:" }
export const TRAINING = `You are a helpful bot that assists with GitHub issues. Please determine if the following user message is suitable for a GitHub issue`;

export const PROMPT = `
Consider the following criteria:
1. If the message describes a clear task and not a regular conversation, consider it suitable for a GitHub issue.
2. If the message is not too vague and includes necessary details, it may be suitable for a GitHub issue.
Expand All @@ -13,15 +11,12 @@ export const PROMPT = `
7. If the message proposes a task related to quality assurance, development, or project management, consider it suitable for a GitHub issue.
8. If the message involves contacting or reaching out to a specific person or team for a task, consider it suitable for a GitHub issue. However, ensure that the message provides sufficient context and details about the task.
9. If the message includes suggestion or proposal question and/or imply a request for feedback, permission, or agreement, do not consider it as a valid task for GitHub issue.
10. If the message explicitly imply a self-delegated tasks (e.g., texts containing "I", "i'd", "i've" or "me"), do not consider it as a valid task for GitHub.
10. If the message explicitly implies a self-delegated task (e.g., texts containing "I", "I'd ", "I've" or "me"), do not consider it as a valid task for GitHub.

Please answer "Yes" and provide a good Issue Title always starting with Issue Title:(with a full stop at the end) and a reasonable Time Estimate always starting with Time Estimate:(with a full stop at the end) based on your experience if the message is suitable for a GitHub issue and "No" with a reason otherwise. Be reasonable, consistent and very accurate with the time estimate, considering the complexity of the task mentioned in the message.
Provide the median of the time range if applicable with the units of measurement at the end too, no ranges

Note: If the message contains a URL but does not describe a clear task, consider it "No" and provide the reason accordingly.

**User Message:** "{messageText}"
`;

export default {
PROMPT,
};
17 changes: 17 additions & 0 deletions src/helpers/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const userSessions = new Map(); // Store user sessions and context

export const setUserSession = (chatId: number, context: string | object) => {
userSessions.set(chatId, context);
};

export const getUserSession = (chatId: number) => {
return userSessions.get(chatId);
};

export const hasUserSession = (chatId: number) => {
return userSessions.has(chatId);
};

export const deleteUserSession = (chatId: number) => {
userSessions.delete(chatId);
};
Loading