├── runtime.txt ├── Procfile ├── .deepsource.toml ├── requirements.txt ├── LICENSE ├── acutebot ├── funcs │ ├── __init__.py │ ├── spotify.py │ ├── favorite.py │ ├── subtitles.py │ ├── lyrics.py │ ├── manga.py │ ├── anime.py │ ├── movies.py │ ├── miscs.py │ ├── tvseries.py │ ├── music.py │ └── inlinequery.py ├── helpers │ ├── database │ │ ├── __init__.py │ │ ├── users_sql.py │ │ ├── favorites_sql.py │ │ └── spotify_sql.py │ ├── parsedata.py │ ├── keyboard.py │ ├── spthelper.py │ └── strings.py ├── __init__.py └── __main__.py ├── README.md ├── app.py └── .gitignore /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.5 2 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python3 -m acutebot 2 | web: python3 app.py 3 | -------------------------------------------------------------------------------- /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "python" 5 | enabled = true 6 | 7 | [analyzers.meta] 8 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-telegram-bot==12.8 2 | pyrogram 3 | requests 4 | sqlalchemy 5 | psycopg2 6 | ujson 7 | tgcrypto 8 | pyfy 9 | lyricsgenius 10 | deezloader 11 | mutagen 12 | speedtest-cli>=2.0.2 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Stɑrry Shivɑm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /acutebot/funcs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | 17 | from acutebot import LOG 18 | from os.path import dirname, basename, isfile 19 | import glob 20 | 21 | def list_modules(): 22 | mod_paths = glob.glob(dirname(__file__) + "/*.py") 23 | all_modules = [basename(f)[:-3] for f in mod_paths if isfile(f) 24 | and f.endswith(".py") 25 | and not f.endswith('__init__.py')] 26 | return all_modules 27 | 28 | ALL_FUNCS = sorted(list_modules()) 29 | LOG.info("Modules loaded: %s", str(ALL_FUNCS)) 30 | __all__ = ALL_FUNCS + ["ALL_FUNCS"] 31 | -------------------------------------------------------------------------------- /acutebot/helpers/database/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | 17 | from acutebot import DB_URI 18 | 19 | from sqlalchemy import create_engine 20 | from sqlalchemy.ext.declarative import declarative_base 21 | from sqlalchemy.orm import sessionmaker, scoped_session 22 | 23 | def start() -> scoped_session: 24 | engine = create_engine(DB_URI, client_encoding="utf8") 25 | BASE.metadata.bind = engine 26 | BASE.metadata.create_all(engine) 27 | return scoped_session(sessionmaker(bind=engine, autoflush=False)) 28 | 29 | BASE = declarative_base() 30 | SESSION = start() 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](https://telegra.ph/file/8109bf8f6b27ce9b45ff1.jpg) 2 |

αcutєвσt

3 | 4 | Modular telegram bot to get Movies, Anime, Muisc & much more! 5 | 6 | 7 | 8 | [![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) 9 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 10 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 11 | 12 | Note: This is an old source code, this repository is not being updated anymore! 13 |
14 | Commands Available: 15 | 16 |
/start
: Cool command to check if bot is working. 17 |
/tvshows
: Gets you tv shows details. 18 |
/movies
: Gets you movie details. 19 |
/anime
: Gets your favorite anime details. 20 |
/manga
: Gets you info about your favorite manga titles 21 |
/lyrics
: Get lyrics of your favourite songs :) 22 |
/music
: Download your favourite songs in FLAC & 320kbs quality! 23 |
/nowplaying
: Flex your currently playing song in spotify. 24 |
watchlist
: Get your saved watchlist :D 25 |
subtitle
: Download subtitles for your favourite anime & movies 26 |
/reddit
: Fun command to get memes scraped from reddit. 27 |
/cancel
: Cancels the current ongoing task. 28 |
29 | 30 | Built with [Python telegram bot](https://github.com/python-telegram-bot/python-telegram-bot) library. 31 | This bot scrapes details of movies and TV shows, using [TheMovieDB](https://developers.themoviedb.org) API, Anime and 32 | Manga data from [Kitsu](https://kitsu.io/explore/anime), & 33 | Music stuffs from deezer using [deezloader](https://github.com/An0nimia/deezloader). 34 | Can be found in telegram as [𝙰𝚌𝚞𝚝𝚎𝙱𝚘𝚝](https://t.me/acutebot)! 35 | 36 | 37 | Contributions of all sizes are welcome :) 38 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import os, tornado 17 | from telegram import Bot 18 | from acutebot import TOKEN 19 | from acutebot.helpers.database.spotify_sql import update_creds 20 | from acutebot.helpers.spthelper import SpotifyClient 21 | 22 | 23 | bot = Bot(TOKEN) 24 | 25 | 26 | class MainHandler(tornado.web.RequestHandler): 27 | def get(self): 28 | self.write("Hello World!") 29 | 30 | 31 | class SpotifyCallback(tornado.web.RequestHandler): 32 | def get(self): 33 | if self.get_argument("code", ""): 34 | grant = self.get_argument("code", "") 35 | callback_state = self.get_argument("state", "") 36 | spotify = SpotifyClient() 37 | user_creds = spotify.build_user_creds(grant=grant) 38 | update_creds( 39 | callback_state, 40 | user_creds.id, 41 | user_creds.access_token, 42 | user_creds.refresh_token, 43 | ) 44 | print("user logged in successfully") 45 | bot.sendMessage(callback_state, "Successfully logged in!") 46 | self.redirect("https://t.me/" + bot.username) 47 | 48 | 49 | urls = [(r"/", MainHandler), (r"/acutebot/webserver", SpotifyCallback)] 50 | PORT = os.environ.get("PORT", 8888) 51 | 52 | 53 | def start(): 54 | 55 | print("Tornado server started") 56 | 57 | app = tornado.web.Application(urls) 58 | app.listen(PORT, address="0.0.0.0") 59 | tornado.ioloop.IOLoop.current().start() 60 | 61 | 62 | if __name__ == "__main__": 63 | start() 64 | -------------------------------------------------------------------------------- /acutebot/helpers/database/users_sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import threading 17 | 18 | from sqlalchemy import Column, Integer, UnicodeText 19 | from acutebot.helpers.database import SESSION, BASE 20 | from acutebot import dp 21 | 22 | 23 | class Users(BASE): 24 | __tablename__ = "users" 25 | user_id = Column(Integer, primary_key=True) 26 | username = Column(UnicodeText) 27 | 28 | def __init__(self, user_id, username=None): 29 | self.user_id = user_id 30 | self.username = username 31 | 32 | def __repr__(self): 33 | return "".format(self.username, self.user_id) 34 | 35 | 36 | Users.__table__.create(checkfirst=True) 37 | INSERTION_LOCK = threading.RLock() 38 | 39 | 40 | def ensure_bot_in_db(): 41 | with INSERTION_LOCK: 42 | bot = Users(dp.bot.id, dp.bot.username) 43 | SESSION.merge(bot) 44 | SESSION.commit() 45 | 46 | 47 | def update_user(user_id, username): 48 | with INSERTION_LOCK: 49 | user = SESSION.query(Users).get(user_id) 50 | if not user: 51 | user = Users(user_id, username) 52 | SESSION.add(user) 53 | SESSION.flush() 54 | else: 55 | user.username = username 56 | SESSION.commit() 57 | 58 | 59 | def get_all_users(): 60 | try: 61 | return SESSION.query(Users).all() 62 | finally: 63 | SESSION.close() 64 | 65 | 66 | def users_count(): 67 | try: 68 | return SESSION.query(Users).count() 69 | finally: 70 | SESSION.close() 71 | 72 | ensure_bot_in_db() 73 | -------------------------------------------------------------------------------- /acutebot/helpers/database/favorites_sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import threading 17 | from acutebot.helpers.database import SESSION, BASE 18 | from sqlalchemy import Column, UnicodeText, Numeric 19 | 20 | 21 | class Favourites(BASE): 22 | __tablename__ = "favourites" 23 | user_id = Column(Numeric, primary_key=True) 24 | data = Column(UnicodeText, primary_key=True) 25 | 26 | def __init__(self, user_id, data): 27 | self.user_id = user_id 28 | self.data = data 29 | 30 | 31 | Favourites.__table__.create(checkfirst=True) 32 | FAV_INSERTION_LOCK = threading.RLock() 33 | 34 | 35 | def check_fav(user_id, data): 36 | try: 37 | return SESSION.query(Favourites).get((int(user_id), str(data))) 38 | finally: 39 | SESSION.close() 40 | 41 | 42 | def get_fav(user_id): 43 | try: 44 | return ( 45 | SESSION.query(Favourites).filter(Favourites.user_id == int(user_id)).all() 46 | ) 47 | finally: 48 | SESSION.close() 49 | 50 | 51 | def add_fav(user_id, data): 52 | with FAV_INSERTION_LOCK: 53 | to_check = check_fav(user_id, data) 54 | if not to_check: 55 | adder = Favourites(int(user_id), str(data)) 56 | SESSION.add(adder) 57 | SESSION.commit() 58 | return True 59 | return False 60 | 61 | 62 | def remove_fav(user_id): 63 | with FAV_INSERTION_LOCK: 64 | to_check = get_fav(user_id) 65 | if not to_check: 66 | return False 67 | rem = SESSION.query(Favourites).filter(Favourites.user_id == user_id) 68 | rem.delete() 69 | SESSION.commit() 70 | return True 71 | 72 | 73 | def fav_count(): 74 | try: 75 | return SESSION.query(Favourites).count() 76 | finally: 77 | SESSION.close() 78 | -------------------------------------------------------------------------------- /acutebot/helpers/database/spotify_sql.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE 14 | 15 | 16 | import threading 17 | 18 | from sqlalchemy import Column, Integer, UnicodeText 19 | from acutebot.helpers.database import SESSION, BASE 20 | 21 | 22 | class SpotifyCreds(BASE): 23 | __tablename__ = "spotifycreds" 24 | user_id = Column(Integer, primary_key=True) 25 | spotify_id = Column(UnicodeText) 26 | spotify_access_token = Column(UnicodeText) 27 | spotify_refresh_token = Column(UnicodeText) 28 | 29 | def __init__( 30 | self, 31 | user_id, 32 | spotify_id=None, 33 | spotify_access_token=None, 34 | spotify_refresh_token=None, 35 | ): 36 | self.user_id = user_id 37 | self.spotify_id = spotify_id 38 | self.spotify_access_token = spotify_access_token 39 | self.spotify_refresh_token = spotify_refresh_token 40 | 41 | 42 | SpotifyCreds.__table__.create(checkfirst=True) 43 | SPT_INSERTION_LOCK = threading.RLock() 44 | 45 | 46 | def update_creds( 47 | user_id, spotify_id=None, spotify_access_token=None, spotify_refresh_token=None 48 | ): 49 | with SPT_INSERTION_LOCK: 50 | sptcreds = SESSION.query(SpotifyCreds).get(user_id) 51 | if not sptcreds: 52 | sptcreds = SpotifyCreds( 53 | user_id, spotify_id, spotify_access_token, spotify_refresh_token 54 | ) 55 | SESSION.add(sptcreds) 56 | SESSION.flush() 57 | else: 58 | sptcreds.spotify_id = spotify_id 59 | sptcreds.spotify_access_token = spotify_access_token 60 | sptcreds.spotify_refresh_token = spotify_refresh_token 61 | SESSION.commit() 62 | 63 | 64 | def get_sptuser(user_id): 65 | try: 66 | return SESSION.query(SpotifyCreds).get(user_id) 67 | finally: 68 | SESSION.close() 69 | -------------------------------------------------------------------------------- /acutebot/funcs/spotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | from telegram.ext import CommandHandler 17 | from telegram.ext.dispatcher import run_async 18 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton 19 | 20 | from acutebot import dp, typing 21 | from acutebot.helpers.spthelper import SpotifyClient, get_spotify_data 22 | import acutebot.helpers.strings as st 23 | 24 | 25 | def authorize(update, user_id): 26 | msg = update.effective_message 27 | user = update.effective_user 28 | spotify = SpotifyClient() 29 | if spotify.is_oauth_ready: 30 | url = spotify.auth_uri(state=user_id) 31 | msg.reply_text( 32 | st.SPT_LOGIN.format(user.first_name), 33 | reply_markup=InlineKeyboardMarkup( 34 | [[InlineKeyboardButton(text="Authorize", url=url)]] 35 | ), 36 | ) 37 | 38 | else: 39 | msg.reply_text("Something went wrong! Please report to @starryboi") 40 | 41 | 42 | @run_async 43 | @typing 44 | def now_playing(update, context): 45 | user = update.effective_user 46 | chat = update.effective_chat 47 | msg = update.effective_message 48 | 49 | spt = get_spotify_data(user.id) 50 | if not spt: 51 | if chat.type == "private": 52 | return authorize(update, user.id) 53 | return msg.reply_text(st.SPT_LOGIN_PM) 54 | 55 | text = "" 56 | music = spt.current_music 57 | if music: 58 | text = f"{user.first_name} is currently listening to" 59 | else: 60 | music = spt.last_music 61 | text = f"{user.first_name} was listening to" 62 | 63 | text += f" {music.name} by {music.artist}" 64 | msg.reply_text( 65 | text, 66 | reply_markup=InlineKeyboardMarkup( 67 | [[InlineKeyboardButton(text="Open in spotify", url=music.url)]] 68 | ), 69 | ) 70 | 71 | 72 | dp.add_handler(CommandHandler("nowplaying", now_playing)) 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Config and db logfile 2 | acutebot/config.py 3 | acute.session 4 | logfile 5 | acute-lyrics.txt 6 | temp/ 7 | 8 | # Byte-compiled / optimized / DLL files 9 | __pycache__/ 10 | *.py[cod] 11 | *$py.class 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | pip-wheel-metadata/ 31 | share/python-wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .nox/ 51 | .coverage 52 | .coverage.* 53 | .cache 54 | nosetests.xml 55 | coverage.xml 56 | *.cover 57 | *.py,cover 58 | .hypothesis/ 59 | .pytest_cache/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | target/ 83 | 84 | # Jupyter Notebook 85 | .ipynb_checkpoints 86 | 87 | # IPython 88 | profile_default/ 89 | ipython_config.py 90 | 91 | # pyenv 92 | .python-version 93 | 94 | # pipenv 95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 98 | # install all needed dependencies. 99 | #Pipfile.lock 100 | 101 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 102 | __pypackages__/ 103 | 104 | # Celery stuff 105 | celerybeat-schedule 106 | celerybeat.pid 107 | 108 | # SageMath parsed files 109 | *.sage.py 110 | 111 | # Environments 112 | .env 113 | .venv 114 | env/ 115 | venv/ 116 | ENV/ 117 | env.bak/ 118 | venv.bak/ 119 | 120 | # Spyder project settings 121 | .spyderproject 122 | .spyproject 123 | 124 | # Rope project settings 125 | .ropeproject 126 | 127 | # mkdocs documentation 128 | /site 129 | 130 | # mypy 131 | .mypy_cache/ 132 | .dmypy.json 133 | dmypy.json 134 | 135 | # Pyre type checker 136 | .pyre/ 137 | -------------------------------------------------------------------------------- /acutebot/funcs/favorite.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | from telegram.ext import CommandHandler, CallbackQueryHandler 17 | from telegram.ext.dispatcher import run_async 18 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton 19 | 20 | from acutebot import dp, typing 21 | import acutebot.helpers.strings as st 22 | import acutebot.helpers.database.favorites_sql as sql 23 | 24 | 25 | @run_async 26 | def add_favorite(update, context): 27 | query = update.callback_query 28 | userid = update.effective_user.id 29 | match = query.data.split("_")[1] 30 | 31 | add = sql.add_fav(userid, match) 32 | if add: 33 | query.answer(st.SAVED_FAV.format(match), show_alert=True) 34 | else: 35 | query.answer(st.FAV_EXIST, show_alert=True) 36 | 37 | 38 | @run_async 39 | @typing 40 | def list_favorite(update, context): 41 | msg = update.effective_message 42 | user = update.effective_user 43 | fav = sql.get_fav(user.id) 44 | if fav: 45 | text = "🔖 Your watchlist:\n\n" 46 | for title in fav: 47 | text += f"• {title.data}\n" 48 | keyb = [ 49 | [InlineKeyboardButton(text="Watched ✅", callback_data=f"remfav_{user.id}")] 50 | ] 51 | msg.reply_text(text, reply_markup=InlineKeyboardMarkup(keyb)) 52 | else: 53 | msg.reply_text(st.NOFAVS) 54 | 55 | 56 | @run_async 57 | def rem_favorite(update, context): 58 | query = update.callback_query 59 | user = update.effective_user 60 | user_id = query.data.split("_")[1] 61 | 62 | if user.id == int(user_id): 63 | sql.remove_fav(user_id) 64 | query.message.edit_text(st.REMFAV) 65 | else: 66 | query.answer(st.NOT_ALLOWED, show_alert=True) 67 | 68 | 69 | LIST_FAV_HANDLER = CommandHandler("watchlist", list_favorite) 70 | FAV_CLEAR_HANDLER = CallbackQueryHandler(rem_favorite, pattern=r"remfav_") 71 | FAV_ADD_HANDLER = CallbackQueryHandler(add_favorite, pattern=r"addfav_") 72 | 73 | 74 | dp.add_handler(LIST_FAV_HANDLER) 75 | dp.add_handler(FAV_CLEAR_HANDLER) 76 | dp.add_handler(FAV_ADD_HANDLER) 77 | -------------------------------------------------------------------------------- /acutebot/helpers/parsedata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import itertools 17 | from uuid import uuid4 18 | from telegram.constants import MAX_CAPTION_LENGTH as MAX_CAP_LEN 19 | from telegram import InlineQueryResultArticle, InputTextMessageContent 20 | 21 | 22 | def byname(val): 23 | if val == "": 24 | return "Not available" 25 | datalist = [] 26 | for x in val: 27 | datalist.append(x["name"]) 28 | data = ", ".join(datalist) 29 | return data 30 | 31 | 32 | def currency(val): 33 | """Format currency""" 34 | if val == "": 35 | return "Not available" 36 | curr = "${:,.2f}".format(val) 37 | return curr 38 | 39 | 40 | def byindex(val): 41 | try: 42 | return val[0]["name"] 43 | except IndexError: 44 | return "Not Available" 45 | 46 | 47 | def tvruntime(val): 48 | try: 49 | return str(val[0]) + " minutes" 50 | except IndexError: 51 | return "Not Available" 52 | 53 | 54 | def article( 55 | title="", description="", message_text="", thumb_url=None, reply_markup=None 56 | ): 57 | return InlineQueryResultArticle( 58 | id=uuid4(), 59 | title=title, 60 | description=description, 61 | thumb_url=thumb_url, 62 | input_message_content=InputTextMessageContent( 63 | message_text=message_text, disable_web_page_preview=False 64 | ), 65 | reply_markup=reply_markup, 66 | ) 67 | 68 | 69 | def paginate(iterable, page_size): 70 | while True: 71 | i1, i2 = itertools.tee(iterable) 72 | iterable, page = ( 73 | itertools.islice(i1, page_size, None), 74 | list(itertools.islice(i2, page_size)), 75 | ) 76 | if len(page) == 0: 77 | break 78 | yield list(page) 79 | 80 | 81 | def sort_caps(text, c_id, tv=False, mv=False, anime=False, manga=False): 82 | if len(text) > MAX_CAP_LEN: 83 | text = text[: MAX_CAP_LEN - 80] 84 | text += "" 85 | if tv: 86 | text += f"...read more" 87 | if mv: 88 | text += ( 89 | f"...read more" 90 | ) 91 | if anime: 92 | text += f"...read more" 93 | if manga: 94 | text += f"...read more" 95 | 96 | return text 97 | -------------------------------------------------------------------------------- /acutebot/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import os, sys, logging 17 | from functools import wraps 18 | from telegram.ext import Updater, Defaults 19 | from telegram import ChatAction, ParseMode 20 | 21 | ENV = bool(os.environ.get("ENV", False)) 22 | if ENV: 23 | TOKEN = os.environ.get("TOKEN") 24 | WORKERS = int(os.environ.get("WORKERS", 8)) 25 | TMDBAPI = os.environ.get("TMDBAPI") 26 | DB_URI = os.environ.get("DATABASE_URL") 27 | GENIUS = os.environ.get("GENIUS") 28 | SPT_CLIENT_SECRET = os.environ.get("SPT_CLIENT_SECRET") 29 | SPT_CLIENT_ID = os.environ.get("SPT_CLIENT_ID") 30 | DEBUG = bool(os.environ.get("DEBUG", False)) 31 | ARLTOKEN = os.environ.get("ARL") 32 | APP_URL = os.environ.get("APP_URL") 33 | APIID = os.environ.get("APIID") 34 | APIHASH = os.environ.get("APIHASH") 35 | 36 | 37 | else: 38 | from acutebot.config import Config 39 | 40 | TOKEN = Config.TOKEN 41 | WORKERS = Config.WORKERS 42 | TMDBAPI = Config.TMDBAPI 43 | DB_URI = Config.DB_URI 44 | GENIUS = Config.GENIUS 45 | SPT_CLIENT_SECRET = Config.SPT_CLIENT_SECRET 46 | SPT_CLIENT_ID = Config.SPT_CLIENT_ID 47 | DEBUG = Config.DEBUG 48 | ARLTOKEN = Config.ARL 49 | APP_URL = Config.APP_URL 50 | APIID = Config.APIID 51 | APIHASH = Config.APIHASH 52 | 53 | 54 | if DEBUG: 55 | logging.basicConfig( 56 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 57 | level=logging.DEBUG, 58 | ) 59 | else: 60 | logging.basicConfig( 61 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 62 | level=logging.INFO, 63 | ) 64 | 65 | __version__ = "1.1.3-rev09" 66 | 67 | DEV_ID = 894380120 68 | LOG = logging.getLogger(__name__) 69 | 70 | # Check python version: 71 | if sys.version_info[0] < 3 or sys.version_info[1] < 6: 72 | LOG.info("You MUST need to have python version 3.6! shutting down...") 73 | sys.exit(1) 74 | 75 | 76 | def typing(func): 77 | """Sends typing action while processing func command.""" 78 | 79 | @wraps(func) 80 | def command_func(update, context, *args, **kwargs): 81 | context.bot.send_chat_action( 82 | chat_id=update.effective_chat.id, action=ChatAction.TYPING 83 | ) 84 | return func(update, context, *args, **kwargs) 85 | 86 | return command_func 87 | 88 | 89 | # Use HTML treewide; 90 | defaults = Defaults(parse_mode=ParseMode.HTML) 91 | updater = Updater(TOKEN, use_context=True, workers=WORKERS, defaults=defaults) 92 | dp = updater.dispatcher 93 | -------------------------------------------------------------------------------- /acutebot/helpers/keyboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | from telegram import InlineKeyboardButton 17 | 18 | 19 | def keyboard( 20 | ytkey=None, 21 | homepage=None, 22 | title=None, 23 | imdbid=None, 24 | tv_id=None, 25 | mv_id=None, 26 | anime_ytkey=None, 27 | anime_id=None, 28 | manga_id=None, 29 | ): 30 | """ 31 | Attach InlineKeyboardButton dynamically from data 32 | """ 33 | 34 | keyblist = [[]] 35 | if ytkey: 36 | if len(ytkey["results"]) > 0: 37 | ytkey = ytkey["results"][0]["key"] 38 | keyblist[0].append( 39 | InlineKeyboardButton( 40 | text="📹 Trailer", url=f"https://www.youtube.com/watch?v={ytkey}" 41 | ) 42 | ) 43 | 44 | if homepage and homepage != "": 45 | keyblist.append([InlineKeyboardButton(text="📃 Homepage", url=homepage)]) 46 | 47 | if imdbid: 48 | keyblist[0].append( 49 | InlineKeyboardButton( 50 | text="🎞️ IMDb", url=f"https://m.imdb.com/title/{imdbid}" 51 | ) 52 | ) 53 | 54 | if title: 55 | keyblist.append( 56 | [ 57 | InlineKeyboardButton( 58 | text="Save to watchlist 🔖", callback_data=f"addfav_{title[:54]}" 59 | ) 60 | ] 61 | ) 62 | 63 | if tv_id: 64 | keyblist.append( 65 | [ 66 | InlineKeyboardButton( 67 | text="More information", 68 | url=f"https://www.themoviedb.org/tv/{tv_id}", 69 | ) 70 | ] 71 | ) 72 | 73 | if mv_id: 74 | keyblist.append( 75 | [ 76 | InlineKeyboardButton( 77 | text="More information", 78 | url=f"https://www.themoviedb.org/movie/{mv_id}", 79 | ) 80 | ] 81 | ) 82 | 83 | if anime_ytkey: 84 | keyblist[0].append( 85 | InlineKeyboardButton( 86 | text="📹 Trailer", url=f"https://www.youtube.com/watch?v={anime_ytkey}" 87 | ) 88 | ) 89 | 90 | if anime_id: 91 | keyblist[0].append( 92 | InlineKeyboardButton( 93 | text="Information", url=f"https://kitsu.io/anime/{anime_id}" 94 | ) 95 | ) 96 | 97 | if manga_id: 98 | keyblist[0].append( 99 | InlineKeyboardButton( 100 | text="More Information", url=f"https://kitsu.io/manga/{manga_id}" 101 | ) 102 | ) 103 | return keyblist 104 | -------------------------------------------------------------------------------- /acutebot/helpers/spthelper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | from pyfy import Spotify as Pyfy, ClientCreds, UserCreds 17 | from pyfy.excs import ApiError 18 | from dataclasses import dataclass 19 | 20 | from acutebot import TOKEN, SPT_CLIENT_SECRET, SPT_CLIENT_ID, APP_URL 21 | from acutebot.helpers.database.spotify_sql import get_sptuser 22 | 23 | import typing 24 | 25 | 26 | @dataclass 27 | class Music: 28 | id: str 29 | name: str 30 | artist: str 31 | url: str 32 | thumbnail: str 33 | 34 | def __init__(self, music: dict): 35 | self.id = music["id"] 36 | self.name = music["name"] 37 | self.artist = music["artists"][0]["name"] 38 | self.url = music["external_urls"]["spotify"] 39 | self.thumbnail = music["album"]["images"][1]["url"] 40 | 41 | 42 | class Spotify: 43 | def __init__(self, user): 44 | 45 | try: 46 | self._client = SpotifyClient( 47 | access_token=user.spotify_access_token, 48 | refresh_token=user.spotify_refresh_token, 49 | ) 50 | except ApiError: 51 | raise Exception("tokens invalid") 52 | 53 | @property 54 | def current_music(self) -> typing.Optional[Music]: 55 | try: 56 | current_status = self._client.currently_playing() 57 | music = current_status["item"] 58 | return Music(music) 59 | except Exception: 60 | return 61 | 62 | @property 63 | def last_music(self) -> Music: 64 | music = self._client.recently_played_tracks(limit=1)["items"][0]["track"] 65 | return Music(music) 66 | 67 | 68 | class SpotifyClient(Pyfy): 69 | def __init__( 70 | self, access_token=None, refresh_token=None, 71 | ): 72 | scopes = [ 73 | "user-read-recently-played", 74 | "user-read-playback-state", 75 | ] 76 | 77 | user_creds = None 78 | 79 | if access_token and refresh_token: 80 | user_creds = UserCreds( 81 | access_token=access_token, refresh_token=refresh_token 82 | ) 83 | 84 | super().__init__( 85 | client_creds=ClientCreds( 86 | client_id=SPT_CLIENT_ID, 87 | client_secret=SPT_CLIENT_SECRET, 88 | redirect_uri=APP_URL + "acutebot/webserver", 89 | scopes=scopes, 90 | ), 91 | user_creds=user_creds, 92 | ) 93 | 94 | 95 | def get_spotify_data(user_id): 96 | try: 97 | user = get_sptuser(user_id) 98 | return Spotify(user) 99 | except Exception: 100 | return False 101 | -------------------------------------------------------------------------------- /acutebot/funcs/subtitles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import requests as r 17 | from io import BytesIO 18 | 19 | from telegram.ext import ( 20 | MessageHandler, 21 | CommandHandler, 22 | CallbackQueryHandler, 23 | Filters, 24 | ConversationHandler, 25 | ) 26 | 27 | from telegram.ext.dispatcher import run_async 28 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, ForceReply 29 | 30 | from acutebot import dp, typing 31 | from acutebot.helpers import strings as st 32 | 33 | base_url = "http://subtitle.iamidiotareyoutoo.com" 34 | 35 | 36 | @run_async 37 | @typing 38 | def subs_entry(update, context): 39 | 40 | update.effective_message.reply_text( 41 | st.TOSEARCHSUBS, reply_markup=ForceReply(selective=True), 42 | ) 43 | 44 | return 1 45 | 46 | 47 | @run_async 48 | @typing 49 | def getsubs(update, context): 50 | msg = update.effective_message 51 | text = update.message.text 52 | query = text.replace(" ", "%20") 53 | 54 | res = r.get(f"{base_url}/search/{query}/1").json() 55 | resarr = res.get("r") 56 | if len(resarr) <= 0: 57 | msg.reply_text(st.NOT_FOUND) 58 | return -1 59 | 60 | keyb = [] 61 | for x in resarr: 62 | keyb.append( 63 | [ 64 | InlineKeyboardButton( 65 | text=x.get("SIQ"), callback_data=f"subs_{x.get('DMCA_ID')}" 66 | ) 67 | ] 68 | ) 69 | msg.reply_text(st.SUBS_STR.format(text), reply_markup=InlineKeyboardMarkup(keyb)) 70 | return ConversationHandler.END 71 | 72 | 73 | @run_async 74 | def subsbutton(update, context): 75 | query = update.callback_query 76 | chat = update.effective_chat 77 | query.answer() 78 | tm = query.message.reply_text("⌛ Hold on . . .") 79 | query.message.delete() 80 | 81 | subs_id = query.data.split("_")[1] 82 | res = r.get(f"{base_url}/get/{subs_id}/").json() 83 | dl_link = base_url + res.get("DL_LINK") 84 | dl_content_name = res.get("DL_SUB_NAME") 85 | 86 | dl_content = BytesIO(r.get(dl_link).content) 87 | dl_content.name = dl_content_name 88 | context.bot.sendDocument(chat.id, dl_content, caption="Subtitle via @acutebot 🎸") 89 | tm.delete() 90 | 91 | 92 | @run_async 93 | @typing 94 | def cancel(update, context): 95 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 96 | return ConversationHandler.END 97 | 98 | 99 | SUBS_HANDLER = ConversationHandler( 100 | entry_points=[CommandHandler("subtitle", subs_entry)], 101 | states={1: [MessageHandler(Filters.text & ~Filters.command, getsubs)]}, 102 | fallbacks=[CommandHandler("cancel", cancel)], 103 | conversation_timeout=120, 104 | ) 105 | 106 | SUBS_CALLBACK_HANDLER = CallbackQueryHandler(subsbutton, pattern=r"subs_") 107 | 108 | 109 | dp.add_handler(SUBS_HANDLER) 110 | dp.add_handler(SUBS_CALLBACK_HANDLER) 111 | -------------------------------------------------------------------------------- /acutebot/funcs/lyrics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import os, lyricsgenius 17 | 18 | from telegram.ext.dispatcher import run_async 19 | from telegram.ext import CommandHandler, MessageHandler, Filters, ConversationHandler 20 | from telegram.error import BadRequest 21 | from telegram import ForceReply 22 | 23 | from acutebot import dp, typing, LOG, GENIUS 24 | from acutebot.helpers import strings as st 25 | 26 | ARTIST, LYRICS = range(2) 27 | SONGDICT = {} 28 | 29 | if GENIUS is not None: 30 | genius = lyricsgenius.Genius(GENIUS) 31 | 32 | 33 | @run_async 34 | @typing 35 | def songname(update, context): 36 | update.effective_message.reply_text( 37 | st.SONGNAME, reply_markup=ForceReply(selective=True) 38 | ) 39 | 40 | return ARTIST 41 | 42 | 43 | @run_async 44 | @typing 45 | def artistname(update, context): 46 | msg = update.effective_message 47 | user = update.effective_user 48 | song = update.message.text 49 | 50 | SONGDICT[user.id] = song 51 | msg.reply_text(st.ARTISTNAME, reply_markup=ForceReply(selective=True)) 52 | 53 | return LYRICS 54 | 55 | 56 | @run_async 57 | @typing 58 | def sendlyrics(update, context): 59 | chat = update.effective_chat 60 | msg = update.effective_message 61 | user = update.effective_user 62 | artist = update.message.text 63 | 64 | try: 65 | song = SONGDICT[user.id] 66 | except KeyError: 67 | msg.reply_text(st.LYRICS_ERR) 68 | return -1 69 | rep = msg.reply_text("🔍 Looking for lyrics . . .") 70 | lyrics = genius.search_song(song, artist) 71 | if lyrics is None: 72 | msg.reply_text(st.LYRIC_NOT_FOUND) 73 | rep.delete() 74 | return -1 75 | try: 76 | msg.reply_text(f"🎸 {song} by {artist}:\n\n{lyrics.lyrics}") 77 | 78 | except BadRequest as excp: 79 | if excp.message == "Message is too long": 80 | msg.reply_text(st.LYRICS_TOO_BIG) 81 | with open("acute-lyrics.txt", "w+") as f: 82 | f.write(f"🎧 {song} by {artist}\n\n{lyrics.lyrics}") 83 | context.bot.sendDocument( 84 | chat_id=chat.id, 85 | document=open("acute-lyrics.txt", "rb"), 86 | caption=f"🎸 {song} - {artist}", 87 | ) 88 | os.remove("acute-lyrics.txt") 89 | else: 90 | LOG.error(excp.message) 91 | 92 | rep.delete() 93 | del SONGDICT[user.id] 94 | return -1 95 | 96 | 97 | @run_async 98 | @typing 99 | def cancel(update, context): 100 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 101 | return ConversationHandler.END 102 | 103 | 104 | LYRICS_HANDLER = ConversationHandler( 105 | entry_points=[CommandHandler("lyrics", songname)], 106 | states={ 107 | ARTIST: [MessageHandler(Filters.text & ~Filters.command, artistname)], 108 | LYRICS: [MessageHandler(Filters.text & ~Filters.command, sendlyrics)], 109 | }, 110 | fallbacks=[CommandHandler("cancel", cancel)], 111 | conversation_timeout=120, 112 | ) 113 | 114 | dp.add_handler(LYRICS_HANDLER) 115 | -------------------------------------------------------------------------------- /acutebot/funcs/manga.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import requests as r 17 | 18 | from telegram.ext import ( 19 | MessageHandler, 20 | CommandHandler, 21 | Filters, 22 | ConversationHandler, 23 | CallbackQueryHandler, 24 | ) 25 | 26 | from telegram.ext.dispatcher import run_async 27 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, ForceReply 28 | 29 | from acutebot import dp, typing 30 | from acutebot.helpers import strings as st 31 | from acutebot.helpers.parsedata import sort_caps 32 | from acutebot.helpers.keyboard import keyboard 33 | 34 | base_url = "https://kitsu.io/api/edge" 35 | tempdict = {} 36 | 37 | 38 | @run_async 39 | @typing 40 | def manga_entry(update, context): 41 | update.effective_message.reply_text( 42 | st.TOSEARCH_MANGA, reply_markup=ForceReply(selective=True), 43 | ) 44 | 45 | return 1 46 | 47 | 48 | @run_async 49 | @typing 50 | def manga(update, context): 51 | msg = update.message 52 | user = update.effective_user 53 | query = msg.text.replace(" ", "%20") 54 | 55 | res = r.get(f"{base_url}/manga?filter%5Btext%5D={query}") 56 | if res.status_code != 200: 57 | msg.reply_text(st.API_ERR) 58 | return -1 59 | 60 | res = res.json()["data"] 61 | if len(res) <= 0: 62 | msg.reply_text(st.NOT_FOUND) 63 | return -1 64 | 65 | # Add results array with user's id as key 66 | tempdict[user.id] = res 67 | 68 | keyb = [] 69 | for x in enumerate(res): 70 | titles = x[1]["attributes"]["titles"] 71 | keyb.append( 72 | [ 73 | InlineKeyboardButton( 74 | text=f"{titles.get('en') if titles.get('en') else titles.get('ja_jp')}", 75 | callback_data=f"manga_{x[0]}_{user.id}", 76 | ) 77 | ] 78 | ) 79 | 80 | msg.reply_text( 81 | f"🔍 Search results for {msg.text}:", 82 | reply_markup=InlineKeyboardMarkup(keyb[:6]), 83 | ) 84 | 85 | return ConversationHandler.END 86 | 87 | 88 | @run_async 89 | def manga_button(update, context): 90 | query = update.callback_query 91 | chat = update.effective_chat 92 | user = update.effective_user 93 | 94 | spl = query.data.split("_") 95 | x, user_id = int(spl[1]), int(spl[2]) 96 | if user.id != user_id: 97 | return query.answer(st.NOT_ALLOWED, show_alert=True) 98 | 99 | try: 100 | res = tempdict[user_id] 101 | except KeyError: 102 | return query.answer(st.KEYERROR, show_alert=True) 103 | 104 | query.answer("Hold on...") 105 | query.message.delete() 106 | 107 | data = res[x]["attributes"] 108 | caption = st.MANGA_STR.format( 109 | data["titles"].get("en", ""), 110 | data["titles"].get("ja_jp", ""), 111 | data.get("subtype", "N/A"), 112 | data.get("averageRating", "N/A"), 113 | data.get("status", "N/A"), 114 | data.get("startDate", "N/A"), 115 | data.get("endDate", "N/A"), 116 | data.get("volumeCount", "N/A"), 117 | data.get("chapterCount", "N/A"), 118 | data.get("serialization", "N/A"), 119 | data.get("synopsis", "N/A"), 120 | ) 121 | 122 | if data.get("posterImage"): 123 | context.bot.sendPhoto( 124 | chat_id=chat.id, 125 | photo=data["posterImage"]["original"], 126 | caption=sort_caps(caption, c_id=data["slug"], manga=True), 127 | reply_markup=InlineKeyboardMarkup(keyboard(manga_id=data["slug"],)), 128 | timeout=60, 129 | disable_web_page_preview=True, 130 | ) 131 | 132 | else: 133 | context.bot.sendMessage( 134 | chat.id, 135 | text=caption, 136 | reply_markup=InlineKeyboardMarkup(keyboard(manga_id=data["slug"],)), 137 | disable_web_page_preview=True, 138 | ) 139 | del tempdict[user_id] 140 | 141 | 142 | @run_async 143 | @typing 144 | def cancel(update, context): 145 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 146 | return ConversationHandler.END 147 | 148 | 149 | MANGA_HANDLER = ConversationHandler( 150 | entry_points=[CommandHandler("manga", manga_entry)], 151 | states={1: [MessageHandler(Filters.text & ~Filters.command, manga)]}, 152 | fallbacks=[CommandHandler("cancel", cancel)], 153 | conversation_timeout=120, 154 | ) 155 | MANGA_BUTTON_HANDLER = CallbackQueryHandler(manga_button, pattern=r"manga_") 156 | 157 | dp.add_handler(MANGA_HANDLER) 158 | dp.add_handler(MANGA_BUTTON_HANDLER) 159 | -------------------------------------------------------------------------------- /acutebot/funcs/anime.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import requests as r 17 | 18 | from telegram.ext import ( 19 | MessageHandler, 20 | CommandHandler, 21 | Filters, 22 | ConversationHandler, 23 | CallbackQueryHandler, 24 | ) 25 | 26 | from telegram.ext.dispatcher import run_async 27 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, ForceReply 28 | 29 | from acutebot import dp, typing 30 | from acutebot.helpers import strings as st 31 | from acutebot.helpers.parsedata import sort_caps 32 | from acutebot.helpers.keyboard import keyboard 33 | 34 | base_url = "https://kitsu.io/api/edge" 35 | tempdict = {} 36 | 37 | 38 | @run_async 39 | @typing 40 | def anime_entry(update, context): 41 | update.effective_message.reply_text( 42 | st.TOSEARCH_ANIME, reply_markup=ForceReply(selective=True), 43 | ) 44 | 45 | return 1 46 | 47 | 48 | @run_async 49 | @typing 50 | def anime(update, context): 51 | msg = update.message 52 | user = update.effective_user 53 | query = msg.text.replace(" ", "%20") 54 | 55 | res = r.get(f"{base_url}/anime?filter%5Btext%5D={query}") 56 | if res.status_code != 200: 57 | msg.reply_text(st.API_ERR) 58 | return -1 59 | 60 | res = res.json()["data"] 61 | if len(res) <= 0: 62 | msg.reply_text(st.NOT_FOUND) 63 | return -1 64 | 65 | # Add results array with user's id as key 66 | tempdict[user.id] = res 67 | 68 | keyb = [] 69 | for x in enumerate(res): 70 | titles = x[1]["attributes"]["titles"] 71 | keyb.append( 72 | [ 73 | InlineKeyboardButton( 74 | text=f"{titles.get('en') if titles.get('en') else titles.get('ja_jp')}", 75 | callback_data=f"anime_{x[0]}_{user.id}", 76 | ) 77 | ] 78 | ) 79 | 80 | msg.reply_text( 81 | f"🔍 Search results for {msg.text}:", 82 | reply_markup=InlineKeyboardMarkup(keyb[:6]), 83 | ) 84 | 85 | return ConversationHandler.END 86 | 87 | 88 | @run_async 89 | def anime_button(update, context): 90 | query = update.callback_query 91 | chat = update.effective_chat 92 | user = update.effective_user 93 | 94 | spl = query.data.split("_") 95 | x, user_id = int(spl[1]), int(spl[2]) 96 | if user.id != user_id: 97 | return query.answer(st.NOT_ALLOWED, show_alert=True) 98 | 99 | try: 100 | res = tempdict[user_id] 101 | except KeyError: 102 | return query.answer(st.KEYERROR, show_alert=True) 103 | 104 | query.answer("Hold on...") 105 | query.message.delete() 106 | 107 | data = res[x]["attributes"] 108 | caption = st.ANIME_STR.format( 109 | data["titles"].get("en", ""), 110 | data["titles"].get("ja_jp", ""), 111 | data.get("subtype", "N/A"), 112 | data.get("ageRatingGuide", "N/A"), 113 | data.get("averageRating", "N/A"), 114 | data.get("status", "N/A"), 115 | data.get("startDate", "N/A"), 116 | data.get("endDate", "N/A"), 117 | data.get("episodeLength", "N/A"), 118 | data.get("episodeCount", "N/A"), 119 | data.get("synopsis", "N/A"), 120 | ) 121 | 122 | if data.get("posterImage"): 123 | context.bot.sendPhoto( 124 | chat_id=chat.id, 125 | photo=data["posterImage"]["original"], 126 | caption=sort_caps(caption, c_id=res[x]["id"], anime=True), 127 | reply_markup=InlineKeyboardMarkup( 128 | keyboard( 129 | title=data["titles"].get("en"), 130 | anime_ytkey=data.get("youtubeVideoId"), 131 | anime_id=res[x]["id"], 132 | ) 133 | ), 134 | timeout=60, 135 | disable_web_page_preview=True, 136 | ) 137 | 138 | else: 139 | context.bot.sendMessage( 140 | chat.id, 141 | text=caption, 142 | reply_markup=InlineKeyboardMarkup( 143 | keyboard( 144 | title=data["titles"].get("en"), 145 | anime_ytkey=data.get("youtubeVideoId"), 146 | anime_id=res[x]["id"], 147 | ) 148 | ), 149 | disable_web_page_preview=True, 150 | ) 151 | del tempdict[user_id] 152 | 153 | 154 | @run_async 155 | @typing 156 | def cancel(update, context): 157 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 158 | return ConversationHandler.END 159 | 160 | 161 | ANIME_HANDLER = ConversationHandler( 162 | entry_points=[CommandHandler("anime", anime_entry)], 163 | states={1: [MessageHandler(Filters.text & ~Filters.command, anime)]}, 164 | fallbacks=[CommandHandler("cancel", cancel)], 165 | conversation_timeout=120, 166 | ) 167 | AN_BUTTON_HANDLER = CallbackQueryHandler(anime_button, pattern=r"anime_") 168 | 169 | dp.add_handler(ANIME_HANDLER) 170 | dp.add_handler(AN_BUTTON_HANDLER) 171 | -------------------------------------------------------------------------------- /acutebot/funcs/movies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import requests as r 17 | 18 | from telegram.ext import ( 19 | MessageHandler, 20 | CommandHandler, 21 | Filters, 22 | ConversationHandler, 23 | CallbackQueryHandler, 24 | ) 25 | 26 | from telegram.ext.dispatcher import run_async 27 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, ForceReply 28 | 29 | from acutebot import dp, TMDBAPI, typing 30 | from acutebot.helpers import strings as st 31 | from acutebot.helpers.parsedata import byname, currency, sort_caps 32 | from acutebot.helpers.keyboard import keyboard 33 | 34 | 35 | base_url = "https://api.themoviedb.org/3" 36 | pic_url = "https://image.tmdb.org/t/p" 37 | 38 | 39 | def moviedata(c_id): 40 | """ 41 | Parse movie data for the id and return class obj 42 | """ 43 | payload = {"api_key": TMDBAPI, "language": "en-US", "append_to_response": "videos"} 44 | 45 | data = r.get(f"{base_url}/movie/{c_id}?", params=payload).json() 46 | 47 | class res: 48 | 49 | c_id = data.get("id") 50 | title = data.get("title") 51 | tagline = data.get("tagline") 52 | status = data.get("status") 53 | genres = byname(data.get("genres")) 54 | language = byname(data.get("spoken_languages")) 55 | runtime = data.get("runtime") 56 | budget = currency(data.get("budget")) 57 | revenue = currency(data.get("revenue")) 58 | release = data.get("release_date") 59 | rating = data.get("vote_average") 60 | popularity = data.get("popularity") 61 | overview = data.get("overview") 62 | 63 | # Keyboard objects 64 | posterpath = data.get("poster_path") 65 | homepage = data.get("homepage") 66 | imdbid = data.get("imdb_id") 67 | ytkey = data.get("videos") 68 | 69 | return res 70 | 71 | 72 | @run_async 73 | @typing 74 | def movie_entry(update, context): 75 | 76 | update.effective_message.reply_text( 77 | st.TOSEARCHMOVIE, reply_markup=ForceReply(selective=True), 78 | ) 79 | 80 | return 1 81 | 82 | 83 | @run_async 84 | @typing 85 | def movie(update, context): 86 | msg = update.message 87 | user = update.effective_user 88 | query = msg.text.replace(" ", "%20") 89 | 90 | results = r.get( 91 | f"{base_url}/search/movie?api_key={TMDBAPI}" 92 | + f"&language=en&query={query}" 93 | + "&page=1&include_adult=true" 94 | ) 95 | 96 | if results.status_code != 200: 97 | msg.reply_text(st.API_ERR) 98 | return -1 99 | 100 | results = results.json()["results"] 101 | if len(results) <= 0: 102 | msg.reply_text(st.NOT_FOUND) 103 | return -1 104 | 105 | keyb = [] 106 | for x in results: 107 | keyb.append( 108 | [ 109 | InlineKeyboardButton( 110 | text=x.get("title"), callback_data=f"movie_{x.get('id')}_{user.id}" 111 | ) 112 | ] 113 | ) 114 | msg.reply_text( 115 | f"🔍 Search results for {msg.text}:", 116 | reply_markup=InlineKeyboardMarkup(keyb[:6]), 117 | ) 118 | 119 | return ConversationHandler.END 120 | 121 | 122 | @run_async 123 | def movie_button(update, context): 124 | query = update.callback_query 125 | chat = update.effective_chat 126 | user = update.effective_user 127 | 128 | spl = query.data.split("_") 129 | c_id, user_id = spl[1], spl[2] 130 | if user.id != int(user_id): 131 | return query.answer(st.NOT_ALLOWED, show_alert=True) 132 | 133 | query.answer("Hold on...") 134 | query.message.delete() 135 | 136 | res = moviedata(c_id) 137 | caption = st.MOVIE_STR.format( 138 | res.title, 139 | res.tagline, 140 | res.status, 141 | res.genres, 142 | res.language, 143 | res.runtime, 144 | res.budget, 145 | res.revenue, 146 | res.release, 147 | res.rating, 148 | res.popularity, 149 | res.overview, 150 | ) 151 | 152 | if res.posterpath: 153 | context.bot.sendPhoto( 154 | chat_id=chat.id, 155 | photo=f"{pic_url}/w500/{res.posterpath}", 156 | caption=sort_caps(caption, c_id=res.c_id, mv=True), 157 | reply_markup=InlineKeyboardMarkup( 158 | keyboard(res.ytkey, res.homepage, res.title, res.imdbid) 159 | ), 160 | timeout=60, 161 | disable_web_page_preview=True, 162 | ) 163 | else: 164 | context.bot.sendMessage( 165 | chat.id, 166 | text=caption, 167 | reply_markup=InlineKeyboardMarkup( 168 | keyboard(res.ytkey, res.homepage, res.title, res.imdbid), 169 | disable_web_page_preview=True, 170 | ), 171 | ) 172 | 173 | 174 | @run_async 175 | @typing 176 | def cancel(update, context): 177 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 178 | return ConversationHandler.END 179 | 180 | 181 | MOVIE_HANDLER = ConversationHandler( 182 | entry_points=[CommandHandler("movies", movie_entry)], 183 | states={1: [MessageHandler(Filters.text & ~Filters.command, movie)]}, 184 | fallbacks=[CommandHandler("cancel", cancel)], 185 | conversation_timeout=120, 186 | ) 187 | MV_BUTTON_HANDLER = CallbackQueryHandler(movie_button, pattern=r"movie_") 188 | 189 | dp.add_handler(MOVIE_HANDLER) 190 | dp.add_handler(MV_BUTTON_HANDLER) 191 | -------------------------------------------------------------------------------- /acutebot/funcs/miscs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import requests as r 17 | import subprocess, random 18 | from time import sleep 19 | 20 | from acutebot.helpers.database import users_sql as sql 21 | from acutebot.helpers.database.favorites_sql import fav_count 22 | import acutebot.helpers.strings as st 23 | from acutebot import dp, typing, DEV_ID, LOG 24 | 25 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, TelegramError 26 | from telegram.ext.dispatcher import run_async 27 | from telegram.ext import CommandHandler, MessageHandler, Filters 28 | from telegram.error import BadRequest 29 | 30 | 31 | @run_async 32 | @typing 33 | def get_ip(update, context): 34 | res = r.get("http://ipinfo.io/ip") 35 | update.message.reply_text(res.text) 36 | 37 | 38 | @run_async 39 | def rmemes(update, context): 40 | msg = update.effective_message 41 | chat = update.effective_chat 42 | 43 | SUBREDS = [ 44 | "meirl", 45 | "dankmemes", 46 | "AdviceAnimals", 47 | "memes", 48 | "meme", 49 | "memes_of_the_dank", 50 | "PornhubComments", 51 | "teenagers", 52 | "memesIRL", 53 | "insanepeoplefacebook", 54 | "terriblefacebookmemes", 55 | "animememes", 56 | "Animemes", 57 | "memeeconomy", 58 | ] 59 | 60 | subreddit = random.choice(SUBREDS) 61 | res = r.get(f"https://meme-api.herokuapp.com/gimme/{subreddit}") 62 | 63 | if res.status_code != 200: # Like if api is down? 64 | return msg.reply_text(st.API_ERR) 65 | res = res.json() 66 | 67 | keyb = [[InlineKeyboardButton(text=f"r/{res['subreddit']}", url=res["postLink"])]] 68 | try: 69 | context.bot.send_chat_action(chat.id, "upload_photo") 70 | context.bot.send_photo( 71 | chat.id, 72 | photo=res["url"], 73 | caption=(res["title"]), 74 | reply_markup=InlineKeyboardMarkup(keyb), 75 | timeout=60, 76 | ) 77 | 78 | except BadRequest as excp: 79 | LOG.error(excp) 80 | 81 | 82 | @run_async 83 | def stats(update, context): 84 | msg = update.effective_message 85 | return msg.reply_text( 86 | st.STATS.format(sql.users_count(), fav_count()), parse_mode=None, 87 | ) 88 | 89 | 90 | @run_async 91 | def greet(update, context): 92 | msg = update.effective_message 93 | user = update.effective_user 94 | chat = update.effective_chat 95 | 96 | new_members = msg.new_chat_members 97 | for new_mem in new_members: 98 | if new_mem.id == context.bot.id: 99 | msg.reply_text(st.GREET.format(user.first_name, chat.title)) 100 | 101 | 102 | @run_async 103 | def shell(update, context): 104 | bot = context.bot 105 | msg = update.effective_message 106 | chat = update.effective_chat 107 | command = " ".join(context.args).split() 108 | rep = msg.reply_text("Running command...") 109 | try: 110 | res = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,) 111 | stdout, stderr = res.communicate() 112 | result = str(stdout.decode().strip()) + str(stderr.decode().strip()) 113 | bot.editMessageText("
" + result + "
", chat.id, rep.message_id) 114 | except Exception as e: 115 | bot.editMessageText(str(e), chat.id, rep.message_id) 116 | 117 | 118 | @run_async 119 | def broadcast(update, context): 120 | to_send = update.effective_message.text.split(None, 1) 121 | if len(to_send) >= 2: 122 | users = sql.get_all_users() or [] 123 | failed = 0 124 | for user in users: 125 | try: 126 | context.bot.sendMessage(int(user.user_id), to_send[1]) 127 | sleep(0.1) 128 | except TelegramError: 129 | failed += 1 130 | LOG.warning( 131 | "Couldn't send broadcast to %s, username %s", 132 | str(user.user_id), 133 | str(user.username), 134 | ) 135 | 136 | update.effective_message.reply_text( 137 | "Broadcast complete. {} users failed to receive the message, probably " 138 | "due to being Blocked.".format(failed) 139 | ) 140 | 141 | 142 | 143 | @run_async 144 | def log_user(update, context): 145 | msg = update.effective_message 146 | 147 | sql.update_user(msg.from_user.id, msg.from_user.username) 148 | 149 | if msg.reply_to_message: 150 | sql.update_user( 151 | msg.reply_to_message.from_user.id, msg.reply_to_message.from_user.username, 152 | ) 153 | 154 | if msg.forward_from: 155 | sql.update_user(msg.forward_from.id, msg.forward_from.username) 156 | 157 | 158 | IP_HANDLER = CommandHandler("ip", get_ip, filters=Filters.chat(DEV_ID)) 159 | REDDIT_HANDLER = CommandHandler("reddit", rmemes) 160 | STATS_HANDLER = CommandHandler("stats", stats, filters=Filters.user(DEV_ID)) 161 | GREET_HANDLER = MessageHandler(Filters.status_update.new_chat_members, greet) 162 | SHELL_HANDLER = CommandHandler("shell", shell, filters=Filters.user(DEV_ID)) 163 | LOG_HANDLER = MessageHandler(Filters.all, log_user) 164 | BROADCAST_HANDLER = CommandHandler( 165 | "broadcast", broadcast, filters=Filters.user(DEV_ID) 166 | ) 167 | 168 | dp.add_handler(IP_HANDLER) 169 | dp.add_handler(REDDIT_HANDLER) 170 | dp.add_handler(STATS_HANDLER) 171 | dp.add_handler(GREET_HANDLER) 172 | dp.add_handler(SHELL_HANDLER) 173 | dp.add_handler(LOG_HANDLER, 1) 174 | dp.add_handler(BROADCAST_HANDLER) 175 | -------------------------------------------------------------------------------- /acutebot/funcs/tvseries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import requests as r 17 | 18 | from telegram.ext import ( 19 | MessageHandler, 20 | CommandHandler, 21 | Filters, 22 | ConversationHandler, 23 | CallbackQueryHandler, 24 | ) 25 | 26 | from telegram.ext.dispatcher import run_async 27 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton, ForceReply 28 | 29 | from acutebot import dp, TMDBAPI, typing 30 | from acutebot.helpers import strings as st 31 | from acutebot.helpers.parsedata import byname, byindex, sort_caps, tvruntime 32 | from acutebot.helpers.keyboard import keyboard 33 | 34 | base_url = "https://api.themoviedb.org/3" 35 | pic_url = "https://image.tmdb.org/t/p" 36 | 37 | 38 | def tvdata(c_id): 39 | """ 40 | Parse TV shows data for the id and return class obj 41 | """ 42 | payload = {"api_key": TMDBAPI, "language": "en-US", "append_to_response": "videos"} 43 | 44 | data = r.get(f"{base_url}/tv/{c_id}?", params=payload).json() 45 | 46 | class res: 47 | 48 | c_id = data.get("id") 49 | title = data.get("original_name") 50 | creator = byindex(data.get("created_by")) 51 | genres = byname(data.get("genres")) 52 | language = data.get("original_language") 53 | runtime = tvruntime(data.get("episode_run_time")) 54 | faired = data.get("first_air_date") 55 | laired = data.get("last_air_date") 56 | status = data.get("status") 57 | seasons = data.get("number_of_seasons") 58 | numeps = data.get("number_of_episodes") 59 | rating = data.get("vote_average") 60 | company = byindex(data.get("production_companies")) 61 | overview = data.get("overview") 62 | 63 | # Keyboard objects 64 | posterpath = data.get("poster_path") 65 | homepage = data.get("homepage") 66 | ytkey = data.get("videos") 67 | 68 | return res 69 | 70 | 71 | @run_async 72 | @typing 73 | def tv_entry(update, context): 74 | 75 | update.effective_message.reply_text( 76 | st.TOSEARCHTV, reply_markup=ForceReply(selective=True), 77 | ) 78 | 79 | return 1 80 | 81 | 82 | @run_async 83 | @typing 84 | def tv(update, context): 85 | msg = update.message 86 | user = update.effective_user 87 | query = msg.text.replace(" ", "%20") 88 | 89 | results = r.get( 90 | f"{base_url}/search/tv?api_key={TMDBAPI}" 91 | + f"&language=en&query={query}" 92 | + "&page=1&include_adult=true" 93 | ) 94 | 95 | if results.status_code != 200: 96 | msg.reply_text(st.API_ERR) 97 | return -1 98 | 99 | results = results.json()["results"] 100 | 101 | if len(results) <= 0: 102 | msg.reply_text(st.NOT_FOUND) 103 | return -1 104 | 105 | keyb = [] 106 | for x in results: 107 | keyb.append( 108 | [ 109 | InlineKeyboardButton( 110 | text=x.get("original_name"), 111 | callback_data=f"tv_{x.get('id')}_{user.id}", 112 | ) 113 | ] 114 | ) 115 | msg.reply_text( 116 | f"🔍 Search results for {msg.text}:", 117 | reply_markup=InlineKeyboardMarkup(keyb[:6]), 118 | ) 119 | 120 | return ConversationHandler.END 121 | 122 | 123 | @run_async 124 | def tv_button(update, context): 125 | query = update.callback_query 126 | chat = update.effective_chat 127 | user = update.effective_user 128 | 129 | spl = query.data.split("_") 130 | c_id, user_id = spl[1], spl[2] 131 | if user.id != int(user_id): 132 | return query.answer(st.NOT_ALLOWED, show_alert=True) 133 | 134 | query.answer("Hold on...") 135 | query.message.delete() 136 | 137 | res = tvdata(c_id) 138 | caption = st.TV_STR.format( 139 | res.title, 140 | res.creator, 141 | res.genres, 142 | res.language, 143 | res.runtime, 144 | res.faired, 145 | res.laired, 146 | res.status, 147 | res.seasons, 148 | res.numeps, 149 | res.rating, 150 | res.company, 151 | res.overview, 152 | ) 153 | 154 | if res.posterpath: 155 | context.bot.sendPhoto( 156 | chat_id=chat.id, 157 | photo=f"{pic_url}/w500/{res.posterpath}", 158 | caption=sort_caps(caption, c_id=res.c_id, tv=True), 159 | reply_markup=InlineKeyboardMarkup( 160 | keyboard(res.ytkey, res.homepage, res.title) 161 | ), 162 | disable_web_page_preview=True, 163 | timeout=60, 164 | ) 165 | else: 166 | context.bot.sendMessage( 167 | chat.id, 168 | text=caption, 169 | reply_markup=InlineKeyboardMarkup( 170 | keyboard(res.ytkey, res.homepage, res.title), 171 | disable_web_page_preview=True, 172 | ), 173 | ) 174 | 175 | 176 | @run_async 177 | @typing 178 | def cancel(update, context): 179 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 180 | return ConversationHandler.END 181 | 182 | 183 | TV_HANDLER = ConversationHandler( 184 | entry_points=[CommandHandler("tvshows", tv_entry)], 185 | states={1: [MessageHandler(Filters.text & ~Filters.command, tv)]}, 186 | fallbacks=[CommandHandler("cancel", cancel)], 187 | conversation_timeout=120, 188 | ) 189 | TV_BUTTON_HANDLER = CallbackQueryHandler(tv_button, pattern=r"tv_") 190 | 191 | 192 | dp.add_handler(TV_HANDLER) 193 | dp.add_handler(TV_BUTTON_HANDLER) 194 | -------------------------------------------------------------------------------- /acutebot/funcs/music.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import os, deezloader, mutagen 17 | from deezloader.exceptions import BadCredentials, TrackNotFound, NoDataApi 18 | 19 | from pathlib import Path 20 | from telegram.ext.dispatcher import run_async 21 | from telegram.ext import CommandHandler, MessageHandler, Filters, ConversationHandler 22 | from telegram import ForceReply, ReplyKeyboardMarkup 23 | from pyrogram import Client 24 | 25 | from acutebot import dp, typing, ARLTOKEN, LOG, APIID, APIHASH 26 | from acutebot.helpers import strings as st 27 | 28 | MUSIC, ARTIST, SENDMUSIC = range(3) 29 | MUSICDICT = {} 30 | 31 | if ARLTOKEN is not None: 32 | try: 33 | downloa = deezloader.Login(ARLTOKEN) 34 | except BadCredentials: 35 | print("Deezer token is dead :(") 36 | 37 | 38 | @run_async 39 | @typing 40 | def musicq(update, context): 41 | reply_keyboard = [["🎵 256KBs", "🎧 320KBs", "🎶 FLAC"]] 42 | 43 | update.effective_message.reply_text( 44 | st.MUSICQ, 45 | reply_markup=ReplyKeyboardMarkup( 46 | reply_keyboard, one_time_keyboard=True, selective=True 47 | ), 48 | ) 49 | return MUSIC 50 | 51 | 52 | @run_async 53 | @typing 54 | def music(update, context): 55 | user = update.effective_user 56 | msg = update.effective_message 57 | songq = update.message.text 58 | 59 | if songq in ("🎵 256KBs", "🎧 320KBs", "🎶 FLAC"): 60 | # save quality data in temp.dict: 61 | MUSICDICT[user.id] = {"q": songq} 62 | msg.reply_text(st.MUSICNAME, reply_markup=ForceReply(selective=True)) 63 | return ARTIST 64 | msg.reply_text(st.INVALIDCAT) 65 | return -1 66 | 67 | 68 | @run_async 69 | @typing 70 | def _artist(update, context): 71 | user = user = update.effective_user 72 | msg = update.effective_message 73 | musicn = update.message.text 74 | # update music title in dict: 75 | MUSICDICT[user.id]["mn"] = musicn 76 | msg.reply_text(st.ARTISTNAME, reply_markup=ForceReply(selective=True)) 77 | return SENDMUSIC 78 | 79 | 80 | @run_async 81 | def sendmusic(update, context): 82 | user = update.effective_user 83 | msg = update.effective_message 84 | chat = update.effective_chat 85 | 86 | artist = update.message.text 87 | try: 88 | song = MUSICDICT[user.id]["mn"] 89 | quality = MUSICDICT[user.id]["q"] 90 | except KeyError: 91 | msg.reply_text(st.LYRICS_ERR) 92 | return -1 93 | 94 | if quality == "🎧 320KBs": 95 | ql = "MP3_320" 96 | elif quality == "🎶 FLAC": 97 | ql = "FLAC" 98 | elif quality == "🎵 256KBs": 99 | ql = "MP3_256" 100 | 101 | try: 102 | context.bot.send_chat_action(chat.id, "upload_document") 103 | file = downloa.download_name( 104 | artist=artist, 105 | song=song, 106 | output="temp", 107 | quality=ql, 108 | recursive_quality=True, 109 | recursive_download=True, 110 | not_interface=True, 111 | ) 112 | except (TrackNotFound, NoDataApi): 113 | msg.reply_text(st.MUSICNOTFOUND) 114 | return -1 115 | 116 | try: 117 | # Fetch correct details from metadata: 118 | aud = mutagen.File(file) 119 | title = aud.get("title") 120 | if title: 121 | title = str(title[0]) 122 | artist = aud.get("artist") 123 | if artist: 124 | artist = str(artist[0]) 125 | duration = aud.get("length") 126 | if duration: 127 | duration = int(duration[0]) 128 | 129 | if Path(file).stat().st_size < 50000000: 130 | rep = msg.reply_text(st.UPLOAD_BOTAPI) 131 | context.bot.sendAudio( 132 | chat.id, 133 | open(file, "rb"), 134 | caption="Via @acutebot 🎸", 135 | title=title, 136 | performer=artist, 137 | duration=duration, 138 | timeout=120, 139 | ) 140 | else: 141 | rep = msg.reply_text(st.UPLOAD_MTPROTO) 142 | send_file_pyro(context.bot.token, file, chat.id, title, artist, duration) 143 | rep.delete() 144 | 145 | except Exception as e: 146 | LOG.error(e) 147 | 148 | if os.path.isfile(file): 149 | os.remove(file) 150 | del MUSICDICT[user.id] 151 | return ConversationHandler.END 152 | 153 | 154 | def send_file_pyro(bot_token, file, chatid, title, artist, duration): 155 | bot = Client("acute", bot_token=bot_token, api_id=APIID, api_hash=APIHASH) 156 | with bot: 157 | bot.send_audio( 158 | chat_id=chatid, 159 | audio=open(file, "rb"), 160 | caption="Via @acutebot 🎸", 161 | title=title, 162 | duration=duration, 163 | performer=artist, 164 | ) 165 | 166 | 167 | @run_async 168 | @typing 169 | def cancel(update, context): 170 | context.bot.sendMessage(update.effective_chat.id, (st.CANCEL)) 171 | return ConversationHandler.END 172 | 173 | 174 | MUSIC_HANDLER = ConversationHandler( 175 | entry_points=[CommandHandler("music", musicq)], 176 | states={ 177 | MUSIC: [MessageHandler(Filters.text & ~Filters.command, music)], 178 | ARTIST: [MessageHandler(Filters.text & ~Filters.command, _artist)], 179 | SENDMUSIC: [MessageHandler(Filters.text & ~Filters.command, sendmusic)], 180 | }, 181 | fallbacks=[CommandHandler("cancel", cancel)], 182 | conversation_timeout=120, 183 | ) 184 | 185 | 186 | dp.add_handler(MUSIC_HANDLER) 187 | -------------------------------------------------------------------------------- /acutebot/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | import os, sys, importlib 17 | from threading import Thread 18 | 19 | from acutebot import LOG, dp, updater, DEV_ID 20 | from acutebot.funcs import ALL_FUNCS 21 | import acutebot.helpers.strings as st 22 | 23 | 24 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton 25 | from telegram.ext.dispatcher import run_async 26 | from telegram.ext import CommandHandler, CallbackQueryHandler, Filters 27 | 28 | 29 | # Import all funcs in main 30 | for func_name in ALL_FUNCS: 31 | imported_module = importlib.import_module("acutebot.funcs." + func_name) 32 | 33 | 34 | class Starter: 35 | def __init__(self, name): 36 | self.photo = "https://telegra.ph/file/8109bf8f6b27ce9b45ff1.jpg" 37 | self.text = st.START_STRING.format(name) 38 | self.reply_markup = InlineKeyboardMarkup( 39 | [ 40 | [ 41 | InlineKeyboardButton( 42 | text="Movie", switch_inline_query_current_chat=" ", 43 | ), 44 | InlineKeyboardButton( 45 | text="TVshow", switch_inline_query_current_chat=" ", 46 | ), 47 | InlineKeyboardButton( 48 | text="Anime", switch_inline_query_current_chat=" ", 49 | ), 50 | ], 51 | [InlineKeyboardButton(text="Help and Commands❔", callback_data="help")], 52 | ] 53 | ) 54 | 55 | 56 | @run_async 57 | def start(update, context): 58 | if update.effective_chat.type == "private": 59 | stuff = Starter(update.effective_user.first_name) 60 | return update.effective_message.reply_photo( 61 | photo=stuff.photo, caption=stuff.text, reply_markup=stuff.reply_markup 62 | ) 63 | 64 | update.effective_message.reply_text(st.START_STRING_GRP) 65 | 66 | 67 | @run_async 68 | def help_button(update, context=None): 69 | query = update.callback_query 70 | query.answer() 71 | query.message.edit_caption( 72 | caption=st.HELP_STR, 73 | reply_markup=InlineKeyboardMarkup( 74 | [ 75 | [ 76 | InlineKeyboardButton(text="Movies & TV", callback_data="h_mv"), 77 | InlineKeyboardButton( 78 | text="Music & lyrics", callback_data="h_music" 79 | ), 80 | ], 81 | [ 82 | InlineKeyboardButton(text="Anime & manga", callback_data="h_anime"), 83 | InlineKeyboardButton(text="Miscellaneous", callback_data="h_misc"), 84 | ], 85 | [ 86 | InlineKeyboardButton( 87 | text="🖤 About and donate 🖤", callback_data="h_about" 88 | ) 89 | ], 90 | [InlineKeyboardButton(text="Go back 🔙", callback_data="back_btn")], 91 | ] 92 | ), 93 | ) 94 | 95 | 96 | def h_for_funcs(update, context): 97 | query = update.callback_query 98 | query.answer() 99 | match = query.data.split("_")[1] 100 | markup = InlineKeyboardMarkup( 101 | [[InlineKeyboardButton(text="Go back 🔙", callback_data="back_btn_help")]] 102 | ) 103 | if match == "mv": 104 | query.message.edit_caption(caption=st.MOVIE_HELP, reply_markup=markup) 105 | elif match == "music": 106 | query.message.edit_caption(caption=st.MUSIC_HELP, reply_markup=markup) 107 | elif match == "anime": 108 | query.message.edit_caption(caption=st.ANIME_HELP, reply_markup=markup) 109 | elif match == "misc": 110 | query.message.edit_caption(caption=st.MISC_HELP, reply_markup=markup) 111 | elif match == "about": 112 | query.message.edit_caption( 113 | caption=st.ABOUT_STR, 114 | reply_markup=InlineKeyboardMarkup( 115 | [ 116 | [ 117 | InlineKeyboardButton( 118 | text="Github 🔭", url="https://github.com/starry69" 119 | ), 120 | InlineKeyboardButton( 121 | text="Donate 🖤", url="paypal.me/starryrays" 122 | ), 123 | ], 124 | [ 125 | InlineKeyboardButton( 126 | text="Go back 🔙", callback_data="back_btn_help" 127 | ) 128 | ], 129 | ] 130 | ), 131 | ) 132 | 133 | 134 | @run_async 135 | def back_btn(update, context): 136 | query = update.callback_query 137 | query.answer() 138 | match = query.data.split("_") 139 | if "help" in match: 140 | return help_button(update) 141 | stuff = Starter(update.effective_user.first_name) 142 | query.message.edit_caption(caption=stuff.text, reply_markup=stuff.reply_markup) 143 | 144 | 145 | BANNER = r""" 146 | ___ ___ ______ ___ 147 | / _ \ | | | ___ \ | | 148 | / /_\ \ ___ _ _| |_ ___| |_/ / ___ | |_ 149 | | _ |/ __| | | | __/ _ \ ___ \/ _ \| __| 150 | | | | | (__| |_| | || __/ |_/ / (_) | |_ 151 | \_| |_/\___|\__,_|\__\___\____/ \___/ \__| 152 | 153 | Is Running 🎶🎶🎵 154 | """ 155 | 156 | 157 | def main(): 158 | def stop_and_restart(): 159 | updater.stop() 160 | os.execl(sys.executable, sys.executable, *sys.argv) 161 | 162 | def restart(update, context): 163 | context.bot.sendMessage(update.effective_chat.id, "Rebooted ✨") 164 | Thread(target=stop_and_restart).start() 165 | 166 | restart_handler = CommandHandler("reboot", restart, filters=Filters.user(DEV_ID)) 167 | start_handler = CommandHandler("start", start) 168 | help_funcs_handler = CallbackQueryHandler(h_for_funcs, pattern=r"h_") 169 | help_handler = CallbackQueryHandler(help_button, pattern=r"help") 170 | back_btn_handler = CallbackQueryHandler(back_btn, pattern=r"back_btn") 171 | 172 | dp.add_handler(restart_handler) 173 | dp.add_handler(start_handler) 174 | dp.add_handler(help_funcs_handler) 175 | dp.add_handler(help_handler) 176 | dp.add_handler(back_btn_handler) 177 | 178 | LOG.info("%s", BANNER) 179 | 180 | # Start the bot. 181 | updater.start_polling(timeout=15, read_latency=4) 182 | updater.idle() 183 | 184 | 185 | if __name__ == "__main__": 186 | main() 187 | -------------------------------------------------------------------------------- /acutebot/funcs/inlinequery.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | import requests as r 16 | 17 | from telegram.ext import InlineQueryHandler 18 | from telegram.ext.dispatcher import run_async 19 | from telegram import InlineKeyboardMarkup, InlineKeyboardButton 20 | 21 | from acutebot import dp, TMDBAPI 22 | from acutebot.helpers import strings as st 23 | from acutebot.helpers.parsedata import article 24 | from acutebot.helpers.keyboard import keyboard 25 | from acutebot.helpers.database import users_sql as sql 26 | 27 | 28 | pic_url = "https://image.tmdb.org/t/p" 29 | base_url = "https://api.themoviedb.org/3" 30 | anime_url = "https://kitsu.io/api/edge" 31 | 32 | 33 | @run_async 34 | def inlinequery(update, context): 35 | query = update.inline_query.query 36 | sql.update_user( 37 | update.inline_query.from_user.id, update.inline_query.from_user.username 38 | ) 39 | results = [] 40 | if len(query) > 0: 41 | if query.startswith(""): 42 | query = query.replace("", "") 43 | res = r.get( 44 | f"{base_url}/search/tv?api_key={TMDBAPI}" 45 | + f"&language=en&query={query}" 46 | + "&page=1&include_adult=true" 47 | ) 48 | 49 | if res.status_code != 200: 50 | return 51 | res = res.json() 52 | 53 | if len(res["results"]) > 0: 54 | for con in res["results"]: 55 | results.append( 56 | article( 57 | title=con.get("name", "N/A"), 58 | description=con.get("first_air_date", "N/A"), 59 | thumb_url=f"{pic_url}/w500/{con['poster_path']}", 60 | message_text=st.INLINE_STR.format( 61 | con.get("original_name", "N/A"), 62 | con.get("first_air_date", "N/A"), 63 | con.get("popularity", "N/A"), 64 | con.get("original_language", "N/A"), 65 | con.get("vote_average", "N/A"), 66 | con.get("overview", "N/A"), 67 | ) 68 | + f"­", 69 | reply_markup=InlineKeyboardMarkup( 70 | keyboard(title=con["original_name"], tv_id=con["id"]) 71 | ), 72 | ) 73 | ) 74 | 75 | elif query.startswith(""): 76 | query = query.replace("", "") 77 | res = r.get( 78 | f"{base_url}/search/movie?api_key={TMDBAPI}" 79 | + f"&language=en&query={query}" 80 | + "&page=1&include_adult=true" 81 | ) 82 | 83 | if res.status_code != 200: 84 | return 85 | res = res.json() 86 | 87 | if len(res["results"]) > 0: 88 | for con in res["results"]: 89 | results.append( 90 | article( 91 | title=con.get("title", "N/A"), 92 | description=con.get("release_date", "N/A"), 93 | thumb_url=f"{pic_url}/w500/{con['poster_path']}", 94 | message_text=st.INLINE_STR.format( 95 | con.get("original_title", "N/A"), 96 | con.get("release_date", "N/A"), 97 | con.get("popularity", "N/A"), 98 | con.get("original_language", "N/A"), 99 | con.get("vote_average", "N/A"), 100 | con.get("overview", "N/A"), 101 | ) 102 | + f"­", 103 | reply_markup=InlineKeyboardMarkup( 104 | keyboard(title=con["original_title"], mv_id=con["id"]) 105 | ), 106 | ) 107 | ) 108 | 109 | elif query.startswith(""): 110 | query = query.replace("", "") 111 | res = r.get(f"{anime_url}/anime?filter%5Btext%5D={query}") 112 | if res.status_code != 200: 113 | return 114 | res = res.json()["data"] 115 | 116 | for con in res: 117 | data = con["attributes"] 118 | results.append( 119 | article( 120 | title=data["titles"].get("en", "NA"), 121 | description=data["titles"].get("ja_jp", ""), 122 | thumb_url=data["posterImage"]["original"], 123 | message_text=st.ANIME_STR.format( 124 | data["titles"].get("en", ""), 125 | data["titles"].get("ja_jp", ""), 126 | data.get("subtype", "N/A"), 127 | data.get("ageRatingGuide", "N/A"), 128 | data.get("averageRating", "N/A"), 129 | data.get("status", "N/A"), 130 | data.get("startDate", "N/A"), 131 | data.get("endDate", "N/A"), 132 | data.get("episodeLength", "N/A"), 133 | data.get("episodeCount", "N/A"), 134 | data.get("synopsis", "N/A"), 135 | ) 136 | + f"­", 137 | reply_markup=InlineKeyboardMarkup( 138 | keyboard( 139 | title=data["titles"].get("en"), 140 | anime_ytkey=data.get("youtubeVideoId"), 141 | anime_id=con["id"], 142 | ) 143 | ), 144 | ) 145 | ) 146 | 147 | else: 148 | results.append( 149 | article( 150 | title="Usage: | | ", 151 | description="Example: Avengers endgame", 152 | message_text=st.INLINE_DESC, 153 | thumb_url="https://telegra.ph/file/8109bf8f6b27ce9b45ff1.jpg", 154 | reply_markup=InlineKeyboardMarkup( 155 | [ 156 | [ 157 | InlineKeyboardButton( 158 | text="Movies", 159 | switch_inline_query_current_chat=" ", 160 | ), 161 | InlineKeyboardButton( 162 | text="TVshows", 163 | switch_inline_query_current_chat=" ", 164 | ), 165 | InlineKeyboardButton( 166 | text="Anime", 167 | switch_inline_query_current_chat=" ", 168 | ), 169 | ] 170 | ] 171 | ), 172 | ) 173 | ) 174 | 175 | update.inline_query.answer(results[:50], cache_time=10) 176 | 177 | 178 | INLINE_HANDLER = InlineQueryHandler(inlinequery) 179 | dp.add_handler(INLINE_HANDLER) 180 | -------------------------------------------------------------------------------- /acutebot/helpers/strings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # MIT License 5 | # Copyright (c) 2020 Stɑrry Shivɑm // This file is part of AcuteBot 6 | # 7 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 13 | # SOFTWARE. 14 | 15 | 16 | from acutebot import __version__ 17 | from platform import python_version 18 | from telegram import __version__ as _libv_ 19 | 20 | # Contents 21 | MOVIE_STR = """ 22 | ️{} : {} 23 | 24 | • Status :
{}
25 | • Genres :
{}
26 | • Languages :
{}
27 | • Runtime :
{} minutes
28 | • Budget :
{}
29 | • Revenue :
{}
30 | • Release Date :
{}
31 | • Average Rating :
{}
32 | • Popularity :
{}
33 | 34 | • OverView : {} 35 | """ 36 | 37 | TV_STR = """ 38 | {} 39 | 40 | • Created by :
{}
41 | • Genres :
{}
42 | • Languages :
{}
43 | • Episodes Runtime :
{}
44 | • First aired :
{}
45 | • Last aired :
{}
46 | • Status :
{}
47 | • Seasons :
{}
48 | • Total EPs :
{}
49 | • Average Rating :
{}
50 | • Production Company :
{}
51 | 52 | • OverView : {} 53 | """ 54 | 55 | ANIME_STR = """ 56 | {} | {} 57 | 58 | • Category :
{}
59 | • Type :
{}
60 | • Average Rating :
{}
61 | • Status :
{}
62 | • First aired :
{}
63 | • Last aired :
{}
64 | • Runtime :
{} minutes
65 | • No of episodes :
{}
66 | 67 | • Synopsis : {} 68 | """ 69 | 70 | MANGA_STR = """ 71 | {} | {} 72 | • Type :
{}
73 | • Average Rating :
{}
74 | • Status :
{}
75 | • First release :
{}
76 | • Last release :
{}
77 | • Volume count :
{}
78 | • No of chapters :
{}
79 | • Serialization :
{}
80 | 81 | • Synopsis : {} 82 | """ 83 | 84 | # Inline Content 85 | INLINE_STR = """ 86 | • Title : {} 87 | • Release :
{}
88 | • Popularity :
{}
89 | • Language :
{}
90 | • Average Rating :
{}
91 | 92 | • OverView : {} 93 | """ 94 | 95 | INLINE_DESC = """ 96 | Usage:
<tv> title
or
<movie> title
in inline query. 97 | 98 | Examples: 99 | ×
<movie> Avengers Endgame
100 | ×
<tv> Breaking Bad
101 | ×
<anime> Attack on Titan
102 | • You can try on buttons below! 103 | """ 104 | 105 | 106 | # Start 107 | START_STRING = """ 108 | Hey {}, I'm acutebot and i can help you to get \ 109 | information about your favorite movies, tv and anime shows, you can also download \ 110 | music & can view song lyrics using me! Just click the button \ 111 | below to get started with list of possible commands... 112 | 113 | You can also search movies, tvshows & \ 114 | anime inline! just type
@acutebot
in \ 115 | your message box and follow the instructions. 116 | 117 | And don't forget to smile, atleast once in a while ;) 118 | """ 119 | START_STRING_GRP = "Hmmm?" 120 | 121 | 122 | # About 123 | ABOUT_STR = f""" 124 | I'm fully written in \ 125 | Python3 by starry, \ 126 | feel free to report him if you find any rough edge inside me. 127 | 128 | × Bot version :
{__version__}
129 | × Python version :
{python_version()}
130 | × Library version :
PTB {_libv_}
131 | × Movies & TV data :
themoviedb.org
132 | × Anime data from :
kitsu.io
133 | × Music data from :
deezer.com
134 | × Lyrics data from :
genius.com
135 | 136 | If you enjoyed using me & wanna support my creator \ 137 | hit the donate button below, since he's just a student so \ 138 | every little helps to pay for my server, and ofcourse boosting morale ;) 139 | 140 | """ 141 | 142 | # Help 143 | HELP_STR = """ 144 | Hey there, click on the buttons below to get documentations \ 145 | for the related functions. 146 | """ 147 | 148 | MOVIE_HELP = """ 149 | 🗒️ Documentation for Movies & TV related functions: 150 | 151 | × /movies : Search for info about your favorite movies. 152 | × /tvshows : Get information for your favotite TV shows. 153 | × /toprated : (Soon) | View information about top rated, Movie & TV titles. 154 | """ 155 | ANIME_HELP = """ 156 | 🗒️ Documentation for Anime & Manga related functionsfunctions: 157 | 158 | × /anime : Search for info about your favorite anime titles. 159 | × /manga : Get information about your favorite manga titles. 160 | """ 161 | MUSIC_HELP = """ 162 | 🗒️ Documentation for music & lyrics related functions: 163 | 164 | × /music : Download your favorite music in high resolution. 165 | × /lyrics : Get lyrics for your favorite songs. 166 | × /nowplaying : Flex you currently or last played song in spotify. 167 | """ 168 | MISC_HELP = """ 169 | 🗒️ Documentation for some miscs command which don't fit anywhere! 170 | 171 | × /reddit : Gets you random memes scraped from popular subreddits. 172 | × /subtitle : Download subtitles for your movies. 173 | × /watchlist : Get list of saved shows from your watchlist :D. 174 | """ 175 | 176 | # Errors 177 | API_ERR = "Sorry, couldn't reach API at the moment :(" 178 | NOT_FOUND = "Sorry, couldn't find any results for the query :(" 179 | INVALIDCAT = "Hmmm.. maybe you've sent wrong category to look for, please try again!" 180 | KEYERROR = "Oops! something went wrong. Please try again :(" 181 | 182 | # Cancel 183 | CANCEL = "Cancelled the current task!" 184 | 185 | # To search 186 | TOSEARCHMOVIE = "Please reply with the movie title you wanna look for." 187 | TOSEARCHTV = "Please reply with the TV title you wanna look for." 188 | TOSEARCH_ANIME = "Please reply with the anime title you want to look for." 189 | TOSEARCH_MANGA = "Please reply with the manga name you wanna look for." 190 | 191 | # Favs 192 | NOFAVS = "Hmmm 🤔 looks like you don't have any title saved in your watchlist yet!" 193 | REMFAV = "Great work! Successfully cleared your watchlist :)" 194 | SAVED_FAV = "Added '{}' to your Watchlist!" 195 | FAV_EXIST = ( 196 | "Hey there this title is already in your watchlist, Go & finish it instead ;)" 197 | ) 198 | NOT_ALLOWED = "The one who issued the command shall only click this holy button." 199 | 200 | # Stats 201 | STATS = """ 202 | 📊 Current Stats; 203 | 👥 Total users : {} 204 | 💛 Watchlist saved : {} 205 | """ 206 | 207 | # Greet 208 | GREET = "Hey {}! Thank you for adding me in {} :)" 209 | 210 | # Lyrics 211 | SONGNAME = "Please tell me name of the song you want lyrics for." 212 | ARTISTNAME = "Great! now tell me name of the artist for this song." 213 | 214 | LYRICS_ERR = """Sorry, looks like i forgot your song name, possibly due to restart \ 215 | Would you mind sending me again? 216 | """ 217 | LYRIC_NOT_FOUND = "Sorry i couldn't find lyrics for that song." 218 | LYRICS_TOO_BIG = ( 219 | "Lyrics of this song is too big for telegram, I'm sending it as a file..." 220 | ) 221 | 222 | # Music 223 | MUSICQ = "Please choose the quality of music :)" 224 | MUSICNAME = "Okay! tell me name of the song you're looking for." 225 | UPLOAD_BOTAPI = "⌛ uploading song please wait..." 226 | UPLOAD_MTPROTO = "Hmm, file size is more than 50MBs, uploading via mtproto this might take around 5 mins, please wait..." 227 | MUSICNOTFOUND = "Sorry i couldn't find that song :(" 228 | 229 | # Subtitles 230 | TOSEARCHSUBS = "Please reply with the Movie | Anime name you want subs for." 231 | SUBS_STR = "🏷 Subtitles for {}.\nClick on buttons below to download!" 232 | 233 | # Spotify 234 | SPT_LOGIN = "Hey {}, Please click the button below to login with your spotify account." 235 | SPT_LOGIN_PM = "Please contact me in PM to login with your spotify account, inorder to use this feature." 236 | --------------------------------------------------------------------------------