Skip to content
This repository has been archived by the owner on Oct 1, 2023. It is now read-only.

Commit

Permalink
Implemented settings. Fixed issues (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
Danie1 committed Jul 17, 2023
1 parent 9360339 commit ddae429
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 94 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ It allows you to configure the session object. Choose between:
> **Note** Since v1.1.10 you can use `requests` or `instagrapi` as HTTP clients, not just `aiohttp`.
> **Note** Since v1.1.12 a `.session.json` file will be created by-default to save default settings (to reduce risk of being flagged). You can disable it by passing `ThreadsAPI(settings_path=None)`
> **Important Tip** Use the same `cached_token_path` for connections, to reduce the number of actual login attempts. When needed, threads-api will reconnect and update the file in `cached_token_path`.
Table of content:
Expand Down Expand Up @@ -62,6 +64,7 @@ load_dotenv()

async def post():
api = ThreadsAPI()

await api.login(os.environ.get('INSTAGRAM_USERNAME'), os.environ.get('INSTAGRAM_PASSWORD'), cached_token_path=".token")
result = await api.post(caption="Posting this from the Danie1/threads-api!", image_path=".github/logo.jpg")

Expand All @@ -72,6 +75,7 @@ async def post():
print("Unable to post.")

await api.close_gracefully()


async def main():
await post()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "threads-api"
version = "1.1.11"
version = "1.1.12"
description = "Unofficial Python client for Meta Threads.net API"
authors = ["Danie1"]
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='threads-api',
version='1.1.11',
version='1.1.12',
description='Unofficial Python client for Meta Threads.net API',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down
10 changes: 8 additions & 2 deletions threads_api/src/anotherlogger.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def is_json_serializable(obj):
except (TypeError, ValueError):
return False

def log(*args, **kwargs):
def format_log(*args, **kwargs):
log_message = f"{Fore.GREEN}<---- START ---->\n"

# Collect positional arguments
Expand All @@ -27,6 +27,12 @@ def log(*args, **kwargs):
log_message += f" [{key}]: [{Style.RESET_ALL}{value}{Fore.GREEN}]\n"

log_message += f"<---- END ---->\n{Style.RESET_ALL}"
return log_message

def log_info(*args, **kwargs):
# Log the message
logging.info(log_message)
logging.info(format_log(*args, **kwargs))

def log_debug(*args, **kwargs):
# Log the message
logging.debug(format_log(*args, **kwargs))
23 changes: 16 additions & 7 deletions threads_api/src/http_sessions/aiohttp_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,39 @@
import json

from threads_api.src.http_sessions.abstract_session import HTTPSession
from threads_api.src.threads_api import log
from threads_api.src.anotherlogger import log_debug

from instagrapi import Client

class AioHTTPSession(HTTPSession):
def __init__(self):
self._session = aiohttp.ClientSession()
self._instagrapi_client = Client()

async def start(self):
if self._session is None:
self._session = aiohttp.ClientSession()

if self._instagrapi_client is None:
self._instagrapi_client = Client()

async def close(self):
await self._session.close()
self._session = None
self._instagrapi_client = None

def auth(self, auth_callback_func, **kwargs):
return auth_callback_func(**kwargs)
def auth(self, **kwargs):
self._instagrapi_client.login(**kwargs)
token = self._instagrapi_client.private.headers['Authorization'].split("Bearer IGT:2:")[1]
return token

async def post(self, **kwargs):
log(title='PRIVATE REQUEST', type='POST', **kwargs)
log_debug(title='PRIVATE REQUEST', type='POST', **kwargs)
async with self._session.post(**kwargs) as response:
try:
text = await response.text()
resp = json.loads(text)
log(title='PRIVATE RESPONSE', response=resp)
log_debug(title='PRIVATE RESPONSE', response=resp)

if resp['status'] == 'fail':
raise Exception(f"Request Failed: [{resp['message']}]")
Expand All @@ -35,12 +44,12 @@ async def post(self, **kwargs):
return resp

async def get(self, **kwargs):
log(title='PRIVATE REQUEST', type='GET', **kwargs)
log_debug(title='PRIVATE REQUEST', type='GET', **kwargs)
async with self._session.get(**kwargs) as response:
try:
text = await response.text()
resp = json.loads(text)
log(title='PRIVATE RESPONSE', response=resp)
log_debug(title='PRIVATE RESPONSE', response=resp)

if resp['status'] == 'fail':
raise Exception(f"Request Failed: [{resp['message']}]")
Expand Down
17 changes: 10 additions & 7 deletions threads_api/src/http_sessions/instagrapi_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import requests

from threads_api.src.http_sessions.abstract_session import HTTPSession
from threads_api.src.threads_api import log
from threads_api.src.anotherlogger import log_debug

class InstagrapiSession(HTTPSession):
def __init__(self):
Expand All @@ -18,13 +18,16 @@ def __init__(self):
# override with Threads headers
self._instagrapi_client.private.headers=self._threads_headers

def auth(self, auth_callback_func, **kwargs):
def auth(self, **kwargs):
# restore original headers for Instagram login
self._instagrapi_client.private.headers = self._instagrapi_headers
ret = self._instagrapi_client.login(**kwargs)
self._instagrapi_client.login(**kwargs)
token = self._instagrapi_client.private.headers['Authorization'].split("Bearer IGT:2:")[1]

# override with Threads headers
self._instagrapi_client.private.headers = self._threads_headers

return token

async def start(self):
pass
Expand All @@ -33,11 +36,11 @@ async def close(self):
pass

async def post(self, **kwargs):
log(title='PRIVATE REQUEST', type='POST', requests_session_params=vars(self._instagrapi_client.private), **kwargs)
log_debug(title='PRIVATE REQUEST', type='POST', requests_session_params=vars(self._instagrapi_client.private), **kwargs)
response = self._instagrapi_client.private.post(**kwargs)
try:
resp = response.json()
log(title='PRIVATE RESPONSE', requests_session_params=vars(self._instagrapi_client.private), response=resp)
log_debug(title='PRIVATE RESPONSE', requests_session_params=vars(self._instagrapi_client.private), response=resp)

if resp['status'] == 'fail':
raise Exception(f"Request Failed: [{resp['message']}]")
Expand All @@ -47,11 +50,11 @@ async def post(self, **kwargs):
return resp

async def get(self, **kwargs):
log(title='PRIVATE REQUEST', type='GET', **kwargs)
log_debug(title='PRIVATE REQUEST', type='GET', **kwargs)
response = self._instagrapi_client.private.get(**kwargs)
try:
resp = response.json()
log(title='PRIVATE RESPONSE', response=resp)
log_debug(title='PRIVATE RESPONSE', response=resp)

if resp['status'] == 'fail':
raise Exception(f"Request Failed: [{resp['message']}]")
Expand Down
19 changes: 12 additions & 7 deletions threads_api/src/http_sessions/requests_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
import json

from threads_api.src.http_sessions.abstract_session import HTTPSession
from threads_api.src.threads_api import log
from threads_api.src.anotherlogger import log_debug

from instagrapi import Client

class RequestsSession(HTTPSession):
def __init__(self):
self._session = requests.Session()
self._instagrapi_client = Client()

async def start(self):
if self._session is None:
Expand All @@ -16,15 +19,17 @@ async def close(self):
self._session.close()
self._session = None

def auth(self, auth_callback_func, **kwargs):
return auth_callback_func(**kwargs)
def auth(self, **kwargs):
self._instagrapi_client.login(**kwargs)
token = self._instagrapi_client.private.headers['Authorization'].split("Bearer IGT:2:")[1]
return token

async def post(self, **kwargs):
log(title='PRIVATE REQUEST', type='POST', requests_session_params=vars(self._session), **kwargs)
log_debug(title='PRIVATE REQUEST', type='POST', requests_session_params=vars(self._session), **kwargs)
response = self._session.post(**kwargs)
try:
resp = response.json()
log(title='PRIVATE RESPONSE', response=resp)
log_debug(title='PRIVATE RESPONSE', response=resp)

if resp['status'] == 'fail':
raise Exception(f"Request Failed: [{resp['message']}]")
Expand All @@ -34,11 +39,11 @@ async def post(self, **kwargs):
return resp

async def get(self, **kwargs):
log(title='PRIVATE REQUEST', type='GET', **kwargs)
log_debug(title='PRIVATE REQUEST', type='GET', **kwargs)
response = self._session.get(**kwargs)
try:
resp = response.json()
log(title='PRIVATE RESPONSE', response=resp)
log_debug(title='PRIVATE RESPONSE', response=resp)

if resp['status'] == 'fail':
raise Exception(f"Request Failed: [{resp['message']}]")
Expand Down
64 changes: 40 additions & 24 deletions threads_api/src/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import hashlib
import time
from datetime import datetime

class Settings:

Expand All @@ -11,16 +12,16 @@ def __init__(self):
Arguments:
settings: (dict/str): a settings dictionary or a path to a JSON file.
"""
self._encrypted_token = None
self._timezone_offset = -14400
self._device_id = self.generate_android_device_id()
self._device_manufacturer = 'OnePlus'
self._device_model = 'ONEPLUS+A3010'
self._device_android_version = 25
self._device_android_release = '7.1.1'
self.encrypted_token = None
self.timezone_offset = "-" + str((datetime.now() - datetime.utcnow()).seconds)
self.device_id = self.generate_android_device_id()
self.device_manufacturer = 'OnePlus'
self.device_model = 'ONEPLUS+A3010'
self.device_android_version = 25
self.device_android_release = '7.1.1'

def set_encrypted_token(self, encrypted_token):
self._encrypted_token = encrypted_token
self.encrypted_token = encrypted_token

def load_settings(self, path):
"""
Expand All @@ -38,7 +39,7 @@ def load_settings(self, path):
"""
with open(path, "r") as fp:
self.set_settings(json.load(fp))
return self.settings
return self.get_settings()
return None

def dump_settings(self, path):
Expand All @@ -55,7 +56,7 @@ def dump_settings(self, path):
Bool
"""
with open(path, "w") as fp:
json.dump(self.get_settings(), fp, indent=4)
fp.write(json.dumps(self.get_settings(), indent=4))
return True

def get_settings(self):
Expand All @@ -69,17 +70,17 @@ def get_settings(self):
"""
return {
'authentication': {
'encrypted_token': self._encrypted_token,
'encrypted_token': self.encrypted_token,
},
'timezone': {
'offset': self._timezone_offset,
'offset': self.timezone_offset,
},
'device': {
'id': self._device_id,
'manufacturer': self._device_manufacturer,
'model': self._device_model,
'android_version': self._device_android_version,
'android_release': self._device_android_release,
'id': self.device_id,
'manufacturer': self.device_manufacturer,
'model': self.device_model,
'android_version': self.device_android_version,
'android_release': self.device_android_release,
},
}

Expand All @@ -88,14 +89,29 @@ def set_settings(self, settings):
if settings is None:
raise Exception("Provide valid settings to set")

self._encrypted_token = settings.get('authentication').get('token')
self._timezone_offset = settings.get('timezone').get('offset')
self._device_id = settings.get('device').get('id')
self._device_manufacturer = settings.get('device').get('manufacturer')
self._device_model = settings.get('device').get('model')
self._device_android_version = settings.get('device').get('android_version')
self._device_android_release = settings.get('device').get('android_release')
self.encrypted_token = settings.get('authentication').get('token')
self.timezone_offset = settings.get('timezone').get('offset')
self.device_id = settings.get('device').get('id')
self.device_manufacturer = settings.get('device').get('manufacturer')
self.device_model = settings.get('device').get('model')
self.device_android_version = settings.get('device').get('android_version')
self.device_android_release = settings.get('device').get('android_release')

@property
def device_as_dict(self) -> dict:
"""
Get a device information.
Returns:
The device information as a dict.
"""
return {
'manufacturer': self.device_manufacturer,
'model': self.device_model,
'android_version': self.device_android_version,
'android_release': self.device_android_release,
}

def generate_android_device_id(self):
"""
Helper to generate Android Device ID
Expand Down
Loading

0 comments on commit ddae429

Please sign in to comment.