// ==UserScript== // @name 饿了么自动出餐 // @namespace https://circlecloud.ltd/ // @version 0.0.5 // @description 自动出餐 // @author MiaoWoo // @match https://melody.shop.ele.me/app/shop/**/order__processing // @icon https://www.google.com/s2/favicons?sz=64&domain=ele.me // @updateURL https://git.yumc.pw/502647092/UserScript/raw/branch/master/ele/auto-meal-complete.user.js // @downloadURL https://git.yumc.pw/502647092/UserScript/raw/branch/master/ele/auto-meal-complete.user.js // @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 getRequestId() { return generateRandomString(32) + "|" + Date.now() } function convertSecondsToMinutesSeconds(seconds) { var minutes = Math.floor(seconds / 60); var remainingSeconds = seconds % 60; return minutes + "分" + remainingSeconds.toFixed(0) + "秒"; } function getShopMeta() { return { "appVersion": "1.0.0", "appName": "melody", "ksid": localStorage.ksid, "shopId": ele.shopId } } function request(service, method, params) { let payload = { "service": service, "method": method, "params": params, "id": getRequestId(), "metas": getShopMeta(), "ncp": "2.0.0" } return fetch("https://app-api.shop.ele.me/fulfill/weborder/" + method + "/?method=" + service + "." + method, { "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/json;charset=UTF-8", "sec-ch-ua": "\"Microsoft Edge\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"", "sec-ch-ua-platform": "\"Windows\"", "sec-fetch-mode": "cors", "sec-fetch-site": "same-site", "x-eleme-requestid": payload.id, "x-shard": "shopid=" + payload.metas.shopId }, "referrer": "https://napos-order-pc.faas.ele.me/", "referrerPolicy": "strict-origin-when-cross-origin", "body": JSON.stringify(payload), "method": "POST", "mode": "cors", "credentials": "omit" }).then(r => r.json()) } function queryInProcessOrders() { return request('OrderWebService', 'queryInProcessOrders', { "shopId": ele.shopId, "queryType": "ALL", // "queryType": "COOKING" }) } async function mealComplete(cook, cookTime, tip) { let orderId = cook.id let daySn = cook.header.daySn let result = await request('ShipmentService', 'mealComplete', { "shopId": ele.shopId, "orderId": orderId }) debug('订单 #' + daySn + ' 上报出餐结束.') ele.submitOrders.push({ completeTime: new Date().toLocaleTimeString(), daySn, orderId, tip, cookTime, result }) if (ele.submitOrders.length > ele.maxLogOrderLength) { ele.submitOrders = ele.submitOrders.slice(ele.submitOrders.length - ele.maxLogOrderLength) } localStorage.setItem(SubmitOrdersKey, JSON.stringify(ele.submitOrders)) await sleep(5) await syncOrders() return result } async function checkCooking() { if (!ele.processOrders) return for (const order of ele.processOrders) { let daySn = order.header.daySn if (!order.mealPreparationInfo || order.mealPreparationInfo.mealComplete) continue // 骑手到店 并且大于最小上报时长 直接上报出餐 let cookTime = order.mealPreparationInfo.cookTime -= 5 let leftTime = order.mealPreparationInfo.commonShowTime -= 5 let submitLeft = order.mealPreparationInfo.submitLeft -= 5 order.mealPreparationInfo.minMealCompleteTimeCount -= 5 if (order.mealPreparationInfo.minMealCompleteTimeCount < 0) { if (order.deliveryInfo.deliveOnShop && leftTime < ele.autoSubmitLeftTimeWhenOnShop) { debug('订单 #' + daySn + ' 骑手已到店.') debug('订单 #' + daySn + ' 剩余出餐时间 ' + leftTime + 's 小于 ' + ele.autoSubmitLeftTimeWhenOnShop + 's 模拟提交出餐.') return mealComplete(order, cookTime, '骑手已到店') } if (leftTime < ele.autoSubmitLeftTime) { // 大于最小上报时长 并且距离上报超时不足 直接上报出餐 debug('订单 #' + daySn + ' 骑手未到店.') debug('订单 #' + daySn + ' 剩余出餐时间 ' + leftTime + 's 小于 ' + ele.autoSubmitLeftTime + 's 模拟提交出餐.') return mealComplete(order, cookTime, '骑手未到店') } if (submitLeft < 0) { // 非预订单且备餐时间大于最大时间 debug('订单 #' + daySn + ' 备餐时间过长.') debug('订单 #' + daySn + ' 备餐时间 ' + cookTime + 's 大于 ' + ele.autoSubmitMaxCookTime + 's 模拟提交出餐.') return mealComplete(order, cookTime, '备餐已完成') } } if (submitLeft < 20) { debug('订单 #' + daySn + ' 还剩 ' + leftTime + 's 上报超时 将于 ' + submitLeft + 's 后自动上报.') } else { updateInfo() } } } function syncOrderTime(order) { if (order.printDataInfo.statusForPrint == "预订单" && order.header.orderPromptDesc.indexOf('备餐提醒时间')) { try { order.activeTime = new Date().toLocaleDateString() + ' ' + order.header.orderPromptDesc.match(/\d{2}:\d{2}/g)[1] } catch (error) { } } let deliveOnShop = false if (order.deliveryInfo) { deliveOnShop = order.deliveryInfo.deliveOnShop = order.deliveryInfo.distTraceView.traceView.status == '骑士已到店' || (order.deliveryInfo.deliveryDistance && !order.deliveryInfo.deliveryDistance.endsWith('km') && parseInt(order.deliveryInfo.deliveryDistance) < 100) } if (order.mealPreparationInfo) { let cookTime = order.mealPreparationInfo.cookTime = ((Date.now() - new Date(order.activeTime).getTime()) / 1000).toFixed(0) order.mealPreparationInfo.submitLeft = Math.max(order.mealPreparationInfo.minMealCompleteTimeCount, Math.min(ele.autoSubmitMaxCookTime - cookTime, order.mealPreparationInfo.commonShowTime - (deliveOnShop ? ele.autoSubmitLeftTimeWhenOnShop : ele.autoSubmitLeftTime)) ).toFixed(0) } } async function syncOrders() { let originCount = ele.processOrders.length let orders = await queryInProcessOrders() ele.processOrders = orders.result || [] for (const order of ele.processOrders) { syncOrderTime(order) } if (originCount != ele.processOrders.length) { debug('更新订单数据 目前进行中订单: ' + ele.processOrders.length + '个') } else { updateInfo() } heartbeat() } function debug(msg) { ele.logs.push('[' + (new Date().toLocaleTimeString()) + '] ' + msg) if (ele.logs.length > ele.maxLogLength) { ele.logs = ele.logs.slice(ele.logs.length - ele.maxLogLength) } updateInfo() } function updateInfo() { try { let title = `${ele.title} Version: ${ele.version} By ${ele.author}` let configInfo = `当前配置:
订单 刷新间隔: ${ele.syncInterval}s 检测间隔: ${ele.checkInterval}s
出餐 最长时间: ${ele.autoSubmitMaxCookTime}s 骑手未到店: ${ele.autoSubmitLeftTime}s 骑手已到店: ${ele.autoSubmitLeftTimeWhenOnShop}s
` let orderInfo = `进行中的订单信息 更新时间:${new Date().toLocaleTimeString()}
${ele.processOrders.length ? ele.processOrders.map(o => ' 订单: #' + o.header.daySn + ' 出餐状态 ' + (o.mealPreparationInfo.mealComplete ? '已出餐' : `未出餐${o.mealPreparationInfo.minMealCompleteTimeCount > 0 ? `(${-o.mealPreparationInfo.minMealCompleteTimeCount}s)` : o.mealPreparationInfo.submitLeft > 0 ? `(${o.mealPreparationInfo.submitLeft}s)` : ''}`) + ' 配送状态 ' + o.deliveryInfo.distTraceView.traceView.status + '' + (o.deliveryInfo.deliveryDistance ? ' ' + o.deliveryInfo.deliveryDistance : '')).join('
') : '当前没有进行中的订单.'}
` let submitInfo = `已自动出餐的订单信息:
${ele.submitOrders.length ? ele.submitOrders .map(o => `
[` + o.completeTime + '] 订单: #' + o.daySn + ' 出餐用时 ' + convertSecondsToMinutesSeconds(o.cookTime) + ' ' + o.tip +'
').join('') : '当前没有自动出餐的订单.'}
` let logs = `运行日志:
${ele.logs.join('
')}
` window.appContainerNoticeBar.innerHTML = `
${title}
${configInfo}
${orderInfo}
${submitInfo}
${logs}
` } catch (error) { console.log(error) } } async function scheduleCheck() { await checkCooking() await sleep(ele.checkInterval * 1000) scheduleCheck() } async function heartbeat() { const script = document.createElement('script'); script.src = `https://kuma.yumc.pw/api/push/L7kq99ruju?status=up&msg=OK&ping=`; document.body.appendChild(script); script.onload = () => script.remove() } async function main() { while (!document.getElementById('app-container-notice-bar')) { await sleep(300) } console.log(`${ele.title} Version: ${ele.version} By ${ele.author}`) window.appContainerNoticeBar = document.getElementById('app-container-notice-bar') debug('页面注入成功 开始运行...') syncOrders() scheduleCheck() setInterval(() => syncOrders(), ele.syncInterval * 1000) } var SubmitOrdersKey = 'AutoMealComplete:SubmitOrders' var ele = { title: '饿了么自动出餐', version: '0.0.5', author: 'MiaoWoo', shopId: parseInt(localStorage.shopId), checkInterval: 5, syncInterval: 15, maxLogLength: 10, maxLogOrderLength: 10, autoSubmitLeftTime: 180, autoSubmitMaxCookTime: 420, autoSubmitLeftTimeWhenOnShop: 240, logs: [], processOrders: [], submitOrders: JSON.parse(localStorage.getItem(SubmitOrdersKey)) || [], debug: debug, syncOrders: syncOrders, checkCooking: checkCooking, mealComplete: mealComplete, } window.ele = ele main() })();