Skip to content

Commit da6cb7b

Browse files
committed
post to twitter
1 parent e76ef8f commit da6cb7b

File tree

4 files changed

+107
-5
lines changed

4 files changed

+107
-5
lines changed

config.yaml.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ twitter: # set this to null to disable
1313
bearer_token: null
1414
accounts:
1515
RiskofRain: '282441291327864834'
16+
twitter_post: # set this to null to disable
17+
consumer_key: null # get these from https://developer.twitter.com/en/apps/
18+
consumer_secret: null
19+
token: null # get these from twitter_key.py
20+
token_secret: null
21+
server: '109469702010478592' # secret hiding room
22+
channel: '316959344681943040' # food
1623
twitch: # set this to null to disable
1724
client_id: ''
1825
client_secret: ''

mock_cmd.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
from pprint import pprint
44

5-
import animal_crossing
5+
import requests
6+
7+
import config
8+
import twitter
69

710
class MockCmd:
811
def __init__(self):
@@ -26,6 +29,14 @@ def __init__(self):
2629
def send_message(self, channel_id, text, embed=None, files=None):
2730
print(channel_id, text, embed, files)
2831

32+
def get(self, path):
33+
response = requests.get('https://discord.com/api' + path, headers={
34+
'Authorization': 'Bot ' + config.bot.token,
35+
'User-Agent': 'DiscordBot (https://github.com/raylu/sbot 0.0)',
36+
})
37+
response.raise_for_status()
38+
return response.json()
39+
2940
class MockGuild:
3041
def __init__(self):
3142
self.roles = {
@@ -34,4 +45,4 @@ def __init__(self):
3445
'cats': {'position': 2, 'name': 'cats', 'color': 13369480, 'id': '2222'},
3546
}
3647

37-
animal_crossing.stalk_market(MockCmd())
48+
twitter.post(MockBot())

twitter.py

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import base64
2+
import copy
3+
import hashlib
4+
import mimetypes
25
import hmac
6+
import os
37
import time
48
import urllib.parse
59

@@ -47,13 +51,93 @@ def new_tweets(bot):
4751
config.state.tweet_ids[account] = tweets[0]['id']
4852
config.state.save()
4953

54+
def post(bot):
55+
rs = requests.Session()
56+
message_id = '316960031314804737'
57+
channel = config.bot.twitter_post['channel']
58+
message = bot.get('/channels/%s/messages/%s' % (channel, message_id))
59+
media_ids = []
60+
upload_url = 'https://upload.twitter.com/1.1/media/upload.json'
61+
for attachment in message['attachments'][:4]:
62+
media = rs.get(attachment['url']).content
63+
64+
media_type, _ = mimetypes.guess_type(attachment['filename'])
65+
rs = requests.Session()
66+
response = signed_request(rs, 'POST', upload_url, params={
67+
'command': 'INIT',
68+
'media_type': media_type,
69+
'total_bytes': str(attachment['size']),
70+
})
71+
media_id = response.json()['media_id_string']
72+
73+
response = signed_request(rs, 'POST', upload_url, params={
74+
'command': 'APPEND',
75+
'media_id': media_id,
76+
'segment_index': '0',
77+
}, files={'media': media})
78+
79+
response = signed_request(rs, 'POST', upload_url, params={
80+
'command': 'FINALIZE',
81+
'media_id': media_id,
82+
})
83+
84+
media_ids.append(media_id)
85+
86+
discord_link = 'https://discord.com/channels/%s/%s/%s' % (
87+
config.bot.twitter_post['server'], config.bot.twitter_post['channel'], message_id)
88+
tweet = '%s on %s\n%s' % (message['author']['username'], message['timestamp'][:10], discord_link)
89+
response = signed_request(rs, 'POST', 'https://api.twitter.com/1.1/statuses/update.json', {
90+
'status': tweet,
91+
'media_ids': ','.join(media_ids),
92+
'trim_user': '1',
93+
}, {})
94+
5095
def tweet_id_to_ts(tweet_id):
5196
# https://github.com/client9/snowflake2time#snowflake-layout
5297
return (tweet_id & 0x7fffffffffffffff) >> 22
5398

54-
def sign(method, url, params, consumer_secret, token_secret):
99+
def signed_request(rs, method, url, params=None, data=None, files=None):
100+
signing_params = {
101+
'oauth_consumer_key': config.bot.twitter_post['consumer_key'],
102+
'oauth_nonce': base64.b64encode(os.getrandom(16)),
103+
'oauth_signature_method': 'HMAC-SHA1',
104+
'oauth_timestamp': str(int(time.time())),
105+
'oauth_version': '1.0',
106+
'oauth_token': config.bot.twitter_post['token'],
107+
}
108+
final_data = None
109+
headers = {}
110+
if files:
111+
# https://github.com/oauthlib/oauthlib/blob/bda81b3cb6306dec19a6e60113e21b2933d0950c/oauthlib/oauth1/rfc5849/__init__.py#L212
112+
request = requests.Request(method, url, data=data, files=files).prepare()
113+
signing_params['oauth_body_hash'] = base64.b64encode(hashlib.sha1(request.body).digest())
114+
final_data = request.body
115+
headers['Content-Type'] = request.headers['Content-Type']
116+
auth_params = copy.copy(signing_params)
117+
if params:
118+
signing_params.update(params)
119+
if data:
120+
signing_params.update(data)
121+
if not files:
122+
final_data = data
123+
124+
consumer_secret = config.bot.twitter_post['consumer_secret']
125+
token_secret = config.bot.twitter_post['token_secret']
126+
auth_params['oauth_signature'] = sign(method, url,
127+
signing_params, consumer_secret, token_secret)
128+
# https://developer.twitter.com/en/docs/authentication/oauth-1-0a/authorizing-a-request
129+
auth = 'OAuth '
130+
auth += ', '.join('%s="%s"' % (k, urllib.parse.quote(v)) for k, v in auth_params.items())
131+
headers['Authorization'] = auth
132+
response = rs.request(method, url, params=params, data=final_data, headers=headers)
133+
response.raise_for_status()
134+
return response
135+
136+
def sign(method, url, signing_params, consumer_secret, token_secret):
55137
# https://developer.twitter.com/en/docs/authentication/oauth-1-0a/creating-a-signature
56-
parameter_string = urllib.parse.urlencode(sorted(params.items()), quote_via=urllib.parse.quote)
138+
# https://github.com/oauthlib/oauthlib/blob/d54965b86ce4ede956db70baff0b3d5e9182a007/oauthlib/oauth1/rfc5849/utils.py#L52
139+
parameter_string = urllib.parse.urlencode(sorted(signing_params.items()),
140+
quote_via=urllib.parse.quote, safe='~')
57141
# that's right! we re-quote the parameter string
58142
encoded_params = urllib.parse.quote_plus(parameter_string)
59143
base_string = '%s&%s&%s' % (method, urllib.parse.quote_plus(url), encoded_params)

twitter_key.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
print(r.content)
2020
elif mode == '2':
2121
# https://developer.twitter.com/en/docs/authentication/oauth-1-0a/pin-based-oauth
22-
# https://developer.twitter.com/en/docs/authentication/api-reference/request_token
2322
params = {
2423
'oauth_callback': 'oob',
2524
'oauth_consumer_key': consumer_key,
@@ -28,6 +27,7 @@
2827
'oauth_timestamp': str(int(time.time())),
2928
'oauth_version': '1.0',
3029
}
30+
# https://developer.twitter.com/en/docs/authentication/api-reference/request_token
3131
url = 'https://api.twitter.com/oauth/request_token'
3232
params['oauth_signature'] = twitter.sign('POST', url, params, consumer_secret, '')
3333
# https://developer.twitter.com/en/docs/authentication/oauth-1-0a/authorizing-a-request

0 commit comments

Comments
 (0)