mirror of
https://github.com/Xtao-Labs/iShotaBot.git
synced 2024-11-24 09:15:51 +00:00
✨ twitter parse support nsfw
This commit is contained in:
parent
3f25dec676
commit
3015a68b64
128
defs/fix_twitter_api.py
Normal file
128
defs/fix_twitter_api.py
Normal 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
|
0
models/apis/fxtwitter/__init__.py
Normal file
0
models/apis/fxtwitter/__init__.py
Normal file
52
models/apis/fxtwitter/client.py
Normal file
52
models/apis/fxtwitter/client.py
Normal 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()
|
76
models/apis/fxtwitter/model.py
Normal file
76
models/apis/fxtwitter/model.py
Normal 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
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user