Skip to content

Commit

Permalink
Merge pull request #27 from birdhouses/23-implement-asyncio
Browse files Browse the repository at this point in the history
23 implement asyncio
  • Loading branch information
birdhouses authored Apr 26, 2023
2 parents da7d646 + 2c72329 commit 4667da8
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 65 deletions.
3 changes: 1 addition & 2 deletions example.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"follow_users": {
"enabled": true,
"follows_per_day": 100,
"unfollow_after": 84600,
"source_account": "instagram",
"engagement": {
"like_recent_posts": true,
Expand All @@ -15,7 +14,7 @@
},
"unfollow_users": {
"enabled": true,
"unfollow_after": 84600
"unfollow_after": "days-hours-minutes-seconds"
},
"comment_on_media": {
"enabled": true,
Expand Down
2 changes: 1 addition & 1 deletion instabot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .utils import get_client, load_config, get_user_id, get_followers, calculate_sleep_time
from .utils import get_client, load_config, get_user_id, get_followers, calculate_sleep_time, logger, parse_time_string
from .follow import follow_user_followers, unfollow_users
from .like_media import like_recent_posts
from .comment import comment_on_media
36 changes: 24 additions & 12 deletions instabot/comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import datetime
import random
from instabot import calculate_sleep_time
import asyncio

logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s',
Expand All @@ -18,24 +19,35 @@

logger = logging.getLogger()

def comment_on_media(cl: Client, account: Dict[str, Any]) -> None:
comment_on_tag = account['comment_on_media']['comment_on_tag']
amount_per_day = account['comment_on_media']['amount_per_day']
async def comment_on_media(cl: Client, account: Dict[str, Any]) -> None:
try:
comment_on_tag = account['comment_on_media']['comment_on_tag']
amount_per_day = account['comment_on_media']['amount_per_day']

posts_to_comment = cl.hashtag_medias_recent(comment_on_tag, amount_per_day)
posts_to_comment = cl.hashtag_medias_recent(comment_on_tag, amount_per_day)

for post in posts_to_comment:
comment = random.choice(account['comment_on_media']['comments'])
for post in posts_to_comment:
try:
comment = random.choice(account['comment_on_media']['comments'])

sleep_time = calculate_sleep_time(amount_per_day)
logger.info(f"Sleeping for {sleep_time} before commenting")
time.sleep(sleep_time)
sleep_time = calculate_sleep_time(amount_per_day)
logger.info(f"Sleeping for {sleep_time} before commenting")
await asyncio.sleep(sleep_time)

comment = cl.media_comment(post.id, comment)
comment = cl.media_comment(post.id, comment)

save_comment(comment.pk, account['username'])
save_comment(comment.pk, account['username'])

logger.info(f"Commented on post {post.id}")
logger.info(f"Commented on post {post.id}")
except Exception as e:
logger.error(f"Error while commenting on media {post['pk']}: {e}")
# Continue with the next iteration without stopping the whole task
continue

except Exception as e:
logger.error(f"Error while fetching media for hashtag {comment_on_tag}: {e}")
# Exit the current task without affecting other tasks
return

def save_comment(comment_pk: int, username: str):
comment_folder = "comments"
Expand Down
37 changes: 23 additions & 14 deletions instabot/follow.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from typing import Any, List, Tuple, Dict
import os
from .like_media import like_recent_posts
import threading
from .utils import load_config, calculate_sleep_time
import asyncio
import instabot

logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s',
Expand Down Expand Up @@ -47,10 +48,12 @@ def load_followed_users(cl: Client) -> List[Tuple[int, datetime.datetime]]:

return followed_users

def filter_users_to_unfollow(followed_users: List[Tuple[int, datetime.datetime]], follow_time: int) -> List[int]:
def filter_users_to_unfollow(followed_users: List[Tuple[int, datetime.datetime]], follow_time: str) -> List[int]:
"""Filter users that should be unfollowed based on follow_time."""
now = datetime.datetime.now()
return [user for user, timestamp, *unfollow_timestamp in followed_users if (now - timestamp).total_seconds() >= follow_time and not unfollow_timestamp]
follow_time_seconds = instabot.parse_time_string(follow_time)
return [user for user, timestamp, *unfollow_timestamp in followed_users if (now - timestamp).total_seconds() >= follow_time_seconds and not unfollow_timestamp]


def remove_unfollowed_user(cl: Client, user: int) -> None:
"""Remove unfollowed user from the followed users file."""
Expand Down Expand Up @@ -78,17 +81,18 @@ def mark_unfollowed_user(cl: Client, user_id: int) -> None:
for user_info in followed_users:
file.write(",".join(str(x) for x in user_info) + "\n")

def unfollow_users(cl: Client, unfollow_after: int) -> None:
async def unfollow_users(cl: Client, account: Dict[str, Any]) -> None:
"""Unfollow users after a specified time."""
logger.info("Started unfollowing process")
unfollow_after = account["unfollow_users"]["unfollow_after"]
followed_users = load_followed_users(cl)
users_to_unfollow = filter_users_to_unfollow(followed_users, follow_time=unfollow_after)
unfollow_users_count = len(users_to_unfollow)
logger.info(f"Going to unfollow {unfollow_users_count} users")
for user in users_to_unfollow:
sleep_time = calculate_sleep_time(unfollow_users_count)
logger.info(f"Sleeping for {sleep_time} before unfollowing {user}")
time.sleep(sleep_time)
await asyncio.sleep(sleep_time)
try:
cl.user_unfollow(user)
except Exception as e:
Expand All @@ -106,15 +110,15 @@ def user_not_followed_before(cl: Client, user_id: int) -> bool:
return False
return True

def follow_user(cl: Client, user_id: int, engagement: Dict[str, Any]) -> bool:
async def follow_user(cl: Client, user_id: int, engagement: Dict[str, Any]) -> bool:
"""Follow a user and add it to followed_users file."""

if user_not_followed_before(cl, user_id):
cl.user_follow(user_id)
save_followed_user(cl, user_id=user_id)

if engagement["like_recent_posts"]:
like_recent_posts(cl, user_id=user_id, engagement=engagement)
await like_recent_posts(cl, user_id=user_id, engagement=engagement)

logger.info(f"Followed user: {user_id}")

Expand All @@ -124,7 +128,7 @@ def follow_user(cl: Client, user_id: int, engagement: Dict[str, Any]) -> bool:
return False


def follow_user_followers(cl: Client, account: Dict[str, Any]) -> None:
async def follow_user_followers(cl: Client, account: Dict[str, Any]) -> None:
"""Follow the followers of the source account and engage with their content."""
logger.info("Started following user followers process..")

Expand All @@ -139,13 +143,18 @@ def follow_user_followers(cl: Client, account: Dict[str, Any]) -> None:

users = cl.user_followers(user_id, True, amount=follows_per_day)

for user in users:
async def process_user(user):
try:
sleep_time = random.uniform(min_sleep_time, max_sleep_time)
if follow_user(cl, user, engagement):
if await follow_user(cl, user, engagement):
sleep_time = random.uniform(min_sleep_time, max_sleep_time)
logger.info(f"Sleeping for {sleep_time} seconds before following next user..")
time.sleep(sleep_time)

await asyncio.sleep(sleep_time)
except Exception as e:
logger.error(f"Error while processing user {user}: {e}")
continue

async def sequential_follow():
for user in users:
await process_user(user)

follow_task = asyncio.create_task(sequential_follow())
await follow_task
7 changes: 3 additions & 4 deletions instabot/like_media.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
import logging
import time
from typing import List, Dict, Any
import os
import datetime
import random
import asyncio

logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s',
Expand All @@ -17,14 +16,14 @@

logger = logging.getLogger()

def like_recent_posts(cl: Client, user_id: int, engagement: Dict[str, Any]) -> None:
async def like_recent_posts(cl: Client, user_id: int, engagement: Dict[str, Any]) -> None:
like_count = engagement['like_count']
min_sleep = 30
max_sleep = 180
recent_posts = cl.user_medias(user_id, amount=like_count)

for post in recent_posts:
sleep_time = random.uniform(min_sleep, max_sleep)
time.sleep(sleep_time)
await asyncio.sleep(sleep_time)
cl.media_like(post.id)
logger.info(f"Liked post {post.id} of user {user_id}")
29 changes: 22 additions & 7 deletions instabot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
import time
import json
import os
import os.path
import logging
import random
import requests
from dateutil import parser

logging.basicConfig(
format='%(asctime)s - %(levelname)s - %(message)s',
Expand Down Expand Up @@ -78,10 +79,14 @@ def load_or_login_and_save_session(client, username, password, session_file_path
login_and_save_session(client, username, password, session_file_path)

def remove_session_and_login(client, username, password, session_file_path):
try:
os.remove(session_file_path)
except OSError as e:
logger.warning(f"Error removing session file: {e}")
if os.path.exists(session_file_path):
try:
os.remove(session_file_path)
logger.info(f"Session file removed: {session_file_path}")
except OSError as e:
logger.warning(f"Error removing session file: {e}")
else:
logger.warning(f"Session file not found: {session_file_path}")

login_and_save_session(client, username, password, session_file_path)

Expand All @@ -96,8 +101,10 @@ def get_client(username: str, password: str) -> Union[Client, None]:
session_file_path = os.path.join(settings_folder, f"settings_{username}.json")

def handle_exception(client: Client, e: Exception) -> Union[bool, None]:
logger.warning(f"Error while logging in: {e}")
if isinstance(e, BadPassword):
raise e
logger.error(f"Login failed for user {username}: {e}")
return None
elif isinstance(e, LoginRequired):
remove_session_and_login(client, username=username, password=password, session_file_path=session_file_path)
return True
Expand All @@ -114,6 +121,7 @@ def handle_exception(client: Client, e: Exception) -> Union[bool, None]:
return True
elif isinstance(e, RetryError):
client.set_proxy(next_proxy())
freeze(e, 1)
remove_session_and_login(client, username=username, password=password, session_file_path=session_file_path)
return True
raise e
Expand All @@ -138,4 +146,11 @@ def calculate_sleep_time(actions_per_day: int) -> float:
average_sleep_time = 86400 / actions_per_day
min_sleep_time = average_sleep_time * 0.5
max_sleep_time = average_sleep_time * 1.5
return random.uniform(min_sleep_time, max_sleep_time)
return random.uniform(min_sleep_time, max_sleep_time)

def parse_time_string(time_string: str) -> int:
"""Parse the time string in the format 'day-hour-min-s' into total seconds."""
parts = time_string.split('-')
days, hours, minutes, seconds = [int(part) for part in parts]
total_seconds = days * 86400 + hours * 3600 + minutes * 60 + seconds
return total_seconds
54 changes: 29 additions & 25 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,44 +1,48 @@
import instabot
import threading
import asyncio
from instabot import follow_user_followers, unfollow_users, comment_on_media
from instabot import logger
from threading import Thread

def run_bot(account):
async def main(account):
username = account['username']
password = account['password']

cl = instabot.get_client(username, password)
worker_threads = []

if account['follow_users']['enabled']:
follow_thread = threading.Thread(target=instabot.follow_user_followers, args=(cl, account))
worker_threads.append(follow_thread)
follow_thread.start()
if account['unfollow_users']['enabled']:
unfollow_after = account['unfollow_users']['unfollow_after']
unfollow_thread = threading.Thread(target=instabot.unfollow_users, args=(cl, unfollow_after))
worker_threads.append(unfollow_thread)
unfollow_thread.start()
if account['comment_on_media']['enabled']:
comment_thread = threading.Thread(target=instabot.comment_on_media, args=(cl, account))
worker_threads.append(comment_thread)
comment_thread.start()

# Wait for all worker threads to finish
for worker_thread in worker_threads:
worker_thread.join()
logger.info(f'Started process for {username}')

async with asyncio.TaskGroup() as tg:
if account['follow_users']['enabled']:
cl = instabot.get_client(username, password)
follow_task = tg.create_task(
follow_user_followers(cl, account)
)
if account['unfollow_users']['enabled']:
cl = instabot.get_client(username, password)
unfollow_task = tg.create_task(
unfollow_users(cl, account)
)
if account['comment_on_media']['enabled']:
cl = instabot.get_client(username, password)
comment_task = tg.create_task(
comment_on_media(cl, account)
)

def run_account(account):
asyncio.run(main(account))

if __name__ == "__main__":
config = instabot.load_config('config.json')

# Get the accounts from the configuration file
accounts = config['accounts']

# Create a thread for each account and run the bot
# Create a thread for each account and run the main function
account_threads = []
for account in accounts:
account_thread = threading.Thread(target=run_bot, args=([account]))
account_thread = Thread(target=run_account, args=(account,))
account_threads.append(account_thread)
account_thread.start()

# Wait for all account threads to finish
for account_thread in account_threads:
account_thread.join()
account_thread.join()

0 comments on commit 4667da8

Please sign in to comment.