diff --git a/.gitignore b/.gitignore index 790a2f18..cf85ac22 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ /venv* /.idea interacted_users.* +sessions.json *.pyc .DS_Store screenshots diff --git a/insomniac.py b/insomniac.py index dc23a0a6..c39d6efe 100644 --- a/insomniac.py +++ b/insomniac.py @@ -13,18 +13,19 @@ import colorama import uiautomator -from src.action_get_my_username import get_my_username +from src.action_get_my_profile_info import get_my_profile_info from src.action_handle_blogger import handle_blogger from src.action_unfollow import unfollow from src.counters_parser import LanguageChangedException from src.filter import Filter from src.navigation import navigate, Tabs -from src.session_state import SessionState +from src.persistent_list import PersistentList +from src.session_state import SessionState, SessionStateEncoder from src.storage import Storage from src.utils import * device_id = None -sessions = [] +sessions = PersistentList("sessions", SessionStateEncoder) def main(): @@ -73,11 +74,12 @@ def main(): while True: session_state = SessionState() + session_state.args = args.__dict__ sessions.append(session_state) print_timeless(COLOR_WARNING + "\n-------- START: " + str(session_state.startTime) + " --------" + COLOR_ENDC) open_instagram(device_id) - session_state.my_username = get_my_username(device) + session_state.my_username, session_state.my_followers_count = get_my_profile_info(device) storage = Storage(session_state.my_username) # IMPORTANT: in each job we assume being on the top of the Profile tab already @@ -108,11 +110,13 @@ def main(): sleep(60 * repeat) except KeyboardInterrupt: _print_report() + sessions.persist(directory=session_state.my_username) sys.exit(0) else: break _print_report() + sessions.persist(directory=session_state.my_username) def _job_handle_bloggers(device, bloggers, likes_count, follow_percentage, storage, profile_filter, on_interaction): @@ -196,23 +200,23 @@ def _parse_arguments(): parser.add_argument('--likes-count', help='number of likes for each interacted user, 2 by default', metavar='2', - default=2) + default='2') parser.add_argument('--total-likes-limit', help='limit on total amount of likes during the session, 300 by default', metavar='300', - default=1000) + default='1000') parser.add_argument('--interactions-count', help='number of interactions per each blogger, 70 by default. Only successful interactions' ' count', metavar='70', - default=70) + default='70') parser.add_argument('--repeat', help='repeat the same session again after N minutes after completion, disabled by default', metavar='180') parser.add_argument('--follow-percentage', help='follow given percentage of interacted users, 0 by default', metavar='50', - default=0) + default='0') parser.add_argument('--unfollow', help='unfollow at most given number of users. Only users followed by this script will ' 'be unfollowed. The order is from oldest to newest followings', @@ -335,6 +339,7 @@ def wrapper(*args, **kwargs): print_timeless(COLOR_WARNING + "-------- FINISH: " + str(datetime.now().time()) + " --------" + COLOR_ENDC) _print_report() + sessions.persist(directory=session_state.my_username) sys.exit(0) except (uiautomator.JsonRPCError, IndexError, HTTPException, timeout): print(COLOR_FAIL + traceback.format_exc() + COLOR_ENDC) @@ -353,6 +358,7 @@ def wrapper(*args, **kwargs): take_screenshot(device) close_instagram(device_id) _print_report() + sessions.persist(directory=session_state.my_username) raise e return wrapper return actual_decorator diff --git a/src/action_get_my_profile_info.py b/src/action_get_my_profile_info.py new file mode 100644 index 00000000..d3a0d635 --- /dev/null +++ b/src/action_get_my_profile_info.py @@ -0,0 +1,38 @@ +from src.counters_parser import parse +from src.navigation import navigate, Tabs +from src.utils import * + + +def get_my_profile_info(device): + navigate(device, Tabs.PROFILE) + + username = None + title_view = device(resourceId='com.instagram.android:id/title_view', + className='android.widget.TextView') + if title_view.exists: + username = title_view.text + else: + print(COLOR_FAIL + "Failed to get username" + COLOR_ENDC) + + followers = None + followers_text_view = device(resourceId='com.instagram.android:id/row_profile_header_textview_followers_count', + className='android.widget.TextView') + if followers_text_view.exists: + followers_text = followers_text_view.text + if followers_text: + followers = parse(device, followers_text) + else: + print(COLOR_FAIL + "Cannot get your followers count text" + COLOR_ENDC) + else: + print(COLOR_FAIL + "Cannot find your followers count view" + COLOR_ENDC) + + report_string = "" + if username: + report_string += "Hello, @" + username + "!" + if followers: + report_string += " You have " + str(followers) + " followers so far." + + if not report_string == "": + print(report_string) + + return username, followers diff --git a/src/action_get_my_username.py b/src/action_get_my_username.py deleted file mode 100644 index 9dd7ed11..00000000 --- a/src/action_get_my_username.py +++ /dev/null @@ -1,15 +0,0 @@ -from src.navigation import navigate, Tabs -from src.utils import * - - -def get_my_username(device): - navigate(device, Tabs.PROFILE) - title_view = device(resourceId='com.instagram.android:id/title_view', - className='android.widget.TextView') - if title_view.exists: - username = title_view.text - print("Hello, @" + username) - return username - else: - print(COLOR_FAIL + "Failed to get username" + COLOR_ENDC) - return "" diff --git a/src/persistent_list.py b/src/persistent_list.py new file mode 100644 index 00000000..b1335381 --- /dev/null +++ b/src/persistent_list.py @@ -0,0 +1,42 @@ +import json +import os + + +class PersistentList(list): + filename = None + encoder = None + + def __init__(self, filename, encoder): + self.filename = filename + self.encoder = encoder + super().__init__() + + def persist(self, directory): + if not os.path.exists(directory): + os.makedirs(directory) + + path = directory + "/" + self.filename + ".json" + + if os.path.exists(path): + with open(path) as json_file: + json_array = json.load(json_file) + os.remove(path) + else: + json_array = [] + + json_array += (self.encoder.default(self.encoder, item) for item in self) + + # Remove duplicates + json_object = {} + for item in json_array: + item_id = item.get("id") + if item_id is None: + raise Exception("Items in PersistentList must have id property!") + json_object[item_id] = item + json_array = list(json_object.values()) + + with open(path, 'w') as outfile: + json.dump(json_array, + outfile, + indent=4, + sort_keys=False) diff --git a/src/session_state.py b/src/session_state.py index 81ff3ff9..aaefb0c9 100644 --- a/src/session_state.py +++ b/src/session_state.py @@ -1,8 +1,13 @@ +import uuid from datetime import datetime +from json import JSONEncoder class SessionState: + id = None + args = {} my_username = None + my_followers_count = None totalInteractions = {} successfulInteractions = {} totalLikes = 0 @@ -12,7 +17,10 @@ class SessionState: finishTime = None def __init__(self): + self.id = str(uuid.uuid4()) + self.args = {} self.my_username = None + self.my_followers_count = None self.totalInteractions = {} self.successfulInteractions = {} self.totalLikes = 0 @@ -41,3 +49,22 @@ def get_successful_interactions_count(self): def is_finished(self): return self.finishTime is not None + + +class SessionStateEncoder(JSONEncoder): + + def default(self, session_state: SessionState): + return { + "id": session_state.id, + "total_interactions": sum(session_state.totalInteractions.values()), + "successful_interactions": sum(session_state.successfulInteractions.values()), + "total_likes": session_state.totalLikes, + "total_followed": session_state.totalFollowed, + "total_unfollowed": session_state.totalUnfollowed, + "start_time": str(session_state.startTime), + "finish_time": str(session_state.finishTime), + "args": session_state.args, + "profile": { + "followers": str(session_state.my_followers_count) + } + }