#!/usr/bin/python3
# Portions of this script directly from https://gist.github.com/jaymzh/3ed8817cf8c20222ca09ce33a544b695
# Permanent home: https://github.com/akhepcat/Miscellaneous/
# Direct download: https://raw.githubusercontent.com/akhepcat/Miscellaneous/master/pidgin-fb-login-2fa.py
import sys
import cgi
from urllib.parse import urlencode, quote_plus
import hashlib
import getpass
import http.client
import urllib
import json
import xmltodict
from pathlib import Path
from optparse import OptionParser
FB_API_KEY = '256002347743983'
FB_API_SECRET = '374e60f8b9bb6b8cbb30f78030438895'
DEBUG = False
# Somehelper methods
def fb_sig(data):
newdata = data.copy()
params = ''.join(['%s=%s' % x for x in sorted(data.items())])
newdata['sig'] = hashlib.md5((params + FB_API_SECRET).encode('utf-8')).hexdigest()
return newdata
def debug(msg):
global DEBUG
if DEBUG:
print("DEBUG: %s", msg)
parser = OptionParser()
parser.add_option('-d', '--debug', action='store_true', dest='debug', default=False)
(options, args) = parser.parse_args()
if options.debug:
DEBUG = True
home = str(Path.home())
ACCOUNTS = home + "/.purple/accounts.xml"
debug("accounts.xml is: %s" % ACCOUNTS)
with open(ACCOUNTS, 'r') as myfile:
obj = xmltodict.parse(myfile.read())
acts = obj["account"]["account"]
found=0
idx=0
while found == 0:
if acts[idx]["protocol"] == "prpl-facebook":
found=1
else:
idx = idx + 1
if found == 0:
print("No prpl-facebook account found in ", ACCOUNTS)
print("exiting")
exit(1)
EMAIL = acts[idx]["name"]
passwd = acts[idx]["password"]
settings = acts[idx]["settings"]
### Get did, uid, mid
found=0
ox=0
while 1:
ix=0
while 1:
debug("Checking ox %s ix %s, it is %s" % (ox, ix, settings[ox]["setting"][ix]["@name"]))
try:
if settings[ox]["setting"][ix]["@name"] == "did":
DID = settings[ox]["setting"][ix]["#text"]
found = found +1
if settings[ox]["setting"][ix]["@name"] == "uid":
UID = settings[ox]["setting"][ix]["#text"]
found = found +1
if settings[ox]["setting"][ix]["@name"] == "mid":
MID = settings[ox]["setting"][ix]["#text"]
found = found +1
if found > 2:
break
except:
break
ix = ix + 1
if ix > 10:
break
if found < 2:
ox = ox + 1
else:
break
if ox > 20:
break
if found < 3:
print("Warning: Either no machine (mid), device (did) or user account (uid) found in prpl-facebook section of ", ACCOUNTS)
print("This may cause unexpected errors in the script")
# exit(1)
debug("Account UID: %s" % UID)
debug("Account DID: %s" % DID)
debug("Account MID: %s" % MID)
if EMAIL == '':
print("ERROR: set an email address, please")
sys.exit()
if DID == '':
print("ERROR: set a device id (to any UUID), please")
sys.exit()
data = {
"fb_api_req_friendly_name": "authenticate",
"locale": "en",
"format": "json",
"api_key": FB_API_KEY,
"method": "auth.login",
"generate_session_cookies": "1",
"generate_machine_id": "1",
"email": EMAIL,
"uid": UID,
"device_id": DID,
}
print('''Access Token generator for Facebook 2factor login
Make sure Pidgin is *not running* before proceeding. Pidgin modifies
the accounts.xml on exit as well as while running, so it is important
to exit pidgin before starting so our changes are not lost.
This tool will perform 2-factor login to FB and then print out an
access token needed for the FB plugin for bitlbee and pidgin.
Do *NOT* select "yes this was me" if you get a security pop-up from
your Facebook app. Instead, enter the code in here.
Take the resulting code and put it in the "token" tag in accounts.xml like so:
THE_CODE_HERE
''')
data['password'] = passwd
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept": "*/*"}
conn = http.client.HTTPSConnection('b-api.facebook.com:443')
params = urlencode(fb_sig(data), quote_via=quote_plus)
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data.decode('utf-8'))
debug(response)
# check to make sure that worked...
if response['error_code'] != 406:
print(
"ERROR: Incorrect password, 2-fac is not enabled, or some other issue."
" Dumping results:\n\n\t",
end=''
)
print(response)
sys.exit(1)
code = input('Code: ')
error_data = json.loads(response['error_data'])
first_fac = error_data['login_first_factor']
data['credentials_type'] = 'two_factor'
data['error_detail_type'] = 'button_with_disabled'
data['first_factor'] = first_fac
data['twofactor_code'] = code
data['password'] = data['twofactor_code']
data['userid'] = error_data['uid']
data['machine_id'] = error_data['machine_id']
debug("FB Account UID: %s" % error_data['uid'] )
try:
data['device_id'] = error_data['device_id']
debug("FB Account DID: %s" % error_data['device_id'])
except:
debug("FB Account DID not present in error_data: equal to accounts.xml did")
debug("FB Account MID: %s" % error_data['machine_id'])
params = urlencode(fb_sig(data))
conn.request('POST', '/method/auth.login', params, headers)
response = conn.getresponse()
debug("status, reason: %s, %s" % (response.status, response.reason))
response_data = response.read()
debug("undecoded response: %s" % response_data)
response = json.loads(response_data.decode('utf-8'))
print("Update or add the following settings in %s under the Facebook account:" % ACCOUNTS)
print("%s" % response['access_token'])
# Pidgin initializes UID to 0...
if UID == 0:
print("%s" % response['uid'])
# We don't always get back a "device_id", but if we do, make sure it's correct
remote_did = response.get('device_id')
if ( remote_did is not None and DID != response.get('device_id') ):
print("%s" % response['device_id'])
# Machine ID seems to change every time, but we're not supposed to update it.
# If we do, pidgin will want to re-sync the history and FB will say it's too
# much data.
if ( MID != response['machine_id'] ):
debug("%s" % response['machine_id'])