diff --git a/libxduauth/sites/ids.py b/libxduauth/sites/ids.py index 59bf7bb..ee1ecf2 100644 --- a/libxduauth/sites/ids.py +++ b/libxduauth/sites/ids.py @@ -1,21 +1,17 @@ import time -from io import BytesIO -from PIL import Image from bs4 import BeautifulSoup from ..AuthSession import AuthSession from ..utils.page import parse_form_hidden_inputs from ..utils.aes import encrypt +from ..utils.vcode import get_solver class IDSSession(AuthSession): cookie_name = 'ids' - def __init__( - self, target, username, password, - *args, **kwargs - ): + def __init__(self, target, username, password): super().__init__(f'{self.cookie_name}_{username}') if self.is_logged_in(): return @@ -25,34 +21,23 @@ def __init__( 'http://ids.xidian.edu.cn/authserver/login', params={'service': target} ).text - is_need_captcha = self.get( + while self.get( 'https://ids.xidian.edu.cn/authserver/checkNeedCaptcha.htl', params={'username': username, '_': str(int(time.time() * 1000))} - ).json()['isNeed'] - if is_need_captcha: - captcha = self.get( - 'https://ids.xidian.edu.cn/authserver/common/openSliderCaptcha.htl', - params={'_': str(int(time.time() * 1000))} - ) - # 返回: { - # 'bigImage': ..., # 背景图(base64) - # 'smallImage': ..., # 滑块图(base64) - # 'tagWidth": 93, # 无用, 恒93 - # 'yHeight': 0 # 无用, 恒0 - # } - img = Image.open(BytesIO(captcha.json()['bigImage'])) - img.show() - # move_len: 背景图左侧到滑块目标位置左侧的宽度 - move_len = input('滑块位移:') - # canvasLength: canvas宽度, 硬编码280 - # moveLength: 按比例缩放后的滑块位移, 有容错 - verify = self.post( + ).json()['isNeed']: + if self.post( 'https://ids.xidian.edu.cn/authserver/common/verifySliderCaptcha.htl', data={ - 'canvasLength': '280', - 'moveLength': str(move_len * 280 // img.width) + # canvasLength: canvas宽度, 硬编码280 + 'canvasLength': '280', + # moveLength: 按比例缩放后的滑块位移, 有容错 + 'moveLength': str(get_solver('ids.xidian.edu.cn')(self.get( + 'https://ids.xidian.edu.cn/authserver/common/openSliderCaptcha.htl', + params={'_': str(int(time.time() * 1000))} + ).json())) } - ) + ).json()['errorMsg'] == 'success': + break # 返回: { # 'errorCode': ..., # 验证通过时为1 # 'errorMsg': ... # 验证通过时为'success' diff --git a/libxduauth/sites/rsbbs.py b/libxduauth/sites/rsbbs.py index 38280e2..b132c9a 100644 --- a/libxduauth/sites/rsbbs.py +++ b/libxduauth/sites/rsbbs.py @@ -5,7 +5,7 @@ from ..AuthSession import AuthSession from ..utils.page import parse_form_hidden_inputs -from ..utils.vcode import _process_vcode +from ..utils.vcode import get_solver class RSBBSSession(AuthSession): @@ -26,13 +26,9 @@ def login(self, username, password): soup = BeautifulSoup(login.text, 'lxml') img = soup.find('img', {'class': 'seccodeimg'}).get('src') - img = _process_vcode(Image.open( - BytesIO(self.get(f'http://{self.HOST}/{img}', headers={ - 'Referer': login.url - }).content) - )) - img.show() - vcode = input('验证码:') + vcode = get_solver('rsbbs.xidian.edu.cn')(self.get(f'http://{self.HOST}/{img}', headers={ + 'Referer': login.url + }).content) page = self.post( f'http://{self.HOST}/' + soup.find('form', id='loginform').get('action'), data=dict( diff --git a/libxduauth/sites/xk.py b/libxduauth/sites/xk.py index 489d614..b9bd74d 100644 --- a/libxduauth/sites/xk.py +++ b/libxduauth/sites/xk.py @@ -1,13 +1,12 @@ import json from base64 import b64encode, b64decode -from io import BytesIO from re import search import requests from Crypto.Cipher import AES from Crypto.Util.Padding import pad -from PIL import Image +from ..utils.vcode import get_solver from ..AuthSession import AuthSession @@ -70,10 +69,7 @@ def login(self, username, password): # show captcha captcha_img = captcha['captcha'][22:] # data:image/png;base64, - Image.open(BytesIO(b64decode(captcha_img))).show() - - # TODO:input function shouldn't exist here - captcha_code = input('验证码:') + captcha_code = get_solver('xk.xidian.edu.cn')(b64decode(captcha_img)) login_resp = self.post(f'{self.BASE}/auth/login', data={ 'loginname': username, diff --git a/libxduauth/utils/cli_viewer.py b/libxduauth/utils/cli_viewer.py new file mode 100644 index 0000000..10e1a60 --- /dev/null +++ b/libxduauth/utils/cli_viewer.py @@ -0,0 +1,28 @@ +from PIL.ImageShow import Viewer, register as register_pil_imshow +from PIL import Image + + +def _image_to_ascii(img, size=(80, 16), invert_pallete=True): + # convert image to ascii art for cli view + # given char dimension 24*16, optimal sizes are + # (80, 16) for xk, (80, 21) for rsbbs + if invert_pallete: + chs = '@&%QWNM0gB$#DR8mHXKAUbGOpV4d9h6Pkqwaxoenut1ivsz/*cr!+<>;=^:\'-.` ' + else: + chs = ' `.-\':^=;><+!rc*/zsvi1tuneoxawqkP6h9d4VpOGbUAKXHm8RD#$Bg0MNWQ%&@' + w, h = size + img.load() + im = img.im.convert('L').resize((w, h)) + return '\n'.join(''.join( + chs[im[i] // 4] for i in range(y, y + w) + ) for y in range(0, w * h, w)) + + +class _CliViewer(Viewer): + def show(self, image: Image.Image, **options: Image.Any) -> int: + print(_image_to_ascii(image)) + # always attempt other viewers + return False + +def register(): + register_pil_imshow(_CliViewer, 0) diff --git a/libxduauth/utils/vcode.py b/libxduauth/utils/vcode.py index 82a1c61..c837034 100644 --- a/libxduauth/utils/vcode.py +++ b/libxduauth/utils/vcode.py @@ -1,35 +1,32 @@ -class Processor: - def __init__(self, img): - self.img = img.convert('L') - self.img_arr = self.img.load() - self.paint() - - DX = [1, 0, -1, 0] - DY = [0, 1, 0, -1] - - def paint(self): - w, h = self.img.size - visited = set() - q = [] - q.append((0, 0, 255)) - while q: - x, y, value = q.pop() - if x < 0 or y < 0 or x >= w or y >= h or \ - (x, y) in visited: - continue - visited.add((x, y)) - for i in range(4): - try: - pixel = self.img_arr[x + self.DX[i], y + self.DY[i]] - except IndexError: - continue - if abs(pixel - self.img_arr[x, y]) > 5: - q.append((x + self.DX[i], y + self.DY[i], 255 - value)) - else: - q.append((x + self.DX[i], y + self.DY[i], value)) - self.img_arr[x, y] = value - - -def _process_vcode(img): - p = Processor(img) - return p.img +from base64 import b64decode +from typing import Callable +from io import BytesIO +from PIL import Image + + +def _default_solver(data): + Image.open(BytesIO(data)).show() + return input('验证码: ') + + +def _ids_solver(data): + # data is { + # 'bigImage': ..., # 背景图(base64) + # 'smallImage': ..., # 滑块图(base64) + # 'tagWidth": 93, # 无用, 恒93 + # 'yHeight': 0 # 无用, 恒0 + # } + img = Image.open(BytesIO(b64decode(data['bigImage']))) + img.show() + + # 输入背景图左侧到滑块目标位置左侧的宽度 + return int(input('滑块位移: ')) * 280 // img.width + + +_solvers = { + 'ids.xidian.edu.cn': _ids_solver, +} + + +def get_solver(key) -> Callable: + return _solvers.get(key, _default_solver) diff --git a/setup.py b/setup.py index 4e2c3df..958d555 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="libxduauth", - version="1.8.2", + version="1.9.0", author="Frank", author_email="frankli0324@hotmail.com", description="login utilities for XDU", @@ -19,7 +19,7 @@ ], python_requires='>=3.7', install_requires=[ - "requests", "bs4", "pycryptodome", + "requests", "bs4", "pycryptodome", "lxml", "importlib-resources", "Pillow", ], )