File

post_x.py

company/content/x/post_x.py · 5m ago
import sys, os, time, uuid, hmac, hashlib, base64, urllib.parse, urllib.request, json

def percent_encode(s):
    return urllib.parse.quote(str(s), safe='')

def oauth_header(method, url, extra_params, key, secret, token, token_secret):
    oauth_params = {
        'oauth_consumer_key': key,
        'oauth_nonce': uuid.uuid4().hex,
        'oauth_signature_method': 'HMAC-SHA1',
        'oauth_timestamp': str(int(time.time())),
        'oauth_token': token,
        'oauth_version': '1.0',
    }
    params = {**oauth_params, **extra_params}
    param_str = '&'.join(f"{percent_encode(k)}={percent_encode(params[k])}" for k in sorted(params))
    base_str = '&'.join([method.upper(), percent_encode(url), percent_encode(param_str)])
    signing_key = f"{percent_encode(secret)}&{percent_encode(token_secret)}"
    signature = base64.b64encode(hmac.new(signing_key.encode(), base_str.encode(), hashlib.sha1).digest()).decode()
    oauth_params['oauth_signature'] = signature
    return 'OAuth ' + ', '.join(f'{percent_encode(k)}="{percent_encode(v)}"' for k, v in sorted(oauth_params.items()))

def upload_media(image_path, keys):
    url = 'https://upload.twitter.com/1.1/media/upload.json'
    with open(image_path, 'rb') as f:
        data = f.read()
    b64data = base64.b64encode(data).decode()
    header = oauth_header('POST', url, {}, keys['key'], keys['secret'], keys['token'], keys['token_secret'])
    boundary = uuid.uuid4().hex
    body = (f'--{boundary}\r\nContent-Disposition: form-data; name="media_data"\r\n\r\n{b64data}\r\n'
            f'--{boundary}--\r\n').encode()
    req = urllib.request.Request(url, data=body, method='POST')
    req.add_header('Authorization', header)
    req.add_header('Content-Type', f'multipart/form-data; boundary={boundary}')
    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        print('MEDIA_UPLOAD_ERROR', e.code, e.read().decode())
        raise
    return result['media_id_string']

def post_tweet(text, media_id, keys):
    url = 'https://api.twitter.com/2/tweets'
    header = oauth_header('POST', url, {}, keys['key'], keys['secret'], keys['token'], keys['token_secret'])
    payload = {'text': text}
    if media_id:
        payload['media'] = {'media_ids': [media_id]}
    body = json.dumps(payload).encode()
    req = urllib.request.Request(url, data=body, method='POST')
    req.add_header('Authorization', header)
    req.add_header('Content-Type', 'application/json')
    try:
        with urllib.request.urlopen(req) as resp:
            result = json.loads(resp.read())
    except urllib.error.HTTPError as e:
        print('TWEET_POST_ERROR', e.code, e.read().decode())
        raise
    return result

if __name__ == '__main__':
    image_path = sys.argv[1]
    text_path = sys.argv[2]
    with open(text_path, encoding='utf-8') as f:
        text = f.read()
    keys = {
        'key': os.environ['X_API_KEY'],
        'secret': os.environ['X_API_SECRET'],
        'token': os.environ['X_ACCESS_TOKEN'],
        'token_secret': os.environ['X_ACCESS_SECRET'],
    }
    media_id = upload_media(image_path, keys) if image_path != '-' else None
    result = post_tweet(text, media_id, keys)
    print(json.dumps(result))