Skip to content

Commit

Permalink
feat: QQ空间登录相关
Browse files Browse the repository at this point in the history
  • Loading branch information
RockChinQ committed Apr 25, 2024
1 parent d39beaf commit af2228b
Show file tree
Hide file tree
Showing 13 changed files with 361 additions and 15 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ CAMPUX_TOKEN="campux"
CAMPUX_REDIS_ADDR="localhost:6379"
CAMPUX_REDIS_PASSWORD=""
CAMPUX_REDIS_PUBLISH_POST_STREAM="campux_publish_post"
CAMPUX_HELP_MESSAGE="填写未匹配指令时的帮助信息"
CAMPUX_HELP_MESSAGE="填写未匹配指令时的帮助信息"

CAMPUX_QQ_BOT_UIN=12345678
CAMPUX_QQ_ADMIN_UIN=12345678
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
venv
venv
cache.json
2 changes: 0 additions & 2 deletions campux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +0,0 @@
from .imbot import nbmod
from . import api
28 changes: 28 additions & 0 deletions campux/common/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

import os
import sys
import json


class CacheManager:

data: dict

file: str

def __init__(self, file: str="cache.json"):
self.data = {}
self.file = file

if not os.path.exists(file):
with open(file, "w", encoding="utf-8") as f:
json.dump({}, f)

def load(self):
with open(self.file, "r", encoding="utf-8") as f:
self.data = json.load(f)

def save(self):
with open(self.file, "w", encoding="utf-8") as f:
json.dump(self.data, f)
41 changes: 39 additions & 2 deletions campux/core/app.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,64 @@
from __future__ import annotations

import asyncio
import threading

import nonebot, nonebot.config
from nonebot.adapters.onebot.v11 import Adapter as OnebotAdapter # 避免重复命名

from ..api import api
from ..mq import redis
from ..social import mgr as social_mgr
from ..imbot import mgr as imbot_mgr
from ..common import cache as cache_mgr


class Application:

cache: cache_mgr.CacheManager

@property
def cpx_api(self) -> api.CampuxAPI:
return api.campux_api

@property
def config(self) -> nonebot.config.Config:
return nonebot.get_driver().config

mq: redis.RedisStreamMQ

social: social_mgr.SocialPlatformManager

imbot: imbot_mgr.IMBotManager

async def run(self):

def nonebot_thread():
nonebot.run()

threading.Thread(target=nonebot_thread).start()

# while True:
# await asyncio.sleep(5)

async def create_app() -> Application:

# 注册适配器
driver = nonebot.get_driver()
driver.register_adapter(OnebotAdapter)

# 在这里加载插件
nonebot.load_plugin("campux.imbot.nbmod") # 本地插件

# 缓存管理器
cache = cache_mgr.CacheManager()
cache.load()

def create_app() -> Application:
ap = Application()
ap.cache = cache

ap.mq = redis.RedisStreamMQ(ap)
ap.social = social_mgr.SocialPlatformManager(ap)
await ap.social.initialize()
ap.imbot = imbot_mgr.IMBotManager(ap)
return ap
return ap
14 changes: 14 additions & 0 deletions campux/imbot/mgr.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from __future__ import annotations

import nonebot

from ..core import app


Expand All @@ -9,3 +11,15 @@ class IMBotManager:

def __init__(self, ap: app.Application):
self.ap = ap

async def send_private_message(
self,
user_id: int,
message
):
bot = nonebot.get_bot()

await bot.send_private_msg(
user_id=user_id,
message=message
)
21 changes: 21 additions & 0 deletions campux/imbot/nbmod.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
from nonebot.adapters import Event

from ..api import api
from ..core import app


ap: app.Application = None

sign_up = on_command("注册账号", rule=to_me(), priority=10, block=True)
reset_password = on_command("重置密码", rule=to_me(), priority=10, block=True)

relogin_qzone = on_command("更新cookies", rule=to_me(), priority=10, block=True)

any_message = on_regex(r".*", rule=to_me(), priority=100, block=True)

@sign_up.handle()
Expand All @@ -37,6 +42,22 @@ async def reset_password_func(event: Event):
traceback.print_exc()
await reset_password.finish(str(e))

@relogin_qzone.handle()
async def relogin_qzone_func(event: Event):
user_id = int(event.get_user_id())

if user_id != ap.config.campux_qq_admin_uin:
await relogin_qzone.finish("无权限")
return

try:
await ap.social.platform_api.relogin()
except Exception as e:
if isinstance(e, nonebot.exception.FinishedException):
return
traceback.print_exc()
await relogin_qzone.finish(str(e))

@any_message.handle()
async def any_message_func(event: Event):
await any_message.finish(nonebot.get_driver().config.campux_help_message)
30 changes: 30 additions & 0 deletions campux/social/mgr.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
from __future__ import annotations

import asyncio

import nonebot

from ..core import app
from .qzone import api as qzone_api


class SocialPlatformManager:

ap: app.Application

platform_api: qzone_api.QzoneAPI

current_invalid_cookies: dict = None

def __init__(self, ap: app.Application):
self.ap = ap
self.platform_api = qzone_api.QzoneAPI(ap)

async def initialize(self):
async def schedule_loop():
await asyncio.sleep(15)
while True:
asyncio.create_task(self.schedule_task())
await asyncio.sleep(30)

asyncio.create_task(schedule_loop())

async def schedule_task(self):
# 检查cookies是否失效
if not await self.platform_api.token_valid() and self.platform_api.cookies != self.current_invalid_cookies:

await self.ap.imbot.send_private_message(
self.ap.config.campux_qq_admin_uin,
"QQ空间cookies已失效,请发送 #更新cookies 命令进行重新登录。"
)

self.current_invalid_cookies = self.platform_api.cookies

async def publish_post(self, post_id: int):
pass
Empty file added campux/social/qzone/__init__.py
Empty file.
104 changes: 104 additions & 0 deletions campux/social/qzone/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from __future__ import annotations

import asyncio
import json
import traceback

from nonebot import logger
import nonebot.adapters.onebot.v11.message as message
import requests

from ...core import app
from . import login


def generate_gtk(skey) -> str:
"""生成gtk"""
hash_val = 5381
for i in range(len(skey)):
hash_val += (hash_val << 5) + ord(skey[i])
return str(hash_val & 2147483647)

GET_VISITOR_AMOUNT_URL="https://h5.qzone.qq.com/proxy/domain/g.qzone.qq.com/cgi-bin/friendshow/cgi_get_visitor_more?uin={}&mask=7&g_tk={}&page=1&fupdate=1&clear=1"

class QzoneAPI:

ap: app.Application

cookies: dict

gtk2: str

uin: int

def __init__(self, ap: app.Application, cookies_dict: dict={}):
self.ap = ap
self.cookies = cookies_dict
self.gtk2 = ''
self.uin = 0

if 'qzone_cookies' in self.ap.cache.data and not cookies_dict and self.ap.cache.data['qzone_cookies']:
self.cookies = self.ap.cache.data['qzone_cookies']

if 'p_skey' in self.cookies:
self.gtk2 = generate_gtk(self.cookies['p_skey'])

if 'uin' in self.cookies:
self.uin = int(self.cookies['uin'][1:])

async def token_valid(self) -> bool:
try:
today, total = await self.get_visitor_amount()
logger.info("检查cookies有效性结果:{}, {}".format(today, total))
return True
except Exception as e:
traceback.print_exc()
return False

async def relogin(self):
loginmgr = login.QzoneLogin()

async def qrcode_callback(content: bytes):
asyncio.create_task(self.ap.imbot.send_private_message(
self.ap.config.campux_qq_admin_uin,
message=[
message.MessageSegment.text("请使用QQ扫描以下二维码以登录QQ空间:"),
message.MessageSegment.image(content)
]
))

self.cookies = await loginmgr.login_via_qrcode(qrcode_callback)

if 'p_skey' in self.cookies:
self.gtk2 = generate_gtk(self.cookies['p_skey'])

if 'uin' in self.cookies:
self.uin = int(self.cookies['uin'][1:])

asyncio.create_task(self.ap.imbot.send_private_message(
self.ap.config.campux_qq_admin_uin,
"登录流程完成。"
))

self.ap.cache.data['qzone_cookies'] = self.cookies
self.ap.cache.save()

async def get_visitor_amount(self) -> tuple[int, int]:
"""获取空间访客信息
Returns:
tuple[int, int]: 今日访客数, 总访客数
"""
res = requests.get(
url=GET_VISITOR_AMOUNT_URL.format(self.uin, self.gtk2),
cookies=self.cookies,
timeout=10
)
json_text = res.text.replace("_Callback(", '')[:-3]

try:
json_obj = json.loads(json_text)
visit_count = json_obj['data']
return visit_count['todaycount'], visit_count['totalcount']
except Exception as e:
raise e
Loading

0 comments on commit af2228b

Please sign in to comment.