├── Makefile ├── setup.py ├── __init__.py ├── tests ├── __init__.py ├── voice_test.py ├── google_search_test.py ├── search_file_test.py ├── test_twitter.py └── test_youtube.py ├── ultron ├── __init__.py ├── helpers │ ├── __init__.py │ ├── memory_loader.py │ └── twitter_helper.py ├── actions │ ├── twitter │ │ ├── __init__.py │ │ ├── hometimeline.py │ │ ├── streamtweets.py │ │ ├── searchusers.py │ │ ├── blockuser.py │ │ ├── followuser.py │ │ ├── unfollowuser.py │ │ ├── retrievemessage.py │ │ ├── sendmessage.py │ │ ├── updateprofile.py │ │ ├── updateprofileimage.py │ │ ├── statusupdate.py │ │ ├── usertimeline.py │ │ ├── fetchdetails.py │ │ └── updateprofilebackground.py │ ├── youtube │ │ ├── __init__.py │ │ ├── searchvideo.py │ │ └── downloadvideo.py │ ├── __init__.py │ ├── voice.py │ ├── google_search.py │ ├── README.md │ └── search_file.py ├── exception │ ├── twitterexceptions.py │ └── __init__.py ├── ui │ ├── __init__.py │ ├── youtube.py │ └── twitter.py └── api_keys │ ├── README.md │ └── README.txt ├── .travis.yml ├── .coafile ├── run.py ├── ultron_cli.py ├── requirements.txt ├── default_settings.py ├── LICENSE ├── .gitignore └── README.md /Makefile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ultron/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ultron/helpers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ultron/actions/twitter/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ultron/actions/youtube/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/voice_test.py: -------------------------------------------------------------------------------- 1 | from ultron.actions.voice import Voice 2 | 3 | 4 | if __name__ == '__main__': 5 | Voice().execute(text='Hello World') 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: python 3 | python: 4 | - "3.4" 5 | 6 | install: 7 | - pip install coala-bears 8 | - cd $TRAVIS_BUILD_DIR 9 | 10 | script: 11 | - coala --ci 12 | -------------------------------------------------------------------------------- /tests/google_search_test.py: -------------------------------------------------------------------------------- 1 | from ultron.actions.google_search import GoogleSearch 2 | 3 | if __name__ == '__main__': 4 | search_google = GoogleSearch('Kamaal Rashid Khan') 5 | search_google.pre_execute() 6 | search_google.execute() 7 | search_google.post_execute() 8 | -------------------------------------------------------------------------------- /.coafile: -------------------------------------------------------------------------------- 1 | # coafile for this project 2 | # Local fixing 3 | # coala --apply-patches -n 4 | 5 | # python files should only use spaces 6 | [Python Spacing] 7 | files = **/*.py 8 | bears = SpaceConsistencyBear 9 | use_spaces = True 10 | 11 | # python lint 12 | [pep8] 13 | files = **/*.py 14 | bears = PEP8Bear 15 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | from default_settings import default_settings 3 | from ultron_cli import UltronCLI 4 | 5 | if __name__ == '__main__': 6 | default_settings() 7 | try: 8 | UltronCLI().cmdloop() 9 | except KeyboardInterrupt: 10 | print("\nInterrupted by user.") 11 | print("Goodbye") 12 | exit(0) 13 | -------------------------------------------------------------------------------- /tests/search_file_test.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pwd 3 | 4 | from ultron.actions.search_file import Search 5 | 6 | 7 | if __name__ == '__main__': 8 | Search('test.txt', 9 | os.path.expanduser('/home/' + 10 | pwd.getpwuid(os.getuid()).pw_name + 11 | '/Desktop/test') 12 | ).execute() 13 | -------------------------------------------------------------------------------- /ultron/exception/twitterexceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidUserException(Exception): 2 | """ 3 | This exception is raised when screen name 4 | entered by user doesn't exists. 5 | """ 6 | pass 7 | 8 | 9 | class MaximumLimitCrossedException(Exception): 10 | """ 11 | Raised when maximum limit for making request is reached. 12 | Request count will reset after 15 minutes. 13 | """ 14 | pass 15 | -------------------------------------------------------------------------------- /ultron_cli.py: -------------------------------------------------------------------------------- 1 | from ultron.ui import BaseInterpreter 2 | from ultron.ui.twitter import Twitter 3 | from ultron.ui.youtube import Youtube 4 | 5 | 6 | class UltronCLI(BaseInterpreter): 7 | def __init__(self): 8 | super().__init__() 9 | self.prompt = "Ultron>>> " 10 | 11 | def do_youtube(self, *args): 12 | Youtube().cmdloop() 13 | 14 | def do_twitter(self, *args): 15 | Twitter().cmdloop() 16 | -------------------------------------------------------------------------------- /ultron/ui/__init__.py: -------------------------------------------------------------------------------- 1 | import cmd 2 | 3 | 4 | class BaseInterpreter(cmd.Cmd): 5 | """ 6 | This class acts as superclass for all interpreters and 7 | defines common function(s) which must be present in every 8 | interpreter. 9 | """ 10 | 11 | def do_exit(self, *args): 12 | """ 13 | Use to exit from current interpreter. Ctrl + D (EOF) can also 14 | be used. 15 | """ 16 | print("Goodbye") 17 | return True 18 | 19 | do_EOF = do_exit 20 | -------------------------------------------------------------------------------- /ultron/helpers/memory_loader.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | 5 | class Default: 6 | memory_dict = None 7 | 8 | @staticmethod 9 | def load_defaults(force_load=False): 10 | if Default.memory_dict is None or force_load: 11 | memory_file = os.path.join(os.environ.get( 12 | 'ULTRON_PROJECT_DIR'), 'memory.json') 13 | with open(memory_file, 'r') as mem_file: 14 | Default.memory_dict = json.load(mem_file) 15 | return Default.memory_dict 16 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.7.1 2 | bs4==0.0.1 3 | certifi==2019.3.9 4 | chardet==3.0.4 5 | fake-useragent==0.1.11 6 | future==0.17.1 7 | Google-Search-API==1.1.14 8 | gTTS==1.1.8 9 | gTTS-token==1.1.1 10 | idna==2.5 11 | multidict==4.5.2 12 | nose==1.3.7 13 | oauthlib==3.0.1 14 | PyYAML==5.1 15 | requests==2.18.1 16 | requests-oauthlib==1.2.0 17 | selenium==2.53.6 18 | six==1.12.0 19 | soupsieve==1.9.1 20 | tweepy==3.5.0 21 | Unidecode==1.0.23 22 | urllib3==1.21.1 23 | vcrpy==2.0.1 24 | wrapt==1.11.1 25 | yarl==1.2.0 26 | youtube-dl==2019.5.20 27 | -------------------------------------------------------------------------------- /ultron/actions/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | The base class for all actions. 3 | For defining a new action, inherit this class, then define all the associated methods. 4 | """ 5 | from abc import abstractmethod, ABC 6 | 7 | 8 | class Action(ABC): 9 | 10 | @abstractmethod 11 | def execute(self, *args, **kwargs): 12 | return 13 | 14 | @abstractmethod 15 | def post_execute(self, *args, **kwargs): 16 | return 17 | 18 | @abstractmethod 19 | def pre_execute(self, *args, **kwargs): 20 | return 21 | 22 | def run(self): 23 | self.pre_execute() 24 | self.execute() 25 | self.post_execute() 26 | -------------------------------------------------------------------------------- /ultron/actions/voice.py: -------------------------------------------------------------------------------- 1 | from ultron.actions import Action 2 | from gtts import gTTS 3 | import os 4 | 5 | 6 | class Voice(Action): 7 | def __init__(self): 8 | self.tts_engine = gTTS(' ') 9 | self.filename = '' 10 | 11 | def execute(self, text, filename='current_text.mp3'): 12 | self.tts_engine = gTTS(text=text, lang='en') 13 | self.filename = filename 14 | self.tts_engine.save(savefile=self.filename) 15 | os.system('mpg321 current_text.mp3 2> /dev/null') 16 | self.post_execute() 17 | pass 18 | 19 | def pre_execute(self): 20 | pass 21 | 22 | def post_execute(self): 23 | if os.path.exists(self.filename): 24 | os.remove(self.filename) 25 | -------------------------------------------------------------------------------- /ultron/actions/twitter/hometimeline.py: -------------------------------------------------------------------------------- 1 | from ultron.actions import Action 2 | from ultron.helpers.twitter_helper import load_api 3 | 4 | 5 | class HomeTimeLine(Action): 6 | def __init__(self): 7 | self.api = load_api() 8 | self.status_list = None 9 | self.home_timeline_status = False 10 | 11 | def pre_execute(self): 12 | pass 13 | 14 | def execute(self): 15 | """ 16 | Retrieves latest 20 tweets on your timeline. 17 | """ 18 | self.status_list = self.api.home_timeline() 19 | self.home_timeline_status = True 20 | 21 | def post_execute(self, *args, **kwargs): 22 | for status in self.status_list: 23 | print(status.text + " " + status.author.screen_name + "\n") 24 | -------------------------------------------------------------------------------- /ultron/api_keys/README.md: -------------------------------------------------------------------------------- 1 | # API Keys 2 | 3 | A folder where user will put all the files containing keys of an API. 4 | 5 | Naming convention for the files will be _keys. For example: `twitter_keys` 6 | 7 | One line will contain only one key. Twitter requires four keys, so the content of twitter_keys will be: 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## Twitter Instructions 15 | Twitter requires four keys for authentication. They are **CONSUMER_KEY**, **CONSUMER_SECRET**, **ACCESS_TOKEN**, **ACCESS_TOKEN_SECRET**. 16 | 17 | In file `twitter_keys`, make sure that following order is followed. 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /ultron/api_keys/README.txt: -------------------------------------------------------------------------------- 1 | API Keys 2 | 3 | A folder where user will put all the files containing keys related to API. 4 | 5 | Naming convention for files will be _keys. For example: twitter_keys 6 | 7 | One line will contain only one key. Twitter requires four keys, so the content of twitter_keys will be: 8 | 9 | 10 | 11 | 12 | 13 | 14 | Twitter Instructions 15 | 16 | Twitter requires four keys for authentication. They are **CONSUMER_KEY**, **CONSUMER_SECRET**, **ACCESS_TOKEN**, **ACCESS_TOKEN_SECRET**. 17 | 18 | In file `twitter_keys`, make sure that following order is followed. 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ultron/actions/twitter/streamtweets.py: -------------------------------------------------------------------------------- 1 | import tweepy 2 | from ultron.actions import Action 3 | from ultron.helpers.twitter_helper import load_api 4 | 5 | 6 | class StreamTweets(Action, tweepy.StreamListener): 7 | def __init__(self, keyword): 8 | super().__init__() 9 | self.api = load_api() 10 | self.keyword = keyword 11 | 12 | @staticmethod 13 | def on_status(status, **kwarg): 14 | print(status.text) 15 | 16 | def pre_execute(self): 17 | pass 18 | 19 | def execute(self): 20 | """ 21 | Tracks a given term in stream of tweets. 22 | """ 23 | curr_stream = tweepy.Stream(auth=self.api.auth, listener=self) 24 | curr_stream.filter(track=[self.keyword], async=True) 25 | 26 | def post_execute(self): 27 | pass 28 | -------------------------------------------------------------------------------- /ultron/exception/__init__.py: -------------------------------------------------------------------------------- 1 | class NotAbsolutePathException(Exception): 2 | """ 3 | This exception is raised when address of destination directory 4 | is not an absolute address. 5 | """ 6 | pass 7 | 8 | 9 | class UnknownException(Exception): 10 | """ 11 | This exception is raised when everything went smoothly, 12 | but your interpreter or OS decides to screw you anyways, 13 | """ 14 | 15 | pass 16 | 17 | 18 | class NoSecretFileException(Exception): 19 | """ 20 | This exception is raised when file containing secret key for a 21 | service(like twitter) doesn't exists. 22 | """ 23 | pass 24 | 25 | 26 | class InvalidAPIException(Exception): 27 | """ 28 | This exception is raised when API key(s) used for authentication 29 | is invalid. 30 | """ 31 | pass 32 | -------------------------------------------------------------------------------- /ultron/actions/twitter/searchusers.py: -------------------------------------------------------------------------------- 1 | from ultron.actions import Action 2 | from ultron.helpers.twitter_helper import load_api 3 | 4 | 5 | class SearchUsers(Action): 6 | def __init__(self, query): 7 | self.api = load_api() 8 | self.users = None 9 | self.query = query 10 | self.search_user_status = False 11 | 12 | def pre_execute(self): 13 | pass 14 | 15 | def execute(self): 16 | """ 17 | Search users based on keyword. 18 | """ 19 | self.users = self.api.search_users(self.query, 20, 1) 20 | self.search_user_status = True 21 | 22 | def post_execute(self): 23 | print('Search results for '+self.query) 24 | for user in self.users: 25 | print("Screen Name: @" + user.screen_name) 26 | print("User Name: " + user.name) 27 | print() 28 | -------------------------------------------------------------------------------- /ultron/actions/twitter/blockuser.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | 3 | from ultron.actions import Action 4 | from ultron.exception.twitterexceptions import InvalidUserException 5 | from ultron.helpers.twitter_helper import load_api 6 | 7 | 8 | class BlockUser(Action): 9 | def __init__(self, screen_name): 10 | self.api = load_api() 11 | self.block_status = False 12 | self.screen_name = screen_name 13 | 14 | def pre_execute(self): 15 | pass 16 | 17 | def execute(self): 18 | """ 19 | Blocks a user. 20 | """ 21 | try: 22 | self.api.create_block(screen_name=self.screen_name) 23 | except TweepError: 24 | raise InvalidUserException 25 | self.block_status = True 26 | 27 | def post_execute(self): 28 | if self.block_status: 29 | print(self.screen_name + ' Blocked') 30 | -------------------------------------------------------------------------------- /ultron/actions/twitter/followuser.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | from ultron.actions import Action 3 | from ultron.exception.twitterexceptions import InvalidUserException 4 | from ultron.helpers.twitter_helper import load_api 5 | 6 | 7 | class FollowUser(Action): 8 | def __init__(self, screen_name): 9 | self.api = load_api() 10 | self.follow_status = False 11 | self.screen_name = screen_name 12 | 13 | def pre_execute(self): 14 | pass 15 | 16 | def execute(self): 17 | """ 18 | Follows a user. 19 | """ 20 | try: 21 | self.api.create_friendship(screen_name=self.screen_name) 22 | except TweepError: 23 | raise InvalidUserException 24 | self.follow_status = True 25 | 26 | def post_execute(self): 27 | if self.follow_status: 28 | print('You have followed ' + self.screen_name) 29 | -------------------------------------------------------------------------------- /ultron/actions/twitter/unfollowuser.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | 3 | from ultron.actions import Action 4 | from ultron.exception.twitterexceptions import InvalidUserException 5 | from ultron.helpers.twitter_helper import load_api 6 | 7 | 8 | class UnfollowUser(Action): 9 | def __init__(self, screen_name): 10 | self.api = load_api() 11 | self.unfollow_status = False 12 | self.screen_name = screen_name 13 | 14 | def pre_execute(self, *args, **kwargs): 15 | pass 16 | 17 | def execute(self): 18 | """ 19 | Un-follows a user. 20 | """ 21 | try: 22 | self.api.destroy_friendship(screen_name=self.screen_name) 23 | except TweepError: 24 | raise InvalidUserException 25 | self.unfollow_status = True 26 | 27 | def post_execute(self): 28 | if self.unfollow_status: 29 | print('You have unfollowed ' + self.screen_name) 30 | -------------------------------------------------------------------------------- /ultron/actions/twitter/retrievemessage.py: -------------------------------------------------------------------------------- 1 | from ultron.actions import Action 2 | from ultron.helpers.twitter_helper import load_api 3 | 4 | 5 | class RetrieveMessage(Action): 6 | def __init__(self): 7 | self.api = load_api() 8 | self.retrieve_message_status = False 9 | self.messages = None 10 | 11 | def pre_execute(self): 12 | pass 13 | 14 | def execute(self): 15 | """ 16 | Retrieves all text messages sent to current user. 17 | """ 18 | self.messages = self.api.direct_messages(fulltext=True) 19 | self.retrieve_message_status = True 20 | 21 | def post_execute(self): 22 | if self.retrieve_message_status: 23 | for message in self.messages: 24 | print("Message:- " + message.text) 25 | print("Sender:- " + str(message.sender.name)) 26 | print("Date:- " + str(message.created_at.date())) 27 | print("Time:- " + str(message.created_at.time())) 28 | print() 29 | -------------------------------------------------------------------------------- /ultron/actions/twitter/sendmessage.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | 3 | from ultron.actions import Action 4 | from ultron.exception.twitterexceptions import InvalidUserException 5 | from ultron.helpers.twitter_helper import load_api 6 | 7 | 8 | class SendMessage(Action): 9 | def __init__(self, screen_name, text): 10 | self.api = load_api() 11 | self.screen_name = screen_name 12 | self.text = text 13 | self.send_message_status = False 14 | 15 | def pre_execute(self): 16 | pass 17 | 18 | def execute(self): 19 | """ 20 | Sends a text message to your follower. 21 | """ 22 | try: 23 | self.api.send_direct_message(screen_name=self.screen_name, 24 | text=self.text) 25 | except TweepError: 26 | raise InvalidUserException 27 | self.send_message_status = True 28 | 29 | def post_execute(self): 30 | if self.send_message_status: 31 | print('Message sent') 32 | -------------------------------------------------------------------------------- /ultron/actions/twitter/updateprofile.py: -------------------------------------------------------------------------------- 1 | from ultron.actions import Action 2 | from ultron.helpers.twitter_helper import load_api 3 | 4 | 5 | class UpdateProfile(Action): 6 | def __init__(self, profile_data=None): 7 | self.api = load_api() 8 | self.update_profile_status = False 9 | self.profile_data = profile_data 10 | 11 | def pre_execute(self): 12 | pass 13 | 14 | def execute(self): 15 | """ 16 | Updates profile of authenticated user. 17 | """ 18 | self.api.update_profile(name=self.profile_data.get('name', None), 19 | location=self.profile_data.get( 20 | 'location', None), 21 | url=self.profile_data.get('url', None), 22 | description=self.profile_data.get('description', None)) 23 | self.update_profile_status = True 24 | 25 | def post_execute(self): 26 | if self.update_profile_status: 27 | print("Profile updated") 28 | -------------------------------------------------------------------------------- /ultron/actions/twitter/updateprofileimage.py: -------------------------------------------------------------------------------- 1 | import os 2 | from ultron.actions import Action 3 | from ultron.exception import NotAbsolutePathException 4 | from ultron.helpers.twitter_helper import load_api 5 | 6 | 7 | class UpdateProfileImage(Action): 8 | def __init__(self, filename): 9 | self.api = load_api() 10 | self.update_profile_status = False 11 | self.filename = filename 12 | 13 | def pre_execute(self): 14 | if self.filename is not None: 15 | if not os.path.abspath(self.filename): 16 | raise NotAbsolutePathException 17 | if not os.path.exists(self.filename): 18 | raise FileNotFoundError 19 | 20 | def execute(self): 21 | """ 22 | Updates the profile image of authenticated user. 23 | """ 24 | self.api.update_profile_image(filename=self.filename) 25 | self.update_profile_status = True 26 | 27 | def post_execute(self): 28 | if self.update_profile_status: 29 | print('Updated Profile Picture') 30 | -------------------------------------------------------------------------------- /default_settings.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import os 4 | 5 | 6 | def default_settings(): 7 | os.environ['ULTRON_PROJECT_DIR'] = os.path.dirname( 8 | os.path.abspath(__file__)) 9 | defaults = {'VIDEO_STORAGE_DIRECTORY': os.path.expanduser('~/Downloads'), 10 | 'AUDIO_STORAGE_DIRECTORY': os.path.expanduser('~/Downloads'), 11 | 'TIMEZONE': 'Asia/Kolkata'} 12 | memory_file_path = os.path.join( 13 | os.environ['ULTRON_PROJECT_DIR'], 'memory.json') 14 | if not os.path.exists(memory_file_path): 15 | with open(memory_file_path, 'w') as memory_file: 16 | json.dump(defaults, memory_file) 17 | if os.environ.get('PYTHONPATH', None) is None: 18 | os.environ['PYTHONPATH'] = os.environ['ULTRON_PROJECT_DIR'] 19 | else: 20 | path_list = str(os.environ['PYTHONPATH']).split(':') 21 | if not os.environ['ULTRON_PROJECT_DIR'] in path_list: 22 | os.environ['PYTHONPATH'] += os.pathsep + \ 23 | os.environ['ULTRON_PROJECT_DIR'] 24 | -------------------------------------------------------------------------------- /ultron/actions/twitter/statusupdate.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ultron.actions import Action 4 | from ultron.helpers.twitter_helper import load_api 5 | 6 | 7 | class StatusUpdate(Action): 8 | def __init__(self, media_file=None, text=None): 9 | self.api = load_api() 10 | self.status_update_status = False 11 | self.media_file = media_file 12 | self.text = text 13 | 14 | def pre_execute(self): 15 | if self.media_file is not None: 16 | if not os.path.exists(self.media_file): 17 | raise FileNotFoundError 18 | 19 | def execute(self): 20 | """ 21 | Tweet from authenticated user account . 22 | """ 23 | if self.media_file is not None: 24 | self.api.update_with_media(filename=self.media_file, 25 | status=self.text) 26 | else: 27 | self.api.update_status(status=self.text) 28 | self.status_update_status = True 29 | 30 | def post_execute(self): 31 | if self.status_update_status: 32 | print('Status Updated') 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Prakash Rai 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 | -------------------------------------------------------------------------------- /ultron/actions/twitter/usertimeline.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | 3 | from ultron.actions import Action 4 | from ultron.exception.twitterexceptions import InvalidUserException 5 | from ultron.helpers.twitter_helper import load_api 6 | 7 | 8 | class UserTimeLineTweets(Action): 9 | def __init__(self, screen_name): 10 | self.api = load_api() 11 | self.user_timeline_tweets_status = True 12 | self.timeline_tweets = None 13 | self.screen_name = screen_name 14 | 15 | def pre_execute(self): 16 | pass 17 | 18 | def execute(self): 19 | """ 20 | Retrieves latest 20 tweets from given user's timeline. 21 | """ 22 | try: 23 | self.timeline_tweets = self.api.user_timeline( 24 | self.screen_name) 25 | except TweepError: 26 | raise InvalidUserException 27 | self.user_timeline_tweets_status = True 28 | 29 | def post_execute(self, *args, **kwargs): 30 | if self.user_timeline_tweets_status: 31 | for status in self.timeline_tweets: 32 | print(status.text+" :tweeted by: "+status.author.screen_name) 33 | -------------------------------------------------------------------------------- /ultron/actions/twitter/fetchdetails.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | 3 | from ultron.actions import Action 4 | from ultron.exception.twitterexceptions import InvalidUserException 5 | from ultron.helpers.twitter_helper import load_api 6 | 7 | 8 | class FetchDetails(Action): 9 | def __init__(self, screen_name): 10 | self.api = load_api() 11 | self.fetch_details_status = False 12 | self.details = None 13 | self.screen_name = screen_name 14 | 15 | def pre_execute(self): 16 | pass 17 | 18 | def execute(self): 19 | """ 20 | Fetch details about given user. 21 | """ 22 | try: 23 | self.details = self.api.get_user(screen_name=self.screen_name) 24 | except TweepError: 25 | raise InvalidUserException 26 | self.fetch_details_status = True 27 | 28 | def post_execute(self): 29 | print('Name :' + str(self.details.name)) 30 | print('Descriptions :' + str(self.details.description)) 31 | print("Followers :" + str(self.details.followers_count)) 32 | print("Friends :" + str(self.details.friends_count)) 33 | print("URL :" + str(self.details.url)) 34 | print("ID string :" + str(self.details.id_str)) 35 | print("Screen name :" + str(self.details.screen_name)) 36 | -------------------------------------------------------------------------------- /ultron/actions/twitter/updateprofilebackground.py: -------------------------------------------------------------------------------- 1 | from tweepy import TweepError 2 | from ultron.actions import Action 3 | from ultron.helpers.twitter_helper import load_api 4 | 5 | 6 | class UpdateProfileBackground(Action): 7 | # Some error is present. Created a PR in tweepy to fix it. 8 | # TODO Fix error here 9 | def __init__(self): 10 | self.api = load_api() 11 | self.update_background_status = True 12 | 13 | def pre_execute(self, *args, **kwargs): 14 | pass 15 | 16 | def execute(self, *args, **kwargs): 17 | """ 18 | Updates background image of authenticated user. 19 | :param args: Currently, no use. 20 | :param kwargs: Keyword arguments, only 'filename' 21 | is accepted, which indicated the path of file. 22 | Path must be absolute and reside on local machine. 23 | :return: None 24 | """ 25 | try: 26 | self.api.update_profile_background_image( 27 | filename=kwargs['filename']) 28 | except TweepError as update_background_error: 29 | print(update_background_error) 30 | self.update_background_status = False 31 | 32 | def post_execute(self, *args, **kwargs): 33 | if self.update_background_status: 34 | print('Background image updated') 35 | -------------------------------------------------------------------------------- /ultron/actions/google_search.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | 3 | from ultron.actions import Action 4 | from google import google 5 | 6 | 7 | class GoogleSearch(Action): 8 | def __init__(self, search_query): 9 | self.search_query = search_query 10 | 11 | def pre_execute(self): 12 | pass 13 | 14 | def execute(self, *args, **kwargs): 15 | """ 16 | Here all the Links and the Names, that are available on the first page of Google Search, 17 | of a given query are displayed. 18 | The user chooses which link to open and that link is opened in a new tab of 19 | the default web browser using the webbrowser module 20 | """ 21 | 22 | search_results = google.search(self.search_query, 1) 23 | 24 | if len(search_results) == 1: 25 | result_stat = "1 result found" 26 | else: 27 | result_stat = str(len(search_results))+" results found" 28 | print(result_stat+"\n") 29 | 30 | tmp = 1 31 | for each_pair in search_results: 32 | print(str(tmp) + ".) " + each_pair.name) 33 | print(each_pair.link) 34 | print() 35 | tmp = tmp + 1 36 | 37 | if len(search_results) > 0: 38 | print("__________________________________________________" 39 | "____________________________________________________________\n") 40 | i = int(input("Enter the link number to be open\n")) 41 | webbrowser.open(search_results[i - 1].link) 42 | pass 43 | 44 | def post_execute(self): 45 | pass 46 | -------------------------------------------------------------------------------- /ultron/actions/README.md: -------------------------------------------------------------------------------- 1 | # Actions 2 | 3 | ## voice.py 4 | A clear female voice, implemented using gtts library. 5 | 6 | ## twitter.py 7 | Handles twitter operations. It has following features: 8 | 9 | 1. Retrieve latest 20 tweets from authenticated user's timeline. 10 | 2. Stream tweets. 11 | 3. Retrieve latest 20 tweets from a user timeline. 12 | 4. Status update of authenticated user with media(optional). 13 | 5. Fetch all details of a given user as well as authenticated user. 14 | 6. Search users. 15 | 7. Send Direct Messages. 16 | 8. Follow a user. 17 | 9. Unfollow a user. 18 | 10. Update the authenticating user’s profile image. Valid formats: GIF, JPG, or PNG 19 | 11. Update authenticating user’s background image. Valid formats: GIF, JPG, or PNG 20 | 12. Sets values that users are able to set under the “Account” tab of their settings page. 21 | 13. Block a user. 22 | 14. Search tweets based on keywords. 23 | 24 | ## youtube.py 25 | 26 | Handles youtube operations. Currently, it has following features 27 | 28 | 1. Search videos. 29 | 2. Download videos in highest quality available for that video. 30 | 31 | ## google_search.py 32 | 1. Scrapes through the first page of Google Search for a given search query and displays all the links of that page. 33 | 2. It also asks the user to select which link to open 34 | 3. The link is then opened in a new tab of the default web browser 35 | 36 | #### Todos 37 | 1. Video to audio conversion. 38 | 2. Allow user to select quality. 39 | 3. Get meta information without downloading the video. 40 | -------------------------------------------------------------------------------- /ultron/actions/youtube/searchvideo.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from bs4 import BeautifulSoup 3 | from ultron.actions import Action 4 | 5 | 6 | class SearchVideo(Action): 7 | """ 8 | execute method raises errors and exceptions, so if you are 9 | using this class anywhere, make sure that you have handled them. 10 | 11 | See execute method documentation for more details. 12 | """ 13 | 14 | def __init__(self, query): 15 | """ 16 | 17 | :param query: query to be searched. 18 | """ 19 | self.search_results = [] 20 | 21 | self.query = query 22 | self.search_status = False 23 | 24 | def pre_execute(self, *args, **kwargs): 25 | pass 26 | 27 | def execute(self, *args, **kwargs): 28 | """ 29 | Scrapes the first page of youtube search results and store 30 | the contents in search_results list. 31 | 32 | Raises error(s) if something is wrong with requests or 33 | network connection 34 | 35 | """ 36 | url = "https://www.youtube.com/results?search_query=" + self.query 37 | try: 38 | response = requests.get(url) 39 | except Exception as e: 40 | raise e 41 | html = response.text 42 | soup = BeautifulSoup(html, "lxml") 43 | for vid in soup.findAll(attrs={'class': 'yt-uix-tile-link'}): 44 | self.search_results.append(vid) 45 | self.search_status = True 46 | 47 | def post_execute(self, *args, **kwargs): 48 | pass 49 | 50 | def get_search_results(self): 51 | """ 52 | :return: list of search results objects. 53 | """ 54 | return self.search_results 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ultron 2 | 3 | [![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.svg)](https://gitter.im/ultron_/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) 4 | 5 | Personal Assistant 6 | 7 | ## Install requirements 8 | 9 | cd ultron 10 | sudo -H pip install -r requirements.txt 11 | sudo apt-get install mpg321 12 | 13 | ## For testing 14 | cd ultron/tests 15 | For voice module: python3 voice_test.py 16 | For twitter tests: python3 -m unittest test_twitter.py 17 | For youtube tests: python3 -m unittest test_youtube.py 18 | 19 | ## Twitter 20 | 21 | ### Twitter Instructions 22 | Twitter requires four keys for authentication. They are **CONSUMER_KEY**, **CONSUMER_SECRET**, **ACCESS_TOKEN**, **ACCESS_TOKEN_SECRET**. 23 | 24 | #### How to get them 25 | 26 | Visit [here](https://apps.twitter.com/) and follow the steps given bellow. 27 | 28 | 1. Sign in from your twitter account 29 | 2. On left hand side, click on Create New App. 30 | 3. Fill anything relevant in first three input fields. 31 | 4. If you want to send and read direct messages from Ultron, go to 'Permissions' tab, 32 | mark the 'Read, Write and Access direct messages' radio button, scroll down and click on 33 | 'Update Settings'. 34 | 5. Go to third tab(Keys and Access Tokens) 35 | 6. Click on'Regenerate Consumer key and secret'. (important step) 36 | 7. Copy the Consumer key and Consumer API Secret. 37 | 8. Scroll down and click on Create my Access Token to get access token and access token secrets. 38 | 9. Put them in a file twitter_keys in ultron/ultron/api_keys/ folder. 39 | 10. While putting the keys, please follow the order mentioned in README.md 40 | in ultrom/ultron/api_keys folder. 41 | 42 | Refer to `actions` README for more information 43 | 44 | ## Youtube 45 | 46 | No special tokens are required. Refer to `actions` README for more information. 47 | -------------------------------------------------------------------------------- /ultron/ui/youtube.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ultron.actions.youtube.downloadvideo import DownloadVideo 4 | from ultron.exception import NotAbsolutePathException, UnknownException 5 | from ultron.helpers.memory_loader import Default 6 | from ultron.ui import BaseInterpreter 7 | 8 | 9 | class Youtube(BaseInterpreter): 10 | def __init__(self): 11 | super().__init__() 12 | self.prompt = "Youtube>> " 13 | self.memory = Default.load_defaults() 14 | 15 | def do_download_video(self, *args): 16 | query = input("Enter search keyword\n") 17 | storage_directory = os.path.expanduser(input("Where do you want to save your file." 18 | " Leave blank to save in ~/Downloads.\n")) 19 | filename = input( 20 | "Enter a custom file name. Leave blank to use auto-generated filename.\n") 21 | if len(storage_directory.strip()) == 0: 22 | storage_directory = os.path.expanduser( 23 | self.memory.get('VIDEO_STORAGE_DIRECTORY')) 24 | if len(filename.strip()) == 0: 25 | filename = None 26 | video_downloader = DownloadVideo(query=query, 27 | filename=filename, 28 | storage_directory=storage_directory) 29 | Youtube.run(video_downloader) 30 | 31 | @staticmethod 32 | def run(youtube_obj): 33 | try: 34 | youtube_obj.run() 35 | except FileExistsError: 36 | print('File with same name exists in ' 37 | 'destination directory.') 38 | except PermissionError: 39 | print("You don't have permission to " 40 | "access the files in destination directory.") 41 | except NotAbsolutePathException: 42 | print("Destination directory path is not absolute.") 43 | except IndexError: 44 | print("Wrong choice. Please retry.") 45 | except UnknownException: 46 | # If this block is being executed, 47 | # then LOL, you are screwed. 48 | print('Unknown exception occurred. Please retry.') 49 | -------------------------------------------------------------------------------- /ultron/helpers/twitter_helper.py: -------------------------------------------------------------------------------- 1 | import tweepy 2 | import os 3 | 4 | from tweepy import TweepError, RateLimitError 5 | 6 | from ultron.exception import NoSecretFileException, InvalidAPIException 7 | from ultron.exception.twitterexceptions import MaximumLimitCrossedException 8 | 9 | 10 | class APIHandler: 11 | """ 12 | This class handles twitter api creation and validation. 13 | """ 14 | _api = None 15 | 16 | @staticmethod 17 | def create_api(): 18 | if APIHandler._api is None: 19 | try: 20 | with open(os.path.join(os.path.dirname(__file__), '../api_keys/twitter_keys'), 'r') as f: 21 | keys = f.readlines() 22 | except FileNotFoundError: 23 | raise NoSecretFileException("File containing twitter OAuth keys doesn't exists. " 24 | "Please create a file named twitter_keys " 25 | "containing your OAuth credentials " 26 | "in ultron/ultron/api_keys directory.") 27 | keys = [key.strip() for key in keys] 28 | if len(keys) != 4: 29 | raise InvalidAPIException('Twitter requires 4 keys. But, ' 30 | + str(len(keys)) + 31 | ' are present. Please ensure ' 32 | 'that keys are placed in same order ' 33 | 'as mentioned in README') 34 | else: 35 | consumer_key = keys[0] 36 | consumer_secret = keys[1] 37 | access_token = keys[2] 38 | access_token_secret = keys[3] 39 | auth = tweepy.OAuthHandler(consumer_key, consumer_secret) 40 | auth.set_access_token(access_token, access_token_secret) 41 | APIHandler._api = tweepy.API(auth) 42 | try: 43 | APIHandler._api.verify_credentials() 44 | except RateLimitError: 45 | raise MaximumLimitCrossedException('Rate limit for API is reached. ' 46 | 'Please wait for 15 minutes.') 47 | except TweepError: 48 | raise InvalidAPIException('Invalid or expired token') 49 | return APIHandler._api 50 | 51 | 52 | def load_api(): 53 | return APIHandler.create_api() 54 | -------------------------------------------------------------------------------- /ultron/actions/search_file.py: -------------------------------------------------------------------------------- 1 | import os 2 | from fnmatch import fnmatch 3 | 4 | from ultron.actions import Action 5 | 6 | 7 | class Search(Action): 8 | class FileObject(object): 9 | def __init__(self, file_path): 10 | self.file_path = file_path 11 | self.file = file_path.split('/').pop() 12 | 13 | def match(self, file): 14 | """ 15 | Function to check match for file. 16 | :return: True if matched else False. 17 | """ 18 | return fnmatch(self.file, file) 19 | 20 | def __init__(self, file, path='/home'): 21 | """ 22 | Initialize object for searching. 23 | :param path: Path to start looking. 24 | :param file: File to search in given path. 25 | """ 26 | self.path = path 27 | self.file = file 28 | self.matched_files = [] 29 | self.matched_paths = [] 30 | 31 | def pre_execute(self): 32 | """ 33 | This will generate a iterable for searching. 34 | :return: Iterable to search the matches for file. 35 | """ 36 | for root, dirs, files in os.walk(self.path): 37 | for directory in dirs: 38 | for filename in directory: 39 | filename = os.path.join(root, filename) 40 | if os.path.isfile(filename) or os.path.isdir(filename): 41 | yield self.FileObject(filename) 42 | 43 | for filename in files: 44 | filename = os.path.join(root, filename) 45 | if os.path.isfile(filename) or os.path.isdir(filename): 46 | yield self.FileObject(filename) 47 | 48 | def execute(self): 49 | """ 50 | This will actually search for file 51 | and generate a iterable of matches 52 | which will be printed in post_execute(). 53 | :return: No returns. 54 | """ 55 | for file in self.pre_execute(): 56 | if file.match(self.file): 57 | self.matched_files.append(file.file) 58 | self.matched_paths.append(file.file_path) 59 | self.post_execute() 60 | 61 | def post_execute(self): 62 | """ 63 | This will print out all the found items. 64 | :return: No returns. 65 | """ 66 | if not self.matched_files: 67 | print('Sorry No matches') 68 | else: 69 | print('The matches for your file are:') 70 | for i in range(len(self.matched_files)): 71 | print(str(i+1), '\b.', self.matched_files[i], 'at:') 72 | print('\t', self.matched_paths[i]) 73 | -------------------------------------------------------------------------------- /tests/test_twitter.py: -------------------------------------------------------------------------------- 1 | import os 2 | import warnings 3 | import unittest 4 | from ultron.actions.twitter.hometimeline import HomeTimeLine 5 | from ultron.actions.twitter.blockuser import BlockUser 6 | from ultron.actions.twitter.fetchdetails import FetchDetails 7 | from ultron.actions.twitter.followuser import FollowUser 8 | from ultron.actions.twitter.unfollowuser import UnfollowUser 9 | from ultron.actions.twitter.updateprofile import UpdateProfile 10 | from ultron.actions.twitter.retrievemessage import RetrieveMessage 11 | from ultron.actions.twitter.sendmessage import SendMessage 12 | from ultron.actions.twitter.updateprofileimage import UpdateProfileImage 13 | from ultron.actions.twitter.statusupdate import StatusUpdate 14 | from ultron.actions.twitter.searchusers import SearchUsers 15 | from ultron.actions.twitter.usertimeline import UserTimeLineTweets 16 | 17 | 18 | class TwitterTest(unittest.TestCase): 19 | def test_home_timeline(self): 20 | warnings.simplefilter("ignore") 21 | obj = HomeTimeLine() 22 | obj.execute() 23 | self.assertEqual(obj.home_timeline_status, True) 24 | 25 | def test_follow(self): 26 | obj = FollowUser(screen_name='kamaalrkhan') 27 | obj.execute() 28 | self.assertEqual(obj.follow_status, True) 29 | 30 | def test_unfollow(self): 31 | obj = UnfollowUser(screen_name='kamaalrkhan') 32 | obj.execute() 33 | self.assertEqual(obj.unfollow_status, True) 34 | 35 | def test_block_user(self): 36 | obj = BlockUser(screen_name='kamaalrkhan') 37 | obj.execute() 38 | self.assertEqual(obj.block_status, True) 39 | 40 | def test_update_profile_image(self): 41 | filename = os.path.join(os.path.expanduser('~'), 'Documents/SJ.jpg') 42 | obj = UpdateProfileImage(filename=filename) 43 | obj.execute() 44 | if os.path.exists(filename): 45 | self.assertEqual(obj.update_profile_status, True) 46 | else: 47 | self.assertEqual(obj.update_profile_status, False) 48 | 49 | def test_update_profile(self): 50 | obj = UpdateProfile(profile_data={'name': 'Prakash Rai'}) 51 | obj.execute() 52 | self.assertEqual(obj.update_profile_status, True) 53 | 54 | def test_send_message(self): 55 | obj = SendMessage(screen_name='@FirebaseUltron', 56 | text='Ultron sent a message to you') 57 | obj.execute() 58 | self.assertEqual(obj.send_message_status, True) 59 | 60 | def test_retrieve_message(self): 61 | obj = RetrieveMessage() 62 | obj.execute() 63 | self.assertEqual(obj.retrieve_message_status, True) 64 | 65 | def test_search_users(self): 66 | obj = SearchUsers(query='Python') 67 | obj.execute() 68 | self.assertEqual(obj.search_user_status, True) 69 | 70 | def test_fetch_details(self): 71 | obj = FetchDetails(screen_name='@FirebaseUltron') 72 | obj.execute() 73 | self.assertEqual(obj.fetch_details_status, True) 74 | 75 | def test_status_update(self): 76 | obj = StatusUpdate( 77 | text='Ultron is being tested again in current configuration') 78 | obj.execute() 79 | self.assertEqual(obj.status_update_status, True) 80 | 81 | def test_user_timeline(self): 82 | obj = UserTimeLineTweets(screen_name='@FirebaseUltron') 83 | obj.execute() 84 | self.assertEqual(obj.user_timeline_tweets_status, True) 85 | -------------------------------------------------------------------------------- /tests/test_youtube.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | 4 | 5 | from ultron.actions.youtube.downloadvideo import DownloadVideo 6 | from ultron.actions.youtube.searchvideo import SearchVideo 7 | from ultron.exception import NotAbsolutePathException 8 | 9 | 10 | class YoutubeTest(unittest.TestCase): 11 | def test_search_video(self): 12 | search_video = SearchVideo('One second video') 13 | try: 14 | search_video.pre_execute() 15 | search_video.execute() 16 | search_video.post_execute() 17 | except Exception: 18 | pass 19 | self.assertEqual(search_video.search_status, True) 20 | 21 | def test_download_video(self): 22 | # Test case 1 23 | download_video = DownloadVideo(query='1 second Video', 24 | filename=None, 25 | storage_directory=None) 26 | try: 27 | download_video.pre_execute(test=True) 28 | download_video.execute() 29 | download_video.post_execute() 30 | except FileExistsError as fee: 31 | print(str(fee)) 32 | except PermissionError as pe: 33 | print(str(pe)) 34 | except NotAbsolutePathException as nape: 35 | print(str(nape)) 36 | self.assertEqual(download_video.download_status, True) 37 | 38 | # Test case 2 39 | download_video = DownloadVideo(query='1 second Video', 40 | filename='onesecvid.mp4', 41 | storage_directory=None) 42 | try: 43 | download_video.pre_execute(test=True) 44 | download_video.execute() 45 | download_video.post_execute() 46 | except FileExistsError as fee: 47 | print(str(fee)) 48 | except PermissionError as pe: 49 | print(str(pe)) 50 | except NotAbsolutePathException as nape: 51 | print(str(nape)) 52 | self.assertEqual(download_video.download_status, True) 53 | 54 | # Test case 3 55 | # If you are going to change storage_directory, 56 | # make sure that you have changed it in print 57 | # statement where we are handling shutil.Error. 58 | 59 | download_video = DownloadVideo(query='1 second Video', 60 | filename='1sv.mp4', 61 | storage_directory=os.path.expanduser('~')) 62 | try: 63 | download_video.pre_execute(test=True) 64 | download_video.execute() 65 | download_video.post_execute() 66 | except FileExistsError as fee: 67 | print(str(fee)) 68 | except PermissionError as pe: 69 | print(str(pe)) 70 | except NotAbsolutePathException as nape: 71 | print(str(nape)) 72 | self.assertEqual(download_video.download_status, True) 73 | 74 | # Test case 4 75 | download_video = DownloadVideo(query='1 second Video', 76 | filename='1SV.mp4', 77 | storage_directory="This path " 78 | "doesn't exists") 79 | try: 80 | download_video.pre_execute(test=True) 81 | download_video.execute() 82 | download_video.post_execute() 83 | except FileExistsError as fee: 84 | print(str(fee)) 85 | except PermissionError as pe: 86 | print(str(pe)) 87 | except NotAbsolutePathException as nape: 88 | print(str(nape)) 89 | self.assertEqual(download_video.download_status, False) 90 | 91 | # Test case 5 92 | download_video = DownloadVideo(query='1 second Video', 93 | filename='1SV.mp4', 94 | storage_directory=os.path.expanduser( 95 | '~') 96 | + "/This path doesn't exists yet") 97 | try: 98 | download_video.pre_execute(test=True) 99 | download_video.execute() 100 | download_video.post_execute() 101 | except FileExistsError as fee: 102 | print(str(fee)) 103 | except PermissionError as pe: 104 | print(str(pe)) 105 | except NotAbsolutePathException as nape: 106 | print(str(nape)) 107 | self.assertEqual(download_video.download_status, True) 108 | -------------------------------------------------------------------------------- /ultron/actions/youtube/downloadvideo.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | from ultron.actions import Action 5 | import youtube_dl 6 | 7 | from ultron.actions.youtube.searchvideo import SearchVideo 8 | from ultron.exception import NotAbsolutePathException, UnknownException 9 | 10 | 11 | class DownloadVideo(Action): 12 | """ 13 | This class handles video downloading from youtube. 14 | 15 | Raises following errors and exceptions: 16 | 17 | 1. PermissionError : This is raised when user doesn't have permission to 18 | create/store the file in destination directory. 19 | 2. FileExistsError: This error is raised when a file with same name as source 20 | file exists in destination folder. 21 | 3. UnknownException: This exception is raised when everything went smoothly, 22 | but suddenly your python interpreter decides to screw you. 23 | 4. NotAbsolutePathException: This exception is raised when address of 24 | destination directory is not an absolute address 25 | """ 26 | 27 | def __init__(self, query, filename, storage_directory): 28 | """ 29 | :param query: Search query. 30 | :param filename: Filename in which video will be saved. 31 | For default filename, see explanation in documentation 32 | of pre_execute method. 33 | :param storage_directory: Directory where user wants to save the file. 34 | Default storage directory is directory from where script is executed. 35 | """ 36 | self.url = None 37 | self.filename = filename 38 | if storage_directory is None: 39 | self.storage_directory = os.path.expanduser("~/Downloads") 40 | else: 41 | self.storage_directory = storage_directory 42 | self.query = query 43 | self.options = {'outtmpl': filename} 44 | self.download_status = True 45 | 46 | def pre_execute(self, *args, **kwargs): 47 | """ 48 | Creates SearchVideo objects and search for the give query. 49 | For testing, we need not to show the search results, so results 50 | are displayed only when kwargs['test'] is True. 51 | If kwargs['test'] is False, then 52 | Displays search results title along with a number 53 | Asks user to enter the index number of video which they want to download. 54 | Then extracts the URL of the video which user has asked. 55 | Then it checks whether user has given any filename for downloaded 56 | video or not. If no name is given by the user, last line provides a 57 | default filename to save the video, which is generated using video title. 58 | 59 | Exceptions and Errors Raised: 60 | 1. NotAbsolutePathException 61 | 2. IndexError 62 | """ 63 | if not os.path.isabs(self.storage_directory): 64 | self.download_status = False 65 | raise NotAbsolutePathException 66 | search_video = SearchVideo(self.query) 67 | search_video.execute() 68 | search_results = search_video.get_search_results() 69 | if not kwargs.get('test', False): 70 | for i in range(len(search_results)): 71 | print("[" + str(i + 1) + "] " + 72 | search_results[i]['title']) 73 | choice = input("\nEnter the index number of video " 74 | "which you want to download\n") 75 | else: 76 | choice = 1 77 | try: 78 | self.url = 'https://www.youtube.com' + \ 79 | search_results[int(choice) - 1]['href'] 80 | except IndexError: 81 | raise 82 | if self.options['outtmpl'] is None: 83 | self.options['outtmpl'] = \ 84 | search_results[int(choice) - 1]['title'] + '.mp4' 85 | 86 | def execute(self, *args, **kwargs): 87 | """ 88 | Downloads the video. 89 | """ 90 | with youtube_dl.YoutubeDL(self.options) as ydl: 91 | ydl.download([self.url]) 92 | 93 | def post_execute(self, *args, **kwargs): 94 | """ 95 | It deals with moving the downloaded file to directory given by 96 | user. Default destination directory is ~/Downloads. 97 | 98 | If due to any reason, video is not copied to destination directory, then 99 | it will be stored in the directory from where script has been executed. 100 | 101 | Exception Raised: 102 | 1. PermissionError 103 | 2. FileExistsError 104 | 3. UnknownException 105 | """ 106 | 107 | if not os.path.exists(self.storage_directory): 108 | if os.access(os.path.dirname(self.storage_directory), mode=os.W_OK): 109 | os.makedirs(self.storage_directory) 110 | else: 111 | self.download_status = False 112 | raise PermissionError 113 | curr_path = os.getcwd() + '/' + self.options['outtmpl'] 114 | if os.path.exists(self.storage_directory + '/' + self.options['outtmpl']): 115 | self.download_status = False 116 | raise FileExistsError 117 | else: 118 | if os.path.exists(curr_path): 119 | if os.access(self.storage_directory, os.W_OK): 120 | shutil.move(curr_path, self.storage_directory) 121 | else: 122 | self.download_status = False 123 | raise PermissionError 124 | else: 125 | self.download_status = False 126 | # I don't know how control reached here. 127 | # Read UnknownException documentation for details. 128 | # LOL 129 | raise UnknownException 130 | -------------------------------------------------------------------------------- /ultron/ui/twitter.py: -------------------------------------------------------------------------------- 1 | from ultron.actions.twitter.hometimeline import HomeTimeLine 2 | from ultron.actions.twitter.blockuser import BlockUser 3 | from ultron.actions.twitter.fetchdetails import FetchDetails 4 | from ultron.actions.twitter.followuser import FollowUser 5 | from ultron.actions.twitter.unfollowuser import UnfollowUser 6 | from ultron.actions.twitter.updateprofile import UpdateProfile 7 | from ultron.actions.twitter.retrievemessage import RetrieveMessage 8 | from ultron.actions.twitter.sendmessage import SendMessage 9 | from ultron.actions.twitter.updateprofileimage import UpdateProfileImage 10 | from ultron.actions.twitter.statusupdate import StatusUpdate 11 | from ultron.actions.twitter.searchusers import SearchUsers 12 | from ultron.actions.twitter.usertimeline import UserTimeLineTweets 13 | from ultron.exception import NotAbsolutePathException, InvalidAPIException, NoSecretFileException 14 | from ultron.exception.twitterexceptions import InvalidUserException 15 | from ultron.ui import BaseInterpreter 16 | 17 | 18 | class Twitter(BaseInterpreter): 19 | def __init__(self): 20 | super().__init__() 21 | self.prompt = "Twitter>>> " 22 | 23 | @staticmethod 24 | def run(twitter_obj): 25 | try: 26 | twitter_obj.run() 27 | except FileNotFoundError: 28 | print("Path entered for media file doesn't exists." 29 | " Please enter a correct path.") 30 | except InvalidUserException: 31 | print('Screen name of user is invalid.') 32 | except NotAbsolutePathException: 33 | print('Path entered by you is not absolute.' 34 | ' Please enter an absolute address.') 35 | except InvalidAPIException: 36 | print('OAuth keys are expired or invalid. ' 37 | 'Please generate a new key. Instructions for generating' 38 | 'keys are given in README file.') 39 | except NoSecretFileException: 40 | print("File containing secret keys doesn't exists. " 41 | "Please follow instructions written in readme.") 42 | 43 | def do_home_timeline(self, *args): 44 | home_time_line = HomeTimeLine() 45 | Twitter.run(home_time_line) 46 | 47 | def do_block_user(self, *args): 48 | screen_name = input( 49 | "Enter the screen name of user you want to block\n") 50 | block_user = BlockUser(screen_name=screen_name) 51 | Twitter.run(block_user) 52 | 53 | def do_user_timeline_details(self, *args): 54 | screen_name = input('Enter the screen name of user\n') 55 | user_timeline = UserTimeLineTweets(screen_name=screen_name) 56 | Twitter.run(user_timeline) 57 | 58 | def do_search_users(self, *args): 59 | keyword = input("Enter search keyword\n") 60 | search_users = SearchUsers(query=keyword) 61 | Twitter.run(search_users) 62 | 63 | def do_update_your_status(self, *args): 64 | status = input("Enter status text.\n") 65 | media_file = input("Enter absolute path of media file you want to attach. " 66 | "Leave blank if you don't want to attach any media.\n") 67 | if len(media_file.strip()) == 0: 68 | status_update = StatusUpdate(text=status) 69 | else: 70 | status_update = StatusUpdate(text=status, media_file=media_file) 71 | Twitter.run(status_update) 72 | 73 | def do_update_your_profile(self, *args): 74 | profile_data = {} 75 | name = input('Enter your name. Leave blank to use previous.\n') 76 | if len(name.strip()) != 0: 77 | profile_data['name'] = name.strip() 78 | description = input( 79 | 'Enter new description. Leave blank to use previous.\n') 80 | if len(description.strip()) != 0: 81 | profile_data['description'] = description.strip() 82 | url = input('Enter url of your website. Leave blank to use previous.\n') 83 | if len(url.strip()) != 0: 84 | profile_data['url'] = url.strip() 85 | location = input( 86 | 'Enter your current location. Leave blank to use previous.\n') 87 | if len(location.strip()) != 0: 88 | profile_data['location'] = location.strip() 89 | update_profile = UpdateProfile(profile_data=profile_data) 90 | Twitter.run(update_profile) 91 | 92 | def do_follow_user(self, *args): 93 | screen_name = input('Enter screen name of user you want to follow\n') 94 | follow_user = FollowUser(screen_name=screen_name) 95 | Twitter.run(follow_user) 96 | 97 | def do_unfollow_user(self, *args): 98 | screen_name = input('Enter screen name of user you want to unfollow\n') 99 | unfollow_user = UnfollowUser(screen_name=screen_name) 100 | Twitter.run(unfollow_user) 101 | 102 | def do_send_message(self, *args): 103 | screen_name = input( 104 | 'Enter screen name of user to whom you want to send message.\n') 105 | text = input('Enter message to be sent\n') 106 | send_message = SendMessage(screen_name=screen_name, text=text) 107 | Twitter.run(send_message) 108 | 109 | def do_retrieve_message(self, *args): 110 | retrieve_message = RetrieveMessage() 111 | Twitter.run(retrieve_message) 112 | 113 | def do_get_details_of_a_user(self, *args): 114 | screen_name = input('Enter screen name of user.\n') 115 | user_details = FetchDetails(screen_name=screen_name) 116 | Twitter.run(user_details) 117 | 118 | def do_update_profile_image(self, *args): 119 | media_file = input("Enter absolute path of image file.\n") 120 | update_profile_image = UpdateProfileImage(filename=media_file) 121 | Twitter.run(update_profile_image) 122 | --------------------------------------------------------------------------------