diff --git a/mt/auto-meal-complete.user.js b/mt/auto-meal-complete.user.js new file mode 100644 index 0000000..76bee79 --- /dev/null +++ b/mt/auto-meal-complete.user.js @@ -0,0 +1,315 @@ +// ==UserScript== +// @name 美团外卖自动出餐 +// @namespace https://circlecloud.ltd/ +// @version 0.0.1 +// @description 自动出餐 +// @author MiaoWoo +// @match https://e.waimai.meituan.com/**region_id=**®ion_version=** +// @icon https://www.google.com/s2/favicons?sz=64&domain=meituan.com +// @grant none +// ==/UserScript== + +(function () { + 'use strict'; + function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } + function generateRandomString(length) { + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + var result = ''; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; + } + function convertSecondsToMinutesSeconds(seconds) { + var minutes = Math.floor(seconds / 60); + var remainingSeconds = seconds % 60; + return minutes + "分" + remainingSeconds.toFixed(0) + "秒"; + } + function request(path, params) { + return fetch(`https://e.waimai.meituan.com/${path}?region_id=${cookies.region_id}®ion_version=${cookies.region_version}`, { + "headers": { + "accept": "application/json, text/plain, */*", + "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "content-type": "application/x-www-form-urlencoded", + "sec-ch-ua": "\"Microsoft Edge\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "\"Windows\"", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin" + }, + "referrer": "https://e.waimai.meituan.com/new_fe/business_gw", + "referrerPolicy": "unsafe-url", + "body": (new URLSearchParams(params)).toString(), + "method": "POST", + "mode": "cors", + "credentials": "include" + }).then(r => r.json()) + } + async function requestGet(path, params) { + return await fetch(`https://e.waimai.meituan.com/gw/phf${path}?${(new URLSearchParams({ + region_id: cookies.region_id, + region_version: cookies.region_version, + ...params + })).toString()}`, { + "headers": { + "accept": "application/json, text/plain, */*", + "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + "sec-ch-ua": "\"Microsoft Edge\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "\"Windows\"", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin" + }, + "referrer": "https://e.waimai.meituan.com/new_fe/business_gw", + "referrerPolicy": "unsafe-url", + "body": null, + "method": "GET", + "mode": "cors", + "credentials": "include" + }).then(r => r.json()); + } + function listUnprocessedOrder() { + return request('/gw/api/order/mix/unprocessed/list/common', { + tag: "process", + pageSize: 10, + pageNum: 1, + pageGray: 1, + }) + } + async function completeMealTime(order, cookTime = order.cookTime, tip = order.deliveOnShop ? '骑手已到店' : '骑手未到店') { + let wmOrderViewId = order.wmOrderViewIdStr + let daySeq = order.daySeq + let result = await request('/v2/common/w/reported/completeMealTime', { + wmPoiId: mt.wmPoiId, + wmOrderViewId: wmOrderViewId + }) + debug('订单 #' + daySeq + ' 上报出餐结束.') + mt.submitOrders.push({ + completeTime: new Date().toLocaleTimeString(), + daySn: daySeq, orderId: wmOrderViewId, tip, cookTime + }) + if (mt.submitOrders.length > mt.maxLogOrderLength) { + mt.submitOrders = mt.submitOrders.slice(mt.submitOrders.length - mt.maxLogOrderLength) + } + localStorage.setItem(SubmitOrdersKey, JSON.stringify(mt.submitOrders)) + await sleep(5) + await syncOrders() + return result + } + async function checkCooking() { + if (!mt.processOrders) return + for (const order of mt.processOrders) { + if (order.isFoodDone) continue + order.cookTime -= 5 + order.leftTime -= 5 + order.submitLeft -= 5 + if (order.submitLeft < 0) { + if (order.cookTime < 180) { + console.log(order) + debug('订单 #' + order.daySeq + ' 出餐时间 ' + order.cookTime + 's 小于 ' + 180 + 's 取消自动上报.') + continue + } + if (order.deliveOnShop) { + debug('订单 #' + order.daySeq + ' 骑手已到店.') + debug('订单 #' + order.daySeq + ' 剩余出餐时间 ' + order.leftTime + 's 小于 ' + mt.autoSubmitLeftTimeWhenOnShop + 's 模拟提交出餐.') + } else { + debug('订单 #' + order.daySeq + ' 骑手未到店.') + debug('订单 #' + order.daySeq + ' 剩余出餐时间 ' + order.leftTime + 's 小于 ' + mt.autoSubmitLeftTime + 's 模拟提交出餐.') + } + return await completeMealTime(order, order.cookTime) + } + if (order.submitLeft < 20) { + debug('订单 #' + order.daySeq + ' 还剩 ' + order.leftTime + 's 上报超时 将于 ' + order.submitLeft + 's 后自动上报.') + } else { + updateInfo() + } + } + } + + async function syncPhfOrderMeal(wmOrderViewId) { + let result = await requestGet('/v2/order/receive/processed/r/orderAsyncInfos/v3', { + orderInfos: JSON.stringify([{ + "wmOrderViewId": wmOrderViewId, + "wmPoiId": cookies.wmPoiId, + "cityId": cookies.city_id + }]) + }) + return result.data[wmOrderViewId] + } + async function syncPhfOrderlLgistic(wmOrderViewId) { + let result = await requestGet('/v2/order/receive/processed/r/distribute/list/v2', { + orderInfos: JSON.stringify([{ + "wmOrderViewId": wmOrderViewId, + "wmPoiId": cookies.wmPoiId, + "cityId": cookies.city_id + }]) + }) + return result.data[wmOrderViewId] + } + async function syncOrderInfo(order) { + let now = Math.floor(Date.now() / 1000) + order.wordLogo = '' + order.daySeq = order.commonInfo.wm_poi_order_dayseq + order.cookTime = now - order.commonInfo.confirmTime + order.leftTime = order.commonInfo.confirmTime + 480 - now + if (order.businessType == 1) { + // 获取订单类型 普通订单存在预订单 + order.wordLogo = order.orderInfo.orderInfo.wordLogo || '普' + order.wmOrderViewIdStr = order.orderInfo.orderInfo.wmOrderViewId + order.isFoodDone = !order.orderInfo.mealInfo.foodDoneButtonVo.isShow + order.cookTime = now - order.orderInfo.mealInfo.countDownTimerVo.timerShowVo.preMealBeginTime + order.logisticsStatus = order.orderInfo.logisticsInfo.logisticsProcessVo.processDesc + // 部分数据存在长 结束备餐时间可能是0 需要计算开始时间 + 480(配置的出餐时间) - 当前时间 + if (order.orderInfo.mealInfo.countDownTimerVo.timerShowVo.preMealEndTime) { + order.leftTime = order.orderInfo.mealInfo.countDownTimerVo.timerShowVo.preMealEndTime - now + } else { + order.leftTime = order.orderInfo.mealInfo.countDownTimerVo.timerShowVo.preMealBeginTime + 480 - now + } + if (order.wordLogo == '预' && !order.isFoodDone) { + // 预订单需要从提醒时间开始计算备餐时间 + order.leftTime = order.orderInfo.mealInfo.foodDoneTitleVo.reminderTime + 480 - now + } + order.submitCanClickLimitTime = order.orderInfo.mealInfo.foodDoneButtonVo.buttonClickVo?.canClickLimitTime || 0 + } else if (order.businessType == 2) { + order.wordLogo = '拼' + order.wmOrderViewIdStr = order.orderInfo.wm_order_id_view_str + order.mealInfo = await syncPhfOrderMeal(order.wmOrderViewIdStr) + order.logisticsInfo = await syncPhfOrderlLgistic(order.wmOrderViewIdStr) + order.isFoodDone = order.mealInfo.prepareStauts == 2 || order.phfOperationContent + order.logisticsStatus = order.logisticsInfo.latestLogisticsLogDesc.split(' ')[1] + order.submitCanClickLimitTime = order.mealInfo.buttonTimeLimit + } + order.deliveOnShop = order.logisticsStatus == '骑手已到店' + // 剩余时间取最小出餐时间和剩余出餐时间大的值 + order.submitLeft = Math.max(order.submitCanClickLimitTime - now, + order.leftTime - (order.deliveOnShop ? mt.autoSubmitLeftTimeWhenOnShop : mt.autoSubmitLeftTime)) + } + async function syncOrders() { + let originCount = mt.processOrders.length + let orders = await listUnprocessedOrder() + orders = orders.data.wmOrderList?.map(o => { + o.commonInfo = JSON.parse(o.commonInfo) + o.orderInfo = JSON.parse(o.orderInfo) + return o + }) + await Promise.all(orders.map(syncOrderInfo)) + mt.processOrders = orders || [] + if (originCount != mt.processOrders.length) { + debug('更新订单数据 目前进行中订单: ' + mt.processOrders.length + '个') + } else { + updateInfo() + } + } + function printOrderInfo(order) { + let leftTime = order.submitCanClickLimitTime < Date.now() / 1000 + ? order.submitLeft : order.submitCanClickLimitTime - Date.now() / 1000 + return `订单: #${order.daySeq} ${order.wordLogo} + 出餐状态 ${order.isFoodDone ? '已出餐' : `未出餐(${leftTime.toFixed(0)}s)`} + 配送状态 ${order.logisticsStatus}` + } + function debug(msg) { + mt.logs.push('[' + (new Date().toLocaleTimeString()) + '] ' + msg) + if (mt.logs.length > mt.maxLogLength) { + mt.logs = mt.logs.slice(mt.logs.length - mt.maxLogLength) + } + updateInfo() + } + function updateInfo() { + try { + let title = `${mt.title} Version: ${mt.version} By ${mt.author}` + let configInfo = `当前配置: +