Exemplo n.º 1
0
async def _checkConnectClient(
    runId: int, tgSession: TgSession, phoneNumber: str
) -> typing.Tuple[typing.Union[None, dict], typing.Union[None,
                                                         TelegramClient]]:
    client = TelegramClient(tgSession.getSessionPath(phoneNumber, noExt=True),
                            novice.py_env['apiId'], novice.py_env['apiHash'])

    try:
        novice.logNeedle.push('(runId: {}) client.connect'.format(runId))
        await client.connect()
    except telethon.errors.PhoneNumberBannedError as err:
        errMsg = 'The phone {} is Banned.'.format(phoneNumber)
        novice.logNeedle.push('(runId: {}) PhoneNumberBannedError: {}'.format(
            runId, errMsg))
        _mvSessionPath(sessionPath, phoneNumber,
                       'telethon-banned-' + novice.py_env['apiId'])
        return ({
            'code': -3,
            'message': errMsg,
            'phoneNumber': phoneNumber
        }, None)
    except Exception as err:
        errMsg = novice.sysTracebackException()
        novice.logNeedle.push('(runId: {}) {}'.format(runId, errMsg))
        _mvSessionPath(sessionPath, phoneNumber,
                       'telethon-rm-' + novice.py_env['apiId'])
        return ({
            'code': -3,
            'message': errMsg,
            'phoneNumber': phoneNumber
        }, None)

    return (None, client)
Exemplo n.º 2
0
async def _paperSlipAction_send(
        pageId: str,
        code: int,
        message: str,
        ynError = False) -> None:
    payload = {
        'type': 'adTool.paperSlipAction',
        'code': code,
        'message': message,
    }
    if ynError:
        errInfo = novice.sysExceptionInfo()
        errMsg = novice.sysTracebackException()
        payload['message'] += '\n{}'.format(errMsg)
        payload['error'] = {
            'name': errInfo['name'],
            'message': errInfo['message'],
            'stackList': errInfo['stackList'],
        }
    await serverMix.wsHouse.send(
        pageId,
        json.dumps([payload])
    )
Exemplo n.º 3
0
async def asyncRun(args: list, _dirpy: str, _dirname: str):
    forwardPeersTxt = args[1]
    url = args[2]
    msg = args[3]

    mainGroup = novice.py_env['peers']['adChannle']
    forwardPeers = forwardPeersTxt.split(',')

    # 用於打印日誌
    runId = random.randrange(1000000, 9999999)
    usedClientCount = 3
    latestStatus = ''
    try:
        latestStatus = '炸群進度: 初始化...'
        novice.logNeedle.push('(runId: {}) {}'.format(runId, latestStatus))
        tgTool = TgDefaultInit(TgBaseTool,
                               clientCount=3,
                               papaPhone=novice.py_env['papaPhoneNumber'])
        await tgTool.init()
    except Exception as err:
        latestStatus += ' (失敗)'
        novice.logNeedle.push('(runId: {}) {}'.format(runId, latestStatus))
        raise err

    try:
        latestStatus = '炸群進度: 上傳圖片...'
        novice.logNeedle.push('(runId: {}) {}'.format(runId, latestStatus))
        messageId = await _sendFile(tgTool, mainGroup, url, msg)

        if messageId == -1:
            raise Exception(
                'Use Papa send file fail. (url: {}, msg: {})'.format(url, msg))
    except Exception as err:
        latestStatus += ' (失敗)'
        novice.logNeedle.push('(runId: {}) {}'.format(runId, latestStatus))
        raise err

    try:
        finalPeers = _filterGuy(tgTool, forwardPeers)
        finalPeersLength = len(finalPeers)
        bandNiUserList = []
        idx = 0
        async for clientInfo in tgTool.iterPickClient(-1, 1,
                                                      whichNiUsers=True):
            readableIdx = idx + 1
            myId = clientInfo['id']
            client = clientInfo['client']

            if novice.indexOf(bandNiUserList, myId) != -1:
                if len(bandNiUserList) == usedClientCount:
                    break
                continue

            if finalPeersLength <= idx:
                break

            latestStatus = '炸群進度: {}/{}'.format(readableIdx, finalPeersLength)
            novice.logNeedle.push('(runId: {}) ok: {}/{}'.format(
                runId, readableIdx, finalPeersLength))
            try:
                forwardPeer = finalPeers[idx]
                await tgTool.joinGroup(client, forwardPeer)
                await client(
                    telethon.functions.messages.ForwardMessagesRequest(
                        from_peer=mainGroup,
                        id=[messageId],
                        to_peer=forwardPeer,
                        random_id=[tgTool.getRandId()]))

                idx += 1
            except telethon.errors.ChannelsTooMuchError as err:
                print(novice.sysTracebackException(ysHasTimestamp=True))
                # 已加入了太多的渠道/超級群組。
                novice.logNeedle.push(
                    '(runId: {}) {} get ChannelsTooMuchError: wait 30 day.'.
                    format(runId, myId))
                maturityDate = novice.dateNowAfter(days=30)
                tgTool.chanDataNiUsers.pushBandData(myId, maturityDate)
                bandNiUserList.append(myId)
            except telethon.errors.FloodWaitError as err:
                print(novice.sysTracebackException(ysHasTimestamp=True))
                waitTimeSec = err.seconds
                novice.logNeedle.push(
                    '(runId: {}) {} get FloodWaitError: wait {} seconds.'.
                    format(runId, myId, waitTimeSec))
                # TODO 秒數待驗證
                if waitTimeSec < 180:
                    await asyncio.sleep(waitTimeSec)
                else:
                    maturityDate = novice.dateNowAfter(seconds=waitTimeSec)
                    tgTool.chanDataNiUsers.pushBandData(myId, maturityDate)
                    bandNiUserList.append(myId)
            except telethon.errors.PeerFloodError as err:
                print(novice.sysTracebackException(ysHasTimestamp=True))
                # 限制發送請求 Too many requests
                novice.logNeedle.push(
                    '(runId: {}) {} get PeerFloodError: wait 1 hour.'.format(
                        runId, myId))
                # TODO 12 小時只是估計值
                maturityDate = novice.dateNowAfter(hours=12)
                tgTool.chanDataNiUsers.pushBandData(myId, maturityDate)
                bandNiUserList.append(myId)
            except Exception as err:
                print(novice.sysTracebackException(ysHasTimestamp=True))
                errType = type(err)
                novice.logNeedle.push(
                    '(runId: {}) {} get {} Error: {} (target group: {})'.
                    format(runId, myId, errType, err, forwardPeer))
                if novice.indexOf(_invalidMessageErrorTypeList, errType) != -1:
                    novice.logNeedle.push(
                        'Invalid Message Error({}): {}'.format(errType, err))
                    break
                elif novice.indexOf(_invalidPeerErrorTypeList, errType) != -1:
                    novice.logNeedle.push('Invalid Peer Error({}): {}'.format(
                        errType, err))
                    idx += 1
                elif novice.indexOf(_knownErrorTypeList, errType) != -1:
                    novice.logNeedle.push('Known Error({}): {}'.format(
                        errType, err))
                    idx += 1
                    bandNiUserList.append(myId)
                else:
                    novice.logNeedle.push('Unknown Error({}): {}'.format(
                        type(err), err))
                    idx += 1
                    bandNiUserList.append(myId)

        latestStatus += ' ({})'.format('仿用戶用盡' if len(bandNiUserList) ==
                                       usedClientCount else '結束')
        novice.logNeedle.push('(runId: {}) {}'.format(runId, latestStatus))
    except Exception as err:
        latestStatus += ' (失敗)'
        novice.logNeedle.push('(runId: {}) {}'.format(runId, latestStatus))
        raise err
Exemplo n.º 4
0
async def asyncRun(args: list, _dirpy: str, _dirname: str):
    if len(args) < 5:
        raise ValueError(
            'Usage: <phoneNumber> <loopTimes> <toGroupPeer> <forwardLink>')

    phoneNumber = args[1]
    loopTimes = int(args[2])
    toGroupPeer = args[3]
    forwardLink = args[4]

    regexForwardLink = r'^https:\/\/t\.me\/([^\/]+)\/(\d+)$'
    matchForwardLink = re.search(regexForwardLink, forwardLink)
    if not matchForwardLink:
        raise ValueError('轉傳來源鏈結 "{}" 不如預期'.format(forwardLink))
    forwardGroup = matchForwardLink.group(1)
    forwardMessageId = int(matchForwardLink.group(2))

    tgTool = TgDefaultInit(TgSimple)

    print('-> 登入用戶')
    client = await tgTool.login(phoneNumber)
    myInfo = await client.get_me()
    print('--> I\m {} {} ({}) and my phone is +{}.'.format(
        str(myInfo.first_name),
        str(myInfo.last_name),
        str(myInfo.username),
        myInfo.phone,
    ))

    print('-> 加入聊天室')
    inputPeer = await client.get_entity(toGroupPeer)
    if type(inputPeer) != telethon.types.User:
        print('--> telethon.functions.channels.JoinChannelRequest')
        await client(
            telethon.functions.channels.JoinChannelRequest(channel=toGroupPeer)
        )

    print('-> 紫爆轉傳 Hi')
    async for idx in tgTool.iterLoopInterval(loopTimes, 1):
        try:
            readableIdx = idx + 1
            print('--> {}/{}: {} -> {}'.format(readableIdx, loopTimes,
                                               phoneNumber, toGroupPeer))

            print('---> telethon.functions.messages.SendMessageRequest')
            await client(
                telethon.functions.messages.ForwardMessagesRequest(
                    from_peer=forwardGroup,
                    id=[forwardMessageId],
                    to_peer=toGroupPeer,
                    random_id=[tgTool.getRandId()]))
        except telethon.errors.FloodWaitError as err:
            print(novice.sysTracebackException(ysHasTimestamp=True))
            waitTimeSec = err.seconds
            print('FloodWaitError: wait {} seconds. {}'.format(
                waitTimeSec, err))
            break
        except telethon.errors.PeerFloodError as err:
            # 限制發送請求 Too many requests
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('PeerFloodError: {}'.format(err))
            break
        except telethon.errors.UserIsBlockedError as err:
            # User is blocked
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('UserIsBlockedError: {}'.format(err))
            break
        except Exception as err:
            print(novice.sysTracebackException(ysHasTimestamp=True))
            errType = type(err)
            if novice.indexOf(_invalidMessageErrorTypeList, errType) == -1:
                print('Invalid Error({}): {}'.format(errType, err))
            elif novice.indexOf(_knownErrorTypeList, errType) == -1:
                print('Known Error({}): {}'.format(errType, err))
                break
            else:
                print('Unknown Error({}): {}'.format(type(err), err))
                break
Exemplo n.º 5
0
async def asyncRun(args: list, _dirpy: str, _dirname: str):
    if len(args) < 4:
        raise ValueError('Usage: <phoneNumber> <loopTimes> <toGroupPeer>')

    phoneNumber = args[1]
    loopTimes = int(args[2])
    toGroupPeer = args[3]

    tgTool = TgDefaultInit(TgSimple)

    print('-> 登入用戶')
    client = await tgTool.login(phoneNumber)
    myInfo = await client.get_me()
    print('--> I\m {} {} ({}) and my phone is +{}.'.format(
        str(myInfo.first_name),
        str(myInfo.last_name),
        str(myInfo.username),
        myInfo.phone,
    ))

    print('-> 加入聊天室')
    # TODO
    # `client.get_entity` 是一項昂貴的操作
    #   https://docs.telethon.dev/en/latest/modules/client.html#telethon.client.users.UserMethods.get_entity
    #   使用 `client.get_entity` 來解析用戶名是一項昂貴的操作,
    #   若在短時間內請求 50 個用戶名則會引發 "FloodWaitError" 的錯誤。
    # 在實際測試中,在短時間內請求相同的群組名也僅有 200 次的許可。 (2020.02.19 紀錄)
    inputPeer = await client.get_entity(toGroupPeer)
    if type(inputPeer) != telethon.types.User:
        print('--> telethon.functions.channels.JoinChannelRequest')
        await client(
            telethon.functions.channels.JoinChannelRequest(channel=toGroupPeer)
        )

    print('-> 紫爆 Hi')
    pushErrMsg = ''
    async for idx in tgTool.iterLoopInterval(loopTimes, 1):
        try:
            readableIdx = idx + 1
            print('--> {}/{}: {} -> {}'.format(readableIdx, loopTimes,
                                               phoneNumber, toGroupPeer))

            print('---> telethon.functions.messages.SendMessageRequest')
            # 在 1970 次請求後需等待 1324 秒 (2020.02.23 紀錄)
            await client(
                telethon.functions.messages.SendMessageRequest(
                    peer=toGroupPeer,
                    message='PurplePink {}{}'.format(
                        readableIdx,
                        '\n' + pushErrMsg if pushErrMsg != '' else ''),
                    random_id=tgTool.getRandId()))
            pushErrMsg = ''
        except ValueError as err:
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('ValueError(沒有此用戶或群組名稱): {}'.format(err))
            break
        except telethon.errors.MessageIdInvalidError as err:
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('MessageIdInvalidError: {}'.format(err))
            raise err
        except telethon.errors.FloodWaitError as err:
            print(novice.sysTracebackException(ysHasTimestamp=True))
            waitTimeSec = err.seconds
            # TODO 印象中有短秒數的 FloodWaitError 錯誤
            print('FloodWaitError: {}'.format(err))
            if waitTimeSec < 720:
                print('---> FloodWaitError: wait {} seconds.'.format(
                    waitTimeSec))
                await asyncio.sleep(waitTimeSec)
                pushErrMsg = 'FloodWaitError: wait {} seconds.'
            else:
                break
        except telethon.errors.PeerFloodError as err:
            # 限制發送請求 Too many requests
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('PeerFloodError: {}'.format(err))
            break
        except telethon.errors.ChatWriteForbiddenError as err:
            # You can't write in this chat
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('ChatWriteForbiddenError: {}'.format(err))
            break
        except Exception as err:
            print(novice.sysTracebackException(ysHasTimestamp=True))
            print('{} Error: {} '.format(type(err), err))
            break
Exemplo n.º 6
0
async def _niGraph(pageId: str, receiveDatasTxt: str) -> None:
    try:
        receiveDatas = json.loads(receiveDatasTxt)

        # 相當於請求錯誤
        if type(receiveDatas) != list:
            return

        resultDatas = []
        for item in receiveDatas:
            resultData = {'type': ''}

            try:
                if not 'type' in item:
                    raise KeyError('wschan: 項目缺少必要的 "type" 成員')

                requestMethod = item['type']
                resultData['type'] = requestMethod
                if type(requestMethod) != str:
                    raise TypeError('wschan: 項目 "type" 成員的類型應為字串')

                matchWsMethod = re.search(_regexWsMethod, requestMethod)
                if matchWsMethod == None:
                    raise ValueError('wschan: 項目 "type" 成員表示式格式錯誤')
                fileName = matchWsMethod.group(1)
                methodName = matchWsMethod.group(2)

                if novice.indexOf(_wsChannelList, fileName) == -1:
                    raise KeyError('wschan: 要求的方法文件不存在')

                pyImportPath = _wsChannelDirPyImportPath + '.' + fileName
                # TODO 不知為 `importlib.import_module()` 何會拋出以下訊息
                # Executing <Task pending name='Task-21' coro=<ASGIWebsocketConnection.handle_websocket() running at /home/bwaycer/ys/gitman/crepo/tgNiren.py/.venv/lib/python3.8/site-packages/quart/asgi.py:147> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f72d22eb250>()] created at /usr/lib/python3.8/asyncio/base_events.py:422> cb=[_wait.<locals>._on_completion() at /usr/lib/python3.8/asyncio/tasks.py:507] created at /home/bwaycer/ys/gitman/crepo/tgNiren.py/.venv/lib/python3.8/site-packages/quart/asgi.py:110> took 0.189 seconds
                module = importlib.import_module(pyImportPath)
                if not hasattr(module, methodName):
                    raise KeyError('wschan: 要求的方法不存在')

                action = getattr(importlib.import_module(pyImportPath),
                                 methodName)
                if 'prop' in item:
                    result = action(pageId, item['prop'])
                else:
                    result = action(pageId)
                if asyncio.iscoroutine(result):
                    result = await result
                # 測試是否可以編譯為 JSON (若其中包含 Python 的類型則會失敗)
                json.dumps(result)

                for key in result:
                    resultData[key] = result[key]
            except Exception:
                errInfo = novice.sysExceptionInfo()
                resultData['error'] = {
                    'name': errInfo['name'],
                    'message': errInfo['message'],
                }
                novice.logNeedle.push('Catch error in ws.py\n'
                                      '  resultData:\n{}\n'
                                      '  errMsg:\n{}'.format(
                                          resultData,
                                          novice.sysTracebackException()))

            if resultData['type'] != '':
                resultDatas.append(resultData)

        await quart.websocket.send(json.dumps(resultDatas))
    except Exception as err:
        raise err
Exemplo n.º 7
0
async def sendCode(pageId: str, prop: typing.Any = None) -> dict:
    innerSession = serverMix.innerSession.get(pageId)

    if 'interactiveLogin' in innerSession:
        info = innerSession['interactiveLogin']
    else:
        info = innerSession['interactiveLogin'] = {
            'runId': random.randrange(1000000, 9999999),
            'phoneNumber': '',
            'sessionPath': '',
            'sentCode': None,
            'client': None,
        }

    if type(prop) != dict:
        return {'code': -1, 'message': '"prop" 參數必須是 `Object` 類型。'}
    if not ('phoneNumber' in prop and type(prop['phoneNumber']) == str):
        return {'code': -1, 'message': '"prop.phoneNumber" 參數不符合預期'}

    runId = info['runId']

    phoneNumber = prop['phoneNumber']
    novice.logNeedle.push('(runId: {}) login sendCode +{}'.format(
        runId, phoneNumber))

    # 檢查是否該仿用戶已登入
    novice.logNeedle.push(
        '(runId: {}) test with common session path'.format(runId))
    tgSession = TgSession('telethon-' + novice.py_env['apiId'])
    sessionPath = tgSession.getSessionPath(phoneNumber)
    if os.path.exists(sessionPath):
        errInfo, client = await _checkConnectClient(runId, tgSession,
                                                    phoneNumber)
        if client != None and await client.is_user_authorized():
            await _disconnectClient(info, client)
            message = '+{} 仿用戶已登入'.format(phoneNumber)
            novice.logNeedle.push('(runId: {}) {}'.format(message))
            return {'code': 1, 'message': message, 'phoneNumber': phoneNumber}

    # 使用暫時的 session 路徑進行登入
    novice.logNeedle.push(
        '(runId: {}) login with temporary session path'.format(runId))
    tgSession = TgSession('telethon-tmpLogin-' + novice.py_env['apiId'])
    errInfo, client = await _checkConnectClient(runId, tgSession, phoneNumber)
    if errInfo != None:
        return errInfo

    info['phoneNumber'] = phoneNumber
    info['sessionPath'] = tgSession.getSessionPath(phoneNumber)
    info['client'] = client

    # 如果已登入的狀態不該存在 "tmpLogin" 前綴的路徑下
    # 所以無需測試 `client.is_user_authorized()` 方法
    try:
        # https://docs.telethon.dev/en/latest/modules/client.html#telethon.client.auth.AuthMethods.send_code_request
        novice.logNeedle.push(
            '(runId: {}) client.send_code_request'.format(runId))
        sentCode = await client.send_code_request(phoneNumber)
        phoneCodeHash = sentCode.phone_code_hash
        novice.logNeedle.push('(runId: {}) sentCode:\n'
                              '  type: {}\n'
                              '  phone_code_hash: {}\n'
                              '  next_type: {}\n'
                              '  timeout: {}'.format(runId, sentCode.type,
                                                     phoneCodeHash,
                                                     sentCode.next_type,
                                                     sentCode.timeout))
        info['sentCode'] = sentCode
    except Exception as err:
        typeName = err.__class__.__name__
        errMsg = _sendCodeKnownErrorTypeInfo[typeName] \
            if typeName in _sendCodeKnownErrorTypeInfo \
            else novice.sysTracebackException()
        novice.logNeedle.push('(runId: {}) sentCode +{} Failed {}'.format(
            runId, phoneNumber, errMsg))
        return {'code': -3, 'message': errMsg, 'phoneNumber': phoneNumber}

    novice.logNeedle.push('(runId: {}) 請查收驗證碼'.format(runId))
    return {
        'code': 2,
        'message': '請查收驗證碼。',
        'phoneNumber': phoneNumber,
        'phoneCodeHash': phoneCodeHash,
    }
Exemplo n.º 8
0
async def verifiedCode(pageId: str, prop: typing.Any = None) -> dict:
    innerSession = serverMix.innerSession.get(pageId)
    if not 'interactiveLogin' in innerSession:
        return {'code': -1, 'message': '沒有待登入的用戶。'}

    if type(prop) != dict:
        return {'code': -1, 'message': '"prop" 參數必須是 `Object` 類型。'}
    if not ('phoneNumber' in prop and type(prop['phoneNumber']) == str):
        return {'code': -1, 'message': '"prop.phoneNumber" 參數不符合預期'}
    if not ('phoneCodeHash' in prop and type(prop['phoneCodeHash']) == str):
        return {'code': -1, 'message': '"prop.phoneCodeHash" 參數不符合預期'}
    if not ('verifiedCode' in prop and type(prop['verifiedCode']) == str):
        return {'code': -1, 'message': '"prop.verifiedCode" 參數不符合預期'}

    info = innerSession['interactiveLogin']
    runId = info['runId']
    sessionPath = info['sessionPath']
    sentCode = info['sentCode']
    client = info['client']

    phoneNumber = prop['phoneNumber']
    phoneCodeHash = prop['phoneCodeHash']
    verifiedCode = prop['verifiedCode']

    if phoneNumber != info['phoneNumber'] \
            and phoneCodeHash != info['sentCode'].phone_code_hash:
        errMsg = '登入的仿用戶 +{} 與主機留存仿用戶 +{} 不相同'.format(phoneNumber,
                                                      info['phoneNumber'])
        novice.logNeedle.push('(runId: {}) {}'.format(runId, errMsg))
        return {
            'code': -2,
            'message': errMsg,
            'phoneNumber': info['phoneNumber'],
            'anotherPhoneNumber': phoneNumber,
        }

    novice.logNeedle.push(
        '(runId: {}) login +{} with {} phoneCodeHash and {} verifiedCode'.
        format(runId, phoneNumber, phoneCodeHash, phoneNumber))
    try:
        await client.sign_in(phoneNumber,
                             verifiedCode,
                             phone_code_hash=phoneCodeHash)
        message = '+{} 仿用戶登入成功'.format(phoneNumber)
        novice.logNeedle.push('(runId: {}) {}'.format(runId, message))

        await _disconnectClient(info, client)
        _mvSessionPath(sessionPath, phoneNumber,
                       'telethon-' + novice.py_env['apiId'])
        novice.logNeedle.push(
            '(runId: {}) disconnect & mv finish'.format(runId))

        return {'code': 3, 'message': message, 'phoneNumber': phoneNumber}
    except Exception as err:
        typeName = err.__class__.__name__
        errMsg = _signInKnownErrorTypeInfo[typeName] \
            if typeName in _signInKnownErrorTypeInfo \
            else novice.sysTracebackException()
        novice.logNeedle.push('(runId: {}) login +{} Failed {}'.format(
            runId, phoneNumber, errMsg))
        return {'code': -3, 'message': errMsg, 'phoneNumber': phoneNumber}