Skip to content

Instantly share code, notes, and snippets.

@hogseedy
Last active September 11, 2023 16:47
Show Gist options
  • Save hogseedy/b149c5f1ad1b628ba00556c7d4a898f8 to your computer and use it in GitHub Desktop.
Save hogseedy/b149c5f1ad1b628ba00556c7d4a898f8 to your computer and use it in GitHub Desktop.
Reddit native auth / GQL example
import requests
import hashlib
import base64
import uuid
import time
import json
import hmac
# Android app api keys, posted here: https://news.ycombinator.com/item?id=36086240
REDDIT_ANDROID_OAUTH_CLIENT_ID = 'ohXpoqrZYub1kg'
REDDIT_ANDROID_HMAC_SECRET = '8c7abaa5f905f70400c81bf3a1a101e75f7210104b1991f0cd5240aa80c4d99d'
class Reddit:
GQL_ENDPOINT = 'https://gql.reddit.com'
AUTH_ENDPOINT = 'https://accounts.reddit.com'
USER_AGENT = 'Reddit/Version 2023.21.0/Build 956283/Android 13'
def __init__(self, client_id, hmac_secret):
self.client_id = client_id
self.hmac_secret = hmac_secret
self.token = None
self.s = requests.session()
self.uuid = str(uuid.uuid4())
self.s.headers.update({
'Client-Vendor-Id': self.uuid,
'X-Reddit-Device-Id': self.uuid,
'User-Agent': self.USER_AGENT
})
def login_anonymous(self):
return self._get_token()
def login_user(self, username, password):
epoch = str(int(time.time()))
body = json.dumps({'username': username, 'password': password}, separators = (',', ':'))
body_sig = f'1:android:2:{epoch}:{self._generate_body_sig(epoch, body)}'
result_sig = f'1:android:2:{epoch}:{self._generate_result_sig(epoch)}'
r = self.s.post(
f'{self.AUTH_ENDPOINT}/api/login',
data = body,
headers = {
'X-Hmac-Signed-Body': body_sig,
'X-Hmac-Signed-Result': result_sig
}
)
if r.json()['success']:
print('reddit_session =', r.cookies['reddit_session'])
print('modhash =', r.json()['modhash'])
return self._get_token()
else:
raise Exception('Login error')
def gql_get_posts(self, subreddit, sort='HOT', after=None):
if not self.token:
raise Exception('Not authorized')
json_body = {
'id': '3496a5858eb9', # SubredditFeedElements
'variables': {
'subredditName': subreddit,
'sort': sort,
'adContext': {
'distance': None,
'layout': 'CARD',
'deviceAdId': '',
'clientSignalSessionData': {
'adsSeenCount': 123,
'totalPostsSeenCount': 456,
'sessionStartTime': '2023-06-05T12:34:56.000000+0000'
}
},
'forceAds': {},
'feedFilters': {},
'optedIn': True,
'includeSubredditInPosts': False,
'includeAwards': False,
'feedContext': {
'experimentOverrides': []
},
'includePostStats': False
}
}
if after:
json_body['variables']['after'] = base64.b64encode(after.encode()).decode()
r = self.s.post(
self.GQL_ENDPOINT,
json = json_body
)
return r.json()
def _get_token(self):
r = self.s.post(
f'{self.AUTH_ENDPOINT}/api/access_token',
json = {
'scopes': ['*','email','pii']
},
auth = requests.auth.HTTPBasicAuth(self.client_id, '')
)
if 'access_token' in r.json():
self.token = r.json()['access_token']
self.s.headers.update({'Authorization': f'Bearer {self.token}'})
return True
else:
raise Exception('Login error')
def _generate_body_sig(self, epoch, body):
return self._hmac_sign_data(f'Epoch:{epoch}|Body:{body}')
def _generate_result_sig(self, epoch):
return self._hmac_sign_data(f'Epoch:{epoch}|User-Agent:{self.USER_AGENT}|Client-Vendor-ID:{self.uuid}')
def _hmac_sign_data(self, data):
return hmac.new(self.hmac_secret.encode(), msg = data.encode(), digestmod = hashlib.sha256).hexdigest()
if __name__ == '__main__':
r = Reddit(REDDIT_ANDROID_OAUTH_CLIENT_ID, REDDIT_ANDROID_HMAC_SECRET)
r.login_anonymous() # r.login_user('user', 'pass')
print(r.gql_get_posts('technology', after='t3_13zvyn5'))
@prime-optimal
Copy link

Thanks for this!

@juanpnvd
Copy link

juanpnvd commented Jul 5, 2023

This is awesome, thank you. Is there a way to save the session cookies and load it to use it after?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment