Codex commited on
Commit ·
7212c19
1
Parent(s): 63b4caa
Use Dropbox API for Circa file discovery
Browse files- .env.example +2 -0
- README.md +3 -1
- deploy/oracle/roibot.env.example +2 -0
- package-lock.json +129 -12
- package.json +1 -0
- src/config.js +10 -1
- src/embeds.js +14 -1
- src/index.js +2 -0
- src/market-scanner.js +104 -2
- test/market-scanner.test.js +31 -0
.env.example
CHANGED
|
@@ -8,6 +8,8 @@ ODDS_API_BASE_URL=https://api.the-odds-api.com/v4
|
|
| 8 |
ODDS_API_SPORT_KEY=baseball_mlb
|
| 9 |
ODDS_API_REGIONS=us
|
| 10 |
ODDS_API_MARKETS=batter_home_runs,batter_hits,batter_total_bases,batter_rbis,batter_runs_scored,batter_hits_runs_rbis,pitcher_strikeouts
|
|
|
|
|
|
|
| 11 |
CIRCA_DROPBOX_URL=
|
| 12 |
SCAN_REPORT_CHANNEL_ID=
|
| 13 |
SCAN_ALERT_CHANNEL_ID=
|
|
|
|
| 8 |
ODDS_API_SPORT_KEY=baseball_mlb
|
| 9 |
ODDS_API_REGIONS=us
|
| 10 |
ODDS_API_MARKETS=batter_home_runs,batter_hits,batter_total_bases,batter_rbis,batter_runs_scored,batter_hits_runs_rbis,pitcher_strikeouts
|
| 11 |
+
DROPBOX_ACCESS_TOKEN=
|
| 12 |
+
CIRCA_DROPBOX_FOLDER_PATH=
|
| 13 |
CIRCA_DROPBOX_URL=
|
| 14 |
SCAN_REPORT_CHANNEL_ID=
|
| 15 |
SCAN_ALERT_CHANNEL_ID=
|
README.md
CHANGED
|
@@ -131,7 +131,9 @@ Environment variables for scanner features:
|
|
| 131 |
- `ODDS_API_SPORT_KEY`
|
| 132 |
- `ODDS_API_REGIONS`
|
| 133 |
- `ODDS_API_MARKETS`
|
| 134 |
-
- `
|
|
|
|
|
|
|
| 135 |
- `SCAN_REPORT_CHANNEL_ID`
|
| 136 |
- `SCAN_ALERT_CHANNEL_ID`
|
| 137 |
- `SCAN_MORNING_TIME`
|
|
|
|
| 131 |
- `ODDS_API_SPORT_KEY`
|
| 132 |
- `ODDS_API_REGIONS`
|
| 133 |
- `ODDS_API_MARKETS`
|
| 134 |
+
- `DROPBOX_ACCESS_TOKEN`
|
| 135 |
+
- `CIRCA_DROPBOX_FOLDER_PATH`
|
| 136 |
+
- `CIRCA_DROPBOX_URL` (deprecated fallback)
|
| 137 |
- `SCAN_REPORT_CHANNEL_ID`
|
| 138 |
- `SCAN_ALERT_CHANNEL_ID`
|
| 139 |
- `SCAN_MORNING_TIME`
|
deploy/oracle/roibot.env.example
CHANGED
|
@@ -8,6 +8,8 @@ ODDS_API_BASE_URL=https://api.the-odds-api.com/v4
|
|
| 8 |
ODDS_API_SPORT_KEY=baseball_mlb
|
| 9 |
ODDS_API_REGIONS=us
|
| 10 |
ODDS_API_MARKETS=batter_home_runs,batter_hits,batter_total_bases,batter_rbis,batter_runs_scored,batter_hits_runs_rbis,pitcher_strikeouts
|
|
|
|
|
|
|
| 11 |
CIRCA_DROPBOX_URL=
|
| 12 |
SCAN_REPORT_CHANNEL_ID=
|
| 13 |
SCAN_ALERT_CHANNEL_ID=
|
|
|
|
| 8 |
ODDS_API_SPORT_KEY=baseball_mlb
|
| 9 |
ODDS_API_REGIONS=us
|
| 10 |
ODDS_API_MARKETS=batter_home_runs,batter_hits,batter_total_bases,batter_rbis,batter_runs_scored,batter_hits_runs_rbis,pitcher_strikeouts
|
| 11 |
+
DROPBOX_ACCESS_TOKEN=
|
| 12 |
+
CIRCA_DROPBOX_FOLDER_PATH=
|
| 13 |
CIRCA_DROPBOX_URL=
|
| 14 |
SCAN_REPORT_CHANNEL_ID=
|
| 15 |
SCAN_ALERT_CHANNEL_ID=
|
package-lock.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
| 11 |
"chart.js": "^4.5.0",
|
| 12 |
"discord.js": "^14.25.1",
|
| 13 |
"dotenv": "^17.2.3",
|
|
|
|
| 14 |
"pdfjs-dist": "^5.6.205",
|
| 15 |
"pg": "^8.16.3",
|
| 16 |
"skia-canvas": "^3.0.8",
|
|
@@ -470,6 +471,17 @@
|
|
| 470 |
"undici-types": "~7.18.0"
|
| 471 |
}
|
| 472 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
"node_modules/@types/ws": {
|
| 474 |
"version": "8.18.1",
|
| 475 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
@@ -498,6 +510,13 @@
|
|
| 498 |
"node": ">= 14"
|
| 499 |
}
|
| 500 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 501 |
"node_modules/bmp-js": {
|
| 502 |
"version": "0.1.0",
|
| 503 |
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
|
@@ -527,7 +546,6 @@
|
|
| 527 |
"version": "1.0.2",
|
| 528 |
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 529 |
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
| 530 |
-
"dev": true,
|
| 531 |
"license": "MIT",
|
| 532 |
"dependencies": {
|
| 533 |
"es-errors": "^1.3.0",
|
|
@@ -566,6 +584,19 @@
|
|
| 566 |
"pnpm": ">=8"
|
| 567 |
}
|
| 568 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
"node_modules/commander": {
|
| 570 |
"version": "2.20.3",
|
| 571 |
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
@@ -608,6 +639,16 @@
|
|
| 608 |
"url": "https://github.com/sponsors/ljharb"
|
| 609 |
}
|
| 610 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
"node_modules/detect-libc": {
|
| 612 |
"version": "2.1.2",
|
| 613 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
@@ -672,11 +713,25 @@
|
|
| 672 |
"url": "https://dotenvx.com"
|
| 673 |
}
|
| 674 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
"node_modules/dunder-proto": {
|
| 676 |
"version": "1.0.1",
|
| 677 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 678 |
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
| 679 |
-
"dev": true,
|
| 680 |
"license": "MIT",
|
| 681 |
"dependencies": {
|
| 682 |
"call-bind-apply-helpers": "^1.0.1",
|
|
@@ -691,7 +746,6 @@
|
|
| 691 |
"version": "1.0.1",
|
| 692 |
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 693 |
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
| 694 |
-
"dev": true,
|
| 695 |
"license": "MIT",
|
| 696 |
"engines": {
|
| 697 |
"node": ">= 0.4"
|
|
@@ -701,7 +755,6 @@
|
|
| 701 |
"version": "1.3.0",
|
| 702 |
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 703 |
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
| 704 |
-
"dev": true,
|
| 705 |
"license": "MIT",
|
| 706 |
"engines": {
|
| 707 |
"node": ">= 0.4"
|
|
@@ -711,7 +764,6 @@
|
|
| 711 |
"version": "1.1.1",
|
| 712 |
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 713 |
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
| 714 |
-
"dev": true,
|
| 715 |
"license": "MIT",
|
| 716 |
"dependencies": {
|
| 717 |
"es-errors": "^1.3.0"
|
|
@@ -720,6 +772,22 @@
|
|
| 720 |
"node": ">= 0.4"
|
| 721 |
}
|
| 722 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 723 |
"node_modules/fast-deep-equal": {
|
| 724 |
"version": "3.1.3",
|
| 725 |
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
@@ -746,11 +814,27 @@
|
|
| 746 |
}
|
| 747 |
}
|
| 748 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 749 |
"node_modules/function-bind": {
|
| 750 |
"version": "1.1.2",
|
| 751 |
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 752 |
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
| 753 |
-
"dev": true,
|
| 754 |
"license": "MIT",
|
| 755 |
"funding": {
|
| 756 |
"url": "https://github.com/sponsors/ljharb"
|
|
@@ -767,7 +851,6 @@
|
|
| 767 |
"version": "1.3.0",
|
| 768 |
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 769 |
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
| 770 |
-
"dev": true,
|
| 771 |
"license": "MIT",
|
| 772 |
"dependencies": {
|
| 773 |
"call-bind-apply-helpers": "^1.0.2",
|
|
@@ -792,7 +875,6 @@
|
|
| 792 |
"version": "1.0.1",
|
| 793 |
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 794 |
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
| 795 |
-
"dev": true,
|
| 796 |
"license": "MIT",
|
| 797 |
"dependencies": {
|
| 798 |
"dunder-proto": "^1.0.1",
|
|
@@ -806,7 +888,6 @@
|
|
| 806 |
"version": "1.2.0",
|
| 807 |
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 808 |
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
| 809 |
-
"dev": true,
|
| 810 |
"license": "MIT",
|
| 811 |
"engines": {
|
| 812 |
"node": ">= 0.4"
|
|
@@ -832,7 +913,6 @@
|
|
| 832 |
"version": "1.1.0",
|
| 833 |
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 834 |
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
| 835 |
-
"dev": true,
|
| 836 |
"license": "MIT",
|
| 837 |
"engines": {
|
| 838 |
"node": ">= 0.4"
|
|
@@ -841,11 +921,26 @@
|
|
| 841 |
"url": "https://github.com/sponsors/ljharb"
|
| 842 |
}
|
| 843 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 844 |
"node_modules/hasown": {
|
| 845 |
"version": "2.0.2",
|
| 846 |
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 847 |
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
| 848 |
-
"dev": true,
|
| 849 |
"license": "MIT",
|
| 850 |
"dependencies": {
|
| 851 |
"function-bind": "^1.1.2"
|
|
@@ -958,12 +1053,34 @@
|
|
| 958 |
"version": "1.1.0",
|
| 959 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 960 |
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
| 961 |
-
"dev": true,
|
| 962 |
"license": "MIT",
|
| 963 |
"engines": {
|
| 964 |
"node": ">= 0.4"
|
| 965 |
}
|
| 966 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
"node_modules/moment": {
|
| 968 |
"version": "2.30.1",
|
| 969 |
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
|
|
|
| 11 |
"chart.js": "^4.5.0",
|
| 12 |
"discord.js": "^14.25.1",
|
| 13 |
"dotenv": "^17.2.3",
|
| 14 |
+
"dropbox": "^10.34.0",
|
| 15 |
"pdfjs-dist": "^5.6.205",
|
| 16 |
"pg": "^8.16.3",
|
| 17 |
"skia-canvas": "^3.0.8",
|
|
|
|
| 471 |
"undici-types": "~7.18.0"
|
| 472 |
}
|
| 473 |
},
|
| 474 |
+
"node_modules/@types/node-fetch": {
|
| 475 |
+
"version": "2.6.13",
|
| 476 |
+
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
|
| 477 |
+
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
|
| 478 |
+
"license": "MIT",
|
| 479 |
+
"peer": true,
|
| 480 |
+
"dependencies": {
|
| 481 |
+
"@types/node": "*",
|
| 482 |
+
"form-data": "^4.0.4"
|
| 483 |
+
}
|
| 484 |
+
},
|
| 485 |
"node_modules/@types/ws": {
|
| 486 |
"version": "8.18.1",
|
| 487 |
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
|
|
|
| 510 |
"node": ">= 14"
|
| 511 |
}
|
| 512 |
},
|
| 513 |
+
"node_modules/asynckit": {
|
| 514 |
+
"version": "0.4.0",
|
| 515 |
+
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
| 516 |
+
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
| 517 |
+
"license": "MIT",
|
| 518 |
+
"peer": true
|
| 519 |
+
},
|
| 520 |
"node_modules/bmp-js": {
|
| 521 |
"version": "0.1.0",
|
| 522 |
"resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
|
|
|
|
| 546 |
"version": "1.0.2",
|
| 547 |
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
| 548 |
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
|
|
|
| 549 |
"license": "MIT",
|
| 550 |
"dependencies": {
|
| 551 |
"es-errors": "^1.3.0",
|
|
|
|
| 584 |
"pnpm": ">=8"
|
| 585 |
}
|
| 586 |
},
|
| 587 |
+
"node_modules/combined-stream": {
|
| 588 |
+
"version": "1.0.8",
|
| 589 |
+
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
| 590 |
+
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
| 591 |
+
"license": "MIT",
|
| 592 |
+
"peer": true,
|
| 593 |
+
"dependencies": {
|
| 594 |
+
"delayed-stream": "~1.0.0"
|
| 595 |
+
},
|
| 596 |
+
"engines": {
|
| 597 |
+
"node": ">= 0.8"
|
| 598 |
+
}
|
| 599 |
+
},
|
| 600 |
"node_modules/commander": {
|
| 601 |
"version": "2.20.3",
|
| 602 |
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
|
|
| 639 |
"url": "https://github.com/sponsors/ljharb"
|
| 640 |
}
|
| 641 |
},
|
| 642 |
+
"node_modules/delayed-stream": {
|
| 643 |
+
"version": "1.0.0",
|
| 644 |
+
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
| 645 |
+
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
| 646 |
+
"license": "MIT",
|
| 647 |
+
"peer": true,
|
| 648 |
+
"engines": {
|
| 649 |
+
"node": ">=0.4.0"
|
| 650 |
+
}
|
| 651 |
+
},
|
| 652 |
"node_modules/detect-libc": {
|
| 653 |
"version": "2.1.2",
|
| 654 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
|
|
|
| 713 |
"url": "https://dotenvx.com"
|
| 714 |
}
|
| 715 |
},
|
| 716 |
+
"node_modules/dropbox": {
|
| 717 |
+
"version": "10.34.0",
|
| 718 |
+
"resolved": "https://registry.npmjs.org/dropbox/-/dropbox-10.34.0.tgz",
|
| 719 |
+
"integrity": "sha512-5jb5/XzU0fSnq36/hEpwT5/QIep7MgqKuxghEG44xCu7HruOAjPdOb3x0geXv5O/hd0nHpQpWO+r5MjYTpMvJg==",
|
| 720 |
+
"license": "MIT",
|
| 721 |
+
"dependencies": {
|
| 722 |
+
"node-fetch": "^2.6.1"
|
| 723 |
+
},
|
| 724 |
+
"engines": {
|
| 725 |
+
"node": ">=0.10.3"
|
| 726 |
+
},
|
| 727 |
+
"peerDependencies": {
|
| 728 |
+
"@types/node-fetch": "^2.5.7"
|
| 729 |
+
}
|
| 730 |
+
},
|
| 731 |
"node_modules/dunder-proto": {
|
| 732 |
"version": "1.0.1",
|
| 733 |
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
| 734 |
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
|
|
|
| 735 |
"license": "MIT",
|
| 736 |
"dependencies": {
|
| 737 |
"call-bind-apply-helpers": "^1.0.1",
|
|
|
|
| 746 |
"version": "1.0.1",
|
| 747 |
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
| 748 |
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
|
|
|
| 749 |
"license": "MIT",
|
| 750 |
"engines": {
|
| 751 |
"node": ">= 0.4"
|
|
|
|
| 755 |
"version": "1.3.0",
|
| 756 |
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
| 757 |
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
|
|
|
| 758 |
"license": "MIT",
|
| 759 |
"engines": {
|
| 760 |
"node": ">= 0.4"
|
|
|
|
| 764 |
"version": "1.1.1",
|
| 765 |
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
| 766 |
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
|
|
|
| 767 |
"license": "MIT",
|
| 768 |
"dependencies": {
|
| 769 |
"es-errors": "^1.3.0"
|
|
|
|
| 772 |
"node": ">= 0.4"
|
| 773 |
}
|
| 774 |
},
|
| 775 |
+
"node_modules/es-set-tostringtag": {
|
| 776 |
+
"version": "2.1.0",
|
| 777 |
+
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
| 778 |
+
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
| 779 |
+
"license": "MIT",
|
| 780 |
+
"peer": true,
|
| 781 |
+
"dependencies": {
|
| 782 |
+
"es-errors": "^1.3.0",
|
| 783 |
+
"get-intrinsic": "^1.2.6",
|
| 784 |
+
"has-tostringtag": "^1.0.2",
|
| 785 |
+
"hasown": "^2.0.2"
|
| 786 |
+
},
|
| 787 |
+
"engines": {
|
| 788 |
+
"node": ">= 0.4"
|
| 789 |
+
}
|
| 790 |
+
},
|
| 791 |
"node_modules/fast-deep-equal": {
|
| 792 |
"version": "3.1.3",
|
| 793 |
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
|
|
|
| 814 |
}
|
| 815 |
}
|
| 816 |
},
|
| 817 |
+
"node_modules/form-data": {
|
| 818 |
+
"version": "4.0.5",
|
| 819 |
+
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
| 820 |
+
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
| 821 |
+
"license": "MIT",
|
| 822 |
+
"peer": true,
|
| 823 |
+
"dependencies": {
|
| 824 |
+
"asynckit": "^0.4.0",
|
| 825 |
+
"combined-stream": "^1.0.8",
|
| 826 |
+
"es-set-tostringtag": "^2.1.0",
|
| 827 |
+
"hasown": "^2.0.2",
|
| 828 |
+
"mime-types": "^2.1.12"
|
| 829 |
+
},
|
| 830 |
+
"engines": {
|
| 831 |
+
"node": ">= 6"
|
| 832 |
+
}
|
| 833 |
+
},
|
| 834 |
"node_modules/function-bind": {
|
| 835 |
"version": "1.1.2",
|
| 836 |
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
| 837 |
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
|
|
|
| 838 |
"license": "MIT",
|
| 839 |
"funding": {
|
| 840 |
"url": "https://github.com/sponsors/ljharb"
|
|
|
|
| 851 |
"version": "1.3.0",
|
| 852 |
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
| 853 |
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
|
|
|
| 854 |
"license": "MIT",
|
| 855 |
"dependencies": {
|
| 856 |
"call-bind-apply-helpers": "^1.0.2",
|
|
|
|
| 875 |
"version": "1.0.1",
|
| 876 |
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
| 877 |
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
|
|
|
| 878 |
"license": "MIT",
|
| 879 |
"dependencies": {
|
| 880 |
"dunder-proto": "^1.0.1",
|
|
|
|
| 888 |
"version": "1.2.0",
|
| 889 |
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
| 890 |
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
|
|
|
| 891 |
"license": "MIT",
|
| 892 |
"engines": {
|
| 893 |
"node": ">= 0.4"
|
|
|
|
| 913 |
"version": "1.1.0",
|
| 914 |
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
| 915 |
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
|
|
|
| 916 |
"license": "MIT",
|
| 917 |
"engines": {
|
| 918 |
"node": ">= 0.4"
|
|
|
|
| 921 |
"url": "https://github.com/sponsors/ljharb"
|
| 922 |
}
|
| 923 |
},
|
| 924 |
+
"node_modules/has-tostringtag": {
|
| 925 |
+
"version": "1.0.2",
|
| 926 |
+
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
| 927 |
+
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
| 928 |
+
"license": "MIT",
|
| 929 |
+
"peer": true,
|
| 930 |
+
"dependencies": {
|
| 931 |
+
"has-symbols": "^1.0.3"
|
| 932 |
+
},
|
| 933 |
+
"engines": {
|
| 934 |
+
"node": ">= 0.4"
|
| 935 |
+
},
|
| 936 |
+
"funding": {
|
| 937 |
+
"url": "https://github.com/sponsors/ljharb"
|
| 938 |
+
}
|
| 939 |
+
},
|
| 940 |
"node_modules/hasown": {
|
| 941 |
"version": "2.0.2",
|
| 942 |
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
| 943 |
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
|
|
|
| 944 |
"license": "MIT",
|
| 945 |
"dependencies": {
|
| 946 |
"function-bind": "^1.1.2"
|
|
|
|
| 1053 |
"version": "1.1.0",
|
| 1054 |
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
| 1055 |
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
|
|
|
| 1056 |
"license": "MIT",
|
| 1057 |
"engines": {
|
| 1058 |
"node": ">= 0.4"
|
| 1059 |
}
|
| 1060 |
},
|
| 1061 |
+
"node_modules/mime-db": {
|
| 1062 |
+
"version": "1.52.0",
|
| 1063 |
+
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
| 1064 |
+
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
| 1065 |
+
"license": "MIT",
|
| 1066 |
+
"peer": true,
|
| 1067 |
+
"engines": {
|
| 1068 |
+
"node": ">= 0.6"
|
| 1069 |
+
}
|
| 1070 |
+
},
|
| 1071 |
+
"node_modules/mime-types": {
|
| 1072 |
+
"version": "2.1.35",
|
| 1073 |
+
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
| 1074 |
+
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
| 1075 |
+
"license": "MIT",
|
| 1076 |
+
"peer": true,
|
| 1077 |
+
"dependencies": {
|
| 1078 |
+
"mime-db": "1.52.0"
|
| 1079 |
+
},
|
| 1080 |
+
"engines": {
|
| 1081 |
+
"node": ">= 0.6"
|
| 1082 |
+
}
|
| 1083 |
+
},
|
| 1084 |
"node_modules/moment": {
|
| 1085 |
"version": "2.30.1",
|
| 1086 |
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
|
package.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
| 15 |
"chart.js": "^4.5.0",
|
| 16 |
"discord.js": "^14.25.1",
|
| 17 |
"dotenv": "^17.2.3",
|
|
|
|
| 18 |
"pdfjs-dist": "^5.6.205",
|
| 19 |
"pg": "^8.16.3",
|
| 20 |
"skia-canvas": "^3.0.8",
|
|
|
|
| 15 |
"chart.js": "^4.5.0",
|
| 16 |
"discord.js": "^14.25.1",
|
| 17 |
"dotenv": "^17.2.3",
|
| 18 |
+
"dropbox": "^10.34.0",
|
| 19 |
"pdfjs-dist": "^5.6.205",
|
| 20 |
"pg": "^8.16.3",
|
| 21 |
"skia-canvas": "^3.0.8",
|
src/config.js
CHANGED
|
@@ -14,6 +14,8 @@ export function getConfig() {
|
|
| 14 |
.split(',')
|
| 15 |
.map((value) => value.trim())
|
| 16 |
.filter(Boolean);
|
|
|
|
|
|
|
| 17 |
const circaDropboxUrl = process.env.CIRCA_DROPBOX_URL?.trim() || null;
|
| 18 |
const scanReportChannelId = process.env.SCAN_REPORT_CHANNEL_ID?.trim() || null;
|
| 19 |
const scanAlertChannelId = process.env.SCAN_ALERT_CHANNEL_ID?.trim() || null;
|
|
@@ -39,12 +41,19 @@ export function getConfig() {
|
|
| 39 |
port,
|
| 40 |
adminRoleName,
|
| 41 |
scanner: {
|
| 42 |
-
enabled: Boolean(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
oddsApiKey,
|
| 44 |
oddsApiBaseUrl,
|
| 45 |
oddsApiSportKey,
|
| 46 |
oddsApiRegions,
|
| 47 |
oddsApiMarkets,
|
|
|
|
|
|
|
| 48 |
circaDropboxUrl,
|
| 49 |
scanReportChannelId,
|
| 50 |
scanAlertChannelId,
|
|
|
|
| 14 |
.split(',')
|
| 15 |
.map((value) => value.trim())
|
| 16 |
.filter(Boolean);
|
| 17 |
+
const dropboxAccessToken = process.env.DROPBOX_ACCESS_TOKEN?.trim() || null;
|
| 18 |
+
const circaDropboxFolderPath = process.env.CIRCA_DROPBOX_FOLDER_PATH?.trim() || null;
|
| 19 |
const circaDropboxUrl = process.env.CIRCA_DROPBOX_URL?.trim() || null;
|
| 20 |
const scanReportChannelId = process.env.SCAN_REPORT_CHANNEL_ID?.trim() || null;
|
| 21 |
const scanAlertChannelId = process.env.SCAN_ALERT_CHANNEL_ID?.trim() || null;
|
|
|
|
| 41 |
port,
|
| 42 |
adminRoleName,
|
| 43 |
scanner: {
|
| 44 |
+
enabled: Boolean(
|
| 45 |
+
oddsApiKey
|
| 46 |
+
&& scanReportChannelId
|
| 47 |
+
&& scanAlertChannelId
|
| 48 |
+
&& ((dropboxAccessToken && circaDropboxFolderPath) || circaDropboxUrl)
|
| 49 |
+
),
|
| 50 |
oddsApiKey,
|
| 51 |
oddsApiBaseUrl,
|
| 52 |
oddsApiSportKey,
|
| 53 |
oddsApiRegions,
|
| 54 |
oddsApiMarkets,
|
| 55 |
+
dropboxAccessToken,
|
| 56 |
+
circaDropboxFolderPath,
|
| 57 |
circaDropboxUrl,
|
| 58 |
scanReportChannelId,
|
| 59 |
scanAlertChannelId,
|
src/embeds.js
CHANGED
|
@@ -402,6 +402,7 @@ export function buildScanStatusEmbed(status) {
|
|
| 402 |
{ name: 'Min Books / Threshold', value: `${status.minBooks} / ${(status.disagreementThreshold * 100).toFixed(2)}%`, inline: true },
|
| 403 |
{ name: 'Last Scan', value: status.lastScanAt ?? 'Never', inline: true },
|
| 404 |
{ name: 'Last Report', value: status.lastReportAt ?? 'Never', inline: true },
|
|
|
|
| 405 |
{ name: 'Last Counts', value: `API: ${status.lastApiEntries ?? 0} | Circa: ${status.lastCircaEntries ?? 0} | Alerts: ${status.lastAlertCount ?? 0}`, inline: false },
|
| 406 |
{ name: 'Last Error', value: status.lastScanError ?? 'None', inline: false },
|
| 407 |
);
|
|
@@ -462,13 +463,25 @@ export function buildCircaDiagnosticEmbed(result) {
|
|
| 462 |
return new EmbedBuilder()
|
| 463 |
.setColor(PALETTE.gold)
|
| 464 |
.setTitle('Circa OCR Diagnostic')
|
| 465 |
-
.setDescription(`Parsed **${result.totalEntries}** Circa entries from the latest
|
| 466 |
.addFields(
|
| 467 |
{ name: 'Parsed Preview', value: truncate(preview, 1000), inline: false },
|
| 468 |
{ name: 'OCR Text Sample', value: truncate(result.rawTextSample || 'No OCR text extracted.', 1000), inline: false },
|
| 469 |
);
|
| 470 |
}
|
| 471 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 472 |
function escapeCsv(value) {
|
| 473 |
const stringValue = String(value);
|
| 474 |
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
|
|
|
|
| 402 |
{ name: 'Min Books / Threshold', value: `${status.minBooks} / ${(status.disagreementThreshold * 100).toFixed(2)}%`, inline: true },
|
| 403 |
{ name: 'Last Scan', value: status.lastScanAt ?? 'Never', inline: true },
|
| 404 |
{ name: 'Last Report', value: status.lastReportAt ?? 'Never', inline: true },
|
| 405 |
+
{ name: 'Last Circa File', value: status.lastCircaFileName ?? 'None', inline: false },
|
| 406 |
{ name: 'Last Counts', value: `API: ${status.lastApiEntries ?? 0} | Circa: ${status.lastCircaEntries ?? 0} | Alerts: ${status.lastAlertCount ?? 0}`, inline: false },
|
| 407 |
{ name: 'Last Error', value: status.lastScanError ?? 'None', inline: false },
|
| 408 |
);
|
|
|
|
| 463 |
return new EmbedBuilder()
|
| 464 |
.setColor(PALETTE.gold)
|
| 465 |
.setTitle('Circa OCR Diagnostic')
|
| 466 |
+
.setDescription(`Parsed **${result.totalEntries}** Circa entries from **${result.fileName ?? 'the latest Circa file'}**.`)
|
| 467 |
.addFields(
|
| 468 |
{ name: 'Parsed Preview', value: truncate(preview, 1000), inline: false },
|
| 469 |
{ name: 'OCR Text Sample', value: truncate(result.rawTextSample || 'No OCR text extracted.', 1000), inline: false },
|
| 470 |
);
|
| 471 |
}
|
| 472 |
|
| 473 |
+
export function buildCircaFailureEmbed(details) {
|
| 474 |
+
return new EmbedBuilder()
|
| 475 |
+
.setColor(PALETTE.red)
|
| 476 |
+
.setTitle('Circa Ingestion Failed')
|
| 477 |
+
.setDescription('The market scanner could not parse the current Circa file, so no Circa-based alerts were posted for this run.')
|
| 478 |
+
.addFields(
|
| 479 |
+
{ name: 'File', value: details.fileName ?? 'Unknown', inline: true },
|
| 480 |
+
{ name: 'Reason', value: details.reason ?? 'Unknown error', inline: true },
|
| 481 |
+
{ name: 'Source', value: details.source ?? 'Dropbox API', inline: true },
|
| 482 |
+
);
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
function escapeCsv(value) {
|
| 486 |
const stringValue = String(value);
|
| 487 |
if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) {
|
src/index.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
| 34 |
buildCommandsEmbed,
|
| 35 |
buildCircaDiagnosticEmbed,
|
| 36 |
buildCircaAlertEmbed,
|
|
|
|
| 37 |
buildDeleteBetEmbed,
|
| 38 |
buildEditBetEmbed,
|
| 39 |
buildErrorEmbed,
|
|
@@ -84,6 +85,7 @@ async function main() {
|
|
| 84 |
embeds: {
|
| 85 |
buildMarketTopEmbed,
|
| 86 |
buildCircaAlertEmbed,
|
|
|
|
| 87 |
},
|
| 88 |
logger: console,
|
| 89 |
});
|
|
|
|
| 34 |
buildCommandsEmbed,
|
| 35 |
buildCircaDiagnosticEmbed,
|
| 36 |
buildCircaAlertEmbed,
|
| 37 |
+
buildCircaFailureEmbed,
|
| 38 |
buildDeleteBetEmbed,
|
| 39 |
buildEditBetEmbed,
|
| 40 |
buildErrorEmbed,
|
|
|
|
| 85 |
embeds: {
|
| 86 |
buildMarketTopEmbed,
|
| 87 |
buildCircaAlertEmbed,
|
| 88 |
+
buildCircaFailureEmbed,
|
| 89 |
},
|
| 90 |
logger: console,
|
| 91 |
});
|
src/market-scanner.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import { Canvas } from 'skia-canvas';
|
|
|
|
| 2 |
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
|
| 3 |
import { createWorker } from 'tesseract.js';
|
| 4 |
|
|
@@ -374,6 +375,25 @@ export function analyzeMarkets(entries, config = {}) {
|
|
| 374 |
};
|
| 375 |
}
|
| 376 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
async function fetchArrayBuffer(url) {
|
| 378 |
const response = await fetch(url);
|
| 379 |
if (!response.ok) {
|
|
@@ -390,6 +410,53 @@ async function fetchJson(url) {
|
|
| 390 |
return response.json();
|
| 391 |
}
|
| 392 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
async function extractPdfText(buffer) {
|
| 394 |
const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(buffer), useWorkerFetch: false, isEvalSupported: false });
|
| 395 |
const pdf = await loadingTask.promise;
|
|
@@ -432,9 +499,23 @@ async function recognizeTextFromImage(imageBuffer) {
|
|
| 432 |
}
|
| 433 |
|
| 434 |
export async function fetchCircaEntries(config) {
|
| 435 |
-
|
| 436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
return {
|
|
|
|
|
|
|
| 438 |
text,
|
| 439 |
entries: parseCircaOcrText(text),
|
| 440 |
};
|
|
@@ -516,6 +597,7 @@ export class MarketScanner {
|
|
| 516 |
lastCircaEntries: 0,
|
| 517 |
lastApiEntries: 0,
|
| 518 |
lastAlertCount: 0,
|
|
|
|
| 519 |
};
|
| 520 |
}
|
| 521 |
|
|
@@ -655,6 +737,7 @@ export class MarketScanner {
|
|
| 655 |
async runCircaDiagnostic() {
|
| 656 |
const result = await fetchCircaEntries(this.config);
|
| 657 |
return {
|
|
|
|
| 658 |
rawTextSample: result.text.slice(0, 1000),
|
| 659 |
parsedEntries: result.entries.slice(0, 10),
|
| 660 |
totalEntries: result.entries.length,
|
|
@@ -683,13 +766,32 @@ export class MarketScanner {
|
|
| 683 |
this.status.lastScanError = null;
|
| 684 |
this.status.lastApiEntries = oddsEntries.length;
|
| 685 |
this.status.lastCircaEntries = circaResult.entries.length;
|
|
|
|
| 686 |
|
| 687 |
return analysis;
|
| 688 |
} catch (error) {
|
| 689 |
this.status.lastScanError = error.message;
|
|
|
|
|
|
|
|
|
|
| 690 |
throw error;
|
| 691 |
} finally {
|
| 692 |
this.running = false;
|
| 693 |
}
|
| 694 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 695 |
}
|
|
|
|
| 1 |
import { Canvas } from 'skia-canvas';
|
| 2 |
+
import { Dropbox } from 'dropbox';
|
| 3 |
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs';
|
| 4 |
import { createWorker } from 'tesseract.js';
|
| 5 |
|
|
|
|
| 375 |
};
|
| 376 |
}
|
| 377 |
|
| 378 |
+
export function matchesCircaMlbFilename(name) {
|
| 379 |
+
return /^MLB\s+Props\s*-\s*\d{4}-\d{1,2}-\d{1,2}\.pdf$/i.test(normalizeWhitespace(name));
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
export function selectLatestCircaFile(entries = []) {
|
| 383 |
+
const candidates = entries
|
| 384 |
+
.filter((entry) => entry['.tag'] === 'file')
|
| 385 |
+
.filter((entry) => /\.pdf$/i.test(entry.name))
|
| 386 |
+
.filter((entry) => matchesCircaMlbFilename(entry.name));
|
| 387 |
+
|
| 388 |
+
if (candidates.length === 0) {
|
| 389 |
+
return null;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
return candidates.sort((left, right) =>
|
| 393 |
+
new Date(right.server_modified).getTime() - new Date(left.server_modified).getTime()
|
| 394 |
+
)[0];
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
async function fetchArrayBuffer(url) {
|
| 398 |
const response = await fetch(url);
|
| 399 |
if (!response.ok) {
|
|
|
|
| 410 |
return response.json();
|
| 411 |
}
|
| 412 |
|
| 413 |
+
function getDropboxClient(config) {
|
| 414 |
+
if (!config.dropboxAccessToken) {
|
| 415 |
+
throw new Error('Missing DROPBOX_ACCESS_TOKEN for Circa Dropbox API access.');
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
return new Dropbox({
|
| 419 |
+
accessToken: config.dropboxAccessToken,
|
| 420 |
+
fetch,
|
| 421 |
+
});
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
async function downloadDropboxFile(config) {
|
| 425 |
+
const client = getDropboxClient(config);
|
| 426 |
+
const folderPath = config.circaDropboxFolderPath;
|
| 427 |
+
if (!folderPath) {
|
| 428 |
+
throw new Error('Missing CIRCA_DROPBOX_FOLDER_PATH for Circa Dropbox API access.');
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
const listResponse = await client.filesListFolder({
|
| 432 |
+
path: folderPath,
|
| 433 |
+
});
|
| 434 |
+
const selectedFile = selectLatestCircaFile(listResponse.result.entries ?? []);
|
| 435 |
+
if (!selectedFile) {
|
| 436 |
+
throw new Error(`No matching MLB Props PDF found in Dropbox folder ${folderPath}.`);
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
const downloadResponse = await client.filesDownload({
|
| 440 |
+
path: selectedFile.path_lower ?? selectedFile.path_display,
|
| 441 |
+
});
|
| 442 |
+
|
| 443 |
+
const binary = downloadResponse.result.fileBinary;
|
| 444 |
+
let buffer;
|
| 445 |
+
if (binary instanceof Blob) {
|
| 446 |
+
buffer = await binary.arrayBuffer();
|
| 447 |
+
} else if (binary?.buffer) {
|
| 448 |
+
buffer = binary.buffer.slice(binary.byteOffset, binary.byteOffset + binary.byteLength);
|
| 449 |
+
} else {
|
| 450 |
+
buffer = binary;
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
return {
|
| 454 |
+
fileName: selectedFile.name,
|
| 455 |
+
buffer,
|
| 456 |
+
source: 'Dropbox API',
|
| 457 |
+
};
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
async function extractPdfText(buffer) {
|
| 461 |
const loadingTask = pdfjsLib.getDocument({ data: new Uint8Array(buffer), useWorkerFetch: false, isEvalSupported: false });
|
| 462 |
const pdf = await loadingTask.promise;
|
|
|
|
| 499 |
}
|
| 500 |
|
| 501 |
export async function fetchCircaEntries(config) {
|
| 502 |
+
let sourceFile;
|
| 503 |
+
if (config.dropboxAccessToken && config.circaDropboxFolderPath) {
|
| 504 |
+
sourceFile = await downloadDropboxFile(config);
|
| 505 |
+
} else if (config.circaDropboxUrl) {
|
| 506 |
+
sourceFile = {
|
| 507 |
+
fileName: 'Circa shared-link file',
|
| 508 |
+
buffer: await fetchArrayBuffer(config.circaDropboxUrl),
|
| 509 |
+
source: 'Shared link',
|
| 510 |
+
};
|
| 511 |
+
} else {
|
| 512 |
+
throw new Error('No Circa source configured. Set Dropbox or shared-link Circa config.');
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
const text = await extractPdfText(sourceFile.buffer);
|
| 516 |
return {
|
| 517 |
+
fileName: sourceFile.fileName,
|
| 518 |
+
source: sourceFile.source,
|
| 519 |
text,
|
| 520 |
entries: parseCircaOcrText(text),
|
| 521 |
};
|
|
|
|
| 597 |
lastCircaEntries: 0,
|
| 598 |
lastApiEntries: 0,
|
| 599 |
lastAlertCount: 0,
|
| 600 |
+
lastCircaFileName: null,
|
| 601 |
};
|
| 602 |
}
|
| 603 |
|
|
|
|
| 737 |
async runCircaDiagnostic() {
|
| 738 |
const result = await fetchCircaEntries(this.config);
|
| 739 |
return {
|
| 740 |
+
fileName: result.fileName,
|
| 741 |
rawTextSample: result.text.slice(0, 1000),
|
| 742 |
parsedEntries: result.entries.slice(0, 10),
|
| 743 |
totalEntries: result.entries.length,
|
|
|
|
| 766 |
this.status.lastScanError = null;
|
| 767 |
this.status.lastApiEntries = oddsEntries.length;
|
| 768 |
this.status.lastCircaEntries = circaResult.entries.length;
|
| 769 |
+
this.status.lastCircaFileName = circaResult.fileName ?? null;
|
| 770 |
|
| 771 |
return analysis;
|
| 772 |
} catch (error) {
|
| 773 |
this.status.lastScanError = error.message;
|
| 774 |
+
if (String(error.message ?? '').toLowerCase().includes('pdf') || String(error.message ?? '').toLowerCase().includes('ocr') || String(error.message ?? '').toLowerCase().includes('dropbox')) {
|
| 775 |
+
await this.sendCircaFailureAlert(error);
|
| 776 |
+
}
|
| 777 |
throw error;
|
| 778 |
} finally {
|
| 779 |
this.running = false;
|
| 780 |
}
|
| 781 |
}
|
| 782 |
+
|
| 783 |
+
async sendCircaFailureAlert(error) {
|
| 784 |
+
const channel = await this.client.channels.fetch(this.config.scanAlertChannelId).catch(() => null);
|
| 785 |
+
if (!channel?.isTextBased()) {
|
| 786 |
+
return;
|
| 787 |
+
}
|
| 788 |
+
|
| 789 |
+
await channel.send({
|
| 790 |
+
embeds: [this.embeds.buildCircaFailureEmbed({
|
| 791 |
+
fileName: this.status.lastCircaFileName,
|
| 792 |
+
reason: error.message,
|
| 793 |
+
source: this.config.dropboxAccessToken ? 'Dropbox API' : 'Shared link',
|
| 794 |
+
})],
|
| 795 |
+
}).catch(() => null);
|
| 796 |
+
}
|
| 797 |
}
|
test/market-scanner.test.js
CHANGED
|
@@ -5,8 +5,10 @@ import {
|
|
| 5 |
analyzeMarkets,
|
| 6 |
buildMarketKey,
|
| 7 |
fetchOddsApiEntries,
|
|
|
|
| 8 |
normalizeOddsApiEntries,
|
| 9 |
parseCircaOcrText,
|
|
|
|
| 10 |
} from '../src/market-scanner.js';
|
| 11 |
|
| 12 |
test('converts american odds to implied probability', () => {
|
|
@@ -165,3 +167,32 @@ test('fetches odds api entries from event-level endpoints', async () => {
|
|
| 165 |
global.fetch = originalFetch;
|
| 166 |
}
|
| 167 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
analyzeMarkets,
|
| 6 |
buildMarketKey,
|
| 7 |
fetchOddsApiEntries,
|
| 8 |
+
matchesCircaMlbFilename,
|
| 9 |
normalizeOddsApiEntries,
|
| 10 |
parseCircaOcrText,
|
| 11 |
+
selectLatestCircaFile,
|
| 12 |
} from '../src/market-scanner.js';
|
| 13 |
|
| 14 |
test('converts american odds to implied probability', () => {
|
|
|
|
| 167 |
global.fetch = originalFetch;
|
| 168 |
}
|
| 169 |
});
|
| 170 |
+
|
| 171 |
+
test('matches expected circa mlb filenames', () => {
|
| 172 |
+
assert.equal(matchesCircaMlbFilename('MLB Props - 2026-4-3.pdf'), true);
|
| 173 |
+
assert.equal(matchesCircaMlbFilename('MLB Props - 2026-04-03.pdf'), true);
|
| 174 |
+
assert.equal(matchesCircaMlbFilename('NBA Props - 2026-4-3.pdf'), false);
|
| 175 |
+
});
|
| 176 |
+
|
| 177 |
+
test('selects newest matching circa mlb pdf', () => {
|
| 178 |
+
const selected = selectLatestCircaFile([
|
| 179 |
+
{
|
| 180 |
+
'.tag': 'file',
|
| 181 |
+
name: 'MLB Props - 2026-4-2.pdf',
|
| 182 |
+
server_modified: '2026-04-02T12:00:00Z',
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
'.tag': 'file',
|
| 186 |
+
name: 'MLB Props - 2026-4-3.pdf',
|
| 187 |
+
server_modified: '2026-04-03T12:00:00Z',
|
| 188 |
+
},
|
| 189 |
+
{
|
| 190 |
+
'.tag': 'file',
|
| 191 |
+
name: 'NBA Props - 2026-4-3.pdf',
|
| 192 |
+
server_modified: '2026-04-04T12:00:00Z',
|
| 193 |
+
},
|
| 194 |
+
]);
|
| 195 |
+
|
| 196 |
+
assert.ok(selected);
|
| 197 |
+
assert.equal(selected.name, 'MLB Props - 2026-4-3.pdf');
|
| 198 |
+
});
|