main
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3import asyncio
4import os
5from pathlib import Path
6
7from cacheout import Cache
8from cutword import Cutter
9
10# init some global instances
11cache = Cache(ttl=0, maxsize=2048)
12semaphore = asyncio.Semaphore(8) # max 8 concurrent downloads
13cutter = Cutter()
14
15DOWNLOAD_DIR = os.getenv("DOWNLOAD_DIR", Path(__file__).parent.joinpath("downloads").as_posix())
16FONTS_DIR = os.getenv("FONTS_DIR", Path(DOWNLOAD_DIR).joinpath("fonts").as_posix())
17FILE_SERVER = os.getenv("FILE_SERVER", "") # expose the download dir to internet (optional). for example: https://server.com/dir
18TZ = os.getenv("TZ", "Asia/Shanghai")
19DEVICE_NAME = os.getenv("DEVICE_NAME", "BennyBot")
20REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "60")) # seconds
21TEXT_LENGTH = int(os.getenv("TEXT_LENGTH", "4096")) # Maximum length of text message
22CAPTION_LENGTH = int(os.getenv("CAPTION_LENGTH", "1024")) # 4096 for Premium user
23MAX_FILE_BYTES = int(os.getenv("MAX_FILE_BYTES", "2000")) * 1024 * 1024 # 4000 MB for Premium user
24MAX_MESSAGE_RETRIEVED = int(os.getenv("MAX_MESSAGE_RETRIEVED", "1000000")) # Maximum number of messages to retrieve
25MAX_MESSAGE_SUMMARY = int(os.getenv("MAX_MESSAGE_SUMMARY", "9999")) # Maximum number of messages to summay
26READING_SPEED = int(os.getenv("READING_SPEED", "600")) # words per minute
27DAILY_MESSAGES = os.getenv("DAILY_MESSAGES", "{}") # Useful for daily checkin for some services. Should be a json string: '{"chat-1": "msg-1", "chat-2": "msg-2"}'
28# For ytdlp downloaded video, re-encoding to H264 format. This set the max file size for re-encoding. Default: 1PB
29YTDLP_RE_ENCODING_MAX_FILE_BYTES = int(os.getenv("YTDLP_RE_ENCODING_MAX_FILE_BYTES", "1125899906842624"))
30# ytdlp max allowed file bytes. Default: 1PB (Set this if the VPS disk space is limited)
31YTDLP_DOWNLOAD_MAX_FILE_BYTES = int(os.getenv("YTDLP_DOWNLOAD_MAX_FILE_BYTES", "1125899906842624"))
32TELEGRAM_UA = os.getenv("TELEGRAM_UA", "TelegramBot (like TwitterBot)")
33NUM_YOUTUBE_SEARCH_RESULTS = int(os.getenv("NUM_YOUTUBE_SEARCH_RESULTS", "10")) # Number of youtube search results
34NUM_GOOGLE_SEARCH_RESULTS = int(os.getenv("NUM_GOOGLE_SEARCH_RESULTS", "10")) # Number of google search results
35GOOGLE_SEARCH_GL = os.getenv("GOOGLE_SEARCH_GL", "cn") # "gl" parameter (Geolocation)
36CLEAN_OLD_FILES_OLDER_THAN_SECONDS = int(os.getenv("CLEAN_OLD_FILES_OLDER_THAN_SECONDS", "7200"))
37ENABLE_CRONTAB = os.getenv("ENABLE_CRONTAB", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
38
39
40class PREFIX:
41 SOCIAL_MEDIA = os.getenv("PREFIX_SOCIAL_MEDIA", "/benny, /dl, !dl")
42 AI_SUMMARY = os.getenv("PREFIX_AI_SUMMARY", "/summary").lower()
43 AI_TEXT_GENERATION = os.getenv("PREFIX_AI_TEXT_GENERATION", "/ai").lower()
44 AI_IMG_GENERATION = os.getenv("PREFIX_AI_IMG_GENERATION", "/img").lower()
45 AI_VIDEO_GENERATION = os.getenv("PREFIX_AI_VIDEO_GENERATION", "/gvid").lower()
46 ASR = os.getenv("PREFIX_ASR", "/asr").lower()
47 AUDIO = os.getenv("PREFIX_AUDIO", "/audio").lower()
48 CONVERT = os.getenv("PREFIX_CONVERT", "/convert").lower() # convert image file to photo
49 SUBTITLE = os.getenv("PREFIX_SUBTITLE", "/subtitle, /sub").lower()
50 WGET = os.getenv("PREFIX_WGET", "/wget, /curl").lower()
51 OCR = os.getenv("PREFIX_OCR", "/ocr").lower()
52 PRICE = os.getenv("PREFIX_PRICE", "/price").lower() # unify crypto, stock
53 CRYPTO = os.getenv("PREFIX_CRYPTO", "/crypto").lower() # crypto only
54 STOCK = os.getenv("PREFIX_STOCK", "/stock").lower() # stock only
55 CHAT_SUMMARY = os.getenv("PREFIX_CHAT_SUMMARY", "/chatsum").lower()
56 COMBINATION = os.getenv("PREFIX_COMBINATION", "/combine").lower()
57 VOICE = os.getenv("PREFIX_VOICE", "/voice").lower()
58 SEARCH_YOUTUBE = os.getenv("PREFIX_SEARCH_YOUTUBE", "/youtube, /ytb").lower()
59 SEARCH_GOOGLE = os.getenv("PREFIX_SEARCH_GOOGLE", "/google").lower()
60 DANMU = os.getenv("PREFIX_DANMU", "/danmu").lower()
61 FAYAN = os.getenv("PREFIX_FAYAN", "/fa").lower()
62 HISTORY = "/history, /hist"
63 TTS = os.getenv("PREFIX_TTS", "/tts").lower()
64 CONVERT_TO_TC = os.getenv("PREFIX_CONVERT_TO_TC", "/tc, /tw").lower()
65 CONVERT_TO_SC = os.getenv("PREFIX_CONVERT_TO_SC", "/sc, /cn").lower()
66 QUOTLY = os.getenv("PREFIX_QUOTLY", "/quote").lower()
67 TMDB = os.getenv("PREFIX_TMDB", "/tmdb").lower()
68 FFMPEG_CUT = os.getenv("PREFIX_FFMPEG_CUT", "/cut").lower()
69 FFMPEG_H264 = os.getenv("PREFIX_FFMPEG_H264", "/h264").lower()
70 FFPROBE = os.getenv("PREFIX_FFPROBE", "/ffprobe").lower()
71 WATERMARK = os.getenv("PREFIX_WATERMARK", "/wm, /watermark").lower()
72 VERSION = os.getenv("PREFIX_VERSION", "/version").lower()
73 MSG_INFO = os.getenv("PREFIX_MSG_INFO", "/info").lower()
74
75
76class API:
77 FXTWITTER = os.getenv("FXTWITTER_API", "https://api.fxtwitter.com")
78 DDINSTAGRAM = os.getenv("DDINSTAGRAM_API", "https://www.ddinstagram.com")
79 TIKHUB = os.getenv("TIKHUB", "https://api.tikhub.io")
80 TIKHUB_FREE = os.getenv("TIKHUB_FREE", "https://api.douyin.wtf")
81 TIKHUB_INSTAGRAM = os.getenv("TIKHUB_INSTAGRAM_API", "https://api.tikhub.io/api/v1/instagram/v1/fetch_post_by_url?post_url=")
82 TIKHUB_INSTAGRAM_STORY = os.getenv("TIKHUB_INSTAGRAM_STORY_API", "https://api.tikhub.io/api/v1/instagram/v3/get_user_stories?username=")
83 TIKHUB_WEIBO_VIDEO = os.getenv("TIKHUB_WEIBO_VIDEO_API", "https://api.tikhub.io/api/v1/weibo/web/fetch_short_video_data?share_text=")
84 TIKHUB_WECHAT = os.getenv("TIKHUB_WECHAT", "https://api.tikhub.io/api/v1/wechat_mp/web/fetch_mp_article_detail_json?url=")
85 BINANCE_SPOT = os.getenv("BINANCE_SPOT_API", "https://data-api.binance.vision")
86 BINANCE_UM = os.getenv("BINANCE_UM_API", "https://fapi.binance.com")
87 OKX = os.getenv("OKX_API", "https://www.okx.com")
88 QUOTELY = os.getenv("QUOTLY_API", "https://bot.lyo.su/quote/generate")
89
90
91class DANMU:
92 BASE_URL = os.getenv("DANMU_BASE_URL", "") # Custom API, No docs
93 STREAMER = os.getenv("DANMU_STREAMER", "Streamer") # streamer name
94 AUTH_USER = os.getenv("DANMU_AUTH_USER", "") # username for basic auth
95 AUTH_PASS = os.getenv("DANMU_AUTH_PASS", "") # password for basic auth
96 QUERY_METHOD = os.getenv("DANMU_QUERY_METHOD", "turso") # Turso or R2+API server
97 NUM_PER_QUERY = int(os.getenv("DANMU_NUM_PER_QUERY", "100")) # Number of items per query to API server
98 D1_DATABASE = os.getenv("DANMU_D1_DATABASE", "bennybot-danmu")
99 TURSO_DATABASE = os.getenv("DANMU_TURSO_DATABASE", "bennybot-danmu")
100 TURSO_USERNAME = os.getenv("DANMU_TURSO_USERNAME", "") # https://turso.tech
101 TURSO_API_TOKEN = os.getenv("DANMU_TURSO_API_TOKEN", "")
102 TURSO_GROUP_TOKEN = os.getenv("DANMU_TURSO_GROUP_TOKEN", "")
103 R2_PREFIX = os.getenv("DANMU_R2_PREFIX", "Streaming/")
104 SYNC_ENABLE = os.getenv("DANMU_SYNC_ENABLE", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
105 SYNC_ENGNIE = os.getenv("DANMU_SYNC_ENGNIE", "turso,R2") # sync livechats to Turso & R2
106 SYNC_DANMU_YEARS = os.getenv("SYNC_DANMU_YEARS", "") # comma separated years to sync. e.g. "2025,2024,2023"
107 SYNC_FAYAN_YEARS = os.getenv("SYNC_FAYAN_YEARS", "") # comma separated years that has live stream
108
109
110class PROVIDER: # default API provider
111 DOUYIN = os.getenv("DOUYIN_PROVIDER", "direct-free-tikhub-bridge").lower()
112 DOUYIN_COMMENTS = os.getenv("DOUYIN_COMMENTS_PROVIDER", "free-tikhub").lower() # a false value (0, false, none, null) to disable it
113 INSTAGRAM = os.getenv("INSTAGRAM_PROVIDER", "tikhub-ddinstagram-bridge").lower()
114 WEIBO = os.getenv("WEIBO_PROVIDER", "direct-bridge").lower()
115 XHS = os.getenv("XHS_PROVIDER", "direct-bridge").lower()
116
117
118class TOKEN:
119 SESSION_STRING = os.getenv("SESSION_STRING", "")
120 TIKHUB = os.getenv("TIKHUB_TOKEN", "")
121 YOUTUBE_API_KEY = os.getenv("YOUTUBE_API_KEY", "")
122 CMC_API_KEY = os.getenv("CMC_API_KEY", "")
123 GOOGLE_SEARCH_API_KEY = os.getenv("GOOGLE_SEARCH_API_KEY", "")
124 GOOGLE_SEARCH_CX = os.getenv("GOOGLE_SEARCH_CX", "")
125 CHART_IMG = os.getenv("CHART_IMG_KEY", "")
126 TELEGRAPH = os.getenv("TELEGRAPH_TOKEN", "")
127 NEOCITIES = os.getenv("NEOCITIES_USERPASS", "") # in "user,pass" format
128 NEOCITIES_IV_HASH = os.getenv("NEOCITIES_INSTANTVIEW_HASH", "")
129 R2_IV_HASH = os.getenv("R2_INSTANTVIEW_HASH", "")
130 GITHUB = os.getenv("GITHUB_TOKEN", "")
131 SPOTIFY_CLIENT_ID = os.getenv("SPOTIFY_CLIENT_ID", "")
132 SPOTIFY_CLIENT_SECRET = os.getenv("SPOTIFY_CLIENT_SECRET", "")
133 V2EX = os.getenv("V2EX_TOKEN", "")
134 TMDB = os.getenv("TMDB_TOKEN", "")
135 SPOOME = os.getenv("SPOOME_TOKEN", "")
136
137
138class PROXY: # format: socks5://127.0.0.1:7890
139 AI_POST = os.getenv("AI_POST_PROXY", None)
140 ALI = os.getenv("ALI_PROXY", None)
141 ANTHROPIC = os.getenv("ANTHROPIC_PROXY", None)
142 CLOUDFLARE = os.getenv("CLOUDFLARE_PROXY", None)
143 COOKIE_CLOUD = os.getenv("COOKIE_CLOUD_PROXY", None)
144 CRYPTO = os.getenv("CRYPTO_PROXY", None)
145 D1 = os.getenv("D1_PROXY", None)
146 DANMU = os.getenv("DANMU_PROXY", None)
147 DOUYIN = os.getenv("DOUYIN_PROXY", None)
148 DOWNLOAD = os.getenv("DOWNLOAD_PROXY", None)
149 EDGE = os.getenv("TTS_EDGE_PROXY", None)
150 GITHUB = os.getenv("GITHUB_PROXY", None)
151 GOOGLE = os.getenv("GOOGLE_PROXY", None)
152 GROQ = os.getenv("GROQ_PROXY", None) # Ban CN & HK IP
153 IMG = os.getenv("IMG_PROXY", "") # https://caravaggio.ramielcreations.com/docs/install
154 INSTAGRAM = os.getenv("INSTAGRAM_PROXY", None)
155 OPENAI = os.getenv("OPENAI_PROXY", None)
156 PODCAST = os.getenv("PODCAST_PROXY", None)
157 REDDIT = os.getenv("REDDIT_PROXY", None)
158 SPOTIFY = os.getenv("SPOTIFY_PROXY", None)
159 SUBTITLE = os.getenv("SUBTITLE_PROXY", None)
160 TELEGRAM = os.getenv("TELEGRAM_PROXY", None) # Telegram
161 TENCENT = os.getenv("TENCENT_PROXY", None) # Banned oversea IP, need a back to China proxy
162 TIKTOK = os.getenv("TIKTOK_PROXY", None)
163 TMDB = os.getenv("TMDB_PROXY", None)
164 TURSO = os.getenv("TURSO_PROXY", None)
165 TWITTER = os.getenv("TWITTER_PROXY", None)
166 V2EX = os.getenv("V2EX_PROXY", None)
167 WARP = os.getenv("WARP_PROXY", None)
168 WECHAT = os.getenv("WECHAT_PROXY", None)
169 WEIBO = os.getenv("WEIBO_PROXY", None)
170 ARXIV = os.getenv("ARXIV_PROXY", None)
171 XHS = os.getenv("XHS_PROXY", None) # Banned VPS IP, need residential proxy
172 YTDLP = os.getenv("YTDLP_PROXY", None) # general proxy for ytdlp
173 YTDLP_FALLBACK = os.getenv("YTDLP_PROXY_FALLBACK", None) # fallback proxy for ytdlp
174 # for ytdlp proxy of specific sites (Like Bilibili), use this format: YTDLP_PROXY_BILIBILI
175
176
177class COOKIE: # See: https://github.com/easychen/CookieCloud
178 CLOUD_SERVER = os.getenv("COOKIE_CLOUD_SERVER", "")
179 CLOUD_KEY = os.getenv("COOKIE_CLOUD_KEY", "")
180 CLOUD_PASS = os.getenv("COOKIE_CLOUD_PASS", "")
181
182
183class TID: # see more TID usecase in `src/permission.py`
184 ME = int(os.getenv("TID_ME", "0")) # a temperary chat for some tasks
185 ADMIN = os.getenv("TID_ADMIN", "") # comma separated userid or @username
186 TEMP = os.getenv("TID_TEMP", "me") # a temperary chat for some tasks
187 HISTORY_ADMIN = os.getenv("TID_HISTORY_ADMIN", "") # comma separated userid (@username is NOT supported!)
188 # back up ytdlp audio if the user does not request it
189 DAILY_SUMMARY = os.getenv("TID_DAILY_SUMMARY", "{}") # {"source-chat-id": "target-chat-id"}, e.g. '{"-1001234567890": "-1009876543210"}'
190 GEMINI_CHATS = os.getenv("TID_GEMINI_CHATS", "") # comma separated chat ids to always use gemini models (no need `/gemini`)
191 OPENAI_CHATS = os.getenv("TID_OPENAI_CHATS", "") # comma separated chat ids to always use openai models (no need `/gpt`)
192 DEEPSEEK_CHATS = os.getenv("TID_DEEPSEEK_CHATS", "") # comma separated chat ids to always use deepseek models (no need `/ds`)
193 QWEN_CHATS = os.getenv("TID_QWEN_CHATS", "") # comma separated chat ids to always use qwen models (no need `/qwen`)
194 DOUBAO_CHATS = os.getenv("TID_DOUBAO_CHATS", "") # comma separated chat ids to always use doubao models (no need `/doubao`)
195 GROK_CHATS = os.getenv("TID_GROK_CHATS", "") # comma separated chat ids to always use grok models (no need `/grok`)
196 KIMI_CHATS = os.getenv("TID_KIMI_CHATS", "") # comma separated chat ids to always use kimi models (no need `/kimi`)
197
198
199class DB:
200 CF_KV_ENABLED = os.getenv("CF_KV_ENABLED", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
201 CF_ACCOUNT_ID = os.getenv("CF_ACCOUNT_ID", "")
202 CF_API_TOKEN = os.getenv("CF_API_TOKEN", "")
203 CF_KV_NAMESPACE_ID = os.getenv("CF_KV_NAMESPACE_ID", "")
204 CF_R2_ENABLED = os.getenv("CF_R2_ENABLED", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
205 CF_R2_BUCKET_NAME = os.getenv("CF_R2_BUCKET_NAME", "bennybot")
206 CF_R2_ACCESS_KEY_ID = os.getenv("CF_R2_ACCESS_KEY_ID", "")
207 CF_R2_SECRET_ACCESS_KEY = os.getenv("CF_R2_SECRET_ACCESS_KEY", "")
208 CF_R2_PUBLIC_URL = os.getenv("CF_R2_PUBLIC_URL", "")
209 CF_D1_ENABLED = os.getenv("CF_D1_ENABLED", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
210 ALIST_ENABLED = os.getenv("ALIST_ENABLED", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
211 ALIST_USERNAME = os.getenv("ALIST_USERNAME", "guest")
212 ALIST_PASSWORD = os.getenv("ALIST_PASSWORD", "guest")
213 ALIST_SERVER = os.getenv("ALIST_SERVER", "")
214 ALIST_BASR_PATH = os.getenv("ALIST_BASR_PATH", "")
215 PASTBIN_SERVER = os.getenv("PASTBIN_SERVER", "https://shz.al")
216 PASTBIN_MAX_BYTES = int(os.getenv("PASTBIN_MAX_BYTES", "10485760")) # 10 MB
217 TURSO_ENABLED = os.getenv("TURSO_ENABLED", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
218 TURSO_USERNAME = os.getenv("TURSO_USERNAME", "") # https://turso.tech
219 TURSO_API_TOKEN = os.getenv("TURSO_API_TOKEN", "")
220 TURSO_GROUP_TOKEN = os.getenv("TURSO_GROUP_TOKEN", "")
221 GH_USER = os.getenv("DB_GH_USER", "")
222 GH_REPO = os.getenv("DB_GH_REPO", "bennybot") # just repo name, not `owner/repo`
223 GH_TOKEN = os.getenv("DB_GH_TOKEN", "")
224
225
226class HISTORY:
227 ENABLE = os.getenv("HISTORY_ENABLE", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
228 ENGINE = os.getenv("HISTORY_ENGINE", "turso").lower() # turso or d1 (This is for sync & backup)
229 QUERY_ENGINE = os.getenv("HISTORY_QUERY_ENGINE", "turso").lower() # turso or d1
230 TURSO_ENABLE = os.getenv("HISTORY_TURSO_ENABLE", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
231 TURSO_DATABASE = os.getenv("HISTORY_TURSO_DATABASE", "bennybot-history")
232 TURSO_USERNAME = os.getenv("HISTORY_TURSO_USERNAME", "") # https://turso.tech
233 TURSO_API_TOKEN = os.getenv("HISTORY_TURSO_API_TOKEN", "")
234 TURSO_GROUP_TOKEN = os.getenv("HISTORY_TURSO_GROUP_TOKEN", "")
235 PERIODICALLY_BACKUP_CHATS = os.getenv("HISTORY_PERIODICALLY_BACKUP_CHATS", "") # "full_table" or comma separated chat ids to include (without `-100` prefix)
236 BACKUP_CHATS_HOURS = float(os.getenv("HISTORY_BACKUP_CHATS_HOURS", "24")) # hours to backup chats
237 D1_ENABLE = os.getenv("HISTORY_D1_ENABLE", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
238 D1_DATABASE = os.getenv("HISTORY_D1_DATABASE", "bennybot-history")
239 INCLUDE_CHATS = os.getenv("HISTORY_INCLUDE_CHATS", "") # "all" or comma separated chat ids to include (without `-100` prefix)
240 IGNORE_CHATS = os.getenv("HISTORY_IGNORE_CHATS", "") # comma separated chat ids to ignore (without `-100` prefix)
241 INCLUDE_PRIVATES = os.getenv("HISTORY_INCLUDE_PRIVATES", "") # "all" or comma separated private chat ids to include (without `-100` prefix)
242 INCLUDE_BOTS = os.getenv("HISTORY_INCLUDE_BOTS", "") # "all" or comma separated private chat ids to include (without `-100` prefix)
243 INCLUDE_GROUPS = os.getenv("HISTORY_INCLUDE_GROUPS", "") # "all" or comma separated private chat ids to include (without `-100` prefix)
244 INCLUDE_CHANNELS = os.getenv("HISTORY_INCLUDE_CHANNELS", "") # "all" or comma separated private chat ids to include (without `-100` prefix)
245
246
247class ASR:
248 # use different engines based on duration
249 # support ali, tencent, gemini, deepgram, cloudflare, groq
250 DEFAULT_ENGINE = os.getenv("ASR_DEFAULT_ENGINE", "auto")
251 SHORT_ENGINE = os.getenv("ASR_SHORT_ENGINE", "tencent") # comma separated engine names
252 SHORT_DURATION = int(os.getenv("ASR_SHORT_DURATION", "60"))
253 MIDDLE_ENGINE = os.getenv("ASR_MIDDLE_ENGINE", "tencent,ali") # comma separated engine names
254 MIDDLE_DURATION = int(os.getenv("ASR_MIDDLE_DURATION", "600"))
255 LONG_ENGINE = os.getenv("ASR_LONG_ENGINE", "gemini") # comma separated engine names
256
257 TENCENT_APPID = os.getenv("ASR_TENCENT_APPID", "")
258 TENCENT_SECRET_ID = os.getenv("ASR_TENCENT_SECRET_ID", "")
259 TENCENT_SECRET_KEY = os.getenv("ASR_TENCENT_SECRET_KEY", "")
260 TENCENT_FS_ENGINE = os.getenv("ASR_TENCENT_FS_ENGINE", "local") # local, uguu or alist.
261 # WARN: some models do not allow oversea VPS. Can upload to an alist server in China.
262 ALI_MODEL = os.getenv("ASR_ALI_MODEL", "paraformer-realtime-v2,paraformer-realtime-v1") # comma separated keys for load balance. e.g. "model1,model2,model3"
263 ALI_API_KEY = os.getenv("ASR_ALI_API_KEY", "") # comma separated keys for load balance. e.g. "key1,key2,key3"
264 # If the bot is running on an oversea VPS, and Ali ASR model doesn't allow oversea fileserver.
265 # Change ASR_ALI_FS_ENGINE to alist (configurations in DB class)
266 ALI_FS_ENGINE = os.getenv("ASR_ALI_FS_ENGINE", "local") # local, uguu or alist.
267 DEEPGRAM_API = os.getenv("ASR_DEEPGRAM_API", "") # comma separated keys for load balance. e.g. "key1,key2,key3"
268 CLOUDFLARE_MODEL = os.getenv("ASR_CLOUDFLARE_MODEL", "@cf/openai/whisper-large-v3-turbo")
269 CLOUDFLARE_CHUNK_SECONDS = float(os.getenv("ASR_CLOUDFLARE_CHUNK_SECONDS", "180")) # split long audio file into chunks
270 CLOUDFLARE_OVERLAP_SECONDS = float(os.getenv("ASR_CLOUDFLARE_OVERLAP_SECONDS", "5")) # overlap seconds between chunks
271 CLOUDFLARE_KEYS = os.getenv("ASR_CLOUDFLARE_KEYS", "") # comma separated keys for load balance. e.g. "AccountID:API_TOKEN, AccountID:API_TOKEN, ..."
272
273 GROQ_MAX_BYTES = int(os.getenv("ASR_GROQ_MAX_BYTES", "26214400")) # 25MB (max file bytes for single file)
274 GROQ_CHUNK_SECONDS = float(os.getenv("ASR_GROQ_CHUNK_SECONDS", "180")) # split long audio file into chunks
275 GROQ_OVERLAP_SECONDS = float(os.getenv("ASR_GROQ_OVERLAP_SECONDS", "5")) # overlap seconds between chunks
276 GROQ_KEYS = os.getenv("ASR_GROQ_KEYS", "") # comma separated keys for load balance.
277 GROQ_MODELS = os.getenv("ASR_GROQ_MODELS", "whisper-large-v3") # comma separated model names.
278
279 GEMINI_CHUNK_SECONDS = float(os.getenv("ASR_GEMINI_CHUNK_SECONDS", "600")) # split long audio file into chunks
280 GEMINI_OVERLAP_SECONDS = float(os.getenv("ASR_GEMINI_OVERLAP_SECONDS", "5")) # overlap seconds between chunks
281 GEMINI_MAX_DURATION = int(os.getenv("ASR_GEMINI_MAX_DURATION", "34200")) # 9.5 hour
282 GEMINI_MODEL = os.getenv("ASR_GEMINI_MODEL", "gemini-2.5-flash")
283 GEMINI_CONFIG = os.getenv("ASR_GEMINI_CONFIG", "{}") # default config passed to GenerateContentConfig. Should be a json string: '{"key": "value"}'
284
285
286class PODCAST:
287 FEED_URLS = os.getenv("PODCAST_FEED_URLS", "") # comma separated feed urls
288 OPML_URLS = os.getenv("PODCAST_OPML_URLS", "") # comma separated opml urls
289 YOUTUBE_CHANNEL_IDS = os.getenv("PODCAST_YOUTUBE_CHANNEL_IDS", "") # comma separated youtube channel ids
290 TID = int(os.getenv("PODCAST_TID", "0")) # send to this chat id
291 FS_ENGINE = os.getenv("PODCAST_FS_ENGINE", "CF-R2") # file storage engine for hosting podcast feeds
292 ASR_ENGINE = os.getenv("PODCAST_ASR_ENGINE", "auto") # default ASR engine
293 IGNORE_OLD_THAN_SECONDS = int(os.getenv("PODCAST_IGNORE_OLD_THAN_SECONDS", "14400")) # in seconds
294 KEEP_LATEST_ENTRIES = int(os.getenv("PODCAST_KEEP_LATEST_ENTRIES", "99999999")) # keep latest entries
295 # To bypass censorship, set asr engines here (comma separated titles or domains)
296 ASR_FORCE_GEMINI_TITLES = os.getenv("PODCAST_ASR_FORCE_GEMINI_TITLES", "")
297 ASR_FORCE_GEMINI_DOMAINS = os.getenv("PODCAST_ASR_FORCE_GEMINI_DOMAINS", "")
298 ASR_FORCE_GROQ_TITLES = os.getenv("PODCAST_ASR_FORCE_GROQ_TITLES", "")
299 ASR_FORCE_GROQ_DOMAINS = os.getenv("PODCAST_ASR_FORCE_GROQ_DOMAINS", "")
300 ASR_FORCE_CLOUDFLARE_TITLES = os.getenv("PODCAST_ASR_FORCE_CLOUDFLARE_TITLES", "")
301 ASR_FORCE_CLOUDFLARE_DOMAINS = os.getenv("PODCAST_ASR_FORCE_CLOUDFLARE_DOMAINS", "")
302 ASR_FORCE_WHISPER_TITLES = os.getenv("PODCAST_ASR_FORCE_WHISPER_TITLES", "")
303 ASR_FORCE_WHISPER_DOMAINS = os.getenv("PODCAST_ASR_FORCE_WHISPER_DOMAINS", "")
304 ASR_FORCE_UNCENSORED_TITLES = os.getenv("PODCAST_ASR_FORCE_UNCENSORED_TITLES", "")
305 ASR_FORCE_UNCENSORED_DOMAINS = os.getenv("PODCAST_ASR_FORCE_UNCENSORED_DOMAINS", "anchor.fm,feeds.acast.com")
306 GH_REPO = os.getenv("PODCAST_GH_REPO", "podcast")
307 GH_TOKEN = os.getenv("PODCAST_GH_TOKEN", "")
308
309
310class FAVORITE:
311 ENABLE_SEND = os.getenv("ENABLE_FAVORITE_SEND", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
312 ENABLE_SAVE = os.getenv("ENABLE_FAVORITE_SAVE", "1").lower() in ["1", "y", "yes", "t", "true", "on"]
313 SEND_PREFIX = os.getenv("FAVORITE_SEND_PREFIX", "/fav").lower()
314 SAVE_PREFIX = os.getenv("FAVORITE_SAVE_PREFIX", "/save").lower()
315 R2_PREFIX = os.getenv("FAVORITE_R2_PREFIX", "Favorite/")
316 BACKUP_CHAT = os.getenv("FAVORITE_BACKUP_CHAT", "") # chat id to backup favorite messages
317 TIDS_ALLOW_SEND = os.getenv("FAVORITE_TIDS_ALLOW_SEND", "all").lower() # or comma separated telegram uids
318 TIDS_ALLOW_SAVE = os.getenv("FAVORITE_TIDS_ALLOW_SAVE", "") # comma separated telegram uids
319
320
321class TTS:
322 # TTS related
323 DEFAULT_ENGINE = os.getenv("TTS_DEFAULT_ENGINE", "edge") # edge, gemini, qwen, sambert
324 GEMINI_MODEL = os.getenv("TTS_GEMINI_MODEL", "gemini-2.5-flash-preview-tts")
325 GEMINI_INPUT_TOKEN_LIMIT = int(os.getenv("TTS_GEMINI_INPUT_TOKEN_LIMIT", "8192")) # token limit of the tts model
326 GEMINI_SPLIT_LENGTH = int(os.getenv("TTS_GEMINI_SPLIT_LENGTH", "8192")) # split token limit of the tts model
327 GEMINI_VOICE = os.getenv("TTS_GEMINI_VOICE", "Sulafat")
328 ALI_API_KEY = os.getenv("TTS_ALI_API_KEY", "") # comma separated keys for load balance. e.g. "key1,key2,key3"
329 QWEN_MODEL = os.getenv("TTS_QWEN_MODEL", "qwen-tts,qwen-tts-latest") # comma separated keys for load balance.
330 QWEN_INPUT_TOKEN_LIMIT = int(os.getenv("TTS_QWEN_INPUT_TOKEN_LIMIT", "512")) # token limit of the tts model
331 QWEN_SPLIT_LENGTH = int(os.getenv("TTS_QWEN_SPLIT_LENGTH", "512")) # split token limit of the tts model
332 QWEN_VOICE = os.getenv("TTS_QWEN_VOICE", "Chelsie")
333 SAMBERT_MODEL = os.getenv("TTS_SAMBERT_MODEL", "ramdom") # comma separated models for load balance. use "random" to randomly choose a model
334 SAMBERT_LENGTH_LIMIT = int(os.getenv("TTS_SAMBERT_LENGTH_LIMIT", "20000")) # token limit of the tts model
335 EDGE_DOMAIN = os.getenv("TTS_EDGE_DOMAIN", "https://tts.wangwangit.com")
336 EDGE_VOICE = os.getenv("TTS_EDGE_VOICE", "晓晓")
337 EDGE_MODEL = os.getenv("TTS_EDGE_MODEL", "zh-CN-XiaoxiaoNeural")
338
339
340class AI:
341 # Text Generation
342 MAX_CONTEXTS_NUM = int(os.getenv("AI_MAX_CONTEXTS_NUM", "30"))
343 TEXT_MODEL_CONFIG_KEY = os.getenv("AI_MODEL_CONFIG_KEY", "AI-TEXT") # model configuration key in CF-KV
344 TEXT_GENERATION_DEFAULT_MODEL = os.getenv("AI_TEXT_GENERATION_DEFAULT_MODEL", "gemini")
345 GEMINI_MODEL_ID = os.getenv("AI_GEMINI_MODEL_ID", "gemini-2.5-flash")
346 GEMINI_API_KEYS = os.getenv("AI_GEMINI_API_KEYS", "") # comma separated keys for load balance. e.g. "key1,key2,key3"
347 GEMINI_BASE_URL = os.getenv("AI_GEMINI_BASE_URL", "https://generativelanguage.googleapis.com")
348 GEMINI_DEFAULT_HEADERS = os.getenv("AI_GEMINI_DEFAULT_HEADERS", "{}") # default headers passed to Gemini API. Should be a json string: '{"key": "value"}'
349 GEMINI_FILES_TTL = int(os.getenv("AI_GEMINI_FILES_TTL", "172800")) # clean gemini files after 48 hours
350
351 ANTHROPIC_MODEL_ID = os.getenv("AI_ANTHROPIC_MODEL_ID", "claude-opus-4-6")
352 ANTHROPIC_API_KEYS = os.getenv("AI_ANTHROPIC_API_KEYS", "") # comma separated keys for load balance. e.g. "key1,key2,key3"
353 ANTHROPIC_BASE_URL = os.getenv("AI_ANTHROPIC_BASE_URL", "https://api.anthropic.com")
354 ANTHROPIC_DEFAULT_HEADERS = os.getenv("AI_ANTHROPIC_DEFAULT_HEADERS", "{}") # default headers passed to Anthropic API. Should be a json string: '{"key": "value"}'
355 ANTHROPIC_FILES_TTL = int(os.getenv("AI_ANTHROPIC_FILES_TTL", "172800")) # clean anthropic files after 48 hours
356
357 OPENAI_MODEL_ID = os.getenv("AI_OPENAI_MODEL_ID", "gpt-4o")
358 OPENAI_API_KEYS = os.getenv("AI_OPENAI_API_KEYS", "") # comma separated keys for load balance. e.g. "key1,key2,key3"
359 OPENAI_BASE_URL = os.getenv("AI_OPENAI_BASE_URL", "https://api.openai.com/v1")
360 TOOL_CALL_MODEL_ALIAS = os.getenv("AI_TOOL_CALL_MODEL_ALIAS", "tool-call")
361 AI_SUMMARY_MODEL_ALIAS = os.getenv("AI_SUMMARY_MODEL_ALIAS", "gemini")
362 PODCAST_SUMMARY_MODEL_ALIAS = os.getenv("PODCAST_SUMMARY_MODEL_ALIAS", "podcast-summary")
363 SUBTITLE_SUMMARY_MODEL_ALIAS = os.getenv("SUBTITLE_SUMMARY_MODEL_ALIAS", "subtitle-summary")
364 CHAT_SUMMARY_MODEL_ALIAS = os.getenv("CHAT_SUMMARY_MODEL_ALIAS", "chat-summary")
365
366 # Image Generation
367 IMG_MODEL_CONFIG_KEY = os.getenv("AI_IMG_MODEL_CONFIG_KEY", "AI-IMG") # model configuration key in CF-KV
368 IMG_GENERATION_DEFAULT_MODEL = os.getenv("AI_IMG_GENERATION_DEFAULT_MODEL", "gpt")
369
370 # Video Generation
371 VIDEO_MODEL_CONFIG_KEY = os.getenv("AI_VIDEO_MODEL_CONFIG_KEY", "AI-VIDEO") # model configuration key in CF-KV
372 VIDEO_GENERATION_DEFAULT_MODEL = os.getenv("AI_VIDEO_GENERATION_DEFAULT_MODEL", "seedance")