#!/usr/bin/env python3 # -*- coding: utf-8 -*- """LICENSE Copyright (C) 2018 Sam Freeside This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ __author__ = 'Sam Freeside (@snovvcrash)' __email__ = 'snovvcrash@protonmail[.]ch' __copyright__ = 'Copyright (C) 2018 Sam Freeside' __license__ = 'GPL-3.0' __date__ = '2018-07-27' __version__ = '0.1' __site__ = 'https://github.com/snovvcrash/tweetlord' __brief__ = 'Twitter profile dumper.' import json import string import sys import time import datetime from queue import PriorityQueue from html import unescape from argparse import ArgumentParser import tweepy import xlsxwriter from tqdm import tqdm from termcolor import cprint, colored from credentials import credentials # ---------------------------------------------------------- # ----------------------- Constants ------------------------ # ---------------------------------------------------------- VERSION_FORMATTED = '\033[0m\033[1;37m{\033[1;35mv%s\033[1;37m}\033[0m' % __version__ SITE_FORMATTED = '\033[0m\033[4;37m%s\033[0m' % __site__ BANNER = '''\033[1;34m\ _ _ _ _ | |___ _____ ___| |_| | \033[1;37m_.+._\033[1;34m _ __ __| | | __\\ \\ /\\ / / _ \\/ _ \\ __| |\033[1;37m(^\\/^\\/^)\033[1;34m| '__/ _` | | |_ \\ V V / __/ __/ |_| | \033[1;37m\\@*@*@/\033[1;34m | | | (_| | \\__| \\_/\\_/ \\___|\\___|\\__|_| \033[1;37m{_____}\033[1;34m |_| \\__,_| %s\033[1;34m %s\ ''' % (VERSION_FORMATTED, SITE_FORMATTED) USER_COLS = [ 'Profile URL', 'Profile Image URL', 'User ID', 'User Login', 'User Name', 'Description', 'Friends Count', 'Followers Count', 'Statuses Count', 'Favorites Count', 'Location', 'Website', 'Created at' ] FRIENDS_COLS = [ 'Profile URL', 'Profile Image URL', 'User ID', 'User Login', 'User Name', 'Description' ] FOLLOWERS_COLS = [ 'Profile URL', 'Profile Image URL', 'User ID', 'User Login', 'User Name', 'Description' ] FAVORITES_COLS = [ 'Tweet Text', 'Tweet URL', 'User ID', 'User Login', 'Favorite Count', 'Retweet Count', 'Latitude', 'Longitude' ] TIMELINE_COLS = [ 'Created at', 'Tweet Text', 'Tweet URL', 'Favorite Count', 'Retweet Count', 'Latitude', 'Longitude' ] PROC_NAMES = { '_api_user': 'api.get_user', '_api_friends': 'api.friends', '_api_followers': 'api.followers', '_api_favorites': 'api.favorites', '_api_timeline': 'api.user_timeline' } SECTION_NAMES = { 'api.get_user': 'users', 'api.friends': 'friends', 'api.followers': 'followers', 'api.favorites': 'favorites', 'api.user_timeline': 'statuses' } # ---------------------------------------------------------- # -------------------------- Core -------------------------- # ---------------------------------------------------------- def user_info(am, username): user = _api_handler(am, _api_user, username) id_str = user.id_str screen_name = user.screen_name name = user.name description = unescape(user.description) friends_count = user.friends_count followers_count = user.followers_count statuses_count = user.statuses_count favorites_count = user.favourites_count profile_url = 'https://twitter.com/' + screen_name profile_image_url = user.profile_image_url_https.replace('_normal', '_400x400') location = user.location try: website = user.entities['url']['urls'][0]['expanded_url'] except (IndexError, KeyError): website = '' created_at = user.created_at.strftime('%Y-%m-%d %H:%M:%S') return ([ profile_url, profile_image_url, id_str, screen_name, name, description, friends_count, followers_count, statuses_count, favorites_count, location, website, created_at ], { 'friends': friends_count, 'followers': followers_count, 'favorites': favorites_count, 'timeline': statuses_count }) def user_friends(am, username, count, max_friends): friends = _api_handler(am, _api_friends, username, count, max_friends, 'fr') table_rows = [] for friend in friends: id_str = friend.id_str screen_name = friend.screen_name name = friend.name profile_url = 'https://twitter.com/' + screen_name profile_image_url = friend.profile_image_url_https.replace('_normal', '_400x400') description = unescape(friend.description) table_rows.append([ profile_url, profile_image_url, id_str, screen_name, name, description ]) return table_rows def user_followers(am, username, count, max_followers): followers = _api_handler(am, _api_followers, username, count, max_followers, 'fol') table_rows = [] for follower in followers: id_str = follower.id_str screen_name = follower.screen_name name = follower.name profile_url = 'https://twitter.com/' + screen_name profile_image_url = follower.profile_image_url_https.replace('_normal', '_400x400') description = unescape(follower.description) table_rows.append([ profile_url, profile_image_url, id_str, screen_name, name, description ]) return table_rows def user_favorites(am, username, count, max_favorites, tweet_extended): statuses = _api_handler(am, _api_favorites, username, count, max_favorites, 'fav', tweet_extended) table_rows = [] for status in statuses: if tweet_extended: text = unescape(status.full_text) else: text = unescape(status.text) screen_name = status.author.screen_name name = status.author.name status_url = 'https://twitter.com/' + screen_name + '/status/' + status.id_str favorite_count = status.favorite_count retweet_count = status.retweet_count geo = status.geo if geo: latitude, longitude = geo['coordinates'] else: latitude = longitude = '' table_rows.append([ text, status_url, screen_name, name, favorite_count, retweet_count, latitude, longitude ]) return table_rows def user_timeline(am, username, count, max_timeline, tweet_extended): statuses = _api_handler(am, _api_timeline, username, count, max_timeline, 'tw', tweet_extended) table_rows = [] for status in statuses: created_at = status.created_at.strftime('%Y-%m-%d %H:%M:%S') if tweet_extended: text = unescape(status.full_text) else: text = unescape(status.text) status_url = 'https://twitter.com/' + status.author.screen_name + '/status/' + status.id_str favorite_count = status.favorite_count retweet_count = status.retweet_count geo = status.geo if geo: latitude, longitude = geo['coordinates'] else: latitude = longitude = '' table_rows.append([ created_at, text, status_url, favorite_count, retweet_count, latitude, longitude ]) return table_rows def build_xlsx(dump, filename, username): workbook = xlsxwriter.Workbook(filename + '.xlsx') worksheet = workbook.add_worksheet(username) header_fmt = workbook.add_format({ 'bold': True, 'font_size': 15, 'font_color': '#1F497D', 'bottom': True, 'bottom_color': '#6699CC', }) header_fmt.set_bottom(5) col_title_fmt = workbook.add_format({ 'bold': True, 'bg_color': '#C6EFCE', 'font_color': '#006100', 'border': True }) signature_fmt = workbook.add_format({ 'italic': True, 'font_color': '#7F7F7F' }) signature_version_fmt = workbook.add_format({ 'italic': True, 'font_color': '#508CD4' }) signature_site_fmt = workbook.add_format({ 'italic': True, 'underline': True, 'font_color': '#508CD4' }) signature_text = 'This document was generated with tweetl\U0001F451rd tool (by snovvcrash)' # 👑 signature_date_and_args = 'at {} as: "py {}"'.format(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), ' '.join(sys.argv)) signature_version = 'v{}'.format(__version__) signature_site = '{}'.format(__site__) worksheet.write(0, 0, signature_text, signature_fmt) worksheet.write(1, 0, signature_date_and_args, signature_fmt) worksheet.write(2, 0, signature_version, signature_version_fmt) worksheet.write(3, 0, signature_site, signature_site_fmt) curr_row = 7 row_lengths = [] # ----------------------- User Info ------------------------ worksheet.write(curr_row, 0, 'User', header_fmt) curr_row += 1 for i, title in enumerate(USER_COLS): worksheet.write(curr_row, i, title, col_title_fmt) row_lengths.append([len(title)]) curr_row += 1 row_lengths[0].extend([ len(signature_text), len(signature_date_and_args), len(signature_version), len(signature_site) ]) for i, elem in enumerate(dump['user']): worksheet.write(curr_row, i, elem) row_lengths[i].append(len(str(elem))) curr_row += 4 # ------------------------- Other -------------------------- for titles, dump_part, header in zip((FRIENDS_COLS, FOLLOWERS_COLS, FAVORITES_COLS, TIMELINE_COLS), (dump['friends'], dump['followers'], dump['favorites'], dump['timeline']), ('Friends', 'Followers', 'Favorites', 'Timeline')): if dump_part: worksheet.write(curr_row, 0, '{} ({})'.format(header, len(dump_part)), header_fmt) curr_row += 1 for i, title in enumerate(titles): worksheet.write(curr_row, i, title, col_title_fmt) row_lengths[i].append(len(title)) curr_row += 1 for table_row in dump_part: for i, elem in enumerate(table_row): worksheet.write(curr_row, i, elem) row_lengths[i].append(len(str(elem))) curr_row += 1 curr_row += 4 # ---------------- Correcting columns width ---------------- for i in range(len(USER_COLS)): # USER_COLS is max length worksheet.set_column(i, i, max(row_lengths[i]) + 2) workbook.close() def show_limits(client): limits = client.rate_limit_status() if 'access_token' in limits['rate_limit_context']: print('USER-AUTH') elif 'application' in limits['rate_limit_context']: print('APP-AUTH') d = limits['resources']['application']['/application/rate_limit_status'] limit = d['limit'] remaining = d['remaining'] reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) print('[0] api.rate_limit_status -- limit: {}, remaining: {}, reset: {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) d = limits['resources']['users']['/users/show/:id'] limit = d['limit'] remaining = d['remaining'] reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) print('[1] api.get_user -- limit: {}, remaining: {}, reset: {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) d = limits['resources']['friends']['/friends/list'] limit = d['limit'] remaining = d['remaining'] reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) print('[2] api.friends -- limit: {}, remaining: {}, reset: {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) d = limits['resources']['followers']['/followers/list'] limit = d['limit'] remaining = d['remaining'] reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) print('[3] api.followers -- limit: {}, remaining: {}, reset: {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) d = limits['resources']['favorites']['/favorites/list'] limit = d['limit'] remaining = d['remaining'] reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) print('[4] api.favorites -- limit: {}, remaining: {}, reset: {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) d = limits['resources']['statuses']['/statuses/user_timeline'] limit = d['limit'] remaining = d['remaining'] reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) print('[5] api.user_timeline -- limit: {}, remaining: {}, reset: {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) # _d = limits['resources']['users'].get('/users/search', None) # _limit = d['limit'] # _remaining = d['remaining'] # _reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) # _print('[6] api.search_users -- \"limit\": {}, \"remaining\": {}, \"reset\": {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) # _d = limits['resources']['search'].get('/search/tweets', None) # _limit = d['limit'] # _remaining = d['remaining'] # _reset = d['reset'] - int(datetime.datetime.timestamp(datetime.datetime.now())) # _print('[7] api.search -- \"limit\": {}, \"remaining\": {}, \"reset\": {} m {} s'.format(limit, remaining, reset // 60, reset % 60)) # ---------------------------------------------------------- # -------------------------- API --------------------------- # ---------------------------------------------------------- def _api_handler(am, api_method, username, count=None, max_items=0, unit='', tweet_extended=False): api_method_name = PROC_NAMES[api_method.__name__] api_section_name = SECTION_NAMES[api_method_name] cred, time_to_wait = am.get(api_section_name) modes, curr_mode = ['app', 'user'], 0 if count is None: # api_method == _api_user while True: client = tweepy_auth(cred, mode=modes[curr_mode]) try: return api_method(client, username) except (tweepy.error.TweepError, tweepy.error.RateLimitError) as e: if e.response.status_code == 404: raise TweetlordError('User not found', errors={'code': 1, 'initial': str(e)}) curr_mode = 1 - curr_mode if not curr_mode: cred, time_to_wait = am.get(api_section_name) if time_to_wait <= 0: print('[*] Account switched') elif WAIT_ON_RATE_LIMIT: print('[*] It\'s {} on the clock'.format(time.strftime('%H:%M:%S', time.localtime()))) print_warning( '{}: Rate limit exceeded, all accounts are empty. Waiting {} minutes {} seconds' .format(api_method_name, time_to_wait // 60, time_to_wait % 60), str(e) ) try: time.sleep(time_to_wait) except KeyboardInterrupt: cprint('{}: Stopped'.format(api_method_name), 'white', 'on_red', attrs=['bold']) break else: raise TweetlordError('Rate limit exceeded, all accounts are empty', errors={'code': 2, 'initial': str(e)}) max_per_page = 200 full_pages = count // max_per_page items_remaining = count % max_per_page start_page = -1 if api_method in (_api_friends, _api_followers) else 0 items = [] with tqdm(total=count, ncols=80, unit=unit, desc=' got') as pbar: while full_pages: client = tweepy_auth(cred, mode=modes[curr_mode]) cursor = api_method( client, username, page=start_page, count=max_per_page, pages_count=full_pages, tweet_extended=tweet_extended ) try: for page in cursor: items += page start_page = cursor.next_cursor if api_method in (_api_friends, _api_followers) else start_page + 1 full_pages -= 1 pbar.update(max_per_page) except (tweepy.error.TweepError, tweepy.error.RateLimitError) as e: if e.response.status_code == 404: raise TweetlordError('User not found', errors={'code': -1, 'initial': str(e)}) curr_mode = 1 - curr_mode if not curr_mode: cred, time_to_wait = am.get(api_section_name) if time_to_wait <= 0: pbar.write('[*] Account switched') elif WAIT_ON_RATE_LIMIT: pbar.write('[*] It\'s {} on the clock'.format(time.strftime('%H:%M:%S', time.localtime()))) print_warning( '{}: Rate limit exceeded, all accounts are empty. Waiting {} minutes {} seconds' .format(api_method_name, time_to_wait // 60, time_to_wait % 60), str(e), write=pbar.write ) try: time.sleep(time_to_wait) except KeyboardInterrupt: pbar.write(colored('{}: Stopped'.format(api_method_name), 'white', 'on_red', attrs=['bold'])) break else: print_warning( '{}: Rate limit exceeded, all accounts are empty' .format(api_method_name), str(e), write=pbar.write ) return items else: full_pages = 0 if len(items) < max_items: while items_remaining: client = tweepy_auth(cred, mode=modes[curr_mode]) cursor = api_method( client, username, page=start_page, count=items_remaining, pages_count=1, tweet_extended=tweet_extended ) try: items += flatten(list(cursor)) pbar.update(items_remaining) except (tweepy.error.TweepError, tweepy.error.RateLimitError) as e: if e.response.status_code == 404: raise TweetlordError('User not found', errors={'code': -1, 'initial': str(e)}) curr_mode = 1 - curr_mode if not curr_mode: cred, time_to_wait = am.get(api_section_name) if time_to_wait <= 0: pbar.write('[*] Account switched') elif WAIT_ON_RATE_LIMIT: pbar.write('[*] It\'s {} on the clock'.format(time.strftime('%H:%M:%S', time.localtime()))) print_warning( '{}: Rate limit exceeded, all accounts are empty. Waiting {} minutes {} seconds' .format(api_method_name, time_to_wait // 60, time_to_wait % 60), str(e), write=pbar.write ) try: time.sleep(time_to_wait) except KeyboardInterrupt: pbar.write(colored('{}: Stopped'.format(api_method_name), 'white', 'on_red', attrs=['bold'])) break else: print_warning( '{}: Rate limit exceeded, all accounts are empty' .format(api_method_name), str(e), write=pbar.write ) return items else: items_remaining = 0 return items def _api_user(client, username, **kwargs): if username.startswith('id'): user_id = username[2:] return client.get_user(user_id=user_id) screen_name = username return client.get_user(screen_name=screen_name) def _api_friends(client, username, **kwargs): if username.startswith('id'): user_id = username[2:] return tweepy.Cursor( client.friends, user_id=user_id, cursor=kwargs['page'], count=kwargs['count'], skip_status=True, include_user_entities=False ).pages(kwargs['pages_count']) screen_name = username return tweepy.Cursor( client.friends, screen_name=screen_name, cursor=kwargs['page'], count=kwargs['count'], skip_status=True, include_user_entities=False ).pages(kwargs['pages_count']) def _api_followers(client, username, **kwargs): if username.startswith('id'): user_id = username[2:] return tweepy.Cursor( client.followers, user_id=user_id, cursor=kwargs['page'], count=kwargs['count'], skip_status=True, include_user_entities=False ).pages(kwargs['pages_count']) screen_name = username return tweepy.Cursor( client.followers, screen_name=screen_name, cursor=kwargs['page'], count=kwargs['count'], skip_status=True, include_user_entities=False ).pages(kwargs['pages_count']) def _api_favorites(client, username, **kwargs): if username.startswith('id'): user_id = username[2:] return tweepy.Cursor( client.favorites, user_id=user_id, page=kwargs['page'], count=kwargs['count'], include_entities=False, tweet_mode=kwargs['tweet_extended'] ).pages(kwargs['pages_count']) screen_name = username return tweepy.Cursor( client.favorites, screen_name=screen_name, page=kwargs['page'], count=kwargs['count'], include_entities=False, tweet_mode=kwargs['tweet_extended'] ).pages(kwargs['pages_count']) def _api_timeline(client, username, **kwargs): if username.startswith('id'): user_id = username[2:] return tweepy.Cursor( client.user_timeline, user_id=user_id, count=kwargs['count'], trim_user=False, exclude_replies=False, include_rts=True, tweet_mode=kwargs['tweet_extended'] ).pages(kwargs['pages_count']) screen_name = username return tweepy.Cursor( client.user_timeline, screen_name=screen_name, count=kwargs['count'], trim_user=False, exclude_replies=False, include_rts=True, tweet_mode=kwargs['tweet_extended'] ).pages(kwargs['pages_count']) # ---------------------------------------------------------- # -------------------------- Auth -------------------------- # ---------------------------------------------------------- def tweepy_auth(cred, mode): auth = None if mode == 'user': auth = tweepy.OAuthHandler(cred['consumer_key'], cred['consumer_secret']) auth.set_access_token(cred['access_token_key'], cred['access_token_secret']) elif mode == 'app': auth = tweepy.AppAuthHandler(cred['consumer_key'], cred['consumer_secret']) return tweepy.API(auth, wait_on_rate_limit=False) if auth else None # ---------------------------------------------------------- # -------------------- Account Manager --------------------- # ---------------------------------------------------------- class AccountManager: METHODS = { 'users': '/users/show/:id', 'friends': '/friends/list', 'followers': '/followers/list', 'favorites': '/favorites/list', 'statuses': '/statuses/user_timeline' } def __init__(self, credentials): unique_creds = {json.dumps(cred) for cred in credentials} self._creds = [json.loads(cred) for cred in unique_creds] self._app_clients = [tweepy_auth(cred, mode='app') for cred in self._creds] self._user_clients = [tweepy_auth(cred, mode='user') for cred in self._creds] self._app_limits, self._user_limits = self._build_limits() self._queues = dict.fromkeys(AccountManager.METHODS.keys()) for section in AccountManager.METHODS: self._queues[section] = self._build_queue(section, AccountManager.METHODS[section]) def get(self, section): if self._queues[section].empty(): self._app_limits, self._user_limits = self._build_limits() self._queues[section] = self._build_queue(section, AccountManager.METHODS[section]) limit, reset, account = self._queues[section].get() if limit: return (account, 0) time_to_wait = reset - int(datetime.datetime.timestamp(datetime.datetime.now())) return (account, time_to_wait) def _build_limits(self): app_limits, user_limits = [], [] for app_client, user_client in zip(self._app_clients, self._user_clients): try: tmp_app = app_client.rate_limit_status() tmp_user = user_client.rate_limit_status() except tweepy.error.RateLimitError: pass else: app_limits.append(tmp_app) user_limits.append(tmp_user) return (app_limits, user_limits) def _build_queue(self, section, method): queue = PriorityQueue(len(self._creds)) for i in range(len(self._creds)): queue.put(( -(self._app_limits[i]['resources'][section][method]['remaining'] + self._user_limits[i]['resources'][section][method]['remaining']), max(self._app_limits[i]['resources'][section][method]['reset'], self._user_limits[i]['resources'][section][method]['reset']), self._creds[i] )) return queue # ---------------------------------------------------------- # ------------------------- Utils -------------------------- # ---------------------------------------------------------- def flatten(pages): return (status for page in pages for status in page) def format_filename(s): valid_chars = "-_.() {!s}{!s}".format(string.ascii_letters, string.digits) filename = ''.join(c for c in s if c in valid_chars) filename = filename.replace(' ', '_') return filename class TweetlordError(Exception): def __init__(self, message, errors=None): super().__init__(message) if not errors: errors = {} self.errors = errors self.errors.setdefault('errcode', 0) self.errors.setdefault('initial_error', '') # ---------------------------------------------------------- # ------------------------ Messages ------------------------ # ---------------------------------------------------------- def print_info(message): cprint('[INFO] {}'.format(message), 'green') def print_warning(message, initial_error='', *, write=print): if DEBUG: if initial_error: write(initial_error, file=sys.stderr) write(colored('[WARNING] {}'.format(message), 'yellow')) def print_critical(message, initial_error=''): if DEBUG: if initial_error: print(initial_error, file=sys.stderr) cprint('[CRITICAL] {}'. format(message), 'white', 'on_red', attrs=['bold']) # ---------------------------------------------------------- # -------------------------- Opts -------------------------- # ---------------------------------------------------------- def cli_options(): parser = ArgumentParser() group = parser.add_mutually_exclusive_group(required=True) group.add_argument('-u', '--user') group.add_argument('-l', '--show-limits', action='store_true') parser.add_argument('-fr', '--friends', type=int, default=0) parser.add_argument('-fo', '--followers', type=int, default=0) parser.add_argument('-fa', '--favorites', type=int, default=0) parser.add_argument('-ti', '--timeline', type=int, default=0) parser.add_argument('-a', '--all', action='store_true') parser.add_argument('-o', '--output', type=str, default='out') parser.add_argument('-w', '--wait-on-limit', action='store_const', const='extended') parser.add_argument('-e', '--tweet-extended', action='store_const', const='extended') parser.add_argument('-d', '--debug', action='store_true') return parser.parse_args() # ---------------------------------------------------------- # -------------------------- Main -------------------------- # ---------------------------------------------------------- def main(): print(BANNER + '\n') args = cli_options() global WAIT_ON_RATE_LIMIT; WAIT_ON_RATE_LIMIT = args.wait_on_limit global DEBUG; DEBUG = args.debug if args.show_limits: for cred in credentials: for key, val in cred.items(): print('{}: \"{}\"'.format(key, val)) print() try: show_limits(tweepy_auth(cred, mode='app')); print() show_limits(tweepy_auth(cred, mode='user')); print() except tweepy.error.RateLimitError as e: print_critical('No rate limit to run \"rate_limit_status()\". Wait 15 minutes and try again', str(e)) return if args.all and (args.friends or args.followers or args.favorites or args.timeline): incompatible = { 'friends': args.friends, 'followers': args.followers, 'favorites': args.favorites, 'timeline': args.timeline } print('Incompatible parameters: all,{}'.format(','.join(key for key, val in incompatible.items() if val))) return timestart = time.time() print('[*] Started at {}\n'.format(time.strftime('%H:%M:%S', time.localtime()))) dump = dict.fromkeys(('user', 'friends', 'followers', 'favorites', 'timeline')) print_info('Initializing account manager') am = AccountManager(credentials) try: print_info('Collecting basic account info') dump['user'], max_items = user_info(am, args.user) if args.friends or args.all: if args.friends == -1 or args.all: args.friends = max_items['friends'] print_info('Collecting user friends info') dump['friends'] = user_friends(am, args.user, args.friends, max_items['friends']) if args.followers or args.all: if args.followers == -1 or args.all: args.followers = max_items['followers'] print_info('Collecting user followers info') dump['followers'] = user_followers(am, args.user, args.followers, max_items['followers']) if args.favorites or args.all: if args.favorites == -1 or args.all: args.favorites = max_items['favorites'] print_info('Collecting user favorites info') dump['favorites'] = user_favorites(am, args.user, args.favorites, max_items['favorites'], args.tweet_extended) if args.timeline or args.all: if args.timeline == -1 or args.all: # From developer.twitter.com: "This method can only return up to 3,200 of a user's most recent Tweets". args.timeline = max_items['timeline'] if max_items['timeline'] < 3200 else 3200 print_info('Collecting user timeline info') dump['timeline'] = user_timeline(am, args.user, args.timeline, max_items['timeline'], args.tweet_extended) except TweetlordError as e: if e.errors['code'] == 1: print_critical(str(e), e.errors['initial']) else: if any(section for section in dump.values()): print_info('Building .xlsx file') filename = format_filename(args.output) build_xlsx(dump, filename, args.user) print(); print_info('Success! Result: {}.xlsx'.format(filename)) else: print_critical('No data collected') print('\n[*] Time taken: {}'.format(datetime.timedelta(seconds=time.time() - timestart))) print('[*] Shut down at {}'.format(time.strftime('%H:%M:%S', time.localtime()))) if __name__ == '__main__': main()