Skip to content

auto_sign_in

IvonWei edited this page May 16, 2022 · 38 revisions

自动签到 包含了 签到,读取收件箱未读消息,统计信息 功能

验证码签到功能需安装baidu-aip

统计信息生成图片需安装pandas matplotlib

dmhy签到需要 baidu-aip fuzzywuzzy python-Levenshtein

aipocr参数获取: https://cloud.baidu.com/doc/OCR/s/dk3iqnq51

    auto_sign_in:
      user-agent: <user-agent>
      max_workers: 4 #线程数
      get_messages: yes #设为no跳过获取未读信息。默认yes
      get_details: yes #设为no跳过获取统计。默认yes
      aipocr:
        app_id: <app_id>
        api_key: <api_key>
        secret_key: <secret_key>
      sites:
        <site_name>: <cookie>

已适配站点

如 ptsites.sites 目录下存在需要的站点 只需配置相应的 主域名: cookie,例:

      sites:
        1ptba: xxxxxxxx

但是也有因cookie过期太快,而改用模拟登陆的站点,如以下站点:

      sites:
        filelist:
          login:
            username: xxxxxxxx
            password: xxxxxxxx
        ourbits:
          # ourbits 在同时配置了cookie 和 login 时,会忽略login 使用cookie签到
          cookie: xxxxxxxx
          login:
            username: xxxxxxxx
            password: xxxxxxxx
            #二次验证, 扫描二维码得到文本 otpauth://totp/*****:****?secret=[secret_key]&issuer=***** (只取[secret_key]部分)
            secret_key: <secret_key>
        skyey2:
          login:
            username: xxxxxxxx
            password: xxxxxxxx
            #二次验证, 扫描二维码得到文本 otpauth://totp/*****:****?secret=[secret_key]&issuer=***** (只取[secret_key]部分)
            secret_key: <secret_key>
        m-team:
          login:
            username: 'xxxxxxxx'
            password: 'xxxxxxxx'
            #二次验证, 扫描二维码得到文本 otpauth://totp/*****:****?secret=[secret_key]&issuer=***** (只取[secret_key]部分)
            secret_key: <secret_key>

dmhy:

      sites:
        dmhy:
          username: <username>
          cookie: 'xxxxxxx'
          # 五个字符以上签到留言
          comment: <comment>
          ocr_config:
            # 重试次数
            retry: 10
            # 最低识别字符数
            char_count: 3
            # 最低匹配分数
            score: 50

gazellegames:

      sites:
        gazellegames:
          cookie: 'xxxxxxx'
          # api key 需要pm 管理申请 (如未申请 api key 请删除该项)
          key: 'xxxxxxx'
          # 用户名 (如未申请 api key 请删除该项)
          name: 'xxxxxxx'

qbittorrent

qbittorrent 信息获取

      sites:
        qbittorrent:
            # 为客户端命名
          - name: <qbittorrent_name>
            host: qbittorrent.example.com
            port: 443
            use_ssl: yes
            username: xxxxxxx
            password: xxxxxxx
          - name: <qbittorrent_name2>
            host: qbittorrent2.example.com
            port: 443
            use_ssl: yes
            username: xxxxxxx
            password: xxxxxxx

签到结果保存在 entry['result'] 属性中

未读信息保存在 entry['messages'] 属性中

生成的 统计信息 保存在 plugins 目录下 details_report.png

适配

import re
from urllib.parse import urljoin

from flexget.utils.soup import get_soup

from ..schema.site_base import SignState, Work, NetworkState
from ..schema.unit3d import Unit3D
from ..utils.net_utils import NetUtils

"""
类名必须为 MainClass, 站点名用文件名区别,继承于 ptsites.schema.site_base
NexusPHP子类: 签到页面: AttendanceHR, 答题: BakatestHR, 访问: VisitHR, 无HR版本:Attendance, Bakatest, Visit
"""
class MainClass(Unit3D):
    URL = 'https://pt.abc.com'
    """
    用户等级
    name: uploaded, downloaded, share_ratio, points, days
    需保证所有数组长度一致, 即使数值相同
    value: [(保号), 毕业]
    uploaded, downloaded 单位 byte
    """
    USER_CLASSES = {
        'uploaded': [10000000, 10000000], # 可选
        'downloaded': [1099511627776, 16492674416640], # 可选
        'share_ratio': [3.05, 4.55], # 可选
        'points': [100, 200] # 可选
        'days': [280, 700] # 可选
    }

    """
    构造flexget auto_sign_in 配置
    """
    @classmethod
    def build_sign_in_schema(cls):
        return {
            cls.get_module_name(): {
                'type': 'object',
                'properties': {
                    'cookie': {'type': 'string'},
                    'login': {
                        'type': 'object',
                        'properties': {
                            'username': {'type': 'string'},
                            'password': {'type': 'string'}
                        },
                        'additionalProperties': False
                    }
                },
                'additionalProperties': False
            }
        }

    """
    构造flexget iyuu 配置
    """
    @classmethod
    def build_reseed_schema(cls):
        return {
            cls.get_module_name(): {
                'type': 'object',
                'properties': {
                    'rsskey': {'type': 'string'}
                },
                'additionalProperties': False
            }
        }

    """
    构造辅种地址
    """
    @classmethod
    def build_reseed(cls, entry, config, site, passkey, torrent_id):
        download_page = site['download_page'].format(torrent_id=torrent_id, rsskey=passkey['rsskey'])
        entry['url'] = urljoin(MainClass.URL, download_page)

    """
    不存在cookie时的登录流程附加到正常签到之前
    """
    def build_login_workflow(self, entry, config):
        return [
            Work(
                url='/login',
                method='get',
                check_state=('network', NetworkState.SUCCEED),
            ),
            Work(
                url='/login',
                # 调用的方法 sign_in_by_{method}
                method='password',
                check_state=('network', NetworkState.SUCCEED),
                response_urls=['', '/pages/1'],
                token_regex=r'(?<=name="_token" value=").+?(?=")',
                captcha_regex=r'(?<=name="_captcha" value=").+?(?=")',
            )
        ]

    """
    签到流程
    """
    def build_workflow(self, entry, config):
        return [
            Work(
                # 请求的url
                url='/',
                # 调用的方法 sign_in_by_{method}
                method='get',
                # 成功判断正则
                succeed_regex=('<a class="top-nav__username" href="https://pt.hdpost.top/users/(.*?)">', 1),
                '''
                判断请求执行的状况(网络错误会导致entry直接失败)
                network: 网络
                sign_in: 使用 succeed_regex 对响应做正则判断
                final: 调用sign_in进行最终确认
                sign_in 和 final 的区别在于 sign_in 在正则判断失败时 返回 SignState.NO_SIGN_IN,而 final 返回 SignState.SIGN_IN_FAILED
                '''
                check_state=('final', SignState.SUCCEED),
                is_base_content=True,
                # 跳转地址在在集合中时不会失败
                response_urls=['', '/']
            )
        ]

    '''
    自定义方法
    每个 sign_in_by_{method} 方法都会接收4个参数
    entry: entry
    config: auto_sign_in 的总配置, 当前站点配置存在 entry['site_config'] 下
    work: 当前调用的work
    last_content: 上次请求返回的结果

    返回请求的 response,供 check_state 检查
    '''
    def sign_in_by_password(self, entry, config, work, last_content):
        login = entry['site_config'].get('login')
        if not login:
            entry.fail_with_prefix('Login data not found!')
            return
        login_page = get_soup(last_content)
        hidden_input = login_page.select_one('#formContent > form > input[type=hidden]:nth-child(7)')
        name = hidden_input.attrs['name']
        value = hidden_input.attrs['value']
        data = {
            '_token': re.search(work.token_regex, last_content).group(),
            'username': login['username'],
            'password': login['password'],
            'remember': 'on',
            '_captcha': re.search(work.captcha_regex, last_content).group(),
            '_username': '',
            name: value,
        }
        login_response = self._request(entry, 'post', work.url, data=data)
        login_network_state = self.check_network_state(entry, work, login_response)
        if login_network_state != NetworkState.SUCCEED:
            return
        return login_response

    '''
    构造统计的选择器
    '''
    def build_selector(self):
        selector = super(MainClass, self).build_selector()
        NetUtils.dict_merge(selector, {
            # 选择用户id
            'user_id': '/users/(.*?)/',
            # 数据源
            'detail_sources': {
                'default': {
                    # 不提取text,false时只会保留element的text
                    'do_not_strip': True,
                    'elements': {
                        # css选择器
                        'bar': 'ul.top-nav__ratio-bar',
                        'header': '.header',
                        'data_table': '.user-info'
                    }
                }
            },
            'details': {
                'uploaded': {
                    # 可使用元组指定提取的分组序号,使用str时,默认提取第一个
                    'regex': '上传.+?([\\d.]+ ?[ZEPTGMK]?iB)'
                },
                'downloaded': {
                    'regex': '下载.+?([\\d.]+ ?[ZEPTGMK]?iB)'
                },
                'share_ratio': {
                    'regex': '分享率.+?([\\d.]+)',
                    # 指定字符串的处理方法,以返回正确的数字或者日期格式
                    'handle': self.handle_share_ratio
                },
                'points': {
                    'regex': '魔力.+?(\\d[\\d,. ]*)',
                    'handle': self.handle_points
                },
                'join_date': {
                    'regex': '注册日期 (.*?\\d{4})',
                    'handle': self.handle_join_date
                },
                'seeding': {
                    'regex': '做种.+?(\\d+)'
                },
                'leeching': {
                    'regex': '吸血.+?(\\d+)'
                },
                'hr': {
                    'regex': '有效.+?(\\d+)'
                }
            }
        })
        return selector

    def handle_share_ratio(self, value):
        if value in ['.']:
            return '0'
        else:
            return value
Clone this wiki locally