277 lines
13 KiB
JavaScript
277 lines
13 KiB
JavaScript
// ==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 = `当前配置: </br>
|
|
<div class="ant-alert-content">
|
|
订单 刷新间隔: ${ele.syncInterval}s 检测间隔: ${ele.checkInterval}s</br>
|
|
出餐 最长时间: ${ele.autoSubmitMaxCookTime}s
|
|
骑手未到店: ${ele.autoSubmitLeftTime}s 骑手已到店: ${ele.autoSubmitLeftTimeWhenOnShop}s</br>
|
|
</div>`
|
|
let orderInfo = `进行中的订单信息 更新时间:${new Date().toLocaleTimeString()}</br>
|
|
<div class="ant-alert-content">
|
|
${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('</br>')
|
|
: '当前没有进行中的订单.'}
|
|
</div>`
|
|
let submitInfo = `已自动出餐的订单信息: </br>
|
|
<div class="ant-alert-content">
|
|
${ele.submitOrders.length
|
|
? ele.submitOrders
|
|
.map(o => `<div title='` + JSON.stringify(o.result || {}, null, 4) + `'>[` + o.completeTime + '] 订单: #' + o.daySn
|
|
+ ' 出餐用时 ' + convertSecondsToMinutesSeconds(o.cookTime) + ' ' + o.tip +'</div>').join('')
|
|
: '当前没有自动出餐的订单.'}
|
|
</div>`
|
|
let logs = `运行日志: </br>
|
|
<div class="ant-alert-content">
|
|
${ele.logs.join('</br>')}
|
|
</div>`
|
|
window.appContainerNoticeBar.innerHTML = `
|
|
<div style="margin-left: 35px; margin-top: 20px; display: flex;">
|
|
<div style="flex: 1 1 0%;">
|
|
${title}
|
|
<div style="margin-top: 20px;">
|
|
${configInfo}
|
|
</div>
|
|
<div style="margin-top: 20px;">
|
|
${orderInfo}
|
|
</div>
|
|
</div>
|
|
<div style="flex: 1 1 0%;">
|
|
${submitInfo}
|
|
<div style="margin-top: 20px;">
|
|
${logs}
|
|
</div>
|
|
</div>
|
|
</div>`
|
|
} 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()
|
|
})();
|