douyinWork_douyinMain.js

const wdio = require("webdriverio");
const cmd = require('node-cmd');
const { log, MyAction, sleep } = require('../utils/baseActions')
const {
    startChecking,
    myDYSignUp,
    searchVideo,
    searchUser,
    releaseVideo,
    replyMessage,
    groupChat,
    clearCache
} = require('./douyinAction')
const { MyApi, setAccout } = require('../utils/database');
const { checkFile, clearDailyFollow, autoClearDailyFollow, getInfo } = require('../utils/localDatabase');

//这里是外部传参
const indOffset = 120
const projectInd = Number(process.argv.slice(2)[0]) + indOffset
const keepCookie = true

//基本信息
const packageName = 'com.ss.android.ugc.aweme.lite'
const activityName = 'com.ss.android.ugc.aweme.main.MainActivity'
const robotName = `robot${projectInd}`
const projectName = `douyinWork`

//创建api
const robotApi = new MyApi('bucket-robots', robotName)
const phoneListApi = new MyApi('json-bucket', 'phonelist')

//appium设置
const opts = {
    path: '/',
    port: 4723,
    capabilities: {
        "platformName": "Android",
        "appium:noReset": keepCookie,
        "appium:newCommandTimeout": "9999",
        "appium:automationName": "UiAutomator2",
        "appium:disableWindowAnimation": true,
        "appium:waitForIdleTimeout": 0,
        "appium:disableIdLocatorAutocompletion": true
    },
    logLevel: "error",
};
//任务执行设置
const taskOpt = {
    bucket: 'bucket-task',
    totalRetry: 86400,  //获取新任务的尝试次数
    taskRetry: 86400,   //等待任务开始的尝试次数
    totalRetryGap: 10,  //单位秒,支持小数
    taskRetryGap: 10,  //单位秒,支持小数
}


/**
 * 执行任务各个功能
 * @param {*} act - MyAction实例
 * @param {*} type - 执行功能的名字
 * @param {*} data - 执行功能所需的数据
 * @returns {string}
 */
const myCapacity = async (act, type, data) => {
    let result = ''
    switch (type) {
        case 'searchVideo':
            result = await searchVideo(act, data);
            break;
        case 'searchUser':
            result = await searchUser(act, data);
            break;
        case 'releaseVideo':
            result = await releaseVideo(act, data);
            break;
        case 'replyMessage':
            result = await replyMessage(act, data);
            break;
        case 'groupChat':
            result = await groupChat(act, data);
            break;
        case 'clearCache':
            result = await clearCache(act);
            break;
        case 'clearDailyFollow':
            result = await clearDailyFollow(projectName);
            break;

        default:
            break;
    }
    return result
}

/**
 * 获取所有任务队列文件,提取要执行的文件
 * @param {Array<string>} queue - 任务对面数组
 * @returns {string}
 */
const getLists = async (queue) => {
    const { bucket } = taskOpt
    const startApi = new MyApi(bucket, '');
    const level = [
        'first', 'second', '--'
    ]
    try {
        let lists = await startApi.getAllInfo();
        //按级筛选任务
        for (let lv = 0; lv < level.length; lv++) {

            let thisLevel = lists.filter(v => v.fileName.includes(level[lv]) && v.fileName.includes('douyin') && v.fileName.includes(robotName))
            if (thisLevel.length > 0) {
                // console.log(level[lv], '有任务', thisLevel.length);
                //筛选未做过且未标记的任务
                for (let i = 0; i < thisLevel.length; i++) {
                    let fileName = thisLevel[i].fileName.replace('.json', '')
                    if (!queue.includes(fileName)) {
                        let fileApi = new MyApi(bucket, fileName)
                        let data = await fileApi.getInfo();
                        if (!data.missionComplete) {
                            log.info(`获取到任务: ${fileName}, 优先级${level[lv]}`);
                            return fileName
                        }
                    };
                }
            }
        }
    }
    catch (err) {
        console.log(err.message);
        log.warning('获取所有任务队列报错');
    }
    return ''
}

/**
 * 查看当前任务文件
 * @param {string} fileName - 任务文件名
 * @returns {string}
 */
const getTodoList = async (fileName) => {
    const { bucket, taskRetry, taskRetryGap } = taskOpt
    const startApi = new MyApi(bucket, fileName)
    for (let i = 0; i < taskRetry; i++) {
        try {
            let data = await startApi.getInfo();
            if (!data) {
                log.warning(`getTodoList ==> missing data`);
            }
            else {
                if (!data.missionStart) {
                    if (i % 5 === 0) log.warning(`等待开始任务, ${i}`);
                }
                else {
                    if (data.result) {
                        let newList = []
                        let arr = data.todoList
                        log.warning(`~~恢复任务中`);
                        for (let i = 0; i < arr.length; i++) {
                            try {
                                if (data.result[arr[i].type].main === 'over') {
                                    console.log('跳过主任务', arr[i].type);
                                    continue
                                }
                            }
                            catch { }
                            if (arr[i].type === 'searchVideo' || arr[i].type === 'replyMessage') {
                                let oldData = arr[i].data
                                let newData = {}
                                for (const value in oldData) {
                                    try {
                                        if (data.result[arr[i].type][value] === 'over') {
                                            console.log('跳过子任务', `${arr[i].type} ===> ${value}`);
                                            continue
                                        }
                                    }
                                    catch { }
                                    if (value === 'review') newData[value] = data.result[arr[i].type][value]
                                    else newData[value] = oldData[value]
                                }
                                arr[i].data = newData
                                newList.push(arr[i])
                            }
                            else newList.push(arr[i])
                        }
                        return newList
                    }

                    return data.todoList
                }
            }
        }
        catch (err) {
            if (i % 3 === 0) {
                console.log(err.message);
                log.warning('请求S3数据库报错');
            }
        }
        await sleep(taskRetryGap);
    }
    return 'timeout'
}

/**
 * 更新任务进度
 * @param {string} fileName - 要更新的任务文件名
 * @param {string|object|null} schedule - 要更新的任务细节
 * @param {*} type - 要更新的任务文件类型
 * @param {*} over - 是否是最后一项更新
 */
const updateTask = async (fileName, schedule, type, over) => {
    const { bucket } = taskOpt
    for (let i = 0; i < 3; i++) {
        try {
            const api = new MyApi(bucket, fileName)
            let obj = await api.getInfo();
            if (!obj) {
                await sleep(10);
                continue
            }
            if (!obj.result) {
                log.info(`该任务没有完成进度,创建新的`)
                obj.result = {}
            }

            if (over) obj.missionComplete = over;
            else obj.result[type] = Object.assign({}, obj.result[type], schedule);

            let updateP = await api.updateInfo(obj);
            if (updateP) break
        }
        catch (err) {
            console.log(err.message);
            log.warning('请求S3数据库报错');
        }
        await sleep(10);
    }
}

const main = async (robotStat, act, restart) => {
    try {
        let phonelist = await phoneListApi.getInfo();
        if (!phonelist[projectInd]) return `get phonelist Fail`
        else console.log('手机号信息', phonelist[projectInd]);

        log.info('app ==>> appium start');

        //0.启动appium打开damai
        let startC = await startChecking(act, restart);
        if (startC === 'error') return `startChecking Error ==>> ${startC}`

        //1.注册登录
        if (robotStat.step === 1) {
            let phoneNumber = phonelist[projectInd].phone;
            let signU = 'login Error'

            if (phonelist[projectInd].msgType === 'bumoyu') {
                log.info(`开始注册登录:${phoneNumber},使用自己的平台`);
                signU = await myDYSignUp(act, robotStat.name, true);
            }
            else {
                log.info(`开始注册登录:${phoneNumber},使用短信收发平台`);
                // signU = await signUp(act, phoneNumber, true);
            }

            if (signU === 'already login') {
                log.info('已有登录账号')
            }
            else if (signU.includes('msg Error') || signU.includes('captcha Error')) {
                return signU
            }
            else if (signU.includes('Error')) {
                return `signUp ${signU}`
            }
            else {
                log.info(`登录完成: ${signU}`)
                if (signU.includes('bmymsg')) {
                    const msgApi = new MyApi('bucket-message', robotName);
                    let msgBase = await msgApi.getInfo();
                    if (!msgBase) return 'mySignUp Error ==>> getRobot in the end'

                    msgBase.status = 'finish';
                    let updateMs = await msgApi.updateInfo(msgBase);

                    let updateAc = await setAccout(signU.split('_')[1], msgBase.taskId, 'finish')
                    if (!updateMs || !updateAc) return 'mySignUp Error ==>> update in the end'
                    log.debug('修改状态完成');

                }

                let newPhonelist = await phoneListApi.getInfo();
                newPhonelist[projectInd].userName = signU;
                newPhonelist[projectInd].platform = robotStat?.name;

                let updateP = await phoneListApi.updateInfo(newPhonelist);
                if (!updateP) return `phoneList Update Fail`
            }

            // if (robotStat?.account !== phoneNumber) log.warning('账号不一致')
            robotStat.account = phoneNumber;
            robotStat.step = 2;
            robotStat.status = "login";

            let update = await robotApi.updateInfo(robotStat);
            if (!update) return `signUp Update Fail`
        }
        //2.执行功能
        if (robotStat.step === 2) {
            log.info('~~~~~~ 开始循环执行任务队列 ~~~~~~');
            var queue = [], waitTime = 5, errorTimes = 0
            const { totalRetry, totalRetryGap } = taskOpt
            for (let i = 0; i < totalRetry; i++) {
                try {
                    let task = await getLists(queue);
                    if (task) {
                        let todoList = await getTodoList(task);
                        log.notice(`开始执行任务列表, ${i} ==> ${task}`)
                        try {
                            let total = await getInfo("douyinWork", 'dailyFollow');
                            log.notice(`今日关注数, ${total}`)
                        }
                        catch { }
                        if (Array.isArray(todoList)) {
                            let fullComplete = true //任务无错检测
                            console.time('任务运行总时间')
                            for (let n = 0; n < todoList.length; n++) {
                                await autoClearDailyFollow(projectName);
                                //任务队列执行
                                let res = await myCapacity(act, todoList[n].type, todoList[n].data);
                                if (res.main !== 'over') {
                                    fullComplete = false
                                    errorTimes++
                                }
                                else {
                                    errorTimes = 0
                                }
                                await updateTask(task, res, todoList[n].type);
                                log.notice(`任务执行完毕 ==> ${todoList[n].type}`);

                                //防崩溃检测
                                await act.client.pressKeyCode(8);
                                try {
                                    await act.waitForElement(['text', '下线提醒'], 3000);
                                    // await act.clickElement(['text', '好']);
                                    log.warning('被踢下线了');
                                    await updateTask(task, { banned: 'true' }, 'banned');
                                    break
                                } catch { }

                                //检测休息弹窗
                                try {
                                    try {
                                        await act.clickElement(['text', '忽略提醒'], 3000);
                                        log.warning('出现休息弹窗');
                                    }
                                    catch {
                                        await act.clickElement(['desc', '忽略提醒'], 3000);
                                        log.warning('出现休息弹窗2');
                                    }
                                } catch { }

                                let start = await act.client.queryAppState(packageName)
                                if (start !== 4 || errorTimes > 2) {
                                    log.warning(`~~程序崩溃,重启app, ${errorTimes}`);
                                    await act.client.startActivity(packageName, activityName);
                                    await startChecking(act, restart);
                                    n--
                                }
                            }
                            console.timeEnd('任务运行总时间')
                            log.notice(`任务列表执行完毕, ${i} ==> ${task}`);
                            if (fullComplete) {
                                log.notice(`所有任务都完成了`);
                                await updateTask(task, null, null, true);
                            }
                            queue.push(task);
                        }
                        else {
                            log.warning('未获取到该任务');
                        }
                    }
                    else {
                        if (i % waitTime === 0) {
                            log.warning(`等待新任务中--${i}, 当前已完成:${queue.length}`);
                            if (waitTime < 80) waitTime = waitTime * 2
                            else if (waitTime < 180) waitTime = waitTime + 50
                        }
                    }

                }
                catch (err) {
                    console.log('~~~任务执行中断,尝试重启中~~~', err.message);
                    return 'Unknown Error'
                }
                await sleep(totalRetryGap);
            }
        }
        return 'over'
    }
    catch (err) {
        log.error('Main error', err)
        return 'Unknown Error'
    }
}

(async () => {
    await checkFile(projectName);

    let robotStat = await robotApi.getInfo();
    if (!robotStat) {
        robotStat.error = 'api getInfo error'
        await robotApi.updateInfo(robotStat);
        return
    }
    else {
        robotStat.error = ''
        await robotApi.updateInfo(robotStat);
    }
    console.log('robot信息', robotStat);

    for (let running = 0; running < 5; running++) {
        try {
            const client = await wdio.remote(opts);
            const act = new MyAction(client);
            let start = await client.queryAppState(packageName)
            if (start === 1) {
                console.log('启动app');
                await client.startActivity(packageName, activityName);
            }
            else if (start === 3 || start === 2) {
                console.log('后台拉出app');
                await client.activateApp(packageName);
            }
            else if (start === 4) {
                if (robotStat.step < 4) {
                    await client.terminateApp(packageName);
                    await new Promise(resolve => setTimeout(resolve, 2000));
                    console.log('重新打开app');
                    await client.startActivity(packageName, activityName);
                }
                else { console.log('app已启动保持状态'); }
            }

            let runningRes = await main(robotStat, act);
            log.info(runningRes);
            await client.pressKeyCode(8);
            if (runningRes !== 'over') {
                if (runningRes.includes('restart')) {
                    log.warning('app卡住了,重启');
                    continue
                }
                else {
                    let cheakApp = await client.queryAppState(packageName);
                    if (cheakApp !== 4) {
                        log.warning('app崩溃了,重启');
                        continue
                    }
                    else {
                        log.warning('代码执行错误,请检查');
                        break
                    }
                }
            }
            else {
                log.info('all over');
                break
            }
        }
        catch (err) {
            console.log(err.message);
            for (let i = 0; i < 99999; i++) {
                let checkAdb = await cmd.runSync(`adb devices`);
                let adbData = checkAdb.data.replace("devices", "")
                if (adbData.includes("device")) {
                    console.log(adbData);
                    break
                }
                else {
                    if (i % 2 === 0) { console.log('adb 断开了', i, adbData); }
                }
                await sleep(2);
            }
            log.info('程序崩溃,尝试完全重启');
        }
    }
})()