polycopy 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +60 -127
- package/dist/index.js +292 -150
- package/dist/{init-Bs-N_AZi.js → process-lock-BbFF27h8.js} +183 -220
- package/package.json +4 -5
- package/dist/config.js +0 -13
- package/dist/paths-CEjGES8j.js +0 -20
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
const readline = require("readline");
|
|
3
3
|
const fsExtra = require("fs-extra");
|
|
4
4
|
const path = require("path");
|
|
5
|
-
const paths = require("./paths-CEjGES8j.js");
|
|
6
5
|
const grammy = require("grammy");
|
|
7
|
-
const
|
|
8
|
-
const
|
|
6
|
+
const child_process = require("child_process");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
9
9
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
10
|
function _interopNamespace(e) {
|
|
11
11
|
if (e && e.__esModule) return e;
|
|
@@ -27,13 +27,21 @@ function _interopNamespace(e) {
|
|
|
27
27
|
const readline__namespace = /* @__PURE__ */ _interopNamespace(readline);
|
|
28
28
|
const fsExtra__default = /* @__PURE__ */ _interopDefault(fsExtra);
|
|
29
29
|
const path__default = /* @__PURE__ */ _interopDefault(path);
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const fs__namespace = /* @__PURE__ */ _interopNamespace(fs);
|
|
31
|
+
const os__default = /* @__PURE__ */ _interopDefault(os);
|
|
32
|
+
const USER_DATA_DIR = path__default.default.join(os__default.default.homedir(), ".polycopy");
|
|
33
|
+
const PID_DIR = USER_DATA_DIR;
|
|
34
|
+
const LOGS_DIR = path__default.default.join(USER_DATA_DIR, "logs");
|
|
35
|
+
const LOCALSTORAGE_DIR = path__default.default.join(USER_DATA_DIR, "localstorage");
|
|
36
|
+
const PID_FILE = path__default.default.join(USER_DATA_DIR, "polycopy.pid");
|
|
37
|
+
const INSTALL_ROOT = path__default.default.resolve(__dirname, "..");
|
|
38
|
+
const DIST_INDEX = path__default.default.join(INSTALL_ROOT, "dist", "index.js");
|
|
39
|
+
const DIST_CLI = path__default.default.join(INSTALL_ROOT, "dist", "cli.js");
|
|
32
40
|
class LocalStorage {
|
|
33
41
|
dbPath;
|
|
34
42
|
data;
|
|
35
43
|
constructor(dbFileName) {
|
|
36
|
-
this.dbPath = path__default.default.join(
|
|
44
|
+
this.dbPath = path__default.default.join(LOCALSTORAGE_DIR, `${dbFileName}.json`);
|
|
37
45
|
if (fsExtra__default.default.existsSync(this.dbPath)) {
|
|
38
46
|
this.data = fsExtra__default.default.readJSONSync(this.dbPath);
|
|
39
47
|
} else {
|
|
@@ -65,153 +73,12 @@ function hasPolyMarketConfig(config) {
|
|
|
65
73
|
}
|
|
66
74
|
function hasTradingConfig(config) {
|
|
67
75
|
const trading = config.trading;
|
|
68
|
-
|
|
76
|
+
const amountValue = trading?.amountMode === "fixed" ? trading.amountFixedValue : trading?.amountMode === "percentage" ? trading.amountPercentageValue : void 0;
|
|
77
|
+
return !!(trading?.amountMode && amountValue !== void 0 && amountValue !== null && trading?.buyPrice !== void 0 && trading?.buyPrice !== null && trading?.sellPrice !== void 0 && trading?.sellPrice !== null);
|
|
69
78
|
}
|
|
70
79
|
function canAutoTrade(config) {
|
|
71
80
|
return hasPolyMarketConfig(config) && hasTradingConfig(config);
|
|
72
81
|
}
|
|
73
|
-
const formatTimestamp = () => {
|
|
74
|
-
const now = /* @__PURE__ */ new Date();
|
|
75
|
-
const pad = (n) => n.toString().padStart(2, "0");
|
|
76
|
-
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
77
|
-
};
|
|
78
|
-
const customFormat = winston__default.default.format.printf(({ message }) => {
|
|
79
|
-
return `[${formatTimestamp()}] ${message}`;
|
|
80
|
-
});
|
|
81
|
-
const fileTransport = new DailyRotateFile__default.default({
|
|
82
|
-
dirname: paths.LOGS_DIR,
|
|
83
|
-
filename: "%DATE%",
|
|
84
|
-
extension: ".log",
|
|
85
|
-
datePattern: "YYYY-MM-DD",
|
|
86
|
-
// 按天创建文件
|
|
87
|
-
maxSize: "2m",
|
|
88
|
-
// 超过 2MB 自动轮转(会在文件名后加序号)
|
|
89
|
-
maxFiles: 10,
|
|
90
|
-
// 最多 10 个文件
|
|
91
|
-
format: customFormat,
|
|
92
|
-
// 不创建 current.log 符号链接
|
|
93
|
-
createSymlink: false
|
|
94
|
-
});
|
|
95
|
-
const winstonLogger = winston__default.default.createLogger({
|
|
96
|
-
level: "info",
|
|
97
|
-
transports: [fileTransport]
|
|
98
|
-
});
|
|
99
|
-
class Logger {
|
|
100
|
-
initialized = false;
|
|
101
|
-
flushing = false;
|
|
102
|
-
/**
|
|
103
|
-
* 初始化日志模块(输出日志路径信息)
|
|
104
|
-
*/
|
|
105
|
-
init() {
|
|
106
|
-
if (this.initialized) return;
|
|
107
|
-
this.initialized = true;
|
|
108
|
-
console.log(`
|
|
109
|
-
📁 日志目录: ${paths.LOGS_DIR}`);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* 获取带时间前缀的消息(用于控制台输出)
|
|
113
|
-
*/
|
|
114
|
-
withTimestamp(message) {
|
|
115
|
-
return `[${formatTimestamp()}] ${message}`;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* 写入日志(同时输出到控制台和文件)
|
|
119
|
-
*/
|
|
120
|
-
write(message, addNewLine = true) {
|
|
121
|
-
const consoleMsg = addNewLine ? `
|
|
122
|
-
${this.withTimestamp(message)}` : this.withTimestamp(message);
|
|
123
|
-
console.log(consoleMsg);
|
|
124
|
-
winstonLogger.info(message);
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* 输出信息日志(前后自动添加空行)
|
|
128
|
-
*/
|
|
129
|
-
info(message, ...args) {
|
|
130
|
-
const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
|
|
131
|
-
this.write(formatted);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* 输出成功日志(前后自动添加空行)
|
|
135
|
-
*/
|
|
136
|
-
success(message, ...args) {
|
|
137
|
-
const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
|
|
138
|
-
this.write(`✅ ${formatted}`);
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* 输出警告日志(前后自动添加空行)
|
|
142
|
-
*/
|
|
143
|
-
warning(message, ...args) {
|
|
144
|
-
const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
|
|
145
|
-
this.write(`⚠️ ${formatted}`);
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* 输出错误日志(前后自动添加空行)
|
|
149
|
-
*/
|
|
150
|
-
error(message, ...args) {
|
|
151
|
-
const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
|
|
152
|
-
console.error(`
|
|
153
|
-
${this.withTimestamp(`❌ ${formatted}`)}`);
|
|
154
|
-
winstonLogger.error(`❌ ${formatted}`);
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* 输出章节标题(前后自动添加空行)
|
|
158
|
-
*/
|
|
159
|
-
section(title) {
|
|
160
|
-
this.write(`=== ${title} ===`);
|
|
161
|
-
}
|
|
162
|
-
/**
|
|
163
|
-
* 输出列表项(不添加空行,用于连续输出)
|
|
164
|
-
*/
|
|
165
|
-
item(message, indent = 0) {
|
|
166
|
-
const indentStr = " ".repeat(indent);
|
|
167
|
-
const formatted = `${indentStr}${message}`;
|
|
168
|
-
console.log(this.withTimestamp(formatted));
|
|
169
|
-
winstonLogger.info(formatted);
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* 输出普通文本(不添加空行,用于连续输出)
|
|
173
|
-
*/
|
|
174
|
-
text(message, ...args) {
|
|
175
|
-
const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
|
|
176
|
-
console.log(this.withTimestamp(formatted));
|
|
177
|
-
winstonLogger.info(formatted);
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* 输出空行
|
|
181
|
-
*/
|
|
182
|
-
blank() {
|
|
183
|
-
console.log("");
|
|
184
|
-
}
|
|
185
|
-
/**
|
|
186
|
-
* 输出带前缀的信息(不添加空行)
|
|
187
|
-
* 用于在同一组日志中输出多行信息
|
|
188
|
-
*/
|
|
189
|
-
line(prefix, message, ...args) {
|
|
190
|
-
const formatted = args.length > 0 ? ` ${prefix} ${message} ${args.join(" ")}` : ` ${prefix} ${message}`;
|
|
191
|
-
console.log(this.withTimestamp(formatted));
|
|
192
|
-
winstonLogger.info(formatted);
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* 刷新日志输出(用于进程退出前)
|
|
196
|
-
*/
|
|
197
|
-
async flush(timeoutMs = 1e3) {
|
|
198
|
-
if (this.flushing) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
this.flushing = true;
|
|
202
|
-
await new Promise((resolve) => {
|
|
203
|
-
const timeout = setTimeout(() => {
|
|
204
|
-
resolve();
|
|
205
|
-
}, timeoutMs);
|
|
206
|
-
winstonLogger.once("finish", () => {
|
|
207
|
-
clearTimeout(timeout);
|
|
208
|
-
resolve();
|
|
209
|
-
});
|
|
210
|
-
winstonLogger.end();
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
const logger = new Logger();
|
|
215
82
|
let globalRl = null;
|
|
216
83
|
function getReadlineInterface() {
|
|
217
84
|
if (!globalRl) {
|
|
@@ -222,6 +89,12 @@ function getReadlineInterface() {
|
|
|
222
89
|
}
|
|
223
90
|
return globalRl;
|
|
224
91
|
}
|
|
92
|
+
function closeReadlineInterface() {
|
|
93
|
+
if (globalRl) {
|
|
94
|
+
globalRl.close();
|
|
95
|
+
globalRl = null;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
225
98
|
function question(query) {
|
|
226
99
|
const rl = getReadlineInterface();
|
|
227
100
|
return new Promise((resolve) => {
|
|
@@ -234,12 +107,12 @@ async function validateBotToken(botToken) {
|
|
|
234
107
|
await bot.api.getMe();
|
|
235
108
|
return true;
|
|
236
109
|
} catch (error) {
|
|
237
|
-
|
|
110
|
+
console.log(`❌ Bot Token 验证失败: ${error}`);
|
|
238
111
|
return false;
|
|
239
112
|
}
|
|
240
113
|
}
|
|
241
114
|
async function runConfigWizard(existingConfig) {
|
|
242
|
-
|
|
115
|
+
console.log("\n=== PolyMarket 跟单机器人配置向导 ===");
|
|
243
116
|
const currentTelegram = existingConfig?.telegram || configLocal.getItem("telegram");
|
|
244
117
|
const currentPolymarket = existingConfig?.polymarket || configLocal.getItem("polymarket");
|
|
245
118
|
const currentTrading = existingConfig?.trading || configLocal.getItem("trading");
|
|
@@ -247,7 +120,7 @@ async function runConfigWizard(existingConfig) {
|
|
|
247
120
|
while (!botToken) {
|
|
248
121
|
const currentToken = currentTelegram?.botToken || "";
|
|
249
122
|
const currentDisplay = currentToken ? `${currentToken.substring(0, 10)}...` : "未配置";
|
|
250
|
-
const hint = currentToken ? "回车保持,或输入新值" : "
|
|
123
|
+
const hint = currentToken ? "回车保持,或输入新值" : "必填,前往 https://t.me/BotFather 申请";
|
|
251
124
|
const input = await question(
|
|
252
125
|
` - Bot Token [${currentDisplay}] (${hint}): `
|
|
253
126
|
);
|
|
@@ -258,6 +131,7 @@ async function runConfigWizard(existingConfig) {
|
|
|
258
131
|
}
|
|
259
132
|
if (!botToken) {
|
|
260
133
|
console.log("❌ Bot Token 不能为空");
|
|
134
|
+
console.log(" 没有 Bot 请前往 https://t.me/BotFather 申请");
|
|
261
135
|
continue;
|
|
262
136
|
}
|
|
263
137
|
console.log("正在验证...");
|
|
@@ -285,8 +159,8 @@ async function runConfigWizard(existingConfig) {
|
|
|
285
159
|
console.log("Chat ID 格式无效,已跳过");
|
|
286
160
|
}
|
|
287
161
|
}
|
|
288
|
-
|
|
289
|
-
|
|
162
|
+
console.log("\n=== PolyMarket 配置(可选) ===");
|
|
163
|
+
console.log("提示: 以下配置项都可以跳过,留空后将无法自动跟单");
|
|
290
164
|
const currentPrivateKey = currentPolymarket?.privateKey || "";
|
|
291
165
|
const pkDisplay = currentPrivateKey ? "已配置" : "未配置";
|
|
292
166
|
const pkHint = currentPrivateKey ? "回车保持,skip 清除" : "回车跳过";
|
|
@@ -309,7 +183,7 @@ async function runConfigWizard(existingConfig) {
|
|
|
309
183
|
}
|
|
310
184
|
const hasBuilderCreds = !!builderCreds;
|
|
311
185
|
const builderDisplay = hasBuilderCreds ? "已配置" : "未配置";
|
|
312
|
-
const builderHint = hasBuilderCreds ? "y=重新配置, n=清除, 回车=保持" : "y=配置, 回车=跳过, 前往 polymarket.com/settings?tab=builder
|
|
186
|
+
const builderHint = hasBuilderCreds ? "y=重新配置, n=清除, 回车=保持" : "y=配置, 回车=跳过, 前往 https://polymarket.com/settings?tab=builder 申请";
|
|
313
187
|
const needBuilderCreds = await question(
|
|
314
188
|
` - Builder API [${builderDisplay}] (${builderHint}): `
|
|
315
189
|
);
|
|
@@ -345,7 +219,7 @@ async function runConfigWizard(existingConfig) {
|
|
|
345
219
|
builderCreds = void 0;
|
|
346
220
|
}
|
|
347
221
|
let tradingConfig = currentTrading || {};
|
|
348
|
-
|
|
222
|
+
console.log("\n=== 交易配置(可选) ===");
|
|
349
223
|
const tradingStatus = currentTrading && Object.keys(currentTrading).length > 0 ? "已配置" : "未配置";
|
|
350
224
|
const needTradingConfig = await question(
|
|
351
225
|
` - 交易配置 [${tradingStatus}] (y=配置, 回车=跳过): `
|
|
@@ -369,44 +243,45 @@ async function runConfigWizard(existingConfig) {
|
|
|
369
243
|
trading: tradingConfig
|
|
370
244
|
};
|
|
371
245
|
if (tradingConfig.sellPrice === 1 && !builderCreds) {
|
|
372
|
-
|
|
373
|
-
"sellPrice = 1 但未配置 builderCreds,Redeem 功能将无法使用"
|
|
374
|
-
);
|
|
375
|
-
logger.line(
|
|
376
|
-
"",
|
|
377
|
-
"请配置 Builder API 凭证,或将卖价设置为 < 1 使用限价卖出模式"
|
|
246
|
+
console.log(
|
|
247
|
+
"⚠️ sellPrice = 1 但未配置 builderCreds,Redeem 功能将无法使用"
|
|
378
248
|
);
|
|
249
|
+
console.log(" 请配置 Builder API 凭证,或将卖价设置为 < 1 使用限价卖出模式");
|
|
379
250
|
}
|
|
380
251
|
configLocal.setItem("telegram", config.telegram);
|
|
381
252
|
configLocal.setItem("polymarket", config.polymarket);
|
|
382
253
|
const cleanTradingConfig = {};
|
|
383
254
|
if (config.trading.amountMode !== void 0)
|
|
384
255
|
cleanTradingConfig.amountMode = config.trading.amountMode;
|
|
385
|
-
if (config.trading.
|
|
386
|
-
cleanTradingConfig.
|
|
256
|
+
if (config.trading.amountFixedValue !== void 0)
|
|
257
|
+
cleanTradingConfig.amountFixedValue = config.trading.amountFixedValue;
|
|
258
|
+
if (config.trading.amountPercentageValue !== void 0)
|
|
259
|
+
cleanTradingConfig.amountPercentageValue = config.trading.amountPercentageValue;
|
|
387
260
|
if (config.trading.buyPrice !== void 0)
|
|
388
261
|
cleanTradingConfig.buyPrice = config.trading.buyPrice;
|
|
389
262
|
if (config.trading.sellPrice !== void 0)
|
|
390
263
|
cleanTradingConfig.sellPrice = config.trading.sellPrice;
|
|
391
264
|
configLocal.setItem("trading", cleanTradingConfig);
|
|
392
|
-
|
|
265
|
+
console.log("✅ 配置已保存!");
|
|
393
266
|
if (privateKeyTrimmed) {
|
|
394
|
-
|
|
267
|
+
console.log("✅ PolyMarket 配置已保存!");
|
|
395
268
|
if (Object.keys(tradingConfig).length > 0) {
|
|
396
|
-
|
|
269
|
+
console.log("✅ 交易配置已保存!");
|
|
397
270
|
} else {
|
|
398
|
-
|
|
271
|
+
console.log("ℹ️ 已跳过交易配置");
|
|
399
272
|
}
|
|
400
273
|
} else {
|
|
401
|
-
|
|
274
|
+
console.log("ℹ️ 已跳过 PolyMarket 配置");
|
|
402
275
|
}
|
|
403
276
|
return config;
|
|
404
277
|
}
|
|
405
278
|
async function initConfig() {
|
|
406
|
-
|
|
279
|
+
const config = await runConfigWizard();
|
|
280
|
+
closeReadlineInterface();
|
|
281
|
+
return config;
|
|
407
282
|
}
|
|
408
283
|
async function configureTrading(existingConfig) {
|
|
409
|
-
|
|
284
|
+
console.log("\n=== 交易配置 ===");
|
|
410
285
|
console.log("提示: 配置不完整时将无法自动跟单");
|
|
411
286
|
const existingMode = existingConfig?.amountMode;
|
|
412
287
|
const defaultMode = existingMode || "fixed";
|
|
@@ -431,34 +306,35 @@ async function configureTrading(existingConfig) {
|
|
|
431
306
|
console.log("无效选择,请输入 1 或 2\n");
|
|
432
307
|
}
|
|
433
308
|
}
|
|
434
|
-
const
|
|
309
|
+
const existingFixedValue = existingConfig?.amountFixedValue;
|
|
310
|
+
const existingPercentageValue = existingConfig?.amountPercentageValue;
|
|
311
|
+
const defaultFixedValue = 5;
|
|
312
|
+
const defaultPercentageValue = 0.1;
|
|
435
313
|
let amountValue;
|
|
436
314
|
while (true) {
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
const
|
|
315
|
+
const isFixed = amountMode === "fixed";
|
|
316
|
+
const unit = isFixed ? " USDC" : "";
|
|
317
|
+
const existingValue = isFixed ? existingFixedValue : existingPercentageValue;
|
|
318
|
+
const defaultValue = isFixed ? defaultFixedValue : defaultPercentageValue;
|
|
319
|
+
const valueDisplay = existingValue !== void 0 ? `${existingValue}${unit}` : `${defaultValue}${unit}`;
|
|
320
|
+
const valueHint = isFixed ? "USDC金额" : "0-1比例";
|
|
440
321
|
const valueInput = await question(
|
|
441
|
-
` - 使用金数值 [${valueDisplay}] (${valueHint}
|
|
322
|
+
` - 使用金数值 [${valueDisplay}] (${valueHint}): `
|
|
442
323
|
);
|
|
443
324
|
if (!valueInput.trim()) {
|
|
444
325
|
if (existingValue !== void 0) {
|
|
445
326
|
amountValue = existingValue;
|
|
446
|
-
break;
|
|
447
327
|
} else {
|
|
448
|
-
amountValue =
|
|
449
|
-
break;
|
|
328
|
+
amountValue = defaultValue;
|
|
450
329
|
}
|
|
451
|
-
}
|
|
452
|
-
if (valueInput.trim().toLowerCase() === "skip") {
|
|
453
|
-
amountValue = void 0;
|
|
454
330
|
break;
|
|
455
331
|
}
|
|
456
332
|
const value = parseFloat(valueInput.trim());
|
|
457
333
|
if (isNaN(value) || value <= 0) {
|
|
458
|
-
console.log("
|
|
334
|
+
console.log("请输入有效的正数\n");
|
|
459
335
|
continue;
|
|
460
336
|
}
|
|
461
|
-
if (
|
|
337
|
+
if (!isFixed && value > 1) {
|
|
462
338
|
console.log("比例不能大于 1,请重新输入\n");
|
|
463
339
|
continue;
|
|
464
340
|
}
|
|
@@ -471,19 +347,15 @@ async function configureTrading(existingConfig) {
|
|
|
471
347
|
while (true) {
|
|
472
348
|
const buyDisplay = existingBuyPrice !== void 0 ? existingBuyPrice : defaultBuyPrice;
|
|
473
349
|
const buyPriceInput = await question(
|
|
474
|
-
` - 买价 [${buyDisplay}] (0.1-0.99
|
|
350
|
+
` - 买价 [${buyDisplay}] (0.1-0.99): `
|
|
475
351
|
);
|
|
476
352
|
if (!buyPriceInput.trim()) {
|
|
477
353
|
buyPrice = existingBuyPrice ?? defaultBuyPrice;
|
|
478
354
|
break;
|
|
479
355
|
}
|
|
480
|
-
if (buyPriceInput.trim().toLowerCase() === "skip") {
|
|
481
|
-
buyPrice = void 0;
|
|
482
|
-
break;
|
|
483
|
-
}
|
|
484
356
|
const price = parseFloat(buyPriceInput.trim());
|
|
485
357
|
if (isNaN(price) || price < 0.1 || price > 0.99) {
|
|
486
|
-
console.log("买价必须在 0.1 到 0.99
|
|
358
|
+
console.log("买价必须在 0.1 到 0.99 之间\n");
|
|
487
359
|
continue;
|
|
488
360
|
}
|
|
489
361
|
buyPrice = price;
|
|
@@ -496,22 +368,16 @@ async function configureTrading(existingConfig) {
|
|
|
496
368
|
const sellDisplay = String(existingSellPrice ?? defaultSellPrice);
|
|
497
369
|
const minSell = buyPrice ?? 0.98;
|
|
498
370
|
const sellPriceInput = await question(
|
|
499
|
-
` - 卖价 [${sellDisplay}] (>${minSell} 且 <=1, 1=不卖出等待自动 redeem
|
|
371
|
+
` - 卖价 [${sellDisplay}] (>${minSell} 且 <=1, 1=不卖出等待自动 redeem): `
|
|
500
372
|
);
|
|
501
373
|
if (!sellPriceInput.trim()) {
|
|
502
374
|
sellPrice = existingSellPrice ?? defaultSellPrice;
|
|
503
375
|
break;
|
|
504
376
|
}
|
|
505
|
-
if (sellPriceInput.trim().toLowerCase() === "skip") {
|
|
506
|
-
sellPrice = void 0;
|
|
507
|
-
break;
|
|
508
|
-
}
|
|
509
377
|
const price = parseFloat(sellPriceInput.trim());
|
|
510
378
|
if (isNaN(price) || buyPrice !== void 0 && price <= buyPrice || price > 1) {
|
|
511
|
-
console.log(
|
|
512
|
-
|
|
513
|
-
`
|
|
514
|
-
);
|
|
379
|
+
console.log(`卖价必须大于 ${buyPrice ?? 0.98} 且小于等于 1
|
|
380
|
+
`);
|
|
515
381
|
continue;
|
|
516
382
|
}
|
|
517
383
|
sellPrice = price;
|
|
@@ -519,22 +385,22 @@ async function configureTrading(existingConfig) {
|
|
|
519
385
|
}
|
|
520
386
|
const tradingConfig = {
|
|
521
387
|
amountMode,
|
|
522
|
-
amountValue,
|
|
388
|
+
amountFixedValue: amountMode === "fixed" ? amountValue : existingFixedValue,
|
|
389
|
+
amountPercentageValue: amountMode === "percentage" ? amountValue : existingPercentageValue,
|
|
523
390
|
buyPrice,
|
|
524
391
|
sellPrice
|
|
525
392
|
};
|
|
526
|
-
|
|
393
|
+
console.log("✅ 交易配置已保存!");
|
|
527
394
|
if (tradingConfig.sellPrice === 1) {
|
|
528
|
-
|
|
395
|
+
console.log("ℹ️ 卖价设置为 1,将使用自动 redeem 模式");
|
|
529
396
|
} else if (tradingConfig.sellPrice !== void 0) {
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
`卖价设置为 ${tradingConfig.sellPrice},将使用限价卖出模式`
|
|
397
|
+
console.log(
|
|
398
|
+
`ℹ️ 卖价设置为 ${tradingConfig.sellPrice},将使用限价卖出模式`
|
|
533
399
|
);
|
|
534
400
|
}
|
|
535
|
-
const hasAllConfig = tradingConfig.amountMode && tradingConfig.
|
|
401
|
+
const hasAllConfig = tradingConfig.amountMode && (tradingConfig.amountMode === "fixed" ? tradingConfig.amountFixedValue !== void 0 : tradingConfig.amountPercentageValue !== void 0) && tradingConfig.buyPrice !== void 0 && tradingConfig.sellPrice !== void 0;
|
|
536
402
|
if (!hasAllConfig) {
|
|
537
|
-
|
|
403
|
+
console.log("⚠️ 交易配置不完整,将无法自动跟单");
|
|
538
404
|
}
|
|
539
405
|
return tradingConfig;
|
|
540
406
|
}
|
|
@@ -543,8 +409,10 @@ async function ensureConfig() {
|
|
|
543
409
|
const polymarket = configLocal.getItem("polymarket");
|
|
544
410
|
const trading = configLocal.getItem("trading");
|
|
545
411
|
if (!telegram && !polymarket && !trading) {
|
|
546
|
-
|
|
547
|
-
|
|
412
|
+
console.log("未找到配置文件,开始初始化配置...");
|
|
413
|
+
const updatedConfig = await initConfig();
|
|
414
|
+
closeReadlineInterface();
|
|
415
|
+
return updatedConfig;
|
|
548
416
|
}
|
|
549
417
|
const config = {
|
|
550
418
|
telegram: telegram || { botToken: "" },
|
|
@@ -553,20 +421,24 @@ async function ensureConfig() {
|
|
|
553
421
|
};
|
|
554
422
|
const validation = validateConfig(config);
|
|
555
423
|
if (!validation.valid) {
|
|
556
|
-
|
|
557
|
-
validation.missingFields.forEach((field) =>
|
|
558
|
-
|
|
559
|
-
|
|
424
|
+
console.log("⚠️ 配置文件不完整,缺少以下字段:");
|
|
425
|
+
validation.missingFields.forEach((field) => console.log(`- ${field}`));
|
|
426
|
+
console.log("开始重新配置...");
|
|
427
|
+
const updatedConfig = await initConfig();
|
|
428
|
+
closeReadlineInterface();
|
|
429
|
+
return updatedConfig;
|
|
560
430
|
}
|
|
561
431
|
if (!hasPolyMarketConfig(config)) {
|
|
562
|
-
|
|
432
|
+
console.log("未检测到 PolyMarket 配置");
|
|
563
433
|
const useFullWizard = await question(
|
|
564
434
|
"是否要运行完整配置向导?(y/n,默认 n,选择 n 将跳过配置): "
|
|
565
435
|
);
|
|
566
436
|
if (useFullWizard.trim().toLowerCase() === "y") {
|
|
567
|
-
|
|
437
|
+
const updatedConfig = await runConfigWizard(config);
|
|
438
|
+
closeReadlineInterface();
|
|
439
|
+
return updatedConfig;
|
|
568
440
|
} else {
|
|
569
|
-
|
|
441
|
+
console.log("ℹ️ 已跳过 PolyMarket 配置");
|
|
570
442
|
}
|
|
571
443
|
}
|
|
572
444
|
if (hasPolyMarketConfig(config) && !hasTradingConfig(config)) {
|
|
@@ -582,8 +454,10 @@ async function ensureConfig() {
|
|
|
582
454
|
const cleanTradingConfig = {};
|
|
583
455
|
if (config.trading.amountMode !== void 0)
|
|
584
456
|
cleanTradingConfig.amountMode = config.trading.amountMode;
|
|
585
|
-
if (config.trading.
|
|
586
|
-
cleanTradingConfig.
|
|
457
|
+
if (config.trading.amountFixedValue !== void 0)
|
|
458
|
+
cleanTradingConfig.amountFixedValue = config.trading.amountFixedValue;
|
|
459
|
+
if (config.trading.amountPercentageValue !== void 0)
|
|
460
|
+
cleanTradingConfig.amountPercentageValue = config.trading.amountPercentageValue;
|
|
587
461
|
if (config.trading.buyPrice !== void 0)
|
|
588
462
|
cleanTradingConfig.buyPrice = config.trading.buyPrice;
|
|
589
463
|
if (config.trading.sellPrice !== void 0)
|
|
@@ -593,8 +467,97 @@ async function ensureConfig() {
|
|
|
593
467
|
}
|
|
594
468
|
return config;
|
|
595
469
|
}
|
|
470
|
+
const normalizeCommand = (command) => command.replace(/\\/g, "/");
|
|
471
|
+
const isPolycopyCommand = (command) => {
|
|
472
|
+
const normalized = normalizeCommand(command).toLowerCase();
|
|
473
|
+
const distIndexMarker = "dist/index.js";
|
|
474
|
+
const distCliMarker = "dist/cli.js";
|
|
475
|
+
return normalized.includes(DIST_INDEX.toLowerCase()) || normalized.includes(DIST_CLI.toLowerCase()) || normalized.includes(distIndexMarker) || normalized.includes(distCliMarker);
|
|
476
|
+
};
|
|
477
|
+
const findPolycopyPids = () => {
|
|
478
|
+
if (process.platform === "win32") {
|
|
479
|
+
return [];
|
|
480
|
+
}
|
|
481
|
+
const result = child_process.spawnSync("ps", ["-ax", "-o", "pid=,command="], {
|
|
482
|
+
encoding: "utf-8"
|
|
483
|
+
});
|
|
484
|
+
if (result.status !== 0) {
|
|
485
|
+
return [];
|
|
486
|
+
}
|
|
487
|
+
return result.stdout.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
488
|
+
const firstSpace = line.indexOf(" ");
|
|
489
|
+
if (firstSpace === -1) return null;
|
|
490
|
+
const pidText = line.slice(0, firstSpace).trim();
|
|
491
|
+
const command = line.slice(firstSpace + 1).trim();
|
|
492
|
+
const pid = parseInt(pidText, 10);
|
|
493
|
+
if (Number.isNaN(pid)) return null;
|
|
494
|
+
if (pid === process.pid) return null;
|
|
495
|
+
if (!isPolycopyCommand(command)) return null;
|
|
496
|
+
return pid;
|
|
497
|
+
}).filter((pid) => pid !== null);
|
|
498
|
+
};
|
|
499
|
+
const resolveRunningPid = () => {
|
|
500
|
+
const pid = getRunningPid();
|
|
501
|
+
if (pid) {
|
|
502
|
+
return pid;
|
|
503
|
+
}
|
|
504
|
+
const fallbackPids = findPolycopyPids();
|
|
505
|
+
return fallbackPids.length > 0 ? fallbackPids[0] : null;
|
|
506
|
+
};
|
|
507
|
+
const ensurePidDir = () => {
|
|
508
|
+
if (!fs__namespace.existsSync(PID_DIR)) {
|
|
509
|
+
fs__namespace.mkdirSync(PID_DIR, { recursive: true });
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const getRunningPid = () => {
|
|
513
|
+
if (!fs__namespace.existsSync(PID_FILE)) {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
const pid = parseInt(fs__namespace.readFileSync(PID_FILE, "utf-8").trim());
|
|
518
|
+
process.kill(pid, 0);
|
|
519
|
+
if (!isPolycopyProcess(pid)) {
|
|
520
|
+
fs__namespace.unlinkSync(PID_FILE);
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
return pid;
|
|
524
|
+
} catch {
|
|
525
|
+
fs__namespace.unlinkSync(PID_FILE);
|
|
526
|
+
return null;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
const savePid = (pid) => {
|
|
530
|
+
ensurePidDir();
|
|
531
|
+
fs__namespace.writeFileSync(PID_FILE, pid.toString());
|
|
532
|
+
};
|
|
533
|
+
const removePid = () => {
|
|
534
|
+
if (fs__namespace.existsSync(PID_FILE)) {
|
|
535
|
+
fs__namespace.unlinkSync(PID_FILE);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
const isPolycopyProcess = (pid) => {
|
|
539
|
+
if (process.platform === "win32") {
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
const result = child_process.spawnSync("ps", ["-p", String(pid), "-o", "command="], {
|
|
543
|
+
encoding: "utf-8"
|
|
544
|
+
});
|
|
545
|
+
if (result.status !== 0) {
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
const command = result.stdout.trim();
|
|
549
|
+
if (!command) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
return isPolycopyCommand(command);
|
|
553
|
+
};
|
|
554
|
+
exports.LOGS_DIR = LOGS_DIR;
|
|
555
|
+
exports.PID_DIR = PID_DIR;
|
|
556
|
+
exports.PID_FILE = PID_FILE;
|
|
596
557
|
exports.canAutoTrade = canAutoTrade;
|
|
597
558
|
exports.configLocal = configLocal;
|
|
598
559
|
exports.ensureConfig = ensureConfig;
|
|
599
|
-
exports.
|
|
600
|
-
exports.
|
|
560
|
+
exports.initConfig = initConfig;
|
|
561
|
+
exports.removePid = removePid;
|
|
562
|
+
exports.resolveRunningPid = resolveRunningPid;
|
|
563
|
+
exports.savePid = savePid;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polycopy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "polycopy test",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"typecheck": "tsc --noEmit",
|
|
11
11
|
"build": "tsc --noEmit && vite build",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
|
-
"config": "node dist/config.js",
|
|
14
13
|
"dev": "tsc --noEmit && vite build && node dist/index.js",
|
|
15
14
|
"cli": "node dist/cli.js",
|
|
16
15
|
"prepublishOnly": "npm run build",
|
|
17
16
|
"test": "HOME=$PWD node --import tsx tests/redeemer.test.ts",
|
|
18
|
-
"test:redeemer": "HOME=$PWD node --import tsx tests/redeemer.test.ts"
|
|
17
|
+
"test:redeemer": "HOME=$PWD node --import tsx tests/redeemer.test.ts",
|
|
18
|
+
"test:log-follow": "node scripts/test-log-follow.cjs"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@polymarket/builder-relayer-client": "^0.0.8",
|
|
@@ -25,8 +25,7 @@
|
|
|
25
25
|
"lodash": "^4.17.21",
|
|
26
26
|
"set-promise-interval": "^1.1.0",
|
|
27
27
|
"viem": "^2.44.2",
|
|
28
|
-
"
|
|
29
|
-
"winston-daily-rotate-file": "^5.0.0"
|
|
28
|
+
"log4js": "^6.9.1"
|
|
30
29
|
},
|
|
31
30
|
"devDependencies": {
|
|
32
31
|
"@types/fs-extra": "^11.0.4",
|
package/dist/config.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const init = require("./init-Bs-N_AZi.js");
|
|
3
|
-
async function main() {
|
|
4
|
-
try {
|
|
5
|
-
await init.runConfigWizard();
|
|
6
|
-
init.logger.success("配置完成!");
|
|
7
|
-
process.exit(0);
|
|
8
|
-
} catch (error) {
|
|
9
|
-
init.logger.error("配置过程中出错:", error);
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
main();
|
package/dist/paths-CEjGES8j.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
const os = require("os");
|
|
3
|
-
const path = require("path");
|
|
4
|
-
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
5
|
-
const os__default = /* @__PURE__ */ _interopDefault(os);
|
|
6
|
-
const path__default = /* @__PURE__ */ _interopDefault(path);
|
|
7
|
-
const USER_DATA_DIR = path__default.default.join(os__default.default.homedir(), ".polycopy");
|
|
8
|
-
const PID_DIR = USER_DATA_DIR;
|
|
9
|
-
const LOGS_DIR = path__default.default.join(USER_DATA_DIR, "logs");
|
|
10
|
-
const LOCALSTORAGE_DIR = path__default.default.join(USER_DATA_DIR, "localstorage");
|
|
11
|
-
const PID_FILE = path__default.default.join(USER_DATA_DIR, "polycopy.pid");
|
|
12
|
-
const INSTALL_ROOT = path__default.default.resolve(__dirname, "..");
|
|
13
|
-
const DIST_INDEX = path__default.default.join(INSTALL_ROOT, "dist", "index.js");
|
|
14
|
-
const DIST_CLI = path__default.default.join(INSTALL_ROOT, "dist", "cli.js");
|
|
15
|
-
exports.DIST_CLI = DIST_CLI;
|
|
16
|
-
exports.DIST_INDEX = DIST_INDEX;
|
|
17
|
-
exports.LOCALSTORAGE_DIR = LOCALSTORAGE_DIR;
|
|
18
|
-
exports.LOGS_DIR = LOGS_DIR;
|
|
19
|
-
exports.PID_DIR = PID_DIR;
|
|
20
|
-
exports.PID_FILE = PID_FILE;
|