-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This module automatically translates foreign-language chat messages to the configured target language (English by default) using the AGPL-licensed, self-hostable LibreTranslate.
- Loading branch information
1 parent
04e2332
commit 1c6dd7e
Showing
7 changed files
with
303 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
"""Because every other bot we've tried can go f**k themselves. | ||
""" | ||
|
||
import logging | ||
from uuid import UUID | ||
|
||
from aiohttp import ClientSession | ||
from discord import Color, Embed, Message | ||
from discord.ext import commands | ||
from pydantic import BaseModel, Field, HttpUrl, Secret | ||
from pydantic_extra_types.language_code import LanguageAlpha2 | ||
|
||
from kolkra_ng.bot import Kolkra | ||
from kolkra_ng.cogs.translate.api import ( | ||
DetectRequest, | ||
DetectResponseItem, | ||
LanguagesResponseItem, | ||
TranslateRequest, | ||
TranslateResponse, | ||
) | ||
from kolkra_ng.embeds import WarningEmbed, icons8 | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
class TranslateConfig(BaseModel): | ||
target_language: LanguageAlpha2 = Field( | ||
default="en", | ||
description="The language to translate foreign-language messages to. Defaults to 'en' (English).", | ||
) | ||
api_base_url: HttpUrl = Field( | ||
description="Base URL of a [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) instance.", | ||
) | ||
api_key: Secret[UUID] | None = Field( | ||
default=None, | ||
description="An API key for the LibreTranslate instance, if required.", | ||
) | ||
aiohttp_params: dict = Field( | ||
default_factory=dict, | ||
description="Extra parameters to pass to the aiohttp.ClientSession constructor.", | ||
) | ||
|
||
|
||
class TranslateCog(commands.Cog): | ||
def __init__(self, bot: Kolkra) -> None: | ||
super().__init__() | ||
self.bot = bot | ||
self.config = TranslateConfig(**bot.config.cogs.get(self.__cog_name__, {})) | ||
self.session = ClientSession( | ||
base_url=self.config.api_base_url.unicode_string(), | ||
**self.config.aiohttp_params, | ||
) | ||
|
||
async def cog_load(self) -> None: | ||
async with self.session.get("/languages") as resp: | ||
resp.raise_for_status() | ||
languages_response = [ | ||
LanguagesResponseItem(**item) for item in await resp.json() | ||
] | ||
self.all_languages = { | ||
language.code: language for language in languages_response | ||
} | ||
self.supported_languages = { | ||
code: language | ||
for code, language in self.all_languages.items() | ||
if self.config.target_language in language.targets | ||
} | ||
|
||
async def cog_unload(self) -> None: | ||
await self.session.close() | ||
|
||
@commands.Cog.listener() | ||
async def on_message(self, message: Message) -> None: | ||
if message.author.bot or not message.content: | ||
return | ||
async with self.session.post( | ||
"/detect", | ||
data=DetectRequest( | ||
q=message.content, | ||
api_key=( | ||
key.get_secret_value() if (key := self.config.api_key) else None | ||
), | ||
).model_dump(mode="json", exclude_none=True), | ||
) as resp: | ||
resp.raise_for_status() | ||
detect_response = [DetectResponseItem(**item) for item in await resp.json()] | ||
detected_language = max(detect_response, key=lambda x: x.confidence) | ||
if detected_language.language == self.config.target_language: | ||
return | ||
elif detected_language.language not in self.supported_languages: | ||
await message.reply( | ||
embed=WarningEmbed( | ||
title="Language not supported!", | ||
description=f"This message's detected language ({self.all_languages[detected_language.language].name}) cannot be translated to {self.all_languages[detected_language.language].name}.", | ||
), | ||
mention_author=False, | ||
) | ||
return | ||
async with self.session.post( | ||
"/translate", | ||
data=TranslateRequest( | ||
q=message.content, | ||
api_key=( | ||
key.get_secret_value() if (key := self.config.api_key) else None | ||
), | ||
source=detected_language.language, | ||
target=self.config.target_language, | ||
).model_dump(mode="json", exclude_none=True), | ||
) as resp: | ||
resp.raise_for_status() | ||
translate_response = TranslateResponse(**await resp.json()) | ||
await message.reply( | ||
embed=Embed( | ||
color=Color.blue(), | ||
title="Automatic translation", | ||
description=translate_response.translatedText, | ||
) | ||
.add_field( | ||
name="Language", | ||
value=f"{self.all_languages[detected_language.language].name} -> {self.all_languages[self.config.target_language].name}", | ||
) | ||
.set_thumbnail(url=icons8("translate-text")) | ||
.set_footer(text="Machine translations may not be 100% accurate.") | ||
) | ||
|
||
|
||
async def setup(bot: Kolkra) -> None: | ||
await bot.add_cog(TranslateCog(bot)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
from typing import Literal | ||
from uuid import UUID | ||
|
||
from pydantic import BaseModel, Field, NonNegativeInt | ||
from pydantic_extra_types.language_code import LanguageAlpha2 | ||
|
||
|
||
class DetectRequest(BaseModel): | ||
q: str | ||
api_key: UUID | None | ||
|
||
|
||
class DetectResponseItem(BaseModel): | ||
confidence: int = Field(ge=0, le=100) | ||
language: LanguageAlpha2 | ||
|
||
|
||
class LanguagesResponseItem(BaseModel): | ||
code: LanguageAlpha2 | ||
name: str | ||
targets: set[LanguageAlpha2] | ||
|
||
|
||
class TranslateRequest(BaseModel): | ||
q: str | ||
source: LanguageAlpha2 | Literal["auto"] | ||
target: LanguageAlpha2 | ||
format: Literal["text", "html"] = "text" | ||
alternatives: NonNegativeInt = 0 | ||
api_key: UUID | None | ||
|
||
|
||
class TranslateResponse(BaseModel): | ||
translatedText: str | ||
detectedLanguage: DetectResponseItem | None = None | ||
alternatives: list[str] | None = None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.