这两周正在学python,第二次拼接 code 。最终目标是发贴,做个美食,中国游的账号。
laptop: windows 11 home.
项目结构:
app.py 主程序
config.ini 这里存所有的 keys Tokens 还有 callback url
目录 Templates 用于存 index.html
目录 static 存放 script.js 与 style.css 幻想着漂亮的界面与功能
干这活儿,就两步:
第一步 有个laptop 注册个发堆账号 x稻糠母 稻糠母 = .com
第二步 注册 twitter 开发平台账号 https://developer.x.com/enx稻糠母https://developer.x.com/en
我使用的是:自由计划(free plan)功能就几个:发贴、删帖、转发 第二天结束从 Oauth 1.0a 又回到原点,在这里做个笔记。
前期准备
安装 Python , 还有以下必要的库 pip install 库名
- hashlib, base64, os:生成
code_verifier
和code_challenge
,用于 OAuth 2.0 PKCE 流程。 - requests:用于与 Twitter API 进行 HTTP 请求。
- configparser:用于从
config.ini
文件读取配置。 - OAuth1:用于 OAuth 1.0a 认证(用于媒体上传)。
- Flask:创建 Web 应用。
- BeautifulSoup:用于解析 HTML(目前没有使用)。
# app.py 代码:
import hashlib
import base64
import os
import requests
import configparser
from requests_oauthlib import OAuth1
from flask import Flask, request, render_template, jsonify, redirect, url_for, session
from bs4 import BeautifulSoup
#创建一个 Flask 应用实例: 我加了注释,粘贴后再检查一遍。
app = Flask(__name__)
#上传媒体的函数,此函数负责将媒体文件(图片或视频)上传到 x 。它使用 OAuth 1.0a 认证。上传成功后,它会返回媒体 ID,供后续发布推文使用。
def upload_media_to_twitter(media_file, media_type):
url = "https://upload.twitter.com/1.1/media/upload.json"
files = {'media': media_file}
data = {
'media_category': 'tweet_image' if media_type == 'image' else 'tweet_video'
}
access_token = session.get('access_token')
headers = {
'Authorization': f'Bearer {access_token}'
}
try:
response = requests.post(url, headers=headers, files=files, data=data)
response.raise_for_status()
media_id = response.json().get('media_id_string')
return media_id
except requests.exceptions.RequestException as e:
print(f"Error uploading media: {e}")
print(f"Response content: {response.content}")
raise e
# 读取 config.ini
配置文件 API 凭证和应用的其他配置信息
def load_config():
config = configparser.RaWConfigParser() # 这里用 rawconfigparser 因为token里可能有%这类的字符,最初在%前加一个% ...
try:
config.read('config.ini')
return config
except Exception as e:
print(f"Failed to load config.ini: {str(e)}")
return None
config = load_config()
#从配置文件中获取关键数据
# config.ini
文件中读取 Flask 的 secret_key
以及 Twitter API 的凭证(Consumer Key、Consumer Secret、Access Token、Access Token Secret 等) 有用没用,都先赋值。
app.secret_key = config.get('flask', 'secret_key')
consumer_key = config.get('twitter', 'consumer_key')
consumer_secret = config.get('twitter', 'consumer_secret')
client_id = config.get('twitter', 'client_id')
client_secret = config.get('twitter', 'client_secret')
access_token = config.get('twitter', 'access_token')
access_token_secret = config.get('twitter', 'access_token_secret')
redirect_url = config.get('twitter', 'redirect_url')
scopes = "tweet.read tweet.write users.read offline.access"
#创建 OAuth 1.0a 认证对象 这用于媒体上传(v1.1 API)还没搞通,不知道是什么原因,图与视频不行。还在耐心试试
auth = OAuth1(consumer_key, consumer_secret, access_token, access_token_secret)
#为 OAuth 2.0 的 PKCE(Proof Key for Code Exchange)流程生成 code_verifier
和 code_challenge
,用于增强安全性。这段是哪儿炒的?想不起来。
def generate_code_verifier_and_challenge():
code_verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b'=').decode('utf-8')
code_challenge = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(code_challenge).rstrip(b'=').decode('utf-8')
return code_verifier, code_challenge
code_verifier, code_challenge = generate_code_verifier_and_challenge()
#生成OAuth 2.0 的授权 URL,用户点击后将重定向到 Twitter 进行授权。
def build_auth_url(client_id, redirect_url, scopes, code_challenge):
auth_url = (
f"https://twitter.com/i/oauth2/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_url}&"
f"scope={scopes}&state=state&code_challenge={code_challenge}&code_challenge_method=S256"
)
return auth_url
auth_url = build_auth_url(client_id, redirect_url, scopes, code_challenge)
#Flask 路由:/
(主页) 如果没有 access_token
,则重定向用户到 Twitter 进行授权。
@app.route('/')
def index():
access_token = session.get('access_token')
if not access_token:
return redirect(auth_url)
return render_template('index.html', auth_url=auth_url, access_token=access_token)
# Flask 路由:/callback
(授权回调)用户授权后,Twitter 将返回授权码,该路由将处理该授权码并获取
@app.route('/callback')
def callback():
code = request.args.get('code')
if not code:
return "Error: No authorization code provided", 400
try:
access_token = get_access_token(code)
session['access_token'] = access_token
return redirect(url_for('index'))
except Exception as ex:
return f"An error occurred: {str(ex)}", 500
# 函数发送 POST 请求到 x 以获取 OAuth 2.0 access_token
def get_access_token(code):
token_url = 'https://api.twitter.com/2/oauth2/token'
data = {
'client_id': client_id,
'grant_type': 'authorization_code',
'code': code,
'redirect_uri': redirect_url,
'code_verifier': code_verifier,
}
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.post(token_url, data=data, headers=headers)
if response.status_code == 200:
tokens = response.json()
return tokens['access_token']
else:
raise Exception(f"Failed to get access token: {response.status_code} {response.text}")
# Flask 路由:/post_tweet
处理提交的推文文本和媒体(图片或视频)。上传媒体后,将发布推文 现在是文字没问题, 图与视频不成功,今天主要在找这儿的问题,还没解决。
@app.route('/post_tweet', methods=['POST'])
def post_tweet():
tweet_text = request.form['tweet_text']
tweet_images = request.files.getlist('tweet_images')
tweet_videos = request.files.getlist('tweet_videos')
media_ids = []
access_token = session.get('access_token')
if not access_token:
return jsonify({'error': 'No access token available. Please log in.'}), 403
try:
for image in tweet_images:
media_id = upload_media_to_twitter(image, 'image')
media_ids.append(media_id)
for video in tweet_videos:
media_id = upload_media_to_twitter(video, 'video')
media_ids.append(media_id)
json_data = {'text': tweet_text}
if media_ids:
json_data['media'] = {'media_ids': media_ids}
url = "https://api.twitter.com/2/tweets"
headers = {
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json'
}
response = requests.post(url, headers=headers, json=json_data)
response.raise_for_status()
return jsonify({'message': 'Tweet posted successfully!'})
except requests.exceptions.RequestException as e:
return jsonify({'error': str(e)}), 500
#启动 Flask 应用,监听 8000 端口
if __name__ == '__main__':
app.secret_key = 'your_secret_key'
app.run(port=8000, debug=True)
config.ini 文件内容
[twitter]
client_id = 看图
client_secret = 看图
bearer_token = %%看图
consumer_key = 看图
consumer_secret = 看图
access_token = 看图
access_token_secret = 看图
redirect_url = http://127.0.0.1:8000/callback
[flask]
secret_key =
获取上面令牌等信息用在你的开发者平台 左边: Projects Apps -> Default project
https://developer.x.com/en/portal
开发主页还有两处设置,关于账号权力与返回数据如图:红框需要相同,(因为是练手,用的本机127.0.0.1 ),蓝线是必须填写的 比如用 自己的域名或仇家的域名。
上面的设置,是要Projects Apps -> Default project 点击 User authentication settings 才有。
index.heml
这个没什么花哨的,就是标准模板上改的。加个fetch 功能,还需要完善 js app 。
结束语:
网上很多是 OAuth 1.0a 的代码, 最开始我也是搬它们的,但总有错误 要不就是 403 401的,都开始用 curl 命令了。 去改修 fetch 只能获得sina
后来不得不去读 官方参考例子,转用 OAuth 2.0 Authorization code with PKCE. 这个更简单。
其它功能在没放弃前,还有可能完善。