这里是官方文档 airsheet,但是官方文档对一些问题并没有直接说明
- 只支持标准 JS(ECMAScript),setTimeout 这类浏览器实现的 Api 无法使用
- 不支持可选链,可以通过
transform-optional-chaining
转换(babel 插件,下同)
- 不完全支持对象属性简写(不支持对象中的方法简写),通过
transform-shorthand-properties
转换
- 请求不支持端口,故非默认端口的任务只能放弃
- 请求头
content-type
中不能使用 charset=UTF-8
,例如 application/json;charset=UTF-8
,而应该使用 application/json
console.log
不支持输出 Error
等对象(输出为 {}),需要通过 JSON.stringify
/ error.message
转为字符串。
- 不支持 new Promise (文档未提及)
- 不支持 new Function (EvalError:Code generation from strings disallowed for this context)
- 不支持 Array.from 的第二个参数,不管写的什么,都是填充
{}
附上阿里云盘的成品代码,(还有移动云盘的就不贴出来了,太长了,可以在下方开源地址查看)
只支持最简单的功能,签到,设备间,每日任务。没有推送,也没有随机执行。没有多余的配置(阿里没福利了,这个相当于 49 年入某军,所以这里只是抛砖引玉)
特性
网上,甚至本站已经有那么多 wps 版的阿里云盘,为啥还发了这一篇。
- 截至目前,唯一一个支持设备间的 wps(其他版本 github 已有新的),而目前 设备间是唯一拥有永久空间的任务
- 截至目前,唯一一个支持每日任务的 wps(其他版本 github 已有新的)
WPS 运行方式
使用只需要把 refresh_token 放 A 列就行了(支持多账号,放多行)
源代码是 typescript 写的,就不放这了,开源地址 https://github.com/asunajs/asign
function randomHex(length) {
return Array.from({
length: length
}).map(()=>Math.floor(Math.random() * 16).toString(16)).join("");
}
function createLogger(options) {
const wrap = (type, ...args)=>{
if (options && options.pushData) {
const msg = args.reduce((str, cur)=>`${str} ${cur}`, "").substring(1);
options.pushData.push({
msg: msg,
type: type,
date: new Date()
});
}
console[type](...args);
};
return {
info: (...args)=>wrap("info", ...args),
error: (...args)=>wrap("error", ...args),
debug: (...args)=>wrap("info", ...args)
};
}
function getHostname(url) {
return url.split("/")[2].split("?")[0];
}
function _send({ logger, http }, name = "自定义消息", options) {
try {
const data = http.fetch(options);
const { errcode, code, err } = data;
if (errcode || err || ![
0,
200,
undefined
].some((c)=>code === c)) {
return logger.error(`${name}发送失败`, JSON.stringify(data));
}
logger.info(`${name}已发送!`);
} catch (error) {
logger.info(`${name}发送失败: ${error.message}`);
logger.error(error);
}
}
function pushCustomPost({ logger, http }, apiTemplate, title, text) {
try {
if (!apiTemplate || !apiTemplate.url) return;
const { data, timeout, headers } = apiTemplate;
const method = apiTemplate.method.toUpperCase() || "POST";
const options = {
method: method,
timeout: timeout,
headers: headers,
url: ""
};
options.url = apiTemplate.url.replace("{title}", encodeURIComponent(title)).replace("{text}", encodeURIComponent(text));
// 处理data
if (data && Object.keys(data).length) {
const str = JSON.stringify(data).replace(/{title}/g, title).replace(/{text}/g, text).replace(/\n/g, "\\n").replace(/\r/g, "\\r");
options.data = JSON.parse(str);
}
http.fetch(options);
logger.info(`自定义消息已发送!`);
} catch (error) {
logger.info(`自定义消息发送失败: ${error.message}`);
logger.error(error);
}
}
function pushplus(apiOption, { token }, title, text) {
return _send(apiOption, "pushplus", {
url: `http://www.pushplus.plus/send`,
method: "POST",
headers: {
"content-type": "application/json"
},
data: {
token: token,
title: title,
content: text
}
});
}
function serverChan(apiOption, { token }, title, text) {
return _send(apiOption, "Server酱", {
url: `https://sctapi.ftqq.com/${token}.send`,
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
data: {
text: title,
desp: text
}
});
}
/**
*
* @description https://developer.work.weixin.qq.com/document/path/90665#corpid
*/ function workWeixin(apiOption, { msgtype = "text", touser = "@all", agentid, corpid, corpsecret }, title, text) {
const { access_token } = apiOption.http.fetch({
url: `https://qyapi.weixin.qq.com/cgi-bin/gettoken`,
method: "POST",
data: {
corpid: corpid,
corpsecret: corpsecret
},
headers: {
"Content-Type": "application/json"
}
});
return _send(apiOption, "企业微信推送", {
url: `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${access_token}`,
method: "POST",
headers: {
"Content-Type": "application/json"
},
data: {
touser: touser,
msgtype: msgtype,
agentid: agentid,
[msgtype]: {
content: `${title}\n\n${text}`
}
}
});
}
function getCookieJSON(cookie) {
if (!cookie) return {};
const matchArray = cookie.match(/([^;=]+)(?:=([^;]*))?/g);
if (!matchArray) return {};
return matchArray.reduce((pre, cur)=>{
const [key, value] = cur.trim().split("=");
pre[key] = value;
return pre;
}, {});
}
function getCookieString(obj) {
const string = Object.keys(obj).reduce((pre, cur)=>pre + `${cur}=${obj[cur]}; `, "");
return string.substring(0, string.length - 2 || 0);
}
function getSetCookieValue(setCookieArray) {
let cookieStr = "";
setCookieArray.filter(Boolean).forEach((setCookie)=>cookieStr += setCookie.split("; ")[0] + "; ");
if (cookieStr.endsWith("; ")) {
cookieStr = cookieStr.substring(0, cookieStr.length - 2 || 0);
}
return cookieStr;
}
function getCookie(cookie = "", setCookie) {
if (!Array.isArray(setCookie)) setCookie = [
setCookie
];
if (!setCookie || setCookie.length === 0) return cookie;
return getCookieString({
...getCookieJSON(cookie),
...getCookieJSON(getSetCookieValue(setCookie))
});
}
function createCookieJar(cookie = "") {
let _cookie;
_cookie = cookie;
return {
getCookieString: function() {
return _cookie;
},
setCookie: function(rawCookie) {
return getCookie(_cookie, rawCookie);
},
toJSON: function() {
return getCookieJSON(_cookie);
}
};
}
function createRequest({ cookieJar, getHeaders }) {
const get = (url, options)=>{
var _resp_;
const resp = HTTP.get(url, {
headers: {
...getHeaders(url),
...options && options.headers
}
});
cookieJar && cookieJar.setCookie(resp.headers["set-cookie"]);
if (!options) return resp.json();
return options.native ? resp : (_resp_ = resp[options.responseType || "json"]) === null || _resp_ === void 0 ? void 0 : _resp_.call(resp);
};
const post = (url, data, options)=>{
var _resp_;
const resp = HTTP.post(url, JSON.stringify(data), {
headers: {
...getHeaders(url),
...options && options.headers
}
});
cookieJar && cookieJar.setCookie(resp.headers["set-cookie"]);
if (!options) return resp.json();
return options.native ? resp : (_resp_ = resp[options.responseType || "json"]) === null || _resp_ === void 0 ? void 0 : _resp_.call(resp);
};
return {
get: get,
post: post
};
}
function getPushConfig() {
const usedRange = Application.Sheets.Item("推送").UsedRange, cells = usedRange.Columns.Cells, columnEnd = Math.min(50, usedRange.ColumnEnd), rowEnd = Math.min(50, usedRange.RowEnd);
const pushConfig = {};
for(let option = usedRange.Column; option <= columnEnd; option++){
const t = {}, item = cells.Item(option);
if (!item.Text) continue;
pushConfig[item.Text] = t;
for(let kv = 1; kv <= rowEnd; kv++){
const key = item.Offset(kv).Text;
if (!key.trim()) continue;
t[key] = valueHandle(item.Offset(kv, 1).Text.trim());
}
}
const base = pushConfig.base;
if (!base) return pushConfig;
delete pushConfig.base;
return {
...pushConfig,
...base
};
function valueHandle(value) {
if (value === "TRUE" || value === "是") return true;
if (value === "FALSE" || value === "否") return false;
return value;
}
}
function email({ logger }, email, title, text) {
try {
if (!email || !email.pass || !email.from || !email.host) return;
const port = email.port || 465, toUser = email.to || email.from;
const mailer = SMTP.login({
host: email.host,
port: port,
secure: port === 465,
username: email.from,
password: email.pass
});
mailer.send({
from: `${title} <${email.from}>`,
to: toUser,
subject: title,
text: text.replace(/\n/g, "\r\n")
});
logger.info(`邮件消息已发送`);
} catch (error) {
logger.error(`邮件消息发送失败`, error.message);
}
}
function sendNotify(op, data, title, text) {
const cbs = {
pushplus: pushplus,
serverChan: serverChan,
workWeixin: workWeixin,
email: email
};
for (const [name, d] of Object.entries(data)){
const cb = cbs[name];
if (!cb) continue;
cb(op, d, title, text);
}
}
function createApi(http) {
const memberUrl = "https://member.aliyundrive.com";
const aliyundriveUrl = "https://api.aliyundrive.com";
const authUrl = "https://auth.aliyundrive.com";
const apiUrl = "https://api.alipan.com";
function refreshToken(refreshToken) {
return http.post(`${aliyundriveUrl}/token/refresh`, {
refresh_token: refreshToken
});
}
function getAccessToken(refreshToken) {
return http.post(`${authUrl}/v2/account/token`, {
refresh_token: refreshToken,
grant_type: "refresh_token"
});
}
function signInList() {
return http.post(`${memberUrl}/v2/activity/sign_in_list?_rx-s=mobile`, {
"_rx-s": "mobile"
});
}
function signIn() {
return http.post(`${memberUrl}/v2/activity/sign_in_info?_rx-s=mobile`, {});
}
function signInReward(signInDay) {
return http.post(`${memberUrl}/v1/activity/sign_in_reward?_rx-s=mobile`, {
signInDay: signInDay
});
}
function signInTaskReward(signInDay) {
return http.post(`${memberUrl}/v2/activity/sign_in_task_reward`, {
signInDay: signInDay
});
}
function updateDeviceExtras() {
return http.post(`${apiUrl}/users/v1/users/update_device_extras`, {
albumAccessAuthority: true,
albumBackupLeftFileTotal: 0,
albumBackupLeftFileTotalSize: 0,
albumFile: 0,
autoBackupStatus: true,
// totalSize: 0,
// useSize: 0,
brand: "xiaomi",
systemVersion: "Android 13"
});
}
function createSession(deviceId, refreshToken, pubKey, deviceName, modelName) {
return http.post(`https://api.alipan.com/users/v1/users/device/create_session`, {
deviceName: deviceName,
modelName: modelName,
nonce: "0",
pubKey: pubKey,
refreshToken: refreshToken
}, {
headers: {
"x-device-id": deviceId
}
});
}
function getDeviceAppletList() {
return http.post(`${apiUrl}/adrive/v2/backup/device_applet_list_summary`, {});
}
function getDeviceList() {
return http.post(`${apiUrl}/users/v2/users/device_list`, {});
}
function getAlbumsInfo() {
return http.post(`${aliyundriveUrl}/adrive/v1/user/albums_info`, {});
}
function getDeviceRoomList() {
return http.post(`https://user.aliyundrive.com/v1/deviceRoom/listDevice`, {});
}
function getDeviceRoomRewardInfoToday() {
return http.post(`${memberUrl}/v1/deviceRoom/rewardInfoToday`, {});
}
function getDeviceRoomRewardEnergy(deviceId) {
return http.post(`${memberUrl}/v1/deviceRoom/rewardEnergy`, {
deviceId: deviceId
});
}
function createFile(deviceId, driveId) {
const size = Math.floor(Math.random() * 30000);
return http.post(`${aliyundriveUrl}/adrive/v2/biz/albums/file/create`, {
drive_id: driveId,
part_info_list: [
{
part_number: 1,
part_size: size
}
],
parent_file_id: "root",
name: Math.floor(Math.random() * 100000000) + ".jpg",
type: "file",
check_name_mode: "auto_rename",
size: size,
create_scene: "album_autobackup",
hidden: false,
content_type: "image/jpeg"
}, {
headers: {
"x-device-id": deviceId
}
});
}
function completeUpload(deviceId, driveId, fileId, uploadId) {
return http.post(`${aliyundriveUrl}/v2/file/complete`, {
drive_id: driveId,
upload_id: uploadId,
file_id: fileId
}, {
headers: {
"x-device-id": deviceId
}
});
}
function completeAlbumsUpload(deviceId, driveId, fileId, contentHash) {
return http.post(`${aliyundriveUrl}/adrive/v2/biz/albums/file/complete`, {
drive_id: driveId,
file_id: fileId,
content_hash: contentHash,
content_hash_name: "sha1"
}, {
headers: {
"x-device-id": deviceId
}
});
}
function deleteFile(deviceId, driveId, fileId) {
return http.post(`${apiUrl}/adrive/v4/batch`, {
requests: [
{
body: {
drive_id: driveId,
file_id: fileId
},
id: fileId,
method: "POST",
url: "/file/delete"
}
],
resource: "file"
}, {
headers: {
"x-device-id": deviceId
}
});
}
function home() {
return http.post(`${aliyundriveUrl}/apps/v2/users/home/widgets`, {});
}
return {
home: home,
signIn: signIn,
signInList: signInList,
createFile: createFile,
deleteFile: deleteFile,
refreshToken: refreshToken,
signInReward: signInReward,
getAlbumsInfo: getAlbumsInfo,
createSession: createSession,
getDeviceList: getDeviceList,
getAccessToken: getAccessToken,
completeUpload: completeUpload,
signInTaskReward: signInTaskReward,
getDeviceRoomList: getDeviceRoomList,
updateDeviceExtras: updateDeviceExtras,
getDeviceAppletList: getDeviceAppletList,
completeAlbumsUpload: completeAlbumsUpload,
getDeviceRoomRewardEnergy: getDeviceRoomRewardEnergy,
getDeviceRoomRewardInfoToday: getDeviceRoomRewardInfoToday
};
}
function request($, api, name, ...args) {
try {
const { success, message, result } = api(...args);
if (!success) {
$.logger.error(`${name}失败`, message);
} else {
return result;
}
} catch (error) {
$.logger.error(`${name}异常`, error.message);
}
return {};
}
function refreshToken($, token) {
try {
const data = $.api.getAccessToken(token);
if (!data.access_token) {
$.logger.error("获取 access_token 失败", JSON.stringify(data));
return;
}
return data;
} catch (error) {
$.logger.error(`获取 access_token 异常`, error.message);
}
}
function createDeviceApi($, refreshToken, deviceId) {
try {
const { success } = $.api.createSession(deviceId, refreshToken, randomHex(32), "XiaoMi 14Pro", "xiaomi");
if (!success) {
$.logger.error(`创建虚拟设备${deviceId}失败`);
return false;
}
$.logger.info(`创建虚拟设备成功`);
return true;
} catch (error) {
$.logger.error(`创建虚拟设备${deviceId}异常`, error.message);
}
return false;
}
function getDeviceRoomListApi($) {
try {
const { items } = $.api.getDeviceRoomList();
if (!items) {
$.logger.error(`获取设备间列表失败`);
return;
}
return items;
} catch (error) {
$.logger.error(`获取设备间列表异常`, error.message);
}
return;
}
function getDeviceRoomRewardInfo($) {
return request($, $.api.getDeviceRoomRewardInfoToday, "获取设备间领取信息");
}
function getAlbumsDriveId($) {
try {
const { code, message, data } = $.api.getAlbumsInfo();
if (code !== "200") {
$.logger.error(`获取相册文件夹失败`, message);
return;
}
return data.driveId;
} catch (error) {
$.logger.error(`获取相册文件夹异常`, error.message);
}
}
function createDevice($) {
const deviceRoom = getDeviceRoomListApi($);
const needNum = 5 - deviceRoom.length;
if (needNum <= 0) {
return;
}
$.logger.info(`需要创建${needNum}个虚拟设备`);
for(let i = 0; i < needNum; i++){
createDeviceApi($, $.DATA.refreshToken, randomHex(64));
}
}
function uploadFile($, deviceId, driveId) {
try {
const { file_id, upload_id } = $.api.createFile(deviceId, driveId);
if (file_id) {
$.sleep(1000);
$.api.completeUpload(deviceId, driveId, file_id, upload_id);
$.api.completeAlbumsUpload(deviceId, driveId, file_id, "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709");
return {
file_id: file_id,
upload_id: upload_id
};
}
$.logger.error(`${deviceId}上传文件失败`);
} catch (error) {
$.logger.error(`上传文件异常`, error.message);
}
return {};
}
function deviceRoomListHandle(deviceRooms) {
const nofinishDevices = new Set();
const rewardEnergys = new Set();
let okNum = 0;
for (const { canCollectEnergy, id, gmtCollectEnergy } of deviceRooms){
if (!canCollectEnergy && new Date(gmtCollectEnergy).getDate() !== new Date().getDate()) {
nofinishDevices.add(id);
} else if (canCollectEnergy) {
rewardEnergys.add(id);
} else {
okNum++;
}
}
return {
nofinishDevices: Array.from(nofinishDevices),
rewardEnergys: Array.from(rewardEnergys),
okNum: okNum
};
}
function getDeviceRoomRewardApi($, id) {
return request($, $.api.getDeviceRoomRewardEnergy, `领取${id}设备空间`, id).size;
}
function deleteFileApi($, deviceId, driveId, fileId) {
try {
$.api.deleteFile(deviceId, driveId, fileId);
} catch (error) {
$.logger.error(`删除文件${fileId}异常`, error.message);
}
}
function deleteFiles($, needDeleteFiles, driveId) {
for (const [fileId, deviceId] of needDeleteFiles){
deleteFileApi($, deviceId, driveId, fileId);
$.sleep(1000);
}
}
function deviceRoomTask($) {
const { rewardCountToday, rewardTotalSize } = getDeviceRoomRewardInfo($);
if (rewardCountToday >= 5) {
$.logger.info(`今日已领取${rewardCountToday}次设备间空间,历史总共${rewardTotalSize}M`);
return;
}
const driveId = getAlbumsDriveId($);
if (!driveId) {
$.logger.error(`未获取到文件夹故跳过执行`);
return;
}
const needDeleteFiles = new Map();
createDevice($);
while(_deviceRoomTask()){
$.sleep(2000);
}
deleteFiles($, needDeleteFiles, driveId);
/**
*
* @returns 是否继续运行
*/ function _deviceRoomTask() {
const items = getDeviceRoomListApi($);
if (!items) {
$.logger.error(`无法获取虚拟设备,跳过执行`);
return false;
}
if (items.length === 0) {
$.logger.error(`无法创建虚拟设备,跳过执行`);
return false;
}
const { nofinishDevices, rewardEnergys, okNum } = deviceRoomListHandle(items);
if (okNum >= 5) {
return false;
}
let tempNum = 0;
for (const deviceId of rewardEnergys){
const size = getDeviceRoomRewardApi($, deviceId);
if (size) {
$.logger.info(`领取设备间成功,获得${size}M`);
tempNum++;
}
// 防止出现有超过 5 个设备间可领取
if (tempNum + okNum >= 5) break;
$.sleep(1000);
}
for (const deviceId of nofinishDevices){
const { file_id } = uploadFile($, deviceId, driveId) || {};
file_id && needDeleteFiles.set(file_id, deviceId);
$.sleep(1000);
}
return true;
}
}
function signIn($) {
const { rewards, signInDay } = request($, $.api.signIn, "签到");
if (!rewards) {
return;
}
for (const { status, type } of rewards){
if (status !== "finished") {
continue;
}
switch(type){
case "dailySignIn":
request($, $.api.signInReward, "领取签到奖励", signInDay);
break;
case "dailyTask":
request($, $.api.signInTaskReward, "领取每日任务奖励", signInDay);
break;
default:
break;
}
}
}
function getDeviceList($) {
try {
const data = $.api.getDeviceAppletList();
if (!data.deviceItems) {
$.logger.error("获取设备信息失败", JSON.stringify(data));
return;
}
if (data.deviceItems.length === 0) {
$.logger.error("获取到的设备列表未空");
return;
}
return data.deviceItems;
} catch (error) {
$.logger.error(`获取设备信息异常`, error.message);
}
}
function getDevice($) {
const devices = getDeviceList($);
if (!devices) return;
const device = devices.find(({ deviceId })=>deviceId);
return device;
}
function signInTask($) {
const { rewards } = request($, $.api.signIn, "签到");
if (rewards.find(({ type, status })=>type === "dailyTask" && (status === "verification" || status === "finished"))) {
return;
}
const { deviceId, backupViews } = getDevice($) || {};
if (!deviceId) {
$.logger.error(`未获取到设备 id,跳过每日任务执行`);
return;
}
$.DATA.deviceId = deviceId;
const backupView = backupViews.find(({ view })=>view === "album");
if (!backupView) {
$.logger.error(`未获取到文件夹 id,跳过每日任务执行`);
return;
}
request($, $.api.updateDeviceExtras, "上报备份");
const needDeleteFiles = new Map();
for(let i = 0; i < 10; i++){
const { file_id } = uploadFile($, deviceId, backupView.driveId);
file_id && needDeleteFiles.set(file_id, deviceId);
$.sleep(2000);
}
$.DATA.afterTask.push(()=>deleteFiles($, needDeleteFiles, backupView.driveId));
}
function printSignInInfo($) {
const { rewards } = request($, $.api.signIn, "签到");
if (!rewards) {
return;
}
const statusMap = {
unfinished: "未完成",
finished: "未领取奖励",
verification: "已领取奖励",
notStart: "未开始"
};
rewards.forEach(({ remind, name, status })=>{
$.logger.info(`${remind}/${name}/${statusMap[status]}`);
});
}
function run($) {
const taskList = [
deviceRoomTask,
signInTask,
signIn,
printSignInInfo
];
for (const task of taskList){
task($);
$.sleep(1000);
}
for (const task of $.DATA.afterTask){
task();
$.sleep(1000);
}
}
function getXSignature() {
return "1176d75ed29c9453de0c9848e47be166e56d5cd57dd6743f71ced1f048e73d847c2847c71f8b5235105456d34054a9b30c8e364e5fee4e4cfa644cc07a45a92a01";
}
function main(index, ASIGN_ALIPAN_TOKEN, option) {
if (!ASIGN_ALIPAN_TOKEN) return;
const logger = createLogger({
pushData: option && option.pushData
});
const DATA = {
deviceId: ActiveSheet.Columns("C").Rows(index).Value,
afterTask: [],
refreshToken: ""
};
let accessToken;
function getHeaders() {
return {
"content-type": "application/json",
referer: "https://alipan.com/",
origin: "https://alipan.com/",
"x-canary": "client=Android,app=adrive,version=v5.3.0",
"user-agent": "AliApp(AYSD/5.3.0) com.alicloud.databox/34760760 Channel/36176727979800@rimet_android_5.3.0 language/zh-CN /Android Mobile/Mi 6X",
"x-device-id": DATA.deviceId,
authorization: accessToken ? `Bearer ${accessToken}` : "",
"x-signature": getXSignature()
};
}
const $ = {
api: createApi(createRequest({
getHeaders: getHeaders
})),
logger: logger,
DATA: DATA,
sleep: Time.sleep
};
const rtData = refreshToken($, ASIGN_ALIPAN_TOKEN.trim());
if (!rtData) return;
DATA.refreshToken = rtData.refresh_token;
accessToken = rtData.access_token;
DATA.deviceId = rtData.device_id;
$.logger.info(`--------------`);
$.logger.info(`你好${rtData.nick_name || rtData.user_name}`);
ActiveSheet.Columns("A").Rows(index).Value = rtData.refresh_token;
ActiveSheet.Columns("B").Rows(index).Value = rtData.nick_name || rtData.user_name;
run($);
ActiveSheet.Columns("C").Rows(index).Value = DATA.deviceId;
}
const columnA = ActiveSheet.Columns("A");
// 获取当前工作表的使用范围
const usedRange = ActiveSheet.UsedRange;
const len = usedRange.Row + usedRange.Rows.Count - 1;
const pushData = [];
for(let i = 1; i <= len; i++){
const cell = columnA.Rows(i);
if (cell.Text) {
console.log(`执行第 ${i} 行`);
main(i, cell.Text, {
pushData: pushData
});
}
}
const pushConfig = getPushConfig();
if (pushData.length && pushConfig) {
if (!(pushConfig.onlyError && !pushData.some((el)=>el.type === "error"))) {
const msg = pushData.map((m)=>`[${m.type} ${m.date.toLocaleTimeString()}]${m.msg}`).join("\n");
msg && sendNotify({
logger: createLogger(),
http: {
fetch: (op)=>{
op.data && typeof op.data !== "string" && (op.body = JSON.stringify(op.data));
return HTTP.fetch(op.url, op).json();
}
}
}, pushConfig, pushConfig.title || "asign 运行推送", msg);
}
}
推送
在原表格的基础上新建一个推送表格(一定要叫推送)
表格如图所示,每两列为一个配置单元(每个单元之间运行空列)
单元的第首行首列放置推送名,如 email,pushplus。base 比较特殊。
除第一行外,每个单元第一列为配置名称,第二列为值,中间允许空行(如email所示)
注意
- 不用的配置就不要写第一行,或者直接删除。
- 运行时点击工作表1(或者其他名字,反正是你放token配置的那个表,不是推送配置),然后再运行代码,不然一堆报错。
- 更多推送参考本地运行填写
本地运行
(包括云函数/青龙面板也能举一反三)
使用 npm 安装包 @asunajs/alipan,然后创建一个 js 文件,内容如下:
const alipan = require('@asunajs/alipan');
const { resolve } = require('path');
(async () => {
await alipan.run(resolve(process.cwd(), './asign.json')); // 按需修改路径
})();
在 js 的旁边创建一个 asign.json 文件,不创建就会使用环境变量 ASIGN_ALIPAN_TOKEN (多个用 token @ 分开),
推送目前支持:email 邮箱, workWeixin 企业微信(app,非 bot),serverChan 等。按需配置即可(不要的记得删除)。onlyError 这一项配置为 true 表示,仅出现 error 级别的日志时才推送
{
"alipan": [
{
"token": "xxxxxxxxxxxxxxxxx"
},
{
"token": "xxxxxxxxxxxxxxxxxxxxxxxxx"
}
],
"message": {
"email": {
"pass": "xxxxxxxxxxxx",
"from": "xxxxxx@163.com",
"host": "smtp.163.com",
"to": "xxx@mht.com"
},
"workWeixin": {
"corpid": "ww4xxxxxxxxxxxxc12",
"corpsecret": "NBMUW4fxxxxxxxxxxxxxxxxxxxxxu2roo",
"touser": "cxxxxxxxxxt",
"agentid": 1008611266666,
"msgtype": "markdown"
},
"serverChan": {
"token": ""
},
"pushplus": {
"token": ""
},
"onlyError": false
}
}