liujiahua123123 / MMLotteryAutomation

only contain logic for filling forms, JS+playwright

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Not include:

  • browser startup
  • captcha solving (interface provided, need implementation)

Example usage:

    const pool = new LotteryWorkerPool(
        "mm2024",
        "inland-2",
        "https://pia.jp/piajp/v/magicalmirai24-2/",
        g,
        5,
        completeInlandLottery,
        8000,
        20*1000*60,
    )

    await pool.run()
/**
 * Lottery Framework
 */

export type Workspace = {
    page: Page,
    close: () => Promise<void>,
}
// Type definition for the factory method that will create a Page
export type BrowserFactory = () => Promise<Workspace>;

export class LotteryWorkerPool{
    private BrowserFactory: BrowserFactory;
    private bundle: string;
    private round: string;
    private URL: string;
    private numWorkers = 0;
    private executeLotteryTask: (
        page: Page,
        lottery: LotteryDocument,
        link: string) => Promise<{
        acpt_no: string,
        summary: string,
    }>;
    private pauseBetweenLottery: number;
    private pauseBetweenAccount: number;

    private lotteryTasksGroupByEmail: LotteryDocument[][];

    constructor(
        bundle: string,
        round: string,
        url: string,
        factory: BrowserFactory,
        numWorkers: number,
        executeLotteryTask: (
            page: Page,
            lottery: LotteryDocument,
            link: string) => Promise<{
            acpt_no: string,
            summary: string,
        }>,
        pauseBetweenLottery = 5000,
        pauseBetweenAccount: number = 50000,
    ) {
        this.bundle = bundle;
        this.round = round;
        this.URL = url;
        this.BrowserFactory = factory;
        this.numWorkers = numWorkers;
        this.executeLotteryTask = executeLotteryTask;
        this.pauseBetweenLottery = pauseBetweenLottery;
        this.pauseBetweenAccount = pauseBetweenAccount;
    }

    private async runWorker(workerId: number){
        let workspace = null;

        const log = (msg: string) => {
            console.log("Worker ", workerId, msg);
        }

        const deallocateWorkspace = async () => {
            try{
                if (workspace){
                    await workspace.close();
                }
            }catch(_){

            }finally {
                workspace = null;
            }
        }

        const allocateWorkspace = async () => {
            if(!workspace){
                while(true){
                    try{
                        workspace = await this.BrowserFactory();
                        break;
                    }catch(e){
                        log("Failed to close workspace: " + e.message);
                        await sendLotteryErrorWebhook("Unable to allocate workspace: " + e.message);
                        await delayWithNormalDistribution(100000);
                    }
                }
            }
        }

        const reallocateWorkspace = async () => {
            await deallocateWorkspace()
            await allocateWorkspace()
        }

        while(true){
            //get one group of tasks or exit
            const group = this.lotteryTasksGroupByEmail.shift();
            if (!group){
                log("No more tasks, exiting");
                await deallocateWorkspace();
                break;
            }

            await allocateWorkspace();
            log("Start processing " + group.length + " tasks");

            for (let i = 0; i < group.length; i++){
                const lottery = group[i];
                try {
                    //account for network error
                    for(let network_retry = 0; network_retry < 3; network_retry++) {
                        try {
                            const result = await this.executeLotteryTask(workspace.page, lottery, this.URL)
                            log("Lottery completed: " + result.acpt_no);
                            await Backend.lotteryComplete(lottery, result.acpt_no);
                            await sendLotteryAcceptedWebhook(result.summary);
                            break;
                        } catch (e) {
                            //if it's not a network error, throw to catch
                            if (e instanceof LotteryError) {
                                log("Failed to attempt lottery due to lottery error " + e.message);
                                throw e
                            }else{
                                //network error
                                log("Failed to attempt lottery due to network issues " + e.message);
                                console.log(e)
                                await reallocateWorkspace();
                                if (network_retry == 2){
                                    throw e;
                                }
                            }
                        }
                    }
                }catch (e) {
                    log("Failed to complete lottery: " + e.message);
                    //console.log(e)
                    if (e instanceof LotteryError) {
                        await sendLotteryErrorWebhook("LotteryError: " + e.message  + "\nEarly Stop: 1");
                        await Backend.lotteryError(lottery, e.message)
                        //early stop all other tasks, mark as error\
                        for(let j = i + 1; j < group.length; j++){
                            await Backend.lotteryError(group[j], e.message)
                        }
                        log("This is a lottery error, early stop all other tasks");
                        break;
                    }else{
                        //network error?
                        //already reallocate workspace above
                        await Backend.lotteryError(lottery, e.message)
                        await sendLotteryErrorWebhook("OtherError: " + e.message);
                    }
                }
                await delayWithNormalDistribution(this.pauseBetweenLottery);
            }

            log("Finish processing " + group.length + " tasks");
            await deallocateWorkspace();


            await delayWithNormalDistribution(this.pauseBetweenAccount);
        }
    }

    private async loadLotteryTasks(){
        const lotteries = await Backend.getUnfinishedLotteries(this.bundle, this.round, true)
        //group by email
        const acc = {}
        for (let i = 0; i < lotteries.length; i++){
            const lottery = lotteries[i];
            if (!acc[lottery.email]){
                acc[lottery.email] = [];
            }
            acc[lottery.email].push(lottery);
        }
        this.lotteryTasksGroupByEmail = Object.values(acc);
    }

    async run(){
        await this.loadLotteryTasks();

        console.log("There are ", this.lotteryTasksGroupByEmail.length, " groups of tasks")
        console.log("There are ", this.numWorkers, " workers")

        const workerPromises: Promise<void>[] = [];
        for(let i = 0; i < this.numWorkers; i++){
            const workerPromise = this.runWorker(i);
            workerPromises.push(workerPromise);
        }
        await Promise.all(workerPromises);
    }
}

About

only contain logic for filling forms, JS+playwright

License:MIT License


Languages

Language:TypeScript 100.0%