Implemented login + bio/website setting + profile pic setting
* save cookies as dict in the db accounts file * md5sum wrapped * db_get and db_set functions * config() function to read config.json * other improvements
This commit is contained in:
parent
47a72b57d6
commit
ef8713438f
|
@ -2,4 +2,5 @@ cache/*
|
||||||
headers/*
|
headers/*
|
||||||
db/*
|
db/*
|
||||||
scripts/user_create
|
scripts/user_create
|
||||||
|
config.json
|
||||||
__pycache__/*
|
__pycache__/*
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"instance": "pixel.nogafam.es"
|
||||||
|
}
|
129
igmirror.py
129
igmirror.py
|
@ -8,14 +8,24 @@ import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
CONFIG = {}
|
||||||
|
|
||||||
def add_igaccount(acc_id):
|
def add_igaccount(acc_id):
|
||||||
accfile = './db/accounts/{}'.format(acc_id)
|
accfile = './db/accounts/{}'.format(acc_id)
|
||||||
|
|
||||||
|
# user_create script must exist before running the API server
|
||||||
if not os.path.exists('./scripts/user_create'):
|
if not os.path.exists('./scripts/user_create'):
|
||||||
print('E| You may need to initialize the server environment first')
|
print('E| You may need to initialize the server environment first')
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if not os.path.exists(accfile):
|
if not os.path.exists(accfile):
|
||||||
|
# get all profile data from instagram acc
|
||||||
data = getig_user_data(acc_id)
|
data = getig_user_data(acc_id)
|
||||||
|
if len(data.keys()) == 0:
|
||||||
|
print('E| User "{}" does not exist on Instagram'.format(acc_id))
|
||||||
|
return 2
|
||||||
|
|
||||||
|
# get account display name to create it
|
||||||
name = data['graphql']['user']['full_name']
|
name = data['graphql']['user']['full_name']
|
||||||
name = re.sub(r'[^a-zA-Z0-9_\s]', '', name)
|
name = re.sub(r'[^a-zA-Z0-9_\s]', '', name)
|
||||||
account = {
|
account = {
|
||||||
|
@ -23,18 +33,115 @@ def add_igaccount(acc_id):
|
||||||
'username': acc_id,
|
'username': acc_id,
|
||||||
'password': random_string()
|
'password': random_string()
|
||||||
}
|
}
|
||||||
|
# we are completely sure the parameters can't contain unwanted characters
|
||||||
|
# a shell exploit is not possible here :)
|
||||||
os.system('./scripts/user_create \'{}\' \'{}\' \'{}\''.format(\
|
os.system('./scripts/user_create \'{}\' \'{}\' \'{}\''.format(\
|
||||||
account['name'], account['username'], account['password']))
|
account['name'], account['username'], account['password']))
|
||||||
w = open(accfile, 'w')
|
|
||||||
w.write(json.dumps(account))
|
# save the account login information for updates and mirroring
|
||||||
w.close()
|
db_set('accounts', acc_id, account)
|
||||||
|
|
||||||
|
# set Pixelfed account data for the username
|
||||||
|
pixelfed_setpic(acc_id, data['graphql']['user']['profile_pic_url'])
|
||||||
|
pixelfed_setinfo(acc_id, data['graphql']['user']['biography'],\
|
||||||
|
data['graphql']['user']['external_url'])
|
||||||
else:
|
else:
|
||||||
print('W| User "{}" already exists in local database'.format(acc_id))
|
print('W| User "{}" already exists in local database'.format(acc_id))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
def update_igaccount(acc_id):
|
||||||
|
print('update igaccount')
|
||||||
|
|
||||||
|
def update_allaccounts():
|
||||||
|
print('update all accounts')
|
||||||
|
|
||||||
|
|
||||||
|
def pixelfed_islogged(acc_id, accdata=None):
|
||||||
|
if accdata is None:
|
||||||
|
accdata = db_get('accounts', acc_id)
|
||||||
|
return 'cookie' in accdata
|
||||||
|
|
||||||
|
def pixelfed_login(acc_id, force=False):
|
||||||
|
# check account is already logged in if not "force"
|
||||||
|
accdata = db_get('accounts', acc_id)
|
||||||
|
if not force and pixelfed_islogged(acc_id, accdata):
|
||||||
|
return
|
||||||
|
|
||||||
|
# obtain one time tokens for the pixelfed instance
|
||||||
|
_cookies, _token = pixelfed_token_url()
|
||||||
|
|
||||||
|
# do the login post and retrieve the raw cookies, the rest of API calls will have this cookies
|
||||||
|
r = requests.post( 'https://'+config()['instance']+'/login' ,\
|
||||||
|
data={
|
||||||
|
'_token': _token, 'email': 'pixelfed.'+acc_id+'@localhost',
|
||||||
|
'password': accdata['password'], 'remember': 'on'
|
||||||
|
},
|
||||||
|
cookies=_cookies
|
||||||
|
)
|
||||||
|
|
||||||
|
# add the raw cookies to the account data for later calls
|
||||||
|
accdata['cookie'] = dict(r.cookies)
|
||||||
|
db_set('accounts', acc_id, accdata)
|
||||||
|
|
||||||
|
def pixelfed_token_url(url='', _cookies=None):
|
||||||
|
r = requests.get( 'https://'+config()['instance']+url, cookies=_cookies )
|
||||||
|
_token = re.search(r'name="_token".+value="([^"]+)"', r.text).group(1)
|
||||||
|
return r.cookies, _token
|
||||||
|
|
||||||
|
def pixelfed_setpic(acc_id, pic_url, count=0):
|
||||||
|
count += 1
|
||||||
|
pixelfed_login(acc_id)
|
||||||
|
|
||||||
|
# get the image by URL but cache it forever, as if the profile changes the pic
|
||||||
|
# the url will be different, and therefore, the sum will also be different
|
||||||
|
cachef = './cache/{}.jpg'.format(md5sum(pic_url))
|
||||||
|
if not os.path.exists(cachef):
|
||||||
|
r = requests.get(pic_url)
|
||||||
|
w = open(cachef, 'wb')
|
||||||
|
w.write(r.content)
|
||||||
|
w.close()
|
||||||
|
|
||||||
|
accdata = db_get('accounts', acc_id)
|
||||||
|
_, _token = pixelfed_token_url('/settings/home', accdata['cookie'])
|
||||||
|
r = requests.post( 'https://'+config()['instance']+'/settings/avatar',\
|
||||||
|
data={'_token': _token}, cookies=accdata['cookie'], files={'avatar': open(cachef, 'rb')}
|
||||||
|
)
|
||||||
|
|
||||||
|
# try to login if the upload failed
|
||||||
|
if r.status_code == 419 and count < 3:
|
||||||
|
pixelfed_login(acc_id, True)
|
||||||
|
return pixelfed_setpic(acc_id, pic_url, count)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def pixelfed_setinfo(acc_id, bio, website, count=0):
|
||||||
|
count += 1
|
||||||
|
pixelfed_login(acc_id)
|
||||||
|
|
||||||
|
accdata = db_get('accounts', acc_id)
|
||||||
|
_, _token = pixelfed_token_url('/settings/home', accdata['cookie'])
|
||||||
|
r = requests.post( 'https://'+config()['instance']+'/settings/home',\
|
||||||
|
data={
|
||||||
|
'_token': _token, 'name': accdata['name'],
|
||||||
|
'website': website, 'bio': bio, 'language': 'en'
|
||||||
|
},
|
||||||
|
cookies=accdata['cookie']
|
||||||
|
)
|
||||||
|
|
||||||
|
# try to login if the upload failed
|
||||||
|
if r.status_code == 419 and count < 3:
|
||||||
|
pixelfed_login(acc_id, True)
|
||||||
|
return pixelfed_setinfo(acc_id, bio, website, count)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def random_string(count=32):
|
def random_string(count=32):
|
||||||
return ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=count))
|
return ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=count))
|
||||||
|
|
||||||
|
def md5sum(_str):
|
||||||
|
return hashlib.md5(_str.encode()).hexdigest()
|
||||||
|
|
||||||
# get all profile data from user:
|
# get all profile data from user:
|
||||||
# - display name
|
# - display name
|
||||||
# - bio description
|
# - bio description
|
||||||
|
@ -63,7 +170,7 @@ def instagram_get(url, CACHE_SECS=600):
|
||||||
if not key in headers:
|
if not key in headers:
|
||||||
headers[key] = default_headers[key]
|
headers[key] = default_headers[key]
|
||||||
url = 'https://www.instagram.com{}'.format(url)
|
url = 'https://www.instagram.com{}'.format(url)
|
||||||
cachef = './cache/'+hashlib.md5(url.encode()).hexdigest()
|
cachef = './cache/'+md5sum(url)
|
||||||
now = str(time.time())
|
now = str(time.time())
|
||||||
now = int(now[:now.index('.')])
|
now = int(now[:now.index('.')])
|
||||||
if os.path.exists(cachef):
|
if os.path.exists(cachef):
|
||||||
|
@ -91,6 +198,20 @@ def get_random_headers():
|
||||||
headers[reg.group(1).strip()] = reg.group(2).strip()
|
headers[reg.group(1).strip()] = reg.group(2).strip()
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
def db_set(table, acc_id, accdata):
|
||||||
|
w = open('./db/{}/{}'.format(table, acc_id), 'w')
|
||||||
|
w.write(json.dumps(accdata))
|
||||||
|
w.close()
|
||||||
|
|
||||||
|
def db_get(table, acc_id):
|
||||||
|
return json.loads(readf('./db/{}/{}'.format(table, acc_id)))
|
||||||
|
|
||||||
|
def config():
|
||||||
|
global CONFIG
|
||||||
|
if len(CONFIG.keys()) == 0:
|
||||||
|
CONFIG = json.loads(readf('./config.json'))
|
||||||
|
return CONFIG
|
||||||
|
|
||||||
def readf(f):
|
def readf(f):
|
||||||
r = open(f,'r')
|
r = open(f,'r')
|
||||||
c = r.read().strip()
|
c = r.read().strip()
|
||||||
|
|
18
server.py
18
server.py
|
@ -3,24 +3,32 @@ import igmirror
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
|
|
||||||
def update_igaccount(name):
|
|
||||||
print(name)
|
|
||||||
|
|
||||||
class MyServer(BaseHTTPRequestHandler):
|
class MyServer(BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-type", "application/json")
|
self.send_header("Content-type", "application/json")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
# the account is the first part of the URL
|
||||||
|
# and the action for the account is the last part
|
||||||
|
# example: /shakira/add
|
||||||
path = self.path.strip('/')
|
path = self.path.strip('/')
|
||||||
parts = path.split('/')
|
parts = path.split('/')
|
||||||
if len(parts) == 2:
|
if len(parts) == 2:
|
||||||
|
# a wilcard select all accounts
|
||||||
|
if parts[0] == '*':
|
||||||
|
if parts[1].lower() == 'update':
|
||||||
|
igmirror.update_allaccounts()
|
||||||
|
else:
|
||||||
# make sure account name contains only safe characters
|
# make sure account name contains only safe characters
|
||||||
|
# i think IG usernames can only have this characters:
|
||||||
accname = re.sub(r'[^a-zA-Z0-9_]+', '', parts[0])
|
accname = re.sub(r'[^a-zA-Z0-9_]+', '', parts[0])
|
||||||
if parts[1].lower() == 'add':
|
if parts[1].lower() == 'add':
|
||||||
igmirror.add_igaccount(accname)
|
igmirror.add_igaccount(accname)
|
||||||
elif parts[1].lower() == 'update':
|
elif parts[1].lower() == 'update':
|
||||||
update_igaccount(accname)
|
igmirror.update_igaccount(accname)
|
||||||
self.wfile.write(bytes('OK', "utf-8"))
|
self.wfile.write(bytes('{"status": "ok"}', "utf-8"))
|
||||||
|
else:
|
||||||
|
self.wfile.write(bytes('{"status": "parameters are not correct"}', "utf-8"))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
port = 8080
|
port = 8080
|
||||||
|
|
Loading…
Reference in New Issue