1
0
Fork 0
UserScript/ele/auto-meal-complete.user.js

232 lines
9.7 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 getShopId() {
return parseInt(localStorage.shopId)
}
function getShopMeta() {
return {
"appVersion": "1.0.0",
"appName": "melody",
"ksid": localStorage.ksid,
"shopId": getShopId()
}
}
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": getShopId(),
"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": getShopId(),
"orderId": orderId
})
debug('订单 #' + daySn + ' 上报出餐结束.')
ele.submitOrders.push({ completeTime: new Date().toLocaleTimeString(), daySn, orderId, tip, cookTime })
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 cook of ele.processOrders) {
let daySn = cook.header.daySn
if (!cook.mealPreparationInfo || cook.mealPreparationInfo.mealComplete) continue
// 骑手到店 并且大于最小上报时长 直接上报出餐
let cookTime = (Date.now() - new Date(cook.activeTime).getTime()) / 1000
let leftTime = cook.mealPreparationInfo.commonShowTime
let deliveOnShop = cook.deliveryInfo.distTraceView.traceView.status == '骑士已到店'
|| (cook.deliveryInfo.deliveryDistance
&& !cook.deliveryInfo.deliveryDistance.endsWith('km')
&& parseInt(cook.deliveryInfo.deliveryDistance) < 100)
if (deliveOnShop && cook.mealPreparationInfo.minMealCompleteTimeCount < 0
&& leftTime < ele.autoSubmitLeftTimeWhenOnShop) {
debug('订单 #' + daySn + ' 骑手已到店.')
debug('订单 #' + daySn + ' 剩余出餐时间 ' + leftTime + 's 小于 ' + ele.autoSubmitLeftTimeWhenOnShop + 's 模拟提交出餐.')
return mealComplete(cook, cookTime, '骑手已到店')
}
if (leftTime < ele.autoSubmitLeftTime) {
// 大于最小上报时长 并且距离上报超时不足 直接上报出餐
debug('订单 #' + daySn + ' 骑手未到店.')
debug('订单 #' + daySn + ' 剩余出餐时间 ' + leftTime + 's 小于 ' + ele.autoSubmitLeftTime + 's 模拟提交出餐.')
return mealComplete(cook, cookTime, '骑手未到店')
}
debug('订单 #' + daySn + ' 还剩 ' + leftTime + 's 上报超时 将于 '
+ (leftTime - (deliveOnShop ? ele.autoSubmitLeftTimeWhenOnShop : ele.autoSubmitLeftTime)) + 's 后自动上报.')
cook.mealPreparationInfo.minMealCompleteTimeCount -= 5
cook.mealPreparationInfo.commonShowTime -= 5
}
}
async function syncOrders() {
let orders = await queryInProcessOrders()
ele.processOrders = orders.result || []
debug('更新订单数据 目前进行中订单: ' + ele.processOrders.length + '个')
}
function debug(msg) {
try {
ele.logs.push('[' + (new Date().toLocaleTimeString()) + '] ' + msg)
if (ele.logs.length > ele.maxLogLength) {
ele.logs = ele.logs.slice(ele.logs.length - ele.maxLogLength)
}
let title = `${ele.title} Version: ${ele.version} By ${ele.author}`
let configInfo = `当前配置: </br>
<div class="ant-alert-content">
骑手到店 提前 ${ele.autoSubmitLeftTimeWhenOnShop}s 出餐
骑手未到店 提前 ${ele.autoSubmitLeftTime}s 出餐</br>
</div>`
let submitInfo = `已自动出餐的订单信息: </br>
<div class="ant-alert-content">
${ele.submitOrders.length
? ele.submitOrders
.map(o => '[' + o.completeTime + '] 订单: #' + o.daySn
+ ' 出餐用时 ' + convertSecondsToMinutesSeconds(o.cookTime) + ' ' + o.tip).join('</br>')
: '当前没有自动出餐的订单.'}
</div>`
let orderInfo = `进行中的订单信息: </br>
<div class="ant-alert-content">
${ele.processOrders.length
? ele.processOrders.map(o => ' 订单: #' + o.header.daySn
+ ' 出餐状态 ' + (o.mealPreparationInfo.mealComplete ? '已出餐' : '未出餐')
+ ' 配送状态 ' + o.deliveryInfo.distTraceView.traceView.status + ''
+ (o.deliveryInfo.deliveryDistance ? ' ' + o.deliveryInfo.deliveryDistance : '')).join('</br>')
: '当前没有进行中的订单.'}
</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>
<div style="flex: 1 1 0%;">
${logs}
</div>
</div>`
} catch (error) {
console.log(msg)
}
}
async function scheduleCheck() {
await sleep(ele.checkInterval * 1000)
await checkCooking()
scheduleCheck()
}
async function scheduleSync() {
await sleep(ele.syncInterval * 1000)
await syncOrders()
scheduleCheck()
}
async function main() {
setTimeout(async () => {
window.appContainerNoticeBar = document.getElementById('app-container-notice-bar')
await scheduleSync()
await scheduleCheck()
}, 3000)
}
var SubmitOrdersKey = 'AutoMealComplete:SubmitOrders'
var ele = {
title: '饿了么自动出餐',
version: '0.0.5',
author: 'MiaoWoo',
logs: [],
checkInterval: 5,
syncInterval: 15,
maxLogLength: 20,
maxLogOrderLength: 20,
orderStatus: {},
processOrders: [],
submitOrders: JSON.parse(localStorage.getItem(SubmitOrdersKey)),
autoCheckTask: undefined,
autoSubmitLeftTime: 200,
autoSubmitLeftTimeWhenOnShop: 320,
debug: debug,
syncOrders: syncOrders,
checkCooking: checkCooking,
mealComplete: mealComplete,
}
window.ele = ele
main()
})();