diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index b4be9b1..ef38ab6 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -12,14 +12,12 @@ permissions: jobs: tests: runs-on: ubuntu-latest - env: - BOT_TOKEN: "example" steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python 3.11 uses: actions/setup-python@v3 with: - python-version: "3.10" + python-version: "3.11" - name: Set up system environment run: | sudo apt-get update -y @@ -28,6 +26,9 @@ jobs: run: | python3 -m pip install --upgrade pip python3 -m pip install -r requirements.txt + - name: Setup configurations + run: | + cp .env.toml env.toml - name: Test with pytest run: | python3 -m pytest diff --git a/README.md b/README.md index 140ada0..2135a20 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Randomology -GitHub Actions Workflow Status Static Badge - +[![Github Actions Status](https://img.shields.io/github/actions/workflow/status/ravachol-yang/randomology/pytest.yml?style=for-the-badge&logo=github&label=tests)](https://github.com/ravachol-yang/randomology/actions) +[![Telegram](https://img.shields.io/badge/telegram-26a4e2?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/randomology_bot) A telegram bot to generate random stuff, I built this to chat with my friend randomly. Using [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI) for talking with telegram server. @@ -67,7 +67,7 @@ copy and change the config file to configure Nginx: ``` shell cp nginx.conf /etc/nginx/sites-available/example.com # don't forget to change it !! -ln /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled +ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled ``` restart `nginx.service` ### Running @@ -88,6 +88,6 @@ now you can run it ! ## LICENSE *(&%&^*&(*&%*^(&))) -GitHub License +[![License](https://img.shields.io/github/license/ravachol-yang/randomology?style=for-the-badge)](https://github.com/ravachol-yang/randomology/blob/master/LICENSE) diff --git a/app/handlers/audio_handler.py b/app/handlers/audio_handler.py index 6b64746..aa92f09 100644 --- a/app/handlers/audio_handler.py +++ b/app/handlers/audio_handler.py @@ -3,35 +3,23 @@ from telebot import TeleBot from telebot.types import Message -from app.services.audio_service import generate_random_noise -from app.services.audio_service import generate_random_sine -from app.services.audio_service import generate_random_mix -from app.services.audio_service import generate_random_voice +from app.models.audio import Audio +from app.models.voice import Voice -# invoke with "/noise" command -def get_random_noise(message: Message, bot: TeleBot): +# invoke with "/audio" command +def get_random_audio(message: Message, bot: TeleBot, data: dict): # pretend to be uploading audio bot.send_chat_action(message.chat.id, "upload_voice") - noise = generate_random_noise() - bot.send_audio(message.chat.id, noise) + options = data['options'] -# invoke with "/sine" command -def get_random_sine(message: Message, bot: TeleBot): - # uploading audio... - bot.send_chat_action(message.chat.id, "upload_voice") - sine = generate_random_sine() - bot.send_audio(message.chat.id, sine) - -# invoke with "/mix" command -def get_random_mix(message: Message, bot: TeleBot): - # uploading audio... - bot.send_chat_action(message.chat.id, "upload_voice") - mix = generate_random_mix() - bot.send_audio(message.chat.id, mix) + audio = Audio(bot, message) + audio.generate(options).to_mpeg().send() # invoke with "/voice" command -def get_random_voice(message: Message, bot: TeleBot): +def get_random_voice(message: Message, bot: TeleBot, data: dict): # pretend to be sending voice bot.send_chat_action(message.chat.id, "record_voice") - voice = generate_random_voice() - bot.send_voice(message.chat.id, voice) + options = data['options'] + + voice = Voice(bot, message) + voice.generate(options).to_voice().send() diff --git a/app/handlers/info_handler.py b/app/handlers/info_handler.py index 18e728e..9f9495b 100644 --- a/app/handlers/info_handler.py +++ b/app/handlers/info_handler.py @@ -3,10 +3,16 @@ from telebot import TeleBot from telebot.types import Message -from app.services.info_service import generate_info +from app.models.text import Text +from configs import templates def get_info(message: Message, bot: TeleBot): + bot.send_chat_action(message.chat.id, "typing") + name = message.from_user.first_name id = message.from_user.id - info = generate_info(name, id) - bot.send_message(message.chat.id, info, parse_mode="MarkdownV2") + + text = Text(bot, message) + text.set_content(templates.INFO_MESSAGE.format(name=name, id=id)) + + text.send(parse_mode="MarkdownV2") diff --git a/app/handlers/inline_handler.py b/app/handlers/inline_handler.py index 90026b5..f056b19 100644 --- a/app/handlers/inline_handler.py +++ b/app/handlers/inline_handler.py @@ -3,16 +3,26 @@ from telebot import TeleBot from telebot import types -from app.services.text_service import generate_random_text -from app.services.audio_service import generate_random_mix +from app.models.text import Text +from app.models.voice import Voice -def inline_dispatch(inline_query, bot: TeleBot): +def inline_text(inline_query, bot: TeleBot, data:dict): + options = data['options'] + text = Text() + voice = Voice() try: - text = types.InlineQueryResultArticle('1', 'Random Text', - types.InputTextMessageContent(generate_random_text())) - audio = types.InlineQueryResultArticle('2', - 'TODO', - types.InputTextMessageContent("TODO")) - bot.answer_inline_query(inline_query.id, [text, audio], cache_time=0) + text = types.InlineQueryResultArticle('2', 'Random Text', + types.InputTextMessageContent(text.generate(options).content())) + bot.answer_inline_query(inline_query.id, [text], cache_time=0) + except Exception as e: + print(e) + +def inline_voice(inline_query, bot:TeleBot, data:dict): + options = data['options'] + voice = Voice().generate(options).to_voice().content() + try: + voice = types.InlineQueryResultVoice('3', title = 'Random Voice', + voice_url=voice) + bot.answer_inline_query(inline_query.id, [voice], cache_time=0) except Exception as e: print(e) diff --git a/app/handlers/member_handler.py b/app/handlers/member_handler.py index cd70a3e..7454e91 100644 --- a/app/handlers/member_handler.py +++ b/app/handlers/member_handler.py @@ -3,12 +3,18 @@ from telebot import TeleBot from telebot.types import Message -from app.services.member_service import generate_welcome +from app.models.text import Text +from configs import templates # invoke when new member joins def get_welcome(message:Message, bot:TeleBot): # typing ... + print("welcome") bot.send_chat_action(message.chat.id, "typing") new_member = message.new_chat_members - welcome = generate_welcome(new_member[0].first_name, new_member[0].id) - bot.send_message(message.chat.id, welcome, parse_mode="MarkdownV2") + welcome = templates.WELCOME_MESSAGE.format(name=new_member[0].first_name, + id=new_member[0].id) + text = Text(bot, message) + text.set_content(welcome) + text.send(parse_mode="MarkdownV2") + diff --git a/app/handlers/text_handler.py b/app/handlers/text_handler.py index 24cdf6e..4a65e18 100644 --- a/app/handlers/text_handler.py +++ b/app/handlers/text_handler.py @@ -2,23 +2,25 @@ from telebot import TeleBot from telebot.types import Message -from app.services.text_service import generate_random_text -import string +from app.models.text import Text -SPECIAL = ['_', '*', '[', ']', '(', ')', '~', '`', '<' , '>', '#', '+', '-', '=', '|', '{', '}', '.', '!' ] - -# invoke with "/random" command -def get_random_text(message: Message, bot: TeleBot): +# invoke with "/text" command +def get_random_text(message: Message, bot: TeleBot, data:dict): # pretend to be typing bot.send_chat_action(message.chat.id, "typing") - bot.send_message(message.chat.id, generate_random_text()) + # get options from message + options = data['options'] + + text = Text(bot, message) + text.generate(options).send() -# invoke with "/random_mono" -def get_random_text_mono(message: Message, bot: TeleBot): +# invoke with "/mono" +def get_random_text_mono(message: Message, bot: TeleBot, data:dict): # pretend to be typing bot.send_chat_action(message.chat.id, "typing") - text = generate_random_text() - # add an "\" before every special char for parsing markdown - for i in SPECIAL: - text = text.replace(i, "\\"+i) - bot.send_message(message.chat.id, "`"+text+"`", parse_mode="MarkdownV2") + + options = data['options'] + + mono = Text(bot, message) + mono.generate(options) + mono.to_mono().send(parse_mode="MarkdownV2") diff --git a/app/middlewares/option_middleware.py b/app/middlewares/option_middleware.py new file mode 100644 index 0000000..e0f8719 --- /dev/null +++ b/app/middlewares/option_middleware.py @@ -0,0 +1,37 @@ +from telebot import TeleBot +from telebot.handler_backends import BaseMiddleware +from telebot.types import InlineQuery +from telebot.types import Message + +class OptionMiddleware(BaseMiddleware): + def __init__(self): + self.update_types = ['message', 'inline_query'] + + def pre_process(self, message, data): + + # if it's an inline query + if isinstance(message, InlineQuery): + msg_text = message.query + if msg_text == "": + msg_text = "*" + if msg_text[0] == '/': + options = msg_text.split(" ",1) + options.pop(0) + else: + options = [msg_text] + + # if it's a message + elif isinstance(message, Message): + msg_text = message.text + options = msg_text.split(" ",1) + options.pop(0) + + if options: + # remove spaces and make options list + options = options[0].replace(" ","") + options = options.split(",") + + data['options'] = options + + def post_process(self, message, data, exception=None): + pass diff --git a/app/services/__init__.py b/app/models/__init__.py similarity index 100% rename from app/services/__init__.py rename to app/models/__init__.py diff --git a/app/models/audio.py b/app/models/audio.py new file mode 100644 index 0000000..a711474 --- /dev/null +++ b/app/models/audio.py @@ -0,0 +1,139 @@ +""" +audio.py +the model for audio messages +""" +import wave +import random +import struct +import uuid +import math +import ffmpeg + +from app.models.base import Base +from app.models.msg_type import MsgType + +from configs import dirs +from configs import env + +class Audio(Base): + + MSG_TYPE:str = MsgType.AUDIO + + HOST_PREFIX = "https://{}:{}".format(env.WEBHOOK_HOST, env.WEBHOOK_PORT) + + SAMPLE_RATE = 8000 + FREQ_HIGH = 800 + FREQ_LOW = 200 + VOLUME_HIGH = 0.6 + VOLUME_LOW = 0.3 + + OPTIONS_AVAILABLE = ['sine', 'noise'] + OPTIONS_DEFAULT = { + "sine": True, + "noise": False + } + + # generate a noise sample + @staticmethod + def noise_sample(): + sample = random.randint(-32768, 32767) + sample_packed = struct.pack('h', sample) + return sample_packed + + # generate a sine sample + @staticmethod + def sine_sample(freq, volume, x): + value = volume * math.sin(2 * math.pi * freq * (x / Audio.SAMPLE_RATE)) + sample = int(value * 32767.0) + return struct.pack('h', sample) + + # mix up according to options + def _mix_up(self, len): + values = [] + for i in range(0, len): + if self._options['sine']: + # two channels + freq_channel_1 = random.randint(Audio.FREQ_LOW, Audio.FREQ_HIGH) + freq_channel_2 = random.randint(Audio.FREQ_LOW, Audio.FREQ_HIGH) + #volume + volume_channel_1 = random.uniform(Audio.VOLUME_LOW, Audio.VOLUME_HIGH) + volume_channel_2 = random.uniform(Audio.VOLUME_LOW, Audio.VOLUME_HIGH) + # build the chunk + # duration of every chunk + chunk_duration = random.randint(1, 3) + for x in range(0, chunk_duration * Audio.SAMPLE_RATE): + if self._options['sine']: + sine_channel_1 = Audio.sine_sample(freq_channel_1, volume_channel_1, x) + sine_channel_2 = Audio.sine_sample(freq_channel_2, volume_channel_2, x) + if self._options['noise']: + noise_channel_1 = Audio.noise_sample() + noise_channel_2 = Audio.noise_sample() + + # build into values + if self._options['sine'] and self._options['noise']: + values.append(random.choice([sine_channel_1, noise_channel_1])) + values.append(random.choice([sine_channel_2, noise_channel_2])) + elif self._options['sine'] and not self._options['noise']: + values.append(sine_channel_1) + values.append(sine_channel_2) + elif self._options['noise'] and not self._options['sine']: + values.append(noise_channel_1) + values.append(noise_channel_2) + + # make a byte string + values_str = b''.join(values) + return values_str + + # generate an audio + def generate(self, options=None): + name = uuid.uuid4().hex + # add prefix to file name + name_prefix = "/sine-" + # set options + self._options = dict(Audio.OPTIONS_DEFAULT) + if options and not options == ['']: + name_prefix = "/" + self._options = dict.fromkeys(Audio.OPTIONS_AVAILABLE, False) + for option in options: + if option in Audio.OPTIONS_AVAILABLE: + self._options[option] = True + name_prefix = name_prefix + option + "-" + + # if options are empty now, return to default + if self._options == dict.fromkeys(Audio.OPTIONS_AVAILABLE, False): + self._options = dict(Audio.OPTIONS_DEFAULT) + name_prefix += "sine-" + + self._filename = name_prefix+name+".wav" + self._filepath = dirs.AUDIO_DIR+self._filename + # open the audio file + audio = wave.open(self._filepath, "wb") + audio.setparams((2, 2, 24000, 0, 'NONE', 'not compressed')) + + # generate random length + len = random.randint(8, 10) + + values_str = self._mix_up(len) + + audio.writeframes(values_str) + audio.close() + + self._content = Audio.HOST_PREFIX+"/storage/audio"+self._filename + + return self + + # convert to mpeg for telegran server to download + def to_mpeg(self): + wavfile = self._filepath + mpegfile_name = self._filename+".mp3" + mpegfile = dirs.AUDIO_DIR + mpegfile_name + ( + ffmpeg + .input(wavfile, format="wav") + .output(mpegfile) + .run() + ) + self._filename = mpegfile_name + self._filepath = dirs.AUDIO_DIR+self._filename + self._content = Audio.HOST_PREFIX+"/storage/audio"+self._filename + return self diff --git a/app/models/base.py b/app/models/base.py new file mode 100644 index 0000000..476801a --- /dev/null +++ b/app/models/base.py @@ -0,0 +1,33 @@ +""" +base.py +the Base model +""" + +from telebot import TeleBot +from telebot.types import Message + +class Base: + + def __init__(self, + bot:TeleBot = None, + message:Message = None): + self.__bot = bot + self.__message = message + + + # get content of this object + def content(self): + return self._content + + # set content + def set_content(self, content:str): + self._content = content + + # generate content + def generate(self): + raise NotImplementedError + + # send the message + def send(self, **kwargs): + send_me = getattr(self.__bot, "send_"+self.MSG_TYPE) + send_me(self.__message.chat.id, self._content, **kwargs) diff --git a/app/models/msg_type.py b/app/models/msg_type.py new file mode 100644 index 0000000..79e4939 --- /dev/null +++ b/app/models/msg_type.py @@ -0,0 +1,10 @@ +""" +msg_type.py +enum for message types +""" +from enum import Enum + +class MsgType(str, Enum): + TEXT:str = "message" + AUDIO:str = "audio" + VOICE:str = "voice" diff --git a/app/models/text.py b/app/models/text.py new file mode 100644 index 0000000..7b1bf2e --- /dev/null +++ b/app/models/text.py @@ -0,0 +1,104 @@ +""" +text.py +the model for text messages +""" + +import random +import string + +from app.models.base import Base +from app.models.msg_type import MsgType + +class Text(Base): + + # type of this object ("message") + MSG_TYPE:str = MsgType.TEXT + + # special chars to be backslashed in mono + SPECIAL = ['_', '*', '[', ']', + '(', ')', '~', '`', + '<', '>', '#', '+', + '-', '=', '|', '{', + '}', '.', '!'] + + OPTIONS_AVAILABLE = ['en', 'zh', 'punc','d'] + OPTIONS_DEFAULT = { + "en": True, + "zh": False, + "punc": True, + "d": True, + } + + # generate random string, returns the object itself + def generate(self, options = None): + + # set default options + self._options = dict(Text.OPTIONS_DEFAULT) + + # if option not nothing + if options and not options == ['']: + option_bodies = [] + # check each option in options + for option in options: + # if the option is empty, we see it as setting all others false + if option == '': + for i in Text.OPTIONS_AVAILABLE: + if i not in option_bodies: + self._options[i] = False + # if the option is not empty + else: + # get option body without "+" or "-" + if option[0] == "+" or option[0] == "-": + option_body = option.replace(option[0],"",1) + option_bodies.append(option_body) + else: + # if option has no operator, it is "+" + option_body = option + option_bodies.append(option_body) + if option_body in Text.OPTIONS_AVAILABLE: + if option[0] == '-': + self._options[option_body] = False + else: + self._options[option_body] = True + + # make choice string + choices_string = "" + + # add strings for each options + if self._options["en"]: + choices_string += string.ascii_letters + + if self._options["zh"]: + zh_string = "" + # generate a Chinese string of 30 chars + for i in range(0,30): + zh_string += chr(random.randint(0x4e00, 0xa000)) + choices_string += zh_string + + if self._options["punc"]: + choices_string += string.punctuation + + if self._options["d"]: + choices_string += string.digits + + # if the string is empty, return random *s + if not choices_string: + choices_string = "*" + + # generate random string + length = random.randint(30,120) + self._content = ''.join(random.choices(choices_string, k = length)) + + return self + + # convert to mono for markdown parsing + def to_mono(self): + # if there is no puncs, no need to check + if self._options['punc']: + # add a "\" before every special char + for i in Text.SPECIAL: + self._content = self._content.replace(i,"\\"+i) + # add ` ` around to parse to mono + self._content = "` "+self._content+" `" + return self + diff --git a/app/models/voice.py b/app/models/voice.py new file mode 100644 index 0000000..1ced845 --- /dev/null +++ b/app/models/voice.py @@ -0,0 +1,30 @@ +""" +voice.py +""" + +import ffmpeg + +from app.models.audio import Audio +from app.models.msg_type import MsgType + +from configs import dirs + +class Voice(Audio): + + MSG_TYPE:str = MsgType.VOICE + + def to_voice(self): + wavfile = self._filepath + oggfile_name = self._filename+".ogg" + oggfile = dirs.AUDIO_DIR + oggfile_name + ( + ffmpeg + .input(wavfile, format="wav") + .output(oggfile, acodec = "libopus") + .run() + ) + self._filename = oggfile_name + self._filepath = dirs.AUDIO_DIR+self._filename + self._content = Voice.HOST_PREFIX+"/storage/audio"+self._filename + + return self diff --git a/app/services/audio_service.py b/app/services/audio_service.py deleted file mode 100644 index 6b0f697..0000000 --- a/app/services/audio_service.py +++ /dev/null @@ -1,141 +0,0 @@ -# audio_service -# TODO: ugly code !!!! - -import wave -import random -import struct -import uuid -import ffmpeg -import math - -from configs import dirs - -SAMPLE_RATE = 8000 -FREQ_HIGH = 800 -FREQ_LOW = 20 -VOLUME_HIGH = 0.7 -VOLUME_LOW = 0.3 - -# generate a noise sample -def noise_sample(): - sample = random.randint(-32768, 32767) - sample_packed = struct.pack('h', sample) - return sample_packed - -# generate a sine wave sample -def sine_sample(freq, volume, x): - value = volume * math.sin(2 * math.pi * freq * (x / SAMPLE_RATE)) - sample = int(value * 32767.0) - return struct.pack('h', sample) - -# Generate a noise -def generate_random_noise(): - name = uuid.uuid4().hex - audio_path = dirs.AUDIO_DIR+"/noise_"+name+".wav" - audio = wave.open(audio_path, "wb") - audio.setparams((2, 2, 24000, 0, 'NONE', 'not compressed')) - len = random.randint(20, 60) - values = [] - # build the noise - for i in range(0, len): - channel_1 = noise_sample() - channel_2 = noise_sample() - chunk_duration = random.randint(1000, 10000) - for j in range(0, chunk_duration): - values.append(channel_1) - values.append(channel_2) - - # make a byte string - values_str = b''.join(values) - audio.writeframes(values_str) - audio.close() - return open(audio_path, "rb") - -# Generate a sine wave audio -def generate_random_sine(): - name = uuid.uuid4().hex - audio_path = dirs.AUDIO_DIR+"/sine_"+name+".wav" - audio = wave.open(audio_path, "wb") - audio.setparams((2, 2, 24000, 0, 'NONE', 'not compressed')) - - values = [] - - len = random.randint(8, 10) - for i in range(0, len): - freq = random.randint(FREQ_LOW, FREQ_HIGH) - chunk_duration = random.randint(1, 2) - volume = random.uniform(VOLUME_LOW, VOLUME_HIGH) - for x in range(0, chunk_duration * SAMPLE_RATE): - sine = sine_sample(freq, volume, x) - values.append(sine) - values.append(sine) - - values_str = b''.join(values) - audio.writeframes(values_str) - audio.close - return open(audio_path, "rb") - - -# Generate random audio in .wav -def generate_random_mix(): - # filename - name = uuid.uuid4().hex - audio_path = dirs.AUDIO_DIR+"/mix_"+name+".wav" - # open file in write mode - audio = wave.open(audio_path, "wb") - audio.setparams((2, 2, 24000, 0, 'NONE', 'not compressed')) - - values = [] - - len = random.randint(8, 10) - - # generate a mix of sine wave and noise - for i in range(0, len): - # two channels - freq_channel_1 = random.randint(FREQ_LOW, FREQ_HIGH) - freq_channel_2 = random.randint(FREQ_LOW, FREQ_HIGH) - # duration of every chunk in seconds - chunk_duration = random.randint(1, 2) - # volume - volume_channel_1 = random.uniform(VOLUME_LOW, VOLUME_HIGH) - volume_channel_2 = random.uniform(VOLUME_LOW, VOLUME_HIGH) - # build the chunk - for x in range(0, chunk_duration * SAMPLE_RATE): - sine_channel_1 = sine_sample(freq_channel_1, volume_channel_1, x) - sine_channel_2 = sine_sample(freq_channel_2, volume_channel_2, x) - noise = noise_sample() - # channel 1 - if random.choice([True, False]): - values.append(sine_channel_1) - else: - values.append(noise) - # channel 2 - if random.choice([True, False]): - values.append(sine_channel_2) - else: - values.append(noise) - - # make a byte string - values_str = b''.join(values) - # write into the file - audio.writeframes(values_str) - # close - audio.close() - - return open(audio_path, "rb") - -# generate random voice in .ogg by converting .wav file -def generate_random_voice(): - # generate a random wav file - wavfile = generate_random_mix() - # file name of the generated .ogg - oggfile = wavfile.name+".ogg" - # convert with ffmpeg - ( - ffmpeg - .input(wavfile.name, format="wav") - .output(oggfile, acodec = "libopus") - .run() - ) - - return open(oggfile, "rb") diff --git a/app/services/info_service.py b/app/services/info_service.py deleted file mode 100644 index 88a683c..0000000 --- a/app/services/info_service.py +++ /dev/null @@ -1,7 +0,0 @@ -# info_service.py - -from configs import templates - -# generate info -def generate_info(name, id): - return templates.INFO_MESSAGE.format(name=name, id=id) diff --git a/app/services/member_service.py b/app/services/member_service.py deleted file mode 100644 index 24243e9..0000000 --- a/app/services/member_service.py +++ /dev/null @@ -1,7 +0,0 @@ -# member_service.py - -from configs import templates - -# generate welcome message -def generate_welcome(name, id): - return templates.WELCOME_MESSAGE.format(name=name, id=id) diff --git a/app/services/text_service.py b/app/services/text_service.py deleted file mode 100644 index 614acd5..0000000 --- a/app/services/text_service.py +++ /dev/null @@ -1,11 +0,0 @@ -# text_service.py - -import random -import string - -# random text generater for the following handlers -def generate_random_text(): - length = random.randint(30,120) - return ''.join(random.choices(string.ascii_letters + - string.digits + - string.punctuation, k=length)) diff --git a/bot.py b/bot.py index 5b09d77..2adb395 100644 --- a/bot.py +++ b/bot.py @@ -1,10 +1,9 @@ # the randomology telegram bot # config -from configs import env - from routes import commands from routes import handlers +from app.middlewares.option_middleware import OptionMiddleware from configs import env @@ -16,7 +15,7 @@ import telebot # bot initialize -bot = telebot.TeleBot(env.BOT_TOKEN) +bot = telebot.TeleBot(env.BOT_TOKEN, use_class_middlewares=True) # register commands commands.register(bot) @@ -24,6 +23,9 @@ # register handlers handlers.register(bot) +# Setup middlewares +bot.setup_middleware(OptionMiddleware()) + # here we go !!! if env.BOT_ENVIRONMENT == "prod": server.run(bot) diff --git a/configs/dirs.py b/configs/dirs.py index f934b23..3ed2099 100644 --- a/configs/dirs.py +++ b/configs/dirs.py @@ -4,7 +4,8 @@ ROOT_DIR = os.path.dirname(os.path.dirname(__file__)) STORAGE_DIR = os.path.join(ROOT_DIR, "storage") -AUDIO_DIR = os.path.join(STORAGE_DIR, "audio") +STORAGE_PUBLIC_DIR = os.path.join(STORAGE_DIR, "public") +AUDIO_DIR = os.path.join(STORAGE_PUBLIC_DIR, "audio") PUBLIC_DIR = os.path.join(ROOT_DIR, "public") PUBLIC_STORAGE_DIR = os.path.join(PUBLIC_DIR, "storage") diff --git a/link_storage.sh b/link_storage.sh index 0aa8ed2..b5a4d3a 100755 --- a/link_storage.sh +++ b/link_storage.sh @@ -1,6 +1,6 @@ #!/bin/bash -FROM=$(pwd)/storage +FROM=$(pwd)/storage/public TO=$(pwd)/public/storage if [[ $1 == "remove" ]]; then diff --git a/routes/commands.py b/routes/commands.py index 596aaee..44cb7dd 100644 --- a/routes/commands.py +++ b/routes/commands.py @@ -10,8 +10,6 @@ def register(bot:TeleBot): telebot.types.BotCommand("help", "Get info"), telebot.types.BotCommand("text", "Random text"), telebot.types.BotCommand("mono", "Monospace random text"), - telebot.types.BotCommand("noise", "Random noise"), - telebot.types.BotCommand("sine", "Random sine wave audio"), - telebot.types.BotCommand("mix", "Random mix"), + telebot.types.BotCommand("audio", "Random audio"), telebot.types.BotCommand("voice", "Random voice") ]) diff --git a/routes/handlers.py b/routes/handlers.py index e2f7c3a..4748944 100644 --- a/routes/handlers.py +++ b/routes/handlers.py @@ -6,12 +6,11 @@ from app.handlers.info_handler import get_info from app.handlers.text_handler import get_random_text from app.handlers.text_handler import get_random_text_mono -from app.handlers.audio_handler import get_random_noise -from app.handlers.audio_handler import get_random_sine -from app.handlers.audio_handler import get_random_mix +from app.handlers.audio_handler import get_random_audio from app.handlers.audio_handler import get_random_voice from app.handlers.member_handler import get_welcome -from app.handlers.inline_handler import inline_dispatch +from app.handlers.inline_handler import inline_text +from app.handlers.inline_handler import inline_voice # register handlers in chats def register(bot:TeleBot): @@ -20,12 +19,11 @@ def register(bot:TeleBot): bot.register_message_handler(get_random_text, commands=['text'], pass_bot=True) bot.register_message_handler(get_random_text_mono, commands=['mono'], pass_bot=True) # random audio - bot.register_message_handler(get_random_noise, commands=['noise'], pass_bot=True) - bot.register_message_handler(get_random_sine, commands=['sine'], pass_bot=True) - bot.register_message_handler(get_random_mix, commands=['mix'], pass_bot=True) + bot.register_message_handler(get_random_audio, commands=['audio'], pass_bot=True) # random voice (a mix of noise and sine wave sent as voice) bot.register_message_handler(get_random_voice, commands=['voice'], pass_bot=True) # chat member change - bot.register_message_handler(get_welcome, content_types=['new_chat_members'], pass_bot=True) + # bot.register_message_handler(get_welcome, content_types=['new_chat_members'], pass_bot=True) # inline - bot.register_inline_handler(inline_dispatch, lambda query: query, pass_bot=True) + bot.register_inline_handler(inline_voice, lambda query: query.query.split(" ",1)[0] == "/v", pass_bot=True) + bot.register_inline_handler(inline_text, lambda query: query , pass_bot=True) diff --git a/storage/audio/.gitignore b/storage/public/audio/.gitignore similarity index 100% rename from storage/audio/.gitignore rename to storage/public/audio/.gitignore diff --git a/tests/audio_test.py b/tests/audio_test.py index eb9aec6..adaffe4 100644 --- a/tests/audio_test.py +++ b/tests/audio_test.py @@ -1,24 +1,29 @@ # audio tests -import io +from app.models.audio import Audio -from app.services.audio_service import generate_random_noise -from app.services.audio_service import generate_random_sine -from app.services.audio_service import generate_random_mix -from app.services.audio_service import generate_random_voice +def test_audio_generated(): + audio = Audio() + assert isinstance(audio.generate(), Audio) -def test_noise_generated(): - noise = generate_random_noise() - assert isinstance(noise, io.BufferedIOBase) +def test_audio_noise(): + audio = Audio() + assert isinstance(audio.generate(options = ['noise']), Audio) -def test_sine_generated(): - sine = generate_random_sine() - assert isinstance(sine, io.BufferedIOBase) +def test_audio_mix(): + audio = Audio() + assert isinstance(audio.generate(options = ['noise', 'sine']), Audio) -def test_mix_generated(): - mix = generate_random_mix() - assert isinstance(mix, io.BufferedIOBase) +def test_audio_wrong_options(): + audio = Audio() + assert isinstance(audio.generate(options = ['aa']), Audio) -def test_voice_generated(): - voice = generate_random_voice() - assert isinstance(voice, io.BufferedIOBase) +def test_audio_empty_options(): + audio = Audio() + assert isinstance(audio.generate(options = ['']), Audio) + +def test_audio_to_mpeg(): + audio = Audio() + audio.generate() + mpeg = audio.to_mpeg() + assert isinstance(mpeg, Audio) diff --git a/tests/info_test.py b/tests/info_test.py deleted file mode 100644 index 743f524..0000000 --- a/tests/info_test.py +++ /dev/null @@ -1,11 +0,0 @@ -# info_test.py - -from app.services.info_service import generate_info -from configs import templates - -def test_welcome_template(): - assert isinstance(templates.INFO_MESSAGE, str) - -def test_info_generated(): - info = generate_info("test", 111) - assert isinstance(info, str) diff --git a/tests/member_test.py b/tests/member_test.py deleted file mode 100644 index 05fc5a8..0000000 --- a/tests/member_test.py +++ /dev/null @@ -1,11 +0,0 @@ -# member tests - -from app.services.member_service import generate_welcome -from configs import templates - -def test_welcome_template(): - assert isinstance(templates.WELCOME_MESSAGE, str) - -def test_welcome_generated(): - welcome = generate_welcome("test",111) - assert isinstance(welcome, str) diff --git a/tests/text_test.py b/tests/text_test.py index ecfb26a..6da578b 100644 --- a/tests/text_test.py +++ b/tests/text_test.py @@ -1,11 +1,43 @@ # text tests -from app.services.text_service import generate_random_text +from app.models.text import Text +from configs import templates def test_text_generated(): - text = generate_random_text() - assert isinstance(text, str) + text = Text() + assert isinstance(text.generate().content(),str) -def test_text_length(): - text = generate_random_text() - assert len(text) >= 30 and len(text) <= 120 +def test_text_options(): + text = Text() + text.generate(options = ['zh','+en', '-d']) + assert isinstance(text.content(),str) + +def test_text_option_only(): + text = Text() + text.generate(options = ['en', '']) + assert isinstance(text.content(),str) and text.content().isascii() + +def test_text_wrong_options(): + text = Text() + text.generate(options = ['aaa']) + assert isinstance(text.content(),str) and text.content().isascii() + +def test_text_empty_options(): + text = Text() + text.generate(options = ['']) + assert isinstance(text.content(),str) and text.content().isascii() + + +def test_text_mono(): + text = Text() + text.generate() + text.to_mono() + assert isinstance(text.content(),str) and (text.content()[0]=="`") and (text.content()[-1]=="`") + +def test_get_welcome(): + welcome = templates.WELCOME_MESSAGE + assert isinstance(welcome, str) + +def test_get_info(): + info = templates.INFO_MESSAGE + assert isinstance(info, str) diff --git a/tests/voice_test.py b/tests/voice_test.py new file mode 100644 index 0000000..100e25f --- /dev/null +++ b/tests/voice_test.py @@ -0,0 +1,11 @@ +# voice tests + +from app.models.voice import Voice + +def test_voice_generated(): + voice = Voice() + assert isinstance(voice.generate().to_voice(), Voice) + +def test_voice_noise(): + voice = Voice() + assert isinstance(voice.generate(options = ['noise']).to_voice(), Voice)