mirror of
https://github.com/aljazceru/chatgpt-telegram-bot.git
synced 2025-12-19 13:44:57 +01:00
added youtube audio extractor, dice, and direct response for images
This commit is contained in:
23
README.md
23
README.md
@@ -110,18 +110,21 @@ Check out the [Budget Manual](https://github.com/n3d1117/chatgpt-telegram-bot/di
|
|||||||
| `SPOTIFY_CLIENT_SECRET` | Spotify app Client Secret (required only for the `spotify` plugin, you can find it on the [dashboard](https://developer.spotify.com/dashboard/)) | - |
|
| `SPOTIFY_CLIENT_SECRET` | Spotify app Client Secret (required only for the `spotify` plugin, you can find it on the [dashboard](https://developer.spotify.com/dashboard/)) | - |
|
||||||
| `SPOTIFY_REDIRECT_URI` | Spotify app Redirect URI (required only for the `spotify` plugin, you can find it on the [dashboard](https://developer.spotify.com/dashboard/)) | - |
|
| `SPOTIFY_REDIRECT_URI` | Spotify app Redirect URI (required only for the `spotify` plugin, you can find it on the [dashboard](https://developer.spotify.com/dashboard/)) | - |
|
||||||
| `WORLDTIME_DEFAULT_TIMEZONE` | Default timezone to use, i.e. `Europe/Rome` (required only for the `worldtimeapi` plugin, you can get TZ Identifiers from [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) | - |
|
| `WORLDTIME_DEFAULT_TIMEZONE` | Default timezone to use, i.e. `Europe/Rome` (required only for the `worldtimeapi` plugin, you can get TZ Identifiers from [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)) | - |
|
||||||
|
| `DUCKDUCKGO_SAFESEARCH` | DuckDuckGo safe search (`on`, `off` or `moderate`) (optional, applies to `ddg_web_search` and `ddg_image_search`) | `moderate` |
|
||||||
|
|
||||||
#### Available plugins
|
#### Available plugins
|
||||||
| Name | Description | Required environment variable(s) |
|
| Name | Description | Required environment variable(s) |
|
||||||
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
|
|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------|
|
||||||
| `weather` | Daily weather and 7-day forecast for any location (powered by [Open-Meteo](https://open-meteo.com)) | - |
|
| `weather` | Daily weather and 7-day forecast for any location (powered by [Open-Meteo](https://open-meteo.com)) | - |
|
||||||
| `wolfram` | WolframAlpha queries (powered by [WolframAlpha](https://www.wolframalpha.com)) | `WOLFRAM_APP_ID` |
|
| `wolfram` | WolframAlpha queries (powered by [WolframAlpha](https://www.wolframalpha.com)) | `WOLFRAM_APP_ID` |
|
||||||
| `ddg_web_search` | Web search (powered by [DuckDuckGo](https://duckduckgo.com)) | - |
|
| `ddg_web_search` | Web search (powered by [DuckDuckGo](https://duckduckgo.com)) | - |
|
||||||
| `ddg_translate` | Translate text to any language (powered by [DuckDuckGo](https://duckduckgo.com)) | - |
|
| `ddg_translate` | Translate text to any language (powered by [DuckDuckGo](https://duckduckgo.com)) | - |
|
||||||
| `ddg_image_search` | Search image or GIF (powered by [DuckDuckGo](https://duckduckgo.com)) | - |
|
| `ddg_image_search` | Search image or GIF (powered by [DuckDuckGo](https://duckduckgo.com)) | - |
|
||||||
| `crypto` | Live cryptocurrencies rate (powered by [CoinCap](https://coincap.io)) - by [@stumpyfr](https://github.com/stumpyfr) | - |
|
| `crypto` | Live cryptocurrencies rate (powered by [CoinCap](https://coincap.io)) - by [@stumpyfr](https://github.com/stumpyfr) | - |
|
||||||
| `spotify` | Spotify top tracks/artists, currently playing song and content search (powered by [Spotify](https://spotify.com)). Requires one-time authorization. | `SPOTIFY_CLIENT_ID`, `SPOTIFY_CLIENT_SECRET`, `SPOTIFY_REDIRECT_URI` |
|
| `spotify` | Spotify top tracks/artists, currently playing song and content search (powered by [Spotify](https://spotify.com)). Requires one-time authorization. | `SPOTIFY_CLIENT_ID`, `SPOTIFY_CLIENT_SECRET`, `SPOTIFY_REDIRECT_URI` |
|
||||||
| `worldtimeapi` | Get latest world time (powered by [WorldTimeAPI](https://worldtimeapi.org/)) | `WORLDTIME_DEFAULT_TIMEZONE` |
|
| `worldtimeapi` | Get latest world time (powered by [WorldTimeAPI](https://worldtimeapi.org/)) | `WORLDTIME_DEFAULT_TIMEZONE` |
|
||||||
|
| `dice` | Send a dice in the chat! | - |
|
||||||
|
| `youtube_audio_extractor` | Extract audio from YouTube videos | - |
|
||||||
|
|
||||||
Check out the [official API reference](https://platform.openai.com/docs/api-reference/chat) for more details.
|
Check out the [official API reference](https://platform.openai.com/docs/api-reference/chat) for more details.
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from calendar import monthrange
|
|||||||
|
|
||||||
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
|
from tenacity import retry, stop_after_attempt, wait_fixed, retry_if_exception_type
|
||||||
|
|
||||||
|
from bot.utils import is_direct_result
|
||||||
from plugin_manager import PluginManager
|
from plugin_manager import PluginManager
|
||||||
|
|
||||||
# Models can be found here: https://platform.openai.com/docs/models/overview
|
# Models can be found here: https://platform.openai.com/docs/models/overview
|
||||||
@@ -118,6 +119,9 @@ class OpenAIHelper:
|
|||||||
response = await self.__common_get_chat_response(chat_id, query)
|
response = await self.__common_get_chat_response(chat_id, query)
|
||||||
if self.config['enable_functions']:
|
if self.config['enable_functions']:
|
||||||
response, plugins_used = await self.__handle_function_call(chat_id, response)
|
response, plugins_used = await self.__handle_function_call(chat_id, response)
|
||||||
|
if is_direct_result(response):
|
||||||
|
return response, '0'
|
||||||
|
|
||||||
answer = ''
|
answer = ''
|
||||||
|
|
||||||
if len(response.choices) > 1 and self.config['n_choices'] > 1:
|
if len(response.choices) > 1 and self.config['n_choices'] > 1:
|
||||||
@@ -158,6 +162,9 @@ class OpenAIHelper:
|
|||||||
response = await self.__common_get_chat_response(chat_id, query, stream=True)
|
response = await self.__common_get_chat_response(chat_id, query, stream=True)
|
||||||
if self.config['enable_functions']:
|
if self.config['enable_functions']:
|
||||||
response, plugins_used = await self.__handle_function_call(chat_id, response, stream=True)
|
response, plugins_used = await self.__handle_function_call(chat_id, response, stream=True)
|
||||||
|
if is_direct_result(response):
|
||||||
|
yield response, '0'
|
||||||
|
return
|
||||||
|
|
||||||
answer = ''
|
answer = ''
|
||||||
async for item in response:
|
async for item in response:
|
||||||
@@ -283,7 +290,16 @@ class OpenAIHelper:
|
|||||||
|
|
||||||
logging.info(f'Calling function {function_name} with arguments {arguments}')
|
logging.info(f'Calling function {function_name} with arguments {arguments}')
|
||||||
function_response = await self.plugin_manager.call_function(function_name, arguments)
|
function_response = await self.plugin_manager.call_function(function_name, arguments)
|
||||||
logging.info(f'Got response {function_response}')
|
|
||||||
|
if function_name not in plugins_used:
|
||||||
|
plugins_used += (function_name,)
|
||||||
|
|
||||||
|
if is_direct_result(function_response):
|
||||||
|
self.__add_function_call_to_history(chat_id=chat_id, function_name=function_name,
|
||||||
|
content=json.dumps({'result': 'Done, the content has been sent'
|
||||||
|
'to the user.'}))
|
||||||
|
return function_response, plugins_used
|
||||||
|
|
||||||
self.__add_function_call_to_history(chat_id=chat_id, function_name=function_name, content=function_response)
|
self.__add_function_call_to_history(chat_id=chat_id, function_name=function_name, content=function_response)
|
||||||
response = await openai.ChatCompletion.acreate(
|
response = await openai.ChatCompletion.acreate(
|
||||||
model=self.config['model'],
|
model=self.config['model'],
|
||||||
@@ -292,8 +308,6 @@ class OpenAIHelper:
|
|||||||
function_call='auto' if times < self.config['functions_max_consecutive_calls'] else 'none',
|
function_call='auto' if times < self.config['functions_max_consecutive_calls'] else 'none',
|
||||||
stream=stream
|
stream=stream
|
||||||
)
|
)
|
||||||
if function_name not in plugins_used:
|
|
||||||
plugins_used += (function_name,)
|
|
||||||
return await self.__handle_function_call(chat_id, response, stream, times + 1, plugins_used)
|
return await self.__handle_function_call(chat_id, response, stream, times + 1, plugins_used)
|
||||||
|
|
||||||
async def generate_image(self, prompt: str) -> tuple[str, str]:
|
async def generate_image(self, prompt: str) -> tuple[str, str]:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
from bot.plugins.dice import DicePlugin
|
||||||
|
from bot.plugins.youtube_audio_extractor import YouTubeAudioExtractorPlugin
|
||||||
from plugins.ddg_image_search import DDGImageSearchPlugin
|
from plugins.ddg_image_search import DDGImageSearchPlugin
|
||||||
from plugins.ddg_translate import DDGTranslatePlugin
|
from plugins.ddg_translate import DDGTranslatePlugin
|
||||||
from plugins.spotify import SpotifyPlugin
|
from plugins.spotify import SpotifyPlugin
|
||||||
@@ -26,6 +28,8 @@ class PluginManager:
|
|||||||
'ddg_image_search': DDGImageSearchPlugin,
|
'ddg_image_search': DDGImageSearchPlugin,
|
||||||
'spotify': SpotifyPlugin,
|
'spotify': SpotifyPlugin,
|
||||||
'worldtimeapi': WorldTimeApiPlugin,
|
'worldtimeapi': WorldTimeApiPlugin,
|
||||||
|
'youtube_audio_extractor': YouTubeAudioExtractorPlugin,
|
||||||
|
'dice': DicePlugin,
|
||||||
}
|
}
|
||||||
self.plugins = [plugin_mapping[plugin]() for plugin in enabled_plugins if plugin in plugin_mapping]
|
self.plugins = [plugin_mapping[plugin]() for plugin in enabled_plugins if plugin in plugin_mapping]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
@@ -10,6 +12,9 @@ class DDGImageSearchPlugin(Plugin):
|
|||||||
"""
|
"""
|
||||||
A plugin to search images and GIFs for a given query, using DuckDuckGo
|
A plugin to search images and GIFs for a given query, using DuckDuckGo
|
||||||
"""
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.safesearch = os.getenv('DUCKDUCKGO_SAFESEARCH', 'moderate')
|
||||||
|
|
||||||
def get_source_name(self) -> str:
|
def get_source_name(self) -> str:
|
||||||
return "DuckDuckGo Images"
|
return "DuckDuckGo Images"
|
||||||
|
|
||||||
@@ -25,19 +30,45 @@ class DDGImageSearchPlugin(Plugin):
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["photo", "gif"],
|
"enum": ["photo", "gif"],
|
||||||
"description": "The type of image to search for. Default to `photo` if not specified",
|
"description": "The type of image to search for. Default to `photo` if not specified",
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ['xa-ar', 'xa-en', 'ar-es', 'au-en', 'at-de', 'be-fr', 'be-nl', 'br-pt', 'bg-bg',
|
||||||
|
'ca-en', 'ca-fr', 'ct-ca', 'cl-es', 'cn-zh', 'co-es', 'hr-hr', 'cz-cs', 'dk-da',
|
||||||
|
'ee-et', 'fi-fi', 'fr-fr', 'de-de', 'gr-el', 'hk-tzh', 'hu-hu', 'in-en', 'id-id',
|
||||||
|
'id-en', 'ie-en', 'il-he', 'it-it', 'jp-jp', 'kr-kr', 'lv-lv', 'lt-lt', 'xl-es',
|
||||||
|
'my-ms', 'my-en', 'mx-es', 'nl-nl', 'nz-en', 'no-no', 'pe-es', 'ph-en', 'ph-tl',
|
||||||
|
'pl-pl', 'pt-pt', 'ro-ro', 'ru-ru', 'sg-en', 'sk-sk', 'sl-sl', 'za-en', 'es-es',
|
||||||
|
'se-sv', 'ch-de', 'ch-fr', 'ch-it', 'tw-tzh', 'th-th', 'tr-tr', 'ua-uk', 'uk-en',
|
||||||
|
'us-en', 'ue-es', 've-es', 'vn-vi', 'wt-wt'],
|
||||||
|
"description": "The region to use for the search. Infer this from the language used for the"
|
||||||
|
"query. Default to `wt-wt` if not specified",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["query", "type"],
|
"required": ["query", "type", "region"],
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
async def execute(self, function_name, **kwargs) -> Dict:
|
async def execute(self, function_name, **kwargs) -> Dict:
|
||||||
with DDGS() as ddgs:
|
with DDGS() as ddgs:
|
||||||
|
image_type = kwargs.get('type', 'photo')
|
||||||
ddgs_images_gen = ddgs.images(
|
ddgs_images_gen = ddgs.images(
|
||||||
kwargs['query'],
|
kwargs['query'],
|
||||||
region="wt-wt",
|
region=kwargs.get('region', 'wt-wt'),
|
||||||
safesearch='off',
|
safesearch=self.safesearch,
|
||||||
type_image=kwargs.get('type', 'photo'),
|
type_image=image_type,
|
||||||
)
|
)
|
||||||
results = list(islice(ddgs_images_gen, 1))
|
results = list(islice(ddgs_images_gen, 10))
|
||||||
return {"result": results[0]["image"]}
|
if not results or len(results) == 0:
|
||||||
|
return {"result": "No results found"}
|
||||||
|
|
||||||
|
# Shuffle the results to avoid always returning the same image
|
||||||
|
random.shuffle(results)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'direct_result': {
|
||||||
|
'kind': image_type,
|
||||||
|
'format': 'url',
|
||||||
|
'value': results[0]['image']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
@@ -10,6 +11,8 @@ class DDGWebSearchPlugin(Plugin):
|
|||||||
"""
|
"""
|
||||||
A plugin to search the web for a given query, using DuckDuckGo
|
A plugin to search the web for a given query, using DuckDuckGo
|
||||||
"""
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
self.safesearch = os.getenv('DUCKDUCKGO_SAFESEARCH', 'moderate')
|
||||||
|
|
||||||
def get_source_name(self) -> str:
|
def get_source_name(self) -> str:
|
||||||
return "DuckDuckGo"
|
return "DuckDuckGo"
|
||||||
@@ -24,9 +27,22 @@ class DDGWebSearchPlugin(Plugin):
|
|||||||
"query": {
|
"query": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "the user query"
|
"description": "the user query"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ['xa-ar', 'xa-en', 'ar-es', 'au-en', 'at-de', 'be-fr', 'be-nl', 'br-pt', 'bg-bg',
|
||||||
|
'ca-en', 'ca-fr', 'ct-ca', 'cl-es', 'cn-zh', 'co-es', 'hr-hr', 'cz-cs', 'dk-da',
|
||||||
|
'ee-et', 'fi-fi', 'fr-fr', 'de-de', 'gr-el', 'hk-tzh', 'hu-hu', 'in-en', 'id-id',
|
||||||
|
'id-en', 'ie-en', 'il-he', 'it-it', 'jp-jp', 'kr-kr', 'lv-lv', 'lt-lt', 'xl-es',
|
||||||
|
'my-ms', 'my-en', 'mx-es', 'nl-nl', 'nz-en', 'no-no', 'pe-es', 'ph-en', 'ph-tl',
|
||||||
|
'pl-pl', 'pt-pt', 'ro-ro', 'ru-ru', 'sg-en', 'sk-sk', 'sl-sl', 'za-en', 'es-es',
|
||||||
|
'se-sv', 'ch-de', 'ch-fr', 'ch-it', 'tw-tzh', 'th-th', 'tr-tr', 'ua-uk', 'uk-en',
|
||||||
|
'us-en', 'ue-es', 've-es', 'vn-vi', 'wt-wt'],
|
||||||
|
"description": "The region to use for the search. Infer this from the language used for the"
|
||||||
|
"query. Default to `wt-wt` if not specified",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["query"],
|
"required": ["query", "region"],
|
||||||
},
|
},
|
||||||
}]
|
}]
|
||||||
|
|
||||||
@@ -34,8 +50,8 @@ class DDGWebSearchPlugin(Plugin):
|
|||||||
with DDGS() as ddgs:
|
with DDGS() as ddgs:
|
||||||
ddgs_gen = ddgs.text(
|
ddgs_gen = ddgs.text(
|
||||||
kwargs['query'],
|
kwargs['query'],
|
||||||
region='wt-wt',
|
region=kwargs.get('region', 'wt-wt'),
|
||||||
safesearch='off'
|
safesearch=self.safesearch
|
||||||
)
|
)
|
||||||
results = list(islice(ddgs_gen, 3))
|
results = list(islice(ddgs_gen, 3))
|
||||||
|
|
||||||
|
|||||||
38
bot/plugins/dice.py
Normal file
38
bot/plugins/dice.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from .plugin import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
class DicePlugin(Plugin):
|
||||||
|
"""
|
||||||
|
A plugin to send a die in the chat
|
||||||
|
"""
|
||||||
|
def get_source_name(self) -> str:
|
||||||
|
return "Dice"
|
||||||
|
|
||||||
|
def get_spec(self) -> [Dict]:
|
||||||
|
return [{
|
||||||
|
"name": "send_dice",
|
||||||
|
"description": "Send a dice in the chat, with a random number between 1 and 6",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"emoji": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["🎲", "🎯", "🏀", "⚽", "🎳", "🎰"],
|
||||||
|
"description": "Emoji on which the dice throw animation is based."
|
||||||
|
"Dice can have values 1-6 for “🎲”, “🎯” and “🎳”, values 1-5 for “🏀” "
|
||||||
|
"and “⚽”, and values 1-64 for “🎰”. Defaults to “🎲”.",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
async def execute(self, function_name, **kwargs) -> Dict:
|
||||||
|
return {
|
||||||
|
'direct_result': {
|
||||||
|
'kind': 'dice',
|
||||||
|
'format': 'dice',
|
||||||
|
'value': kwargs.get('emoji', '🎲')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ class WorldTimeApiPlugin(Plugin):
|
|||||||
"""
|
"""
|
||||||
A plugin to get the current time from a given timezone, using WorldTimeAPI
|
A plugin to get the current time from a given timezone, using WorldTimeAPI
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
default_timezone = os.getenv('WORLDTIME_DEFAULT_TIMEZONE')
|
default_timezone = os.getenv('WORLDTIME_DEFAULT_TIMEZONE')
|
||||||
if not default_timezone:
|
if not default_timezone:
|
||||||
|
|||||||
44
bot/plugins/youtube_audio_extractor.py
Normal file
44
bot/plugins/youtube_audio_extractor.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from pytube import YouTube
|
||||||
|
|
||||||
|
from .plugin import Plugin
|
||||||
|
|
||||||
|
|
||||||
|
class YouTubeAudioExtractorPlugin(Plugin):
|
||||||
|
"""
|
||||||
|
A plugin to extract audio from a YouTube video
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_source_name(self) -> str:
|
||||||
|
return "YouTube Audio Extractor"
|
||||||
|
|
||||||
|
def get_spec(self) -> [Dict]:
|
||||||
|
return [{
|
||||||
|
"name": "extract_youtube_audio",
|
||||||
|
"description": "Extract audio from a YouTube video",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"youtube_link": {"type": "string", "description": "YouTube video link to extract audio from"}
|
||||||
|
},
|
||||||
|
"required": ["youtube_link"],
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
|
||||||
|
async def execute(self, function_name, **kwargs) -> Dict:
|
||||||
|
link = kwargs['youtube_link']
|
||||||
|
try:
|
||||||
|
video = YouTube(link)
|
||||||
|
audio = video.streams.filter(only_audio=True, file_extension='mp4').first()
|
||||||
|
output = video.title + '.mp4'
|
||||||
|
audio.download(filename=output)
|
||||||
|
return {
|
||||||
|
'direct_result': {
|
||||||
|
'kind': 'file',
|
||||||
|
'format': 'path',
|
||||||
|
'value': output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
except:
|
||||||
|
return {'result': 'Failed to extract audio'}
|
||||||
@@ -16,7 +16,8 @@ from pydub import AudioSegment
|
|||||||
|
|
||||||
from utils import is_group_chat, get_thread_id, message_text, wrap_with_indicator, split_into_chunks, \
|
from utils import is_group_chat, get_thread_id, message_text, wrap_with_indicator, split_into_chunks, \
|
||||||
edit_message_with_retry, get_stream_cutoff_values, is_allowed, get_remaining_budget, is_admin, is_within_budget, \
|
edit_message_with_retry, get_stream_cutoff_values, is_allowed, get_remaining_budget, is_admin, is_within_budget, \
|
||||||
get_reply_to_message_id, add_chat_request_to_usage_tracker, error_handler
|
get_reply_to_message_id, add_chat_request_to_usage_tracker, error_handler, is_direct_result, handle_direct_result, \
|
||||||
|
cleanup_intermediate_files
|
||||||
from openai_helper import OpenAIHelper, localized_text
|
from openai_helper import OpenAIHelper, localized_text
|
||||||
from usage_tracker import UsageTracker
|
from usage_tracker import UsageTracker
|
||||||
|
|
||||||
@@ -401,6 +402,9 @@ class ChatGPTTelegramBot:
|
|||||||
stream_chunk = 0
|
stream_chunk = 0
|
||||||
|
|
||||||
async for content, tokens in stream_response:
|
async for content, tokens in stream_response:
|
||||||
|
if is_direct_result(content):
|
||||||
|
return await handle_direct_result(self.config, update, content)
|
||||||
|
|
||||||
if len(content.strip()) == 0:
|
if len(content.strip()) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -472,6 +476,9 @@ class ChatGPTTelegramBot:
|
|||||||
nonlocal total_tokens
|
nonlocal total_tokens
|
||||||
response, total_tokens = await self.openai.get_chat_response(chat_id=chat_id, query=prompt)
|
response, total_tokens = await self.openai.get_chat_response(chat_id=chat_id, query=prompt)
|
||||||
|
|
||||||
|
if is_direct_result(response):
|
||||||
|
return await handle_direct_result(self.config, update, response)
|
||||||
|
|
||||||
# Split into chunks of 4096 characters (Telegram's message limit)
|
# Split into chunks of 4096 characters (Telegram's message limit)
|
||||||
chunks = split_into_chunks(response)
|
chunks = split_into_chunks(response)
|
||||||
|
|
||||||
@@ -585,12 +592,21 @@ class ChatGPTTelegramBot:
|
|||||||
is_inline=True)
|
is_inline=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
unavailable_message = localized_text("function_unavailable_in_inline_mode", bot_language)
|
||||||
if self.config['stream']:
|
if self.config['stream']:
|
||||||
stream_response = self.openai.get_chat_response_stream(chat_id=user_id, query=query)
|
stream_response = self.openai.get_chat_response_stream(chat_id=user_id, query=query)
|
||||||
i = 0
|
i = 0
|
||||||
prev = ''
|
prev = ''
|
||||||
backoff = 0
|
backoff = 0
|
||||||
async for content, tokens in stream_response:
|
async for content, tokens in stream_response:
|
||||||
|
if is_direct_result(content):
|
||||||
|
cleanup_intermediate_files(content)
|
||||||
|
await edit_message_with_retry(context, chat_id=None,
|
||||||
|
message_id=inline_message_id,
|
||||||
|
text=f'{query}\n\n_{answer_tr}:_\n{unavailable_message}',
|
||||||
|
is_inline=True)
|
||||||
|
return
|
||||||
|
|
||||||
if len(content.strip()) == 0:
|
if len(content.strip()) == 0:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -648,6 +664,14 @@ class ChatGPTTelegramBot:
|
|||||||
logging.info(f'Generating response for inline query by {name}')
|
logging.info(f'Generating response for inline query by {name}')
|
||||||
response, total_tokens = await self.openai.get_chat_response(chat_id=user_id, query=query)
|
response, total_tokens = await self.openai.get_chat_response(chat_id=user_id, query=query)
|
||||||
|
|
||||||
|
if is_direct_result(response):
|
||||||
|
cleanup_intermediate_files(response)
|
||||||
|
await edit_message_with_retry(context, chat_id=None,
|
||||||
|
message_id=inline_message_id,
|
||||||
|
text=f'{query}\n\n_{answer_tr}:_\n{unavailable_message}',
|
||||||
|
is_inline=True)
|
||||||
|
return
|
||||||
|
|
||||||
text_content = f'{query}\n\n_{answer_tr}:_\n{response}'
|
text_content = f'{query}\n\n_{answer_tr}:_\n{response}'
|
||||||
|
|
||||||
# We only want to send the first 4096 characters. No chunking allowed in inline mode.
|
# We only want to send the first 4096 characters. No chunking allowed in inline mode.
|
||||||
|
|||||||
69
bot/utils.py
69
bot/utils.py
@@ -2,7 +2,9 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import itertools
|
import itertools
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
import telegram
|
import telegram
|
||||||
from telegram import Message, MessageEntity, Update, ChatMember, constants
|
from telegram import Message, MessageEntity, Update, ChatMember, constants
|
||||||
@@ -173,6 +175,7 @@ async def is_allowed(config, update: Update, context: CallbackContext, is_inline
|
|||||||
f'(id: {user_id}) are not allowed')
|
f'(id: {user_id}) are not allowed')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_admin(config, user_id: int, log_no_admin=False) -> bool:
|
def is_admin(config, user_id: int, log_no_admin=False) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks if the user is the admin of the bot.
|
Checks if the user is the admin of the bot.
|
||||||
@@ -284,6 +287,9 @@ def add_chat_request_to_usage_tracker(usage, config, user_id, used_tokens):
|
|||||||
:param used_tokens: The number of tokens used
|
:param used_tokens: The number of tokens used
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
|
if int(used_tokens) == 0:
|
||||||
|
logging.warning('No tokens used. Not adding chat request to usage tracker.')
|
||||||
|
return
|
||||||
# add chat request to users usage tracker
|
# add chat request to users usage tracker
|
||||||
usage[user_id].add_chat_tokens(used_tokens, config['token_price'])
|
usage[user_id].add_chat_tokens(used_tokens, config['token_price'])
|
||||||
# add guest chat request to guest usage tracker
|
# add guest chat request to guest usage tracker
|
||||||
@@ -305,3 +311,66 @@ def get_reply_to_message_id(config, update: Update):
|
|||||||
if config['enable_quoting'] or is_group_chat(update):
|
if config['enable_quoting'] or is_group_chat(update):
|
||||||
return update.message.message_id
|
return update.message.message_id
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_direct_result(response: any) -> bool:
|
||||||
|
"""
|
||||||
|
Checks if the dict contains a direct result that can be sent directly to the user
|
||||||
|
:param response: The response value
|
||||||
|
:return: Boolean indicating if the result is a direct result
|
||||||
|
"""
|
||||||
|
if type(response) is not dict:
|
||||||
|
try:
|
||||||
|
json_response = json.loads(response)
|
||||||
|
return json_response.get('direct_result', False)
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return response.get('direct_result', False)
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_direct_result(config, update: Update, response: any):
|
||||||
|
"""
|
||||||
|
Handles a direct result from a plugin
|
||||||
|
"""
|
||||||
|
if type(response) is not dict:
|
||||||
|
response = json.loads(response)
|
||||||
|
|
||||||
|
result = response['direct_result']
|
||||||
|
kind = result['kind']
|
||||||
|
format = result['format']
|
||||||
|
value = result['value']
|
||||||
|
|
||||||
|
common_args = {
|
||||||
|
'message_thread_id': get_thread_id(update),
|
||||||
|
'reply_to_message_id': get_reply_to_message_id(config, update),
|
||||||
|
}
|
||||||
|
|
||||||
|
if kind == 'photo':
|
||||||
|
if format == 'url':
|
||||||
|
await update.effective_message.reply_photo(**common_args, photo=value)
|
||||||
|
elif kind == 'gif':
|
||||||
|
if format == 'url':
|
||||||
|
await update.effective_message.reply_document(**common_args, document=value)
|
||||||
|
elif kind == 'file':
|
||||||
|
if format == 'path':
|
||||||
|
await update.effective_message.reply_document(**common_args, document=open(value, 'rb'))
|
||||||
|
cleanup_intermediate_files(response)
|
||||||
|
elif kind == 'dice':
|
||||||
|
await update.effective_message.reply_dice(**common_args, emoji=value)
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_intermediate_files(response: any):
|
||||||
|
"""
|
||||||
|
Deletes intermediate files created by plugins
|
||||||
|
"""
|
||||||
|
if type(response) is not dict:
|
||||||
|
response = json.loads(response)
|
||||||
|
|
||||||
|
result = response['direct_result']
|
||||||
|
kind = result['kind']
|
||||||
|
format = result['format']
|
||||||
|
value = result['value']
|
||||||
|
|
||||||
|
if kind == 'file' and format == 'path':
|
||||||
|
os.remove(value)
|
||||||
@@ -7,4 +7,5 @@ requests~=2.31.0
|
|||||||
tenacity==8.2.2
|
tenacity==8.2.2
|
||||||
wolframalpha==5.0.0
|
wolframalpha==5.0.0
|
||||||
duckduckgo_search==3.8.3
|
duckduckgo_search==3.8.3
|
||||||
spotipy==2.23.0
|
spotipy==2.23.0
|
||||||
|
pytube==15.0.0
|
||||||
@@ -39,7 +39,8 @@
|
|||||||
"try_again":"Please try again in a while",
|
"try_again":"Please try again in a while",
|
||||||
"answer_with_chatgpt":"Answer with ChatGPT",
|
"answer_with_chatgpt":"Answer with ChatGPT",
|
||||||
"ask_chatgpt":"Ask ChatGPT",
|
"ask_chatgpt":"Ask ChatGPT",
|
||||||
"loading":"Loading..."
|
"loading":"Loading...",
|
||||||
|
"function_unavailable_in_inline_mode": "This function is unavailable in inline mode"
|
||||||
},
|
},
|
||||||
"es": {
|
"es": {
|
||||||
"help_description":"Muestra el mensaje de ayuda",
|
"help_description":"Muestra el mensaje de ayuda",
|
||||||
@@ -81,9 +82,10 @@
|
|||||||
"try_again":"Por favor, inténtalo de nuevo más tarde",
|
"try_again":"Por favor, inténtalo de nuevo más tarde",
|
||||||
"answer_with_chatgpt":"Responder con ChatGPT",
|
"answer_with_chatgpt":"Responder con ChatGPT",
|
||||||
"ask_chatgpt":"Preguntar a ChatGPT",
|
"ask_chatgpt":"Preguntar a ChatGPT",
|
||||||
"loading":"Cargando..."
|
"loading":"Cargando...",
|
||||||
|
"function_unavailable_in_inline_mode": "Esta función no está disponible en el modo inline"
|
||||||
},
|
},
|
||||||
"pt-br":{
|
"pt-br": {
|
||||||
"help_description": "Mostra a mensagem de ajuda",
|
"help_description": "Mostra a mensagem de ajuda",
|
||||||
"reset_description": "Redefine a conversa. Opcionalmente, passe instruções de alto nível (por exemplo, /reset Você é um assistente útil)",
|
"reset_description": "Redefine a conversa. Opcionalmente, passe instruções de alto nível (por exemplo, /reset Você é um assistente útil)",
|
||||||
"image_description": "Gera uma imagem a partir do prompt (por exemplo, /image gato)",
|
"image_description": "Gera uma imagem a partir do prompt (por exemplo, /image gato)",
|
||||||
@@ -123,7 +125,9 @@
|
|||||||
"try_again": "Por favor, tente novamente mais tarde",
|
"try_again": "Por favor, tente novamente mais tarde",
|
||||||
"answer_with_chatgpt": "Responder com ChatGPT",
|
"answer_with_chatgpt": "Responder com ChatGPT",
|
||||||
"ask_chatgpt": "Perguntar ao ChatGPT",
|
"ask_chatgpt": "Perguntar ao ChatGPT",
|
||||||
"loading": "Carregando..."},
|
"loading": "Carregando...",
|
||||||
|
"function_unavailable_in_inline_mode": "Esta função não está disponível no modo inline"
|
||||||
|
},
|
||||||
"de": {
|
"de": {
|
||||||
"help_description":"Zeige die Hilfenachricht",
|
"help_description":"Zeige die Hilfenachricht",
|
||||||
"reset_description":"Setze die Konversation zurück. Optionale Eingabe einer grundlegenden Anweisung (z.B. /reset Du bist ein hilfreicher Assistent)",
|
"reset_description":"Setze die Konversation zurück. Optionale Eingabe einer grundlegenden Anweisung (z.B. /reset Du bist ein hilfreicher Assistent)",
|
||||||
@@ -164,7 +168,8 @@
|
|||||||
"try_again":"Bitte versuche es später erneut",
|
"try_again":"Bitte versuche es später erneut",
|
||||||
"answer_with_chatgpt":"Antworte mit ChatGPT",
|
"answer_with_chatgpt":"Antworte mit ChatGPT",
|
||||||
"ask_chatgpt":"Frage ChatGPT",
|
"ask_chatgpt":"Frage ChatGPT",
|
||||||
"loading":"Lade..."
|
"loading":"Lade...",
|
||||||
|
"function_unavailable_in_inline_mode": "Diese Funktion ist im Inline-Modus nicht verfügbar"
|
||||||
},
|
},
|
||||||
"fi": {
|
"fi": {
|
||||||
"help_description":"Näytä ohjeet",
|
"help_description":"Näytä ohjeet",
|
||||||
@@ -206,7 +211,8 @@
|
|||||||
"try_again":"Yritä myöhemmin uudelleen",
|
"try_again":"Yritä myöhemmin uudelleen",
|
||||||
"answer_with_chatgpt":"Vastaa ChatGPT:n avulla",
|
"answer_with_chatgpt":"Vastaa ChatGPT:n avulla",
|
||||||
"ask_chatgpt":"Kysy ChatGPT:ltä",
|
"ask_chatgpt":"Kysy ChatGPT:ltä",
|
||||||
"loading":"Lataa..."
|
"loading":"Lataa...",
|
||||||
|
"function_unavailable_in_inline_mode": "Tämä toiminto ei ole käytettävissä sisäisessä tilassa"
|
||||||
},
|
},
|
||||||
"ru": {
|
"ru": {
|
||||||
"help_description":"Показать справочное сообщение",
|
"help_description":"Показать справочное сообщение",
|
||||||
@@ -248,7 +254,8 @@
|
|||||||
"try_again":"Пожалуйста, повторите попытку позже",
|
"try_again":"Пожалуйста, повторите попытку позже",
|
||||||
"answer_with_chatgpt":"Ответить с помощью ChatGPT",
|
"answer_with_chatgpt":"Ответить с помощью ChatGPT",
|
||||||
"ask_chatgpt":"Спросить ChatGPT",
|
"ask_chatgpt":"Спросить ChatGPT",
|
||||||
"loading":"Загрузка..."
|
"loading":"Загрузка...",
|
||||||
|
"function_unavailable_in_inline_mode": "Эта функция недоступна в режиме inline"
|
||||||
},
|
},
|
||||||
"tr": {
|
"tr": {
|
||||||
"help_description":"Yardım mesajını göster",
|
"help_description":"Yardım mesajını göster",
|
||||||
@@ -290,7 +297,8 @@
|
|||||||
"try_again":"Lütfen birazdan tekrar deneyiniz",
|
"try_again":"Lütfen birazdan tekrar deneyiniz",
|
||||||
"answer_with_chatgpt":"ChatGPT ile cevapla",
|
"answer_with_chatgpt":"ChatGPT ile cevapla",
|
||||||
"ask_chatgpt":"ChatGPT'ye sor",
|
"ask_chatgpt":"ChatGPT'ye sor",
|
||||||
"loading":"Yükleniyor..."
|
"loading":"Yükleniyor...",
|
||||||
|
"function_unavailable_in_inline_mode": "Bu işlev inline modda kullanılamaz"
|
||||||
},
|
},
|
||||||
"it": {
|
"it": {
|
||||||
"help_description":"Mostra il messaggio di aiuto",
|
"help_description":"Mostra il messaggio di aiuto",
|
||||||
@@ -332,7 +340,8 @@
|
|||||||
"try_again":"Riprova più tardi",
|
"try_again":"Riprova più tardi",
|
||||||
"answer_with_chatgpt":"Rispondi con ChatGPT",
|
"answer_with_chatgpt":"Rispondi con ChatGPT",
|
||||||
"ask_chatgpt":"Chiedi a ChatGPT",
|
"ask_chatgpt":"Chiedi a ChatGPT",
|
||||||
"loading":"Carico..."
|
"loading":"Carico...",
|
||||||
|
"function_unavailable_in_inline_mode": "Questa funzione non è disponibile in modalità inline"
|
||||||
},
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"help_description": "Menampilkan pesan bantuan",
|
"help_description": "Menampilkan pesan bantuan",
|
||||||
@@ -374,7 +383,8 @@
|
|||||||
"try_again": "Silakan coba lagi nanti",
|
"try_again": "Silakan coba lagi nanti",
|
||||||
"answer_with_chatgpt": "Jawaban dengan ChatGPT",
|
"answer_with_chatgpt": "Jawaban dengan ChatGPT",
|
||||||
"ask_chatgpt": "Tanya ChatGPT",
|
"ask_chatgpt": "Tanya ChatGPT",
|
||||||
"loading": "Sedang memuat..."
|
"loading": "Sedang memuat...",
|
||||||
|
"function_unavailable_in_inline_mode": "Fungsi ini tidak tersedia dalam mode inline"
|
||||||
},
|
},
|
||||||
"nl": {
|
"nl": {
|
||||||
"help_description":"Toon uitleg",
|
"help_description":"Toon uitleg",
|
||||||
@@ -416,7 +426,8 @@
|
|||||||
"try_again":"Probeer het a.u.b. later opnieuw",
|
"try_again":"Probeer het a.u.b. later opnieuw",
|
||||||
"answer_with_chatgpt":"Antwoord met ChatGPT",
|
"answer_with_chatgpt":"Antwoord met ChatGPT",
|
||||||
"ask_chatgpt":"Vraag ChatGPT",
|
"ask_chatgpt":"Vraag ChatGPT",
|
||||||
"loading":"Laden..."
|
"loading":"Laden...",
|
||||||
|
"function_unavailable_in_inline_mode": "Deze functie is niet beschikbaar in de inline modus"
|
||||||
},
|
},
|
||||||
"zh-cn": {
|
"zh-cn": {
|
||||||
"help_description":"显示帮助信息",
|
"help_description":"显示帮助信息",
|
||||||
@@ -458,7 +469,8 @@
|
|||||||
"try_again":"请稍后再试",
|
"try_again":"请稍后再试",
|
||||||
"answer_with_chatgpt":"使用ChatGPT回答",
|
"answer_with_chatgpt":"使用ChatGPT回答",
|
||||||
"ask_chatgpt":"询问ChatGPT",
|
"ask_chatgpt":"询问ChatGPT",
|
||||||
"loading":"载入中..."
|
"loading":"载入中...",
|
||||||
|
"function_unavailable_in_inline_mode": "此功能在内联模式下不可用"
|
||||||
},
|
},
|
||||||
"zh-tw": {
|
"zh-tw": {
|
||||||
"help_description":"顯示幫助訊息",
|
"help_description":"顯示幫助訊息",
|
||||||
@@ -500,7 +512,8 @@
|
|||||||
"try_again":"請稍後重試",
|
"try_again":"請稍後重試",
|
||||||
"answer_with_chatgpt":"使用 ChatGPT 回答",
|
"answer_with_chatgpt":"使用 ChatGPT 回答",
|
||||||
"ask_chatgpt":"詢問 ChatGPT",
|
"ask_chatgpt":"詢問 ChatGPT",
|
||||||
"loading":"載入中…"
|
"loading":"載入中…",
|
||||||
|
"function_unavailable_in_inline_mode": "此功能在內嵌模式下不可用"
|
||||||
},
|
},
|
||||||
"vi": {
|
"vi": {
|
||||||
"help_description":"Hiển thị trợ giúp",
|
"help_description":"Hiển thị trợ giúp",
|
||||||
@@ -542,7 +555,8 @@
|
|||||||
"try_again":"Vui lòng thử lại sau một lúc",
|
"try_again":"Vui lòng thử lại sau một lúc",
|
||||||
"answer_with_chatgpt":"Trả lời với ChatGPT",
|
"answer_with_chatgpt":"Trả lời với ChatGPT",
|
||||||
"ask_chatgpt":"Hỏi ChatGPT",
|
"ask_chatgpt":"Hỏi ChatGPT",
|
||||||
"loading":"Đang tải..."
|
"loading":"Đang tải...",
|
||||||
|
"function_unavailable_in_inline_mode": "Chức năng này không khả dụng trong chế độ nội tuyến"
|
||||||
},
|
},
|
||||||
"fa": {
|
"fa": {
|
||||||
"help_description":"نمایش پیغام راهنما",
|
"help_description":"نمایش پیغام راهنما",
|
||||||
@@ -584,7 +598,8 @@
|
|||||||
"try_again":"لطفا بعد از مدتی دوباره امتحان کنید",
|
"try_again":"لطفا بعد از مدتی دوباره امتحان کنید",
|
||||||
"answer_with_chatgpt":"با ChatGPT پاسخ دهید",
|
"answer_with_chatgpt":"با ChatGPT پاسخ دهید",
|
||||||
"ask_chatgpt":"از ChatGPT بپرسید",
|
"ask_chatgpt":"از ChatGPT بپرسید",
|
||||||
"loading":"در حال بارگذاری..."
|
"loading":"در حال بارگذاری...",
|
||||||
|
"function_unavailable_in_inline_mode": "این عملکرد در حالت آنلاین در دسترس نیست"
|
||||||
},
|
},
|
||||||
"uk": {
|
"uk": {
|
||||||
"help_description":"Показати повідомлення допомоги",
|
"help_description":"Показати повідомлення допомоги",
|
||||||
@@ -626,6 +641,7 @@
|
|||||||
"try_again":"Будь ласка, спробуйте знову через деякий час",
|
"try_again":"Будь ласка, спробуйте знову через деякий час",
|
||||||
"answer_with_chatgpt":"Відповідь за допомогою ChatGPT",
|
"answer_with_chatgpt":"Відповідь за допомогою ChatGPT",
|
||||||
"ask_chatgpt":"Запитати ChatGPT",
|
"ask_chatgpt":"Запитати ChatGPT",
|
||||||
"loading":"Завантаження..."
|
"loading":"Завантаження...",
|
||||||
|
"function_unavailable_in_inline_mode": "Ця функція недоступна в режимі Inline"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user