twitter parse support nsfw

This commit is contained in:
xtaodada 2023-09-10 12:49:11 +08:00
parent 3f25dec676
commit 3015a68b64
Signed by: xtaodada
GPG Key ID: 4CBB3F4FA8C85659
5 changed files with 280 additions and 20 deletions

128
defs/fix_twitter_api.py Normal file
View File

@ -0,0 +1,128 @@
from typing import Optional, List
from pyrogram.enums import ParseMode
from pyrogram.types import (
InlineKeyboardMarkup,
InlineKeyboardButton,
InputMediaPhoto,
InputMediaVideo,
InputMediaAnimation,
)
from init import logs
from models.apis.fxtwitter.client import fix_twitter_client, FixTwitterError
from models.apis.fxtwitter.model import User, FixTweet, FixTweetMedia
def twitter_link(tweet: FixTweet):
origin = tweet.retweet_or_quoted
button = [
[
InlineKeyboardButton(
text="Source",
url=tweet.url,
),
InlineKeyboardButton(text="Author", url=tweet.author.url),
]
]
if origin:
button[0].insert(1, InlineKeyboardButton(text="RSource", url=origin.url))
return InlineKeyboardMarkup(button)
def twitter_user_link(user: User):
return InlineKeyboardMarkup([[InlineKeyboardButton(text="Author", url=user.url)]])
def twitter_medias(tweet: FixTweet):
tweet_media_lists = []
if tweet.medias:
tweet_media_lists.extend(tweet.medias)
if tweet.retweet_or_quoted:
tweet_media_lists.extend(tweet.retweet_or_quoted.medias)
return tweet_media_lists
def twitter_media(tweet_media_lists: List[FixTweetMedia], text: str):
media_lists = []
for idx, media in enumerate(tweet_media_lists):
if len(media_lists) > 10:
break
if media.type == "photo":
media_lists.append(
InputMediaPhoto(
media.media_url,
caption=text if idx == 0 else None,
parse_mode=ParseMode.HTML,
)
)
elif media.type == "gif":
media_lists.append(
InputMediaAnimation(
media.media_url,
caption=text if idx == 0 else None,
parse_mode=ParseMode.HTML,
)
)
else:
media_lists.append(
InputMediaVideo(
media.media_url,
caption=text if idx == 0 else None,
parse_mode=ParseMode.HTML,
)
)
return media_lists
def get_twitter_user(user: User):
user_name = user.name
user_username = user.screen_name
text = (
f"<b>Twitter User Info</b>\n\n"
f"Name: <code>{user_name}</code>\n"
f'Username: <a href="https://twitter.com/{user_username}">@{user_username}</a>\n'
f"Bio: <code>{user.description}</code>\n"
f"Joined: <code>{user.created.strftime('%Y-%m-%d %H:%M:%S')}</code>\n"
f"📤 {user.tweets} ❤️{user.likes} "
f"粉丝 {user.followers} 关注 {user.following}"
)
return text
def get_twitter_status(tweet: FixTweet):
text = tweet.text or "暂 无 内 容"
text = f"<code>{text}</code>"
final_text = "<b>Twitter Status Info</b>\n\n" f"{text}\n\n"
if tweet.retweet_or_quoted:
roq = tweet.retweet_or_quoted
final_text += (
f'<code>RT: {roq.text or "暂 无 内 容"}</code>\n\n'
f'{roq.author.one_line} 发表于 {roq.created.strftime("%Y-%m-%d %H:%M:%S")}'
f"\n👁 {roq.views} 👍 {roq.likes} 🔁 {roq.retweets}\n"
f'{tweet.author.one_line} 转于 {tweet.created.strftime("%Y-%m-%d %H:%M:%S")}\n'
f"👁 {tweet.views} 👍 {tweet.likes} 🔁 {tweet.retweets}"
)
else:
final_text += (
f'{tweet.author.one_line} 发表于 {tweet.created.strftime("%Y-%m-%d %H:%M:%S")}'
f"\n👁 {tweet.views} 👍 {tweet.likes} 🔁 {tweet.retweets}"
)
return final_text
async def fetch_tweet(tweet_id: int) -> Optional[FixTweet]:
try:
return await fix_twitter_client.tweet_detail(tweet_id)
except FixTwitterError as e:
logs.error(f"Twitter Error: {e}")
return None
async def fetch_user(username: str) -> Optional[User]:
try:
user = await fix_twitter_client.user_by_screen_name(username)
except FixTwitterError as e:
logs.error(f"Twitter Error: {e}")
return None
return user

View File

View File

@ -0,0 +1,52 @@
from typing import Dict
from httpx import AsyncClient
from .model import FixTweet, FixTweetMedia, User
class FixTwitterError(Exception):
def __init__(self, status_code: int, message: str):
self.status_code = status_code
self.message = message
class FixTwitterClient:
def __init__(self):
self.client = AsyncClient(timeout=60.0)
self.url = "https://api.fxtwitter.com/"
@staticmethod
def gen_tweet(data: Dict) -> FixTweet:
tweet = FixTweet(**data)
if medias := data.get("media"):
if all_media := medias.get("all"):
tweet.medias = [FixTweetMedia(**i) for i in all_media]
if quote := data.get("quote"):
tweet.quoted = FixTwitterClient.gen_tweet(quote)
if retweet := data.get("retweet"):
tweet.retweeted = FixTwitterClient.gen_tweet(retweet)
return tweet
async def tweet_detail(self, tid: int) -> FixTweet:
url = f"{self.url}X/status/{tid}"
response = await self.client.get(url)
if response.status_code != 200:
raise FixTwitterError(response.status_code, response.text)
data = response.json()
if data.get("code", 200) != 200:
raise FixTwitterError(data.get("code"), data.get("message"))
return self.gen_tweet(data["tweet"])
async def user_by_screen_name(self, username: str) -> User:
url = f"{self.url}{username}"
response = await self.client.get(url)
if response.status_code != 200:
raise FixTwitterError(response.status_code, response.text)
data = response.json()
if data.get("code", 200) != 200:
raise FixTwitterError(data.get("code"), data.get("message"))
return User(**data["user"])
fix_twitter_client = FixTwitterClient()

View File

@ -0,0 +1,76 @@
from datetime import datetime, timedelta
from typing import List, Optional
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
screen_name: str
avatar_url: str = ""
banner_url: str = ""
description: str = ""
location: str = ""
url: str
followers: int = 0
following: int = 0
joined: str
tweets: int = 0
likes: int = 0
@property
def created(self) -> datetime:
"""入推时间"""
# 'Fri Jun 30 12:08:56 +0000 2023'
return datetime.strptime(self.joined, "%a %b %d %H:%M:%S %z %Y") + timedelta(
hours=8
)
@property
def icon(self) -> str:
"""头像"""
return self.avatar_url.replace("_normal.jpg", ".jpg")
@property
def one_line(self) -> str:
return f'<a href="{self.url}">{self.screen_name}</a>'
class FixTweetMedia(BaseModel):
type: str
url: str
width: int = 0
height: int = 0
altText: str = ""
class FixTweet(BaseModel):
url: str
id: int
text: str
replies: int
""" 回复 """
retweets: int
""" 转推 """
likes: int
""" 喜欢 """
created_at: str
views: int
""" 阅读次数 """
source: str
medias: Optional[List[FixTweetMedia]] = None
author: User
retweeted: "FixTweet" = None
quoted: "FixTweet" = None
@property
def created(self) -> datetime:
# 'Fri Jun 30 12:08:56 +0000 2023'
return datetime.strptime(
self.created_at, "%a %b %d %H:%M:%S %z %Y"
) + timedelta(hours=8)
@property
def retweet_or_quoted(self) -> "FixTweet":
return self.retweeted or self.quoted

View File

@ -4,7 +4,7 @@ from pyrogram import Client, filters, ContinuePropagation
from pyrogram.enums import MessageEntityType from pyrogram.enums import MessageEntityType
from pyrogram.types import Message from pyrogram.types import Message
from defs.twitter_api import ( from defs.fix_twitter_api import (
fetch_tweet, fetch_tweet,
get_twitter_status, get_twitter_status,
twitter_link, twitter_link,
@ -85,6 +85,28 @@ async def process_user(message: Message, username: str):
) )
async def process_url(url: str, message: Message):
url = urlparse(url)
if url.hostname and url.hostname in ["twitter.com", "vxtwitter.com"]:
if url.path.find("status") >= 0:
status_id = str(
url.path[url.path.find("status") + 7 :].split("/")[0]
).split("?")[0]
try:
await process_status(message, status_id)
except Exception as e:
print(e)
elif url.path == "/":
return
else:
# 解析用户
uid = url.path.replace("/", "")
try:
await process_user(message, uid)
except Exception as e:
print(e)
@bot.on_message(filters.incoming & filters.text & filters.regex(r"twitter.com/")) @bot.on_message(filters.incoming & filters.text & filters.regex(r"twitter.com/"))
async def twitter_share(_: Client, message: Message): async def twitter_share(_: Client, message: Message):
if not message.text: if not message.text:
@ -97,23 +119,5 @@ async def twitter_share(_: Client, message: Message):
url = entity.url url = entity.url
else: else:
continue continue
url = urlparse(url) await process_url(url, message)
if url.hostname and url.hostname in ["twitter.com", "vxtwitter.com"]:
if url.path.find("status") >= 0:
status_id = str(
url.path[url.path.find("status") + 7 :].split("/")[0]
).split("?")[0]
try:
await process_status(message, status_id)
except Exception as e:
print(e)
elif url.path == "/":
return
else:
# 解析用户
uid = url.path.replace("/", "")
try:
await process_user(message, uid)
except Exception as e:
print(e)
raise ContinuePropagation raise ContinuePropagation