Skip to content

Commit

Permalink
Merge pull request #19 from hschickdevs/dev/harrison
Browse files Browse the repository at this point in the history
🚀 Version 2.0.0 Released
  • Loading branch information
hschickdevs authored Oct 29, 2023
2 parents a4ec301 + 46794dd commit 3539df9
Show file tree
Hide file tree
Showing 13 changed files with 143 additions and 60 deletions.
20 changes: 7 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ This Telegram bot leverages the power of OpenAI's GPT language models to provide

3. **Typo Detection**: The advanced AI model automatically detects typos and corrects them, ensuring the translation is as accurate as possible.

<!-- Head to [Installation & Deployment]() to get started. -->
> 💡 **Tip:** This bot can be used in group chats as well as private chats for live translations!

<p align="right">(<a href="#readme-top">back to top</a>)</p>
Expand All @@ -108,12 +108,6 @@ Before you continue, you will need to do the following:
2. Create a new **Telegram bot** and get the **bot token** using [**BotFather**](https://t.me/botfather). If you don't know how to do so, use [**this guide**](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token) for reference.

3. Upload the bot commands to botfather for command hits on your new bot.

1. Copy the contents of [**`commands.txt`**](/commands.txt).

2. See this guide to learn how to [**upload the commands**](docs/upload-commands.gif) to your bot.

### Quick Setup

Although there isn't a completely simple way to set up this bot, the easiest way to do so is by deploy the Docker image on Google Cloud Platform. If you don't know what a Docker image is, don't worry, the following steps will guide you through the process of deploying the bot to the cloud using Google Cloud Platform:
Expand Down Expand Up @@ -218,16 +212,16 @@ _For example, the following command translates English to Spanish in the dialect
___
#### `/s <source (context)> - <target (context)>`
#### `/s <language1 (context)> - <language2 (context)>`
(Use **/s** or **/session**) 🔄 Start a continuous translation session. In this mode, every following message you send will be automatically translated to the specified target language.
(Use **/s** or **/session**) 🔄 Start a continuous translation session. In this mode, every following message you send will be automatically language detected and translated to the other language in the pair.
| Parameter | Description |
|--------------------|-------------------------------------------------------------------------------------------------|
| source (context) | The source language and context (e.g., dialect) from which you want to translate continuously. |
| target (context) | The target language and context (e.g., dialect) to which you want to translate continuously. |
| language1 (context) | The first language and context (e.g., dialect) in the translation pair. |
| target (context) | The second language and context (e.g., dialect) in the translation pair. |
_For example, the following command starts a continuous translation session from English to Spanish in the Mexico City dialect:_
_For example, the following command starts a continuous translation session with English and Spanish in the Mexico City dialect:_
`/session English - Spanish (Mexico City Dialect)`
Expand Down Expand Up @@ -271,7 +265,7 @@ Thanks to these awesome tools and frameworks for aiding in the development of th
- [x] Add detailed documentation
- [x] Add Docker image
- [x] Add bot Demo feature
- [ ] Add group translation sessions
- [X] Add group translation sessions
- [ ] Multi-platform Support
- [ ] Whatsapp
- [ ] Discord
Expand Down
3 changes: 2 additions & 1 deletion commands.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
help - Get information about bot commands and their usages
t - (or /translate) Translate text | Usage: /t <source lang> - <target lang> - <text>
s - (or /session) Start a continuous translation session | Usage: /session <source lang> - <target lang>
s - (or /session) Start a continuous translation session | Usage: /session <language 1> - <language 2>
contact - Get in touch with the developer
2 changes: 1 addition & 1 deletion src/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.0"
__version__ = "2.0.0"
9 changes: 8 additions & 1 deletion src/__main__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from .telegram import TranslateBot
from .translator import Translator
from .utils import handle_env
from .utils import handle_env, get_commands
from .logger import logger

import threading
from os import getenv
from time import sleep
from telebot import types

RESTART_DELAY = 5 # The number of seconds to wait before restarting the bot after an error is thrown

Expand All @@ -19,7 +20,13 @@ def start_bot(bot_instance: TranslateBot):
handle_env()

bot = TranslateBot(getenv("BOT_TOKEN"), Translator(getenv("OPENAI_TOKEN")))

# Set the bot commands:
user_commands = [types.BotCommand(command=command, description=description)
for command, description in get_commands().items()]
bot.set_my_commands(user_commands)

# Start the bot
while True:
logger.info(f"Bot started with token {getenv('BOT_TOKEN')} ...")

Expand Down
25 changes: 25 additions & 0 deletions src/resources/prompts/session.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Translate the following text based on the specified language direction. The input text may be in either {lang1} or {lang2}. Your task is to detect the language of the input and translate it to the other specified language. The languages can range from real-world ones like English or Chinese to fictional but recognizable ones like Pig Latin.

Ensure the translation:
- Accuracy & Context: Go beyond literal translation. Understand and convey the cultural, situational, and topical nuances of the original text.
- Essence & Punctuation: Preserve the tone, mood, and intent. Adapt punctuation to align with the target language's norms without losing the original's meaning.
- Dialects & Nuances: When a specific dialect is mentioned, tailor the translation to that variation. Adjust vocabulary and sentence structures as needed.
- Idioms: Translate the sentiment of idiomatic or colloquial phrases, not just the words.
- Technical Vocabulary: Use equivalent terms and punctuation in the target language for technical or specialized vocabulary. If no direct equivalent exists, choose a close match or provide a brief explanation. If typos are identified, do your best to fix these as needed.

If the translation is successful, format the response as JSON with the following keys:
- "success": True
- "message": <translated_text>

If there's an error or the language pair {lang1} and {lang2} is invalid, format the response as JSON with the following keys:
- "success": False
- "message": <reason_for_error>

The only possible reason for invalid language pairs include:
- One or both languages do not exist in the real world or in the fictional universe. If this is the case, it should be stated in the reason for error.
Otherwise, you must do your best to translate the text regardless.
If the "started" value below is True, then you must attempt to translate the text regardless. This means that the language pair has already been verified.

Started: {started}

Text to be translated: {text}
File renamed without changes.
9 changes: 9 additions & 0 deletions src/resources/templates/contact.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
🤝 *Contact & Support for {bot_name}* 🌐

For detailed information, source code, and to contribute, visit the GitHub repository:
🔗 [GitHub - Telegram Translate AI](https://github.com/hschickdevs/Telegram-Translate-AI)

For direct inquiries, feedback, or assistance, reach out to me on Telegram:
📩 [Contact @hschickdevs](https://t.me/hschickdevs)

_I appreciate your support and feedback. Together, we can make {bot_name} even better! 💬_
4 changes: 2 additions & 2 deletions src/resources/templates/help.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Here are the commands you can use:
- _Usage_: `/t <source lang> - <target lang> - <text>`
- _Example_: `/t English - Spanish (Madrid Dialect) - Hi there, I'm a bot!`

`/s` or `/session` - 🔄 Start a continuous translation session. In this mode, every message you send will be automatically translated to the specified target language.
- _Usage_: `/session <source lang> - <target lang>`
`/s` or `/session` - 🔄 Start a continuous translation session. In this mode, every message you send will be automatically detected and translated to the other language in the pair. This session can be started in group channels as well as private chats.
- _Usage_: `/session <language 1> - <language 2>`
- _Example_: `/session English - Spanish (Mexico City Dialect)`

To end a continuous session, click the "Quit Session" button on the inline keyboard below any of the translated messages.
Expand Down
4 changes: 3 additions & 1 deletion src/resources/templates/session.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
*🔄 Continuous Translation Session Started*

You've entered a continuous translation session from `{from_lang}` to `{to_lang}`. All of your messages will be automatically translated. 👇
_Tip: You can click on the translated text to copy it to your clipboard._

All messages will be automatically detected and translated between `{lang1}` and `{lang2}` 👇
1 change: 0 additions & 1 deletion src/resources/templates/start.md

This file was deleted.

64 changes: 39 additions & 25 deletions src/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ def on_start(message):
@self.message_handler(commands=['help'])
def on_help(message):
self.reply_to(message, get_command_template('help').format(bot_name=self.get_me().first_name), parse_mode='Markdown')

@self.message_handler(commands=['contact'])
def on_contact(message):
self.reply_to(message, get_command_template('contact').format(bot_name=self.get_me().first_name), parse_mode='Markdown')

@self.message_handler(commands=['t', 'translate'])
def on_translate(message):
Expand Down Expand Up @@ -56,28 +60,36 @@ def on_translate(message):
@self.message_handler(commands=['s', 'session'])
def start_session(message):
try:
from_lang, to_lang = self._parse_command(message.text)[:2] # Parse only the first two
lang1, lang2 = self._parse_command(message.text)[:2] # Parse only the first two
except:
self.reply_to(message, "*❌ Invalid command syntax - Please use /session <source lang> - <target lang>*", parse_mode='Markdown')
self.reply_to(message,
"*❌ Invalid command syntax.\n\nPlease use the following format:*\n`/session <language 1> - <language 2>`\n\n *Example:*\n`/session English - Spanish (Mexico City Dialect)`",
parse_mode='Markdown')
return

try:
self.active_sessions[message.from_user.id] = {'from_lang': from_lang, 'to_lang': to_lang}
# Verify that the languages are supported with an attempted translation
response = self.translator.translate_session("Test text to verify the language pair.",
lang1, lang2, started=False)
if not response['success']:
raise Exception(response['message'])

self.active_sessions[message.chat.id] = {'lang1': lang1, 'lang2': lang2}
# markup = types.InlineKeyboardMarkup()
# markup.add(types.InlineKeyboardButton("Quit Session", callback_data="quit_session"))
self.reply_to(message,
get_command_template('session').format(from_lang=from_lang, to_lang=to_lang),
parse_mode='Markdown') # , reply_markup=markup
except Exception:
self.reply_to(message, "*❌ Could not start session - please check your syntax and try again*", parse_mode='Markdown')
get_command_template('session').format(lang1=lang1, lang2=lang2),
parse_mode='Markdown') # , reply_markup=markup
except Exception as err:
self.reply_to(message, f"*❌ Could not start session - {err}*", parse_mode='Markdown')

@self.message_handler(func=lambda message: True) # This should be your last handler
def generic_handler(message):
# Generic handler to check and handle active sessions
user_id = message.from_user.id
if user_id in self.active_sessions:
from_lang = self.active_sessions[user_id]['from_lang']
to_lang = self.active_sessions[user_id]['to_lang']
chat_id = message.chat.id
if chat_id in self.active_sessions:
lang1 = self.active_sessions[chat_id]['lang1']
lang2 = self.active_sessions[chat_id]['lang2']
text = message.text

# Send initial "Processing translation..." message and store its message ID
Expand All @@ -90,17 +102,19 @@ def generic_handler(message):

try:
# Call Translator
data = self.translator.translate(text, from_lang, to_lang)
data = self.translator.translate_session(text, language1=lang1, language2=lang2)

# Update message based on translation success status if response from API is successful
if data['success']:
template = get_command_template('translate-success')
# template = get_command_template('translate-success')
text = f"`{data['message']}`"
self.edit_message_text(chat_id=message.chat.id,
message_id=processing_msg_id,
text=template.format(from_lang=from_lang,
to_lang=to_lang,
translated_text=data['message']),
parse_mode='Markdown', reply_markup=markup)
message_id=processing_msg_id,
text=text,
# text=template.format(from_lang=from_lang,
# to_lang=to_lang,
# translated_text=data['message']),
parse_mode='Markdown', reply_markup=markup)
else:
raise Exception(data['message'])
except Exception as err:
Expand All @@ -111,21 +125,21 @@ def generic_handler(message):

@self.callback_query_handler(func=lambda call: call.data == 'quit_session')
def end_session(call):
user_id = call.from_user.id
if user_id in self.active_sessions:
from_lang = self.active_sessions[user_id]['from_lang']
to_lang = self.active_sessions[user_id]['to_lang']
chat_id = call.message.chat.id
if chat_id in self.active_sessions:
lang1 = self.active_sessions[chat_id]['lang1']
lang2 = self.active_sessions[chat_id]['lang2']

# Remove the "Quit Session" button
self.edit_message_reply_markup(call.message.chat.id, call.message.message_id, reply_markup=None)

# Append "Session Ended" to the existing message and remove the "Quit Session" button
text = f"*🛑 {from_lang} to {to_lang} Translation Session Ended.*"
text = f"*🛑 Translation Session Ended for {lang1} and {lang2}.*"
self.send_message(chat_id=call.message.chat.id,
text=text,
parse_mode='Markdown')

del self.active_sessions[user_id]
del self.active_sessions[chat_id]
self.answer_callback_query(call.id, "Session ended.")

@staticmethod
Expand All @@ -137,7 +151,7 @@ def _parse_command(text: str) -> list:
text (str): The message.text, expected in the format of /t from_lang - to_lang - text
Returns:
list: The parsed message in the order of [from_lang, to_lang, text]
list: The parsed message in the order of [lang1, lang2, text]
"""
# Initial split to separate the command from the rest of the text
cmd, rest_of_text = text.split(' ', 1)
Expand Down
33 changes: 28 additions & 5 deletions src/translator.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
from .utils import get_prompt

from json import loads
import openai
from os.path import join, dirname, isdir
from os import getenv, getcwd, mkdir
from dotenv import load_dotenv, find_dotenv


class Translator:
def __init__(self, api_key: str, model: str = "gpt-3.5-turbo"):
"""
Initializes the translator object.
Args:
api_key (str): Your OpenAI API key
model (str, optional): The OpenAI model to use. Defaults to "gpt-3.5-turbo".
"""
self.model = model
openai.api_key = api_key

Expand All @@ -23,7 +31,6 @@ def _call_api(self, prompt: str) -> dict:
model=self.model,
messages=[{"role": "user", "content": prompt}]
)

return loads(response["choices"][0]["message"]["content"])

def translate(self, text: str, source_lang: str, target_lang: str) -> dict:
Expand All @@ -38,7 +45,23 @@ def translate(self, text: str, source_lang: str, target_lang: str) -> dict:
Returns:
dict: Returns the dict object from the self._call_api return
"""
# Construct the detailed prompt for the translation task
prompt = get_prompt(text, source_lang, target_lang)
# Format the translate.txt prompt file to construct the detailed prompt for the translation task
with open(join(dirname(__file__), 'resources', 'prompts', 'translate.txt'), 'r') as f:
prompt = f.read().format(from_lang=source_lang, to_lang=target_lang, text=text)

return self._call_api(prompt)

def translate_session(self, text: str, language1: str, language2: str, started: bool = True) -> dict:
"""
Translate the text from source language to target language using the session prompt
Args:
text (str): Text to be language autodetected and translated.
language1 (str): First language of the session pair.
language2 (str): Second language of the session pair.
"""
# Format the session.txt prompt file to construct the detailed prompt for the session task
with open(join(dirname(__file__), 'resources', 'prompts', 'session.txt'), 'r') as f:
prompt = f.read().format(lang1=language1, lang2=language2, text=text, started=started)

return self._call_api(prompt)
29 changes: 19 additions & 10 deletions src/utils.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
from os.path import join, dirname, isdir
from os.path import join, dirname, isdir, abspath
from os import getenv, getcwd, mkdir
from dotenv import load_dotenv, find_dotenv


def get_prompt(text: str, from_lang: str, to_lang: str) -> str:
"""
TODO: SHOULD FORMAT THE PROMPT WITH THE USER'S INPUT
"""
with open(join(dirname(__file__), 'resources', 'prompt.txt'), 'r') as f:
return f.read().format(from_lang=from_lang, to_lang=to_lang, text=text)



def get_command_template(context: str) -> str:
Expand Down Expand Up @@ -43,4 +36,20 @@ def get_logfile() -> str:
log_dir = join(getcwd(), 'logs')
if not isdir(log_dir):
mkdir(log_dir)
return join(log_dir, 'log.txt')
return join(log_dir, 'log.txt')


def get_commands() -> dict:
"""Fetches the commands from the templates for the help command"""
commands = {}

# Define the path to the commands.txt file
file_path = join(dirname(abspath(__file__)), '..', 'commands.txt')

with open(file_path, 'r') as f:
for line in f.readlines():
# Splitting at the first '-' to separate command and description
command, description = line.strip().split(' - ', 1)
commands[command.strip()] = description.strip()

return commands

0 comments on commit 3539df9

Please sign in to comment.