Compare commits
10 Commits
825cdc7008
...
0b91c0dcf5
Author | SHA1 | Date |
---|---|---|
sloppyjosh | 0b91c0dcf5 | |
sloppyjosh | f2b4d367a4 | |
sloppyjosh | f80ef1005d | |
sloppyjosh | 28e280556e | |
sloppyjosh | 1603c7bf07 | |
sloppyjosh | 35956bb7b1 | |
sloppyjosh | faa74287bb | |
sloppyjosh | af5b35589d | |
sloppyjosh | 522ec2de51 | |
sloppyjosh | a1d5f75d3c |
|
@ -9,8 +9,10 @@
|
|||
"axios-request-throttle": "^1.0.0",
|
||||
"axios-retry": "^4.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"form-data": "^4.0.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"path": "^0.12.7",
|
||||
"qs": "^6.11.2",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
|
@ -306,6 +308,24 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
|
@ -448,6 +468,22 @@
|
|||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
|
@ -519,6 +555,25 @@
|
|||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
|
@ -600,6 +655,14 @@
|
|||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
|
||||
|
@ -628,6 +691,24 @@
|
|||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
|
@ -653,18 +734,73 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"dependencies": {
|
||||
"es-define-property": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
|
@ -1152,6 +1288,14 @@
|
|||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
@ -1270,6 +1414,20 @@
|
|||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
|
@ -1375,6 +1533,39 @@
|
|||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"dependencies": {
|
||||
"define-data-property": "^1.1.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
|
@ -2044,6 +2235,18 @@
|
|||
"unique-filename": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
|
||||
"integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"set-function-length": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
|
@ -2150,6 +2353,16 @@
|
|||
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
|
||||
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
|
||||
},
|
||||
"define-data-property": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
|
||||
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
|
@ -2206,6 +2419,19 @@
|
|||
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
|
||||
"optional": true
|
||||
},
|
||||
"es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="
|
||||
},
|
||||
"escalade": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
|
||||
|
@ -2261,6 +2487,11 @@
|
|||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="
|
||||
},
|
||||
"gauge": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
|
||||
|
@ -2283,6 +2514,18 @@
|
|||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true
|
||||
},
|
||||
"get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"requires": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"github-from-package": {
|
||||
"version": "0.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||
|
@ -2302,18 +2545,52 @@
|
|||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"requires": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"optional": true
|
||||
},
|
||||
"has-property-descriptors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
|
||||
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
|
||||
"requires": {
|
||||
"es-define-property": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q=="
|
||||
},
|
||||
"has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||
},
|
||||
"has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"optional": true
|
||||
},
|
||||
"hasown": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
|
||||
"integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
|
||||
"requires": {
|
||||
"function-bind": "^1.1.2"
|
||||
}
|
||||
},
|
||||
"http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
|
@ -2685,6 +2962,11 @@
|
|||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
|
@ -2782,6 +3064,14 @@
|
|||
"once": "^1.3.1"
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"rc": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
|
||||
|
@ -2849,6 +3139,30 @@
|
|||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"optional": true
|
||||
},
|
||||
"set-function-length": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.1.tgz",
|
||||
"integrity": "sha512-j4t6ccc+VsKwYHso+kElc5neZpjtq9EnRICFZtWyBsLojhmeF/ZBd/elqm22WJh/BziDe/SBiOeAt0m2mfLD0g==",
|
||||
"requires": {
|
||||
"define-data-property": "^1.1.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-intrinsic": "^1.2.3",
|
||||
"gopd": "^1.0.1",
|
||||
"has-property-descriptors": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"side-channel": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
|
||||
"integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.7",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.4",
|
||||
"object-inspect": "^1.13.1"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
|
|
|
@ -4,8 +4,10 @@
|
|||
"axios-request-throttle": "^1.0.0",
|
||||
"axios-retry": "^4.0.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"form-data": "^4.0.0",
|
||||
"fs": "^0.0.1-security",
|
||||
"path": "^0.12.7",
|
||||
"qs": "^6.11.2",
|
||||
"sqlite": "^5.1.1",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
|
@ -15,7 +17,7 @@
|
|||
"typescript": "^5.3.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc && copyfiles -u 3 \"src/db/migrations/*.sql\" dist/db/migrations && copyfiles -u 3 \"src/db/seed/*.sql\" dist/db/seed",
|
||||
"build": "tsc && copyfiles -u 3 \"src/db/migrations/*.sql\" dist/db/migrations && copyfiles -u 3 \"src/db/seed/*.sql\" dist/db/seed && copyfiles -u 2 \"src/messages/*.txt\" dist/messages",
|
||||
"start": "node dist/index.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
||||
id INTEGER PRIMARY KEY,
|
||||
token_identifier TEXT NOT NULL UNIQUE, -- Static identifier for the OAuth token
|
||||
access_token TEXT NOT NULL,
|
||||
token_type TEXT NOT NULL,
|
||||
expires_in INTEGER NOT NULL,
|
||||
expiry_timestamp INTEGER NOT NULL,
|
||||
scope TEXT NOT NULL
|
||||
);
|
|
@ -1,30 +1,50 @@
|
|||
import { Database } from 'sqlite';
|
||||
import { Comment } from '../../rdrama/models/Comment';
|
||||
import { DatabaseInitializer } from '../initializeDatabase';
|
||||
|
||||
/**
|
||||
* Service for interacting with the SQLite database for operations related to comments and user mentions.
|
||||
*/
|
||||
export class DatabaseService {
|
||||
private db: Database;
|
||||
|
||||
/**
|
||||
* Creates a new DatabaseService instance with a provided database connection.
|
||||
* Retrieves the singleton instance of the database.
|
||||
* This static method ensures that a single database instance is used throughout the application,
|
||||
* following the singleton pattern for managing database connections.
|
||||
*
|
||||
* @param {Database} db - The SQLite database connection.
|
||||
* @example
|
||||
* const db = await DatabaseService.getDatabase();
|
||||
*
|
||||
* @returns {Promise<Database>} A promise that resolves to the initialized database instance.
|
||||
* @throws {Error} Will throw an error if the database cannot be initialized.
|
||||
*/
|
||||
public constructor(db: Database) {
|
||||
this.db = db;
|
||||
private static async getDatabase(): Promise<Database> {
|
||||
const databaseInitializer = DatabaseInitializer.getInstance();
|
||||
const db = await databaseInitializer.getDbInstance()
|
||||
if (!db) {
|
||||
throw new Error('Failed to initialize the database.');
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new comment into the database.
|
||||
*
|
||||
* This method constructs an SQL statement to insert all fields of the Comment object
|
||||
* This static method constructs an SQL statement to insert all fields of the Comment object
|
||||
* into the corresponding columns in the 'comments' table.
|
||||
*
|
||||
* @example
|
||||
* await DatabaseService.insertComment({
|
||||
* id: 1,
|
||||
* author_id: 123,
|
||||
* author_name: 'exampleUser',
|
||||
* body: 'This is a comment.',
|
||||
* // More fields as per the Comment type
|
||||
* });
|
||||
*
|
||||
* @param {Comment} comment - The comment object to insert.
|
||||
* @throws {Error} Will throw an error if the insert operation fails.
|
||||
*/
|
||||
public async insertComment(comment: Comment): Promise<void> {
|
||||
public static async insertComment(comment: Comment): Promise<void> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const sql = `
|
||||
INSERT INTO comments (
|
||||
id, author_id, author_name, body, body_html, created_utc, deleted_utc,
|
||||
|
@ -36,7 +56,7 @@ export class DatabaseService {
|
|||
?, ?, ?, ?, ?, ?, ?
|
||||
)
|
||||
`;
|
||||
await this.db.run(sql, [
|
||||
await db.run(sql, [
|
||||
comment.id, comment.author_id, comment.author_name, comment.body, comment.body_html, comment.created_utc, comment.deleted_utc,
|
||||
comment.distinguished ? 1 : 0, comment.downvotes, comment.edited_utc, comment.is_banned ? 1 : 0, comment.is_bot ? 1 : 0, comment.is_nsfw ? 1 : 0, comment.level,
|
||||
comment.permalink, comment.pinned, comment.post_id, JSON.stringify(comment.replies), JSON.stringify(comment.reports), comment.score, comment.upvotes
|
||||
|
@ -45,62 +65,91 @@ export class DatabaseService {
|
|||
|
||||
/**
|
||||
* Inserts a new user mention into the database.
|
||||
* This static method adds a record of a user being mentioned in a comment.
|
||||
*
|
||||
* @param {Object} mention - The user mention object to insert, containing rdrama_comment_id, username, and optionally message.
|
||||
* @example
|
||||
* await DatabaseService.insertUserMention({
|
||||
* rdrama_comment_id: 456,
|
||||
* username: 'mentionedUser',
|
||||
* message: 'You were mentioned in a comment.'
|
||||
* });
|
||||
*
|
||||
* @param {Object} mention - The user mention object to insert.
|
||||
* @param {number} mention.rdrama_comment_id - The ID of the comment from the r/Drama platform.
|
||||
* @param {string} mention.username - The mentioned Reddit username.
|
||||
* @param {string} [mention.message] - The content of the message sent to the mentioned user.
|
||||
* @throws {Error} Will throw an error if the insert operation fails.
|
||||
*/
|
||||
public async insertUserMention(mention: { rdrama_comment_id: string; username: string; message?: string }): Promise<void> {
|
||||
public static async insertUserMention(mention: { rdrama_comment_id: number; username: string; message?: string }): Promise<void> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const sql = `INSERT INTO user_mentions (rdrama_comment_id, username, message) VALUES (?, ?, ?)`;
|
||||
await this.db.run(sql, [mention.rdrama_comment_id, mention.username, mention.message]);
|
||||
await db.run(sql, [mention.rdrama_comment_id, mention.username, mention.message]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the database for an existing comment.
|
||||
* Queries the database for an existing comment by its ID.
|
||||
*
|
||||
* @example
|
||||
* const exists = await DatabaseService.commentExists('123');
|
||||
* console.log(exists ? 'Comment exists.' : 'Comment does not exist.');
|
||||
*
|
||||
* @param {string} commentId - The ID of the comment to search for.
|
||||
* @returns {Promise<boolean>} A boolean indicating whether the comment exists.
|
||||
* @throws {Error} Will throw an error if the query operation fails.
|
||||
*/
|
||||
public async commentExists(commentId: string): Promise<boolean> {
|
||||
public static async commentExists(commentId: string): Promise<boolean> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const sql = `SELECT 1 FROM comments WHERE id = ?`;
|
||||
const result = await this.db.get(sql, [commentId]);
|
||||
const result = await db.get(sql, [commentId]);
|
||||
return !!result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries the database for existing mentions of a username.
|
||||
* Queries the database to check if a username has been mentioned.
|
||||
*
|
||||
* @example
|
||||
* const mentioned = await DatabaseService.userMentionExists('exampleUser');
|
||||
* console.log(mentioned ? 'User has been mentioned.' : 'User has not been mentioned.');
|
||||
*
|
||||
* @param {string} username - The username to search for.
|
||||
* @returns {Promise<boolean>} A boolean indicating whether the username has been mentioned.
|
||||
* @throws {Error} Will throw an error if the query operation fails.
|
||||
*/
|
||||
public async userMentionExists(username: string): Promise<boolean> {
|
||||
public static async userMentionExists(username: string): Promise<boolean> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const sql = `SELECT 1 FROM user_mentions WHERE username = ?`;
|
||||
const result = await this.db.get(sql, [username]);
|
||||
const result = await db.get(sql, [username]);
|
||||
return !!result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the last run timestamp for a specified maintenance task.
|
||||
* This method queries the `maintenance_log` table to find the last time a specific task was run.
|
||||
* Updates the last run timestamp for a maintenance task, using an "upsert" approach.
|
||||
*
|
||||
* @param {string} taskName - The name of the maintenance task for which to retrieve the last run timestamp.
|
||||
* @returns {Promise<Date | null>} A promise that resolves to the Date of the last run if found, or null if not found.
|
||||
* @example
|
||||
* await DatabaseService.updateLastRunTimestamp('purgeOldComments');
|
||||
*
|
||||
* @param {string} taskName - The name of the maintenance task.
|
||||
* @throws {Error} Will throw an error if the update operation fails.
|
||||
*/
|
||||
public async getLastRunTimestamp(taskName: string): Promise<Date | null> {
|
||||
const result = await this.db.get(`SELECT last_run FROM maintenance_log WHERE task_name = ?`, [taskName]);
|
||||
public static async getLastRunTimestamp(taskName: string): Promise<Date | null> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const result = await db.get(`SELECT last_run FROM maintenance_log WHERE task_name = ?`, [taskName]);
|
||||
return result ? new Date(result.last_run) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the last run timestamp for a specific maintenance task in the `maintenance_log` table.
|
||||
* This method uses an "upsert" approach, which inserts a new record if one doesn't exist for the task,
|
||||
* or updates the existing record if it does. This ensures that each task has only one record in the table
|
||||
* reflecting its most recent execution time.
|
||||
* Updates the last run timestamp for a maintenance task, using an "upsert" approach.
|
||||
*
|
||||
* @param {string} taskName - The name of the maintenance task for which to update the last run timestamp.
|
||||
* @returns {Promise<void>} A promise that resolves when the operation is complete.
|
||||
* @example
|
||||
* await DatabaseService.updateLastRunTimestamp('purgeOldComments');
|
||||
*
|
||||
* @param {string} taskName - The name of the maintenance task.
|
||||
* @throws {Error} Will throw an error if the update operation fails.
|
||||
*/
|
||||
public async updateLastRunTimestamp(taskName: string): Promise<void> {
|
||||
public static async updateLastRunTimestamp(taskName: string): Promise<void> {
|
||||
// Assumes an "upsert" approach for the maintenance_log table
|
||||
await this.db.run(
|
||||
const db = await DatabaseService.getDatabase()
|
||||
await db.run(
|
||||
`INSERT INTO maintenance_log (task_name, last_run)
|
||||
VALUES (?, ?)
|
||||
ON CONFLICT(task_name)
|
||||
|
@ -110,16 +159,113 @@ export class DatabaseService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Deletes comments from the database that are older than a specified number of days.
|
||||
* This method is intended to be run as part of periodic maintenance tasks to keep the database size manageable.
|
||||
* Deletes comments from the database older than a specified number of days.
|
||||
*
|
||||
* @param {number} [days=1] - The age of comments to be purged, in days. Defaults to 30 days.
|
||||
* @example
|
||||
* await DatabaseService.purgeOldComments(30); // Purge comments older than 30 days
|
||||
*
|
||||
* @param {number} days - The age of comments to be purged, in days.
|
||||
* @throws {Error} Will throw an error if the purge operation fails.
|
||||
*/
|
||||
public async purgeOldComments(days: number = 1): Promise<void> {
|
||||
public static async purgeOldComments(days: number = 1): Promise<void> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
console.log(`Purging comments older than ${days} days...`);
|
||||
await this.db.run(`
|
||||
await db.run(`
|
||||
DELETE FROM comments
|
||||
WHERE datetime(created_utc, 'unixepoch') < datetime('now', '-${days} days')
|
||||
`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates the OAuth token in the database for a specific service.
|
||||
*
|
||||
* @example
|
||||
* await DatabaseService.upsertOAuthToken('https://oauth.reddit.com', {
|
||||
* access_token: 'abc123',
|
||||
* token_type: 'bearer',
|
||||
* expires_in: 3600,
|
||||
* scope: 'read'
|
||||
* });
|
||||
*
|
||||
* @param {string} token_identifier - A unique identifier for the token, typically the service's base URL.
|
||||
* @param {Object} tokenData - The OAuth token data including access_token, token_type, expires_in, and scope.
|
||||
* @throws {Error} Will throw an error if the upsert operation fails.
|
||||
*/
|
||||
public static async upsertOAuthToken(token_identifier: string, tokenData: any) {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const { access_token, token_type, expires_in, scope } = tokenData;
|
||||
const expiryTimestamp = Math.floor(Date.now() / 1000) + expires_in;
|
||||
console.log('token_identifier', token_identifier)
|
||||
console.log('access_token', `${access_token.substring(0, 5)}XXXXX`)
|
||||
console.log('token_type', token_type)
|
||||
console.log('expires_in', expires_in)
|
||||
console.log('scope', scope)
|
||||
|
||||
await db.run(`
|
||||
INSERT INTO oauth_tokens (token_identifier, access_token, token_type, expires_in, expiry_timestamp, scope)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(token_identifier) DO UPDATE SET
|
||||
access_token = excluded.access_token,
|
||||
token_type = excluded.token_type,
|
||||
expires_in = excluded.expires_in,
|
||||
expiry_timestamp = excluded.expiry_timestamp,
|
||||
scope = excluded.scope
|
||||
`, [token_identifier, access_token, token_type, expires_in, expiryTimestamp, scope]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the current, unexpired OAuth token for a specific service.
|
||||
*
|
||||
* @example
|
||||
* const token = await DatabaseService.getCurrentOAuthToken('https://oauth.reddit.com');
|
||||
* console.log(token ? `Current token: ${token.access_token}` : 'No valid token found.');
|
||||
*
|
||||
* @param {string} token_identifier - The unique identifier for the token, typically the service's base URL.
|
||||
* @returns {Promise<Object|null>} The current OAuth token data or null if expired or not found.
|
||||
* @throws {Error} Will throw an error if the query operation fails.
|
||||
*/
|
||||
public static async getCurrentOAuthToken(token_identifier: string) {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const tokenRow = await db.get(`
|
||||
SELECT access_token, token_type, scope, expiry_timestamp FROM oauth_tokens
|
||||
WHERE token_identifier = ?
|
||||
`, token_identifier);
|
||||
|
||||
return tokenRow || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cooldown period has passed since the last notification was sent, allowing for a new notification to be sent.
|
||||
*
|
||||
* @example
|
||||
* const canSend = await DatabaseService.canSendNotification();
|
||||
* console.log(canSend ? 'Can send a new notification.' : 'Still in cooldown period.');
|
||||
*
|
||||
* @returns {Promise<boolean>} True if the cooldown period has passed, allowing new notifications to be sent.
|
||||
* @throws {Error} Will throw an error if the check operation fails.
|
||||
*/
|
||||
public static async canSendNotification(): Promise<boolean> {
|
||||
const db = await DatabaseService.getDatabase()
|
||||
const cooldownHours = process.env.NOTIFICATION_COOLDOWN_HOURS || 4;
|
||||
const sql = `
|
||||
SELECT MAX(sent_time) as last_notification_time
|
||||
FROM user_mentions
|
||||
`;
|
||||
const result = await db.get(sql);
|
||||
|
||||
if (!result || !result.last_notification_time) {
|
||||
// No notifications have been sent yet, or unable to retrieve the last sent time.
|
||||
return true;
|
||||
}
|
||||
|
||||
const lastNotificationTime = new Date(result.last_notification_time).getTime();
|
||||
const currentTime = new Date(new Date().toISOString().slice(0, 19).replace('T', ' ')).getTime();
|
||||
const timeElapsed = currentTime - lastNotificationTime;
|
||||
console.log('timeElapsed', timeElapsed)
|
||||
const cooldownPeriod = +cooldownHours * 60 * 60 * 1000; // Convert hours to milliseconds
|
||||
console.log('cooldownPeriod', cooldownPeriod)
|
||||
|
||||
return timeElapsed >= cooldownPeriod;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,15 +5,7 @@ import { DatabaseService } from './Database';
|
|||
* This service is responsible for periodically running maintenance tasks based on specified intervals.
|
||||
*/
|
||||
export class DatabaseMaintenanceService {
|
||||
private databaseService: DatabaseService;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the DatabaseMaintenanceService.
|
||||
* @param {DatabaseService} databaseService - An instance of DatabaseService for database operations.
|
||||
*/
|
||||
constructor(databaseService: DatabaseService) {
|
||||
this.databaseService = databaseService;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of maintenance tasks to be executed, each with a name, action, and interval.
|
||||
|
@ -49,7 +41,7 @@ export class DatabaseMaintenanceService {
|
|||
*/
|
||||
private async shouldRunTask(taskName: string, interval: number): Promise<boolean> {
|
||||
// Use the DatabaseService to check the last run timestamp from the maintenance_log table
|
||||
const lastRun = await this.databaseService.getLastRunTimestamp(taskName);
|
||||
const lastRun = await DatabaseService.getLastRunTimestamp(taskName);
|
||||
if (!lastRun) return true; // Task has never run
|
||||
|
||||
const now = Date.now();
|
||||
|
@ -62,7 +54,7 @@ export class DatabaseMaintenanceService {
|
|||
private async purgeOldComments() {
|
||||
console.log("Purging old comments...");
|
||||
// Use the DatabaseService for the SQL operation
|
||||
await this.databaseService.purgeOldComments();
|
||||
await DatabaseService.purgeOldComments();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -72,6 +64,6 @@ export class DatabaseMaintenanceService {
|
|||
*/
|
||||
private async updateLastRunTimestamp(taskName: string) {
|
||||
// Use the DatabaseService to update the last run timestamp in the maintenance_log table
|
||||
await this.databaseService.updateLastRunTimestamp(taskName);
|
||||
await DatabaseService.updateLastRunTimestamp(taskName);
|
||||
}
|
||||
}
|
39
src/index.ts
39
src/index.ts
|
@ -2,40 +2,55 @@ import dotenv from 'dotenv';
|
|||
dotenv.config();
|
||||
|
||||
import WorkflowOrchestrator from './workflows/WorkflowOrchestrator';
|
||||
import SessionManager from './rdrama/session/SessionManager';
|
||||
import rDramaSession from './rdrama/session/SessionManager';
|
||||
import redditSession from './reddit/session/SessionManager';
|
||||
import { CommentParser } from './rdrama/services/CommentParser';
|
||||
import { DatabaseInitializer } from './db/initializeDatabase';
|
||||
import { DatabaseService } from './db/services/Database';
|
||||
import { DatabaseMaintenanceService } from './db/services/DatabaseMaintenance';
|
||||
import { CommentProcessor } from './rdrama/services/CommentProcessor';
|
||||
import { CommentPoster } from './rdrama/services/CommentPoster';
|
||||
// Import other necessary services or configurations
|
||||
|
||||
async function startApplication() {
|
||||
// Initialize SessionManager or other global configurations
|
||||
const sessionManager = SessionManager.getInstance();
|
||||
if (!process.env.RDRAMA_API_KEY) {
|
||||
throw new Error('RDRAMA_API_KEY is undefined. Please set this environment variable.');
|
||||
}
|
||||
sessionManager.setAuthorizationToken(process.env.RDRAMA_API_KEY);
|
||||
|
||||
console.log('Database Start')
|
||||
const databaseInitializer = DatabaseInitializer.getInstance();
|
||||
const db = await databaseInitializer.getDbInstance()
|
||||
if (!db) {
|
||||
throw new Error('Failed to initialize the database.');
|
||||
}
|
||||
const databaseService = new DatabaseService(db)
|
||||
const databaseMaintenance = new DatabaseMaintenanceService(databaseService)
|
||||
const canSend = await DatabaseService.canSendNotification();
|
||||
const coolDownHours = process.env.NOTIFICATION_COOLDOWN_HOURS
|
||||
if (!canSend) {
|
||||
console.log(`Last Message Sent less than ${coolDownHours ? coolDownHours : 4} hours ago. Set NOTIFICATION_COOLDOWN_HOURS to change this`)
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('RDrama Session Start')
|
||||
// Initialize SessionManager or other global configurations
|
||||
const rDramaSessionManager = rDramaSession.getInstance();
|
||||
if (!process.env.RDRAMA_API_KEY) {
|
||||
throw new Error('RDRAMA_API_KEY is undefined. Please set this environment variable.');
|
||||
}
|
||||
rDramaSessionManager.setAuthorizationToken(process.env.RDRAMA_API_KEY);
|
||||
|
||||
console.log('Database Maintenance Start')
|
||||
const databaseMaintenance = new DatabaseMaintenanceService()
|
||||
await databaseMaintenance.runMaintenanceTasks()
|
||||
|
||||
console.log('Reddit Session Start')
|
||||
await redditSession.getInstance()
|
||||
|
||||
// Initialize services with any required dependencies
|
||||
const commentFetcher = new CommentProcessor(databaseService);
|
||||
const commentFetcher = new CommentProcessor();
|
||||
const commentParser = new CommentParser();
|
||||
const commentPoster = new CommentPoster()
|
||||
|
||||
// Initialize and start your workflow
|
||||
const workflowOrchestrator = new WorkflowOrchestrator(
|
||||
commentFetcher,
|
||||
commentParser
|
||||
commentParser,
|
||||
commentPoster
|
||||
);
|
||||
await workflowOrchestrator.executeWorkflow();
|
||||
}
|
||||
|
|
|
@ -10,20 +10,20 @@ export class CommentParser {
|
|||
*/
|
||||
public extractUsernames(comment: Comment): string[] {
|
||||
const foundUsernames: Set<string> = new Set();
|
||||
|
||||
|
||||
const matches = comment.body.match(this.regexPattern);
|
||||
if (matches) {
|
||||
matches.forEach(match => {
|
||||
// Ensure the username is captured in a standardized format
|
||||
const usernameMatch = match.trim().match(/\/?u\/([a-zA-Z0-9_]+)/);
|
||||
if (usernameMatch) {
|
||||
// Standardize to "u/username" format
|
||||
const username = `u/${usernameMatch[1].toLowerCase()}`;
|
||||
// Standardize to "username" format
|
||||
const username = `${usernameMatch[1].toLowerCase()}`;
|
||||
foundUsernames.add(username);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return Array.from(foundUsernames);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import SessionManager from '../session/SessionManager';
|
||||
import { Comment } from '../models/Comment';
|
||||
import FormData from 'form-data';
|
||||
|
||||
export class CommentPoster {
|
||||
private sessionManager: SessionManager;
|
||||
|
||||
constructor() {
|
||||
this.sessionManager = SessionManager.getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts a comment as a reply to a given rdrama comment.
|
||||
*
|
||||
* @param parentId The ID of the parent comment to reply to. Expected format: 'c_{id}'.
|
||||
* @param body The body of the comment to post.
|
||||
* @returns A promise resolving to the Axios response.
|
||||
*/
|
||||
public async postComment(parentId: string, body: string): Promise<Comment> {
|
||||
const formData = new FormData();
|
||||
formData.append('parent_fullname', parentId);
|
||||
formData.append('body', body);
|
||||
|
||||
try {
|
||||
const response = await this.sessionManager.axiosInstance.post('/comment', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
console.log(`Comment posted successfully to ${parentId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(`Failed to post comment to ${parentId}:`, error);
|
||||
throw error; // Rethrow for handling elsewhere
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,15 +9,13 @@ import { CommentFetcher } from './CommentFetcher';
|
|||
*/
|
||||
export class CommentProcessor {
|
||||
private maxPages: number;
|
||||
private databaseService: DatabaseService;
|
||||
|
||||
/**
|
||||
* Creates an instance of CommentProcessor.
|
||||
* @param {DatabaseService} databaseService - The service for database operations.
|
||||
* @param {number} maxPages - The maximum number of pages to fetch from the r/Drama API. Defaults to 10.
|
||||
*/
|
||||
constructor(databaseService: DatabaseService, maxPages: number = 10) {
|
||||
this.databaseService = databaseService;
|
||||
constructor(maxPages: number = 10) {
|
||||
this.maxPages = maxPages;
|
||||
}
|
||||
|
||||
|
@ -41,12 +39,12 @@ export class CommentProcessor {
|
|||
// Check if the comment was already processed in this batch
|
||||
if (comments.some(c => c.id === comment.id)) continue;
|
||||
|
||||
const exists = await this.databaseService.commentExists(comment.id.toString());
|
||||
const exists = await DatabaseService.commentExists(comment.id.toString());
|
||||
if (exists) {
|
||||
stopFetching = true;
|
||||
break; // Stop processing this batch of comments
|
||||
}
|
||||
await this.databaseService.insertComment(comment)
|
||||
await DatabaseService.insertComment(comment)
|
||||
comments.push(comment);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
export type Subreddit = {
|
||||
default_set?: boolean;
|
||||
user_is_contributor?: boolean;
|
||||
banner_img?: string;
|
||||
allowed_media_in_comments?: any[];
|
||||
user_is_banned?: boolean;
|
||||
free_form_reports?: boolean;
|
||||
community_icon?: string | null;
|
||||
show_media?: boolean;
|
||||
icon_color?: string;
|
||||
user_is_muted?: boolean | null;
|
||||
display_name?: string;
|
||||
header_img?: string | null;
|
||||
title?: string;
|
||||
coins?: number;
|
||||
previous_names?: any[];
|
||||
over_18?: boolean;
|
||||
icon_size?: number[] | null;
|
||||
primary_color?: string;
|
||||
icon_img?: string;
|
||||
description?: string;
|
||||
submit_link_label?: string;
|
||||
header_size?: number[] | null;
|
||||
restrict_posting?: boolean;
|
||||
restrict_commenting?: boolean;
|
||||
subscribers?: number;
|
||||
submit_text_label?: string;
|
||||
is_default_icon?: boolean;
|
||||
link_flair_position?: string;
|
||||
display_name_prefixed?: string;
|
||||
key_color?: string;
|
||||
name?: string;
|
||||
is_default_banner?: boolean;
|
||||
url?: string;
|
||||
quarantine?: boolean;
|
||||
banner_size?: number[] | null;
|
||||
user_is_moderator?: boolean;
|
||||
accept_followers?: boolean;
|
||||
public_description?: string;
|
||||
link_flair_enabled?: boolean;
|
||||
disable_contributor_requests?: boolean;
|
||||
subreddit_type?: string;
|
||||
user_is_subscriber?: boolean;
|
||||
};
|
|
@ -0,0 +1,71 @@
|
|||
import { Subreddit } from "./Subreddit";
|
||||
|
||||
export type RedditUser = {
|
||||
kind: string;
|
||||
data: {
|
||||
is_employee?: boolean;
|
||||
has_visited_new_profile?: boolean;
|
||||
is_friend: boolean;
|
||||
pref_no_profanity?: boolean;
|
||||
has_external_account?: boolean;
|
||||
pref_geopopular?: string;
|
||||
pref_show_trending?: boolean;
|
||||
subreddit: Subreddit
|
||||
pref_show_presence?: boolean;
|
||||
snoovatar_img?: string;
|
||||
snoovatar_size?: number[] | null;
|
||||
gold_expiration?: null;
|
||||
has_gold_subscription?: boolean;
|
||||
is_sponsor?: boolean;
|
||||
num_friends?: number;
|
||||
features?: any;
|
||||
can_edit_name?: boolean;
|
||||
is_blocked?: boolean;
|
||||
verified?: boolean;
|
||||
new_modmail_exists?: null;
|
||||
pref_autoplay?: boolean;
|
||||
coins?: number;
|
||||
has_paypal_subscription?: boolean;
|
||||
has_subscribed_to_premium?: boolean;
|
||||
id: string;
|
||||
can_create_subreddit?: boolean;
|
||||
over_18?: boolean;
|
||||
is_gold?: boolean;
|
||||
is_mod?: boolean;
|
||||
awarder_karma?: number;
|
||||
suspension_expiration_utc?: null;
|
||||
has_stripe_subscription?: boolean;
|
||||
is_suspended?: boolean;
|
||||
pref_video_autoplay?: boolean;
|
||||
in_chat?: boolean;
|
||||
has_android_subscription?: boolean;
|
||||
in_redesign_beta?: boolean;
|
||||
icon_img: string;
|
||||
has_mod_mail?: boolean;
|
||||
pref_nightmode?: boolean;
|
||||
awardee_karma?: number;
|
||||
hide_from_robots?: boolean;
|
||||
password_set?: boolean;
|
||||
modhash?: null;
|
||||
link_karma: number;
|
||||
force_password_reset?: boolean;
|
||||
total_karma: number;
|
||||
inbox_count?: number;
|
||||
pref_top_karma_subreddits?: boolean;
|
||||
has_mail?: boolean;
|
||||
pref_show_snoovatar?: boolean;
|
||||
name: string;
|
||||
pref_clickgadget?: number;
|
||||
created: number;
|
||||
has_verified_email: boolean;
|
||||
gold_creddits?: number;
|
||||
created_utc: number;
|
||||
has_ios_subscription?: boolean;
|
||||
pref_show_twitter?: boolean;
|
||||
in_beta?: boolean;
|
||||
comment_karma: number;
|
||||
accept_followers: boolean;
|
||||
has_subscribed: boolean;
|
||||
accept_pms?: boolean;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,37 @@
|
|||
import axios, { AxiosError } from 'axios';
|
||||
import SessionManager from '../session/SessionManager';
|
||||
import { RedditUser } from '../model/User';
|
||||
|
||||
export class RedditService {
|
||||
private sessionManager: SessionManager;
|
||||
|
||||
constructor(sessionManager: SessionManager) {
|
||||
this.sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
async getUserInfo(username: string): Promise<RedditUser> {
|
||||
try {
|
||||
const response = await this.sessionManager.axiosInstance.get(`/user/${username}/about`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching user info:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage(username: string, subject: string, message: string): Promise<void> {
|
||||
try {
|
||||
console.log(`await this.sessionManager.axiosInstance.post('/api/compose', {\n\tapi_type: 'json',\n\tto: ${username},\n\tsubject: ${subject},\n\ttext: ${message},\n});`)
|
||||
//await this.sessionManager.axiosInstance.post('/api/compose', {
|
||||
// api_type: 'json',
|
||||
// to: username,
|
||||
// subject: subject,
|
||||
// text: message,
|
||||
//});
|
||||
console.log(`Message sent to ${username}`);
|
||||
} catch (error) {
|
||||
console.error('Error sending message:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
import axios, { AxiosInstance, AxiosError } from 'axios';
|
||||
const qs = require('qs');
|
||||
import dotenv from 'dotenv';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import axiosThrottle from 'axios-request-throttle';
|
||||
import { DatabaseService } from '../../db/services/Database';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
class RedditSessionManager {
|
||||
private static instance: RedditSessionManager;
|
||||
public axiosInstance: AxiosInstance;
|
||||
|
||||
private constructor() {
|
||||
axiosThrottle.use(axios, { requestsPerSecond: 1 }); // Throttle setup
|
||||
|
||||
this.axiosInstance = axios.create({
|
||||
baseURL: 'https://oauth.reddit.com/', // Base URL for OAuth2 Reddit API
|
||||
headers: {
|
||||
'User-Agent': 'CrossTalk PM/0.1 by Whitneywisconson'
|
||||
}
|
||||
});
|
||||
axiosRetry(this.axiosInstance, {
|
||||
retries: 3,
|
||||
retryDelay: this.retryDelayStrategy,
|
||||
retryCondition: this.retryCondition,
|
||||
});
|
||||
}
|
||||
|
||||
public static async getInstance(): Promise<RedditSessionManager> {
|
||||
if (!RedditSessionManager.instance) {
|
||||
RedditSessionManager.instance = new RedditSessionManager();
|
||||
await RedditSessionManager.instance.initializeAuthentication();
|
||||
}
|
||||
return RedditSessionManager.instance;
|
||||
}
|
||||
|
||||
private async initializeAuthentication() {
|
||||
// Check the database for an existing token
|
||||
const currentToken = await DatabaseService.getCurrentOAuthToken(this.axiosInstance.defaults.baseURL as string);
|
||||
if (currentToken && new Date() < new Date(currentToken.expiry_timestamp * 1000)) {
|
||||
this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${currentToken.access_token}`;
|
||||
console.log('Using existing Reddit API token from database.');
|
||||
return;
|
||||
}
|
||||
console.log('No current Reddit API token from database requesting one')
|
||||
|
||||
// Authenticate with Reddit API to get a new token
|
||||
await this.authenticate();
|
||||
}
|
||||
|
||||
private async authenticate() {
|
||||
if (!process.env.redditUsername) throw 'No Reddit Username Found in .env'
|
||||
if (!process.env.redditPassword) throw 'No Reddit Password Found in .env'
|
||||
const redditUsername = process.env.redditUsername as string
|
||||
const redditPassword = process.env.redditPassword as string
|
||||
const credentials = qs.stringify({
|
||||
grant_type: 'password',
|
||||
username: redditUsername,
|
||||
password: redditPassword,
|
||||
});
|
||||
|
||||
const authString = `${process.env.redditClientId}:${process.env.redditSecret}`;
|
||||
const buffer = Buffer.from(authString);
|
||||
const base64AuthString = buffer.toString('base64');
|
||||
|
||||
try {
|
||||
const response = await this.axiosInstance.post('https://www.reddit.com/api/v1/access_token', credentials, {
|
||||
headers: {
|
||||
'User-Agent': `CrossTalk PM/0.1 by ${redditUsername}`, //TODO Dynamically set app name here
|
||||
'Authorization': `Basic ${base64AuthString}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
});
|
||||
|
||||
// Upsert the new token into the database
|
||||
await DatabaseService.upsertOAuthToken(
|
||||
this.axiosInstance.defaults.baseURL as string,
|
||||
{
|
||||
access_token: response.data.access_token,
|
||||
token_type: response.data.token_type,
|
||||
expires_in: response.data.expires_in,
|
||||
scope: response.data.scope,
|
||||
});
|
||||
|
||||
this.axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${response.data.access_token}`;
|
||||
console.log('Reddit API authenticated successfully.');
|
||||
} catch (error) {
|
||||
console.error('Error authenticating with Reddit API:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private retryDelayStrategy(retryCount: number, error: AxiosError): number {
|
||||
const retryAfter = error.response?.headers['retry-after'];
|
||||
if (retryAfter) {
|
||||
console.log(`429 Retry After: ${retryAfter}`);
|
||||
return +retryAfter * 1000;
|
||||
}
|
||||
return Math.pow(2, retryCount) * 2000;
|
||||
}
|
||||
|
||||
private retryCondition(error: AxiosError): boolean {
|
||||
const status = error.response?.status ?? 0;
|
||||
return status === 429 || status >= 400;
|
||||
}
|
||||
}
|
||||
|
||||
export default RedditSessionManager;
|
|
@ -1,41 +1,93 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Manages the retrieval and formatting of messages stored in text files.
|
||||
* This class provides functionality to load messages for rDrama and Reddit,
|
||||
* select a random message, and replace placeholders within that message
|
||||
* with specified values.
|
||||
*/
|
||||
export class MessageService {
|
||||
private redditMessages: string[] = [];
|
||||
private rdramaMessages: string[] = [];
|
||||
|
||||
constructor() {
|
||||
this.loadMessages();
|
||||
}
|
||||
|
||||
private loadMessages() {
|
||||
/**
|
||||
* Loads rDrama messages from a text file, splitting by a specific delimiter.
|
||||
* Each message is separated by '---END---' in the text file.
|
||||
*
|
||||
* @example
|
||||
* const rdramaMessages = MessageService.loadRdramaMessages();
|
||||
*
|
||||
* @returns {string[] | undefined} An array of rDrama messages, or undefined if there was an error loading the messages.
|
||||
*/
|
||||
private static loadRdramaMessages(): string[] | undefined {
|
||||
try {
|
||||
const redditMessagesPath = path.join(__dirname, 'messages', 'reddit_messages.txt');
|
||||
this.redditMessages = fs.readFileSync(redditMessagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
||||
} catch (error) {
|
||||
console.error('Failed to load Reddit messages:', error);
|
||||
}
|
||||
|
||||
try {
|
||||
const rdramaMessagesPath = path.join(__dirname, 'messages', 'rdrama_messages.txt');
|
||||
this.rdramaMessages = fs.readFileSync(rdramaMessagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
||||
const rdramaMessagesPath = path.join(__dirname, '..', 'messages', 'rdrama_messages.txt');
|
||||
return fs.readFileSync(rdramaMessagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
||||
} catch (error) {
|
||||
console.error('Failed to load rDrama messages:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public getRandomRedditMessage(placeholders: { [key: string]: string }): string {
|
||||
const message = this.redditMessages[Math.floor(Math.random() * this.redditMessages.length)];
|
||||
/**
|
||||
* Loads Reddit messages from a text file, splitting by a specific delimiter.
|
||||
* Each message is separated by '---END---' in the text file.
|
||||
*
|
||||
* @example
|
||||
* const redditMessages = MessageService.loadRedditMessages();
|
||||
*
|
||||
* @returns {string[] | undefined} An array of Reddit messages, or undefined if there was an error loading the messages.
|
||||
*/
|
||||
private static loadRedditMessages(): string[] | undefined {
|
||||
try {
|
||||
const redditMessagesPath = path.join(__dirname, '..', 'messages', 'reddit_messages.txt');
|
||||
return fs.readFileSync(redditMessagesPath, 'utf-8').split('---END---').filter(line => line.trim());
|
||||
} catch (error) {
|
||||
console.error('Failed to load Reddit messages:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a random Reddit message from the loaded messages and replaces placeholders within it.
|
||||
*
|
||||
* @example
|
||||
* const message = MessageService.getRandomRedditMessage({ username: 'exampleUser' });
|
||||
*
|
||||
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
||||
* @returns {string | undefined} A formatted Reddit message with placeholders replaced, or undefined if messages couldn't be loaded.
|
||||
*/
|
||||
public static getRandomRedditMessage(placeholders: { [key: string]: string }): string | undefined {
|
||||
const redditMessages = this.loadRedditMessages()
|
||||
if (!redditMessages) return
|
||||
const message = redditMessages[Math.floor(Math.random() * redditMessages.length)];
|
||||
return this.replacePlaceholders(message, placeholders);
|
||||
}
|
||||
|
||||
public getRandomRdramaMessage(placeholders: { [key: string]: string }): string {
|
||||
const message = this.rdramaMessages[Math.floor(Math.random() * this.rdramaMessages.length)];
|
||||
/**
|
||||
* Selects a random rDrama message from the loaded messages and replaces placeholders within it.
|
||||
*
|
||||
* @example
|
||||
* const message = MessageService.getRandomRdramaMessage({ username: 'exampleUser' });
|
||||
*
|
||||
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
||||
* @returns {string | undefined} A formatted rDrama message with placeholders replaced, or undefined if messages couldn't be loaded.
|
||||
*/
|
||||
public static getRandomRdramaMessage(placeholders: { [key: string]: string }): string | undefined {
|
||||
const rdramaMessages = this.loadRdramaMessages()
|
||||
if (!rdramaMessages) return
|
||||
const message = rdramaMessages[Math.floor(Math.random() * rdramaMessages.length)];
|
||||
return this.replacePlaceholders(message, placeholders);
|
||||
}
|
||||
|
||||
private replacePlaceholders(message: string, placeholders: { [key: string]: string }): string {
|
||||
/**
|
||||
* Replaces placeholders in a message with values from a provided mapping.
|
||||
*
|
||||
* @example
|
||||
* const formattedMessage = MessageService.replacePlaceholders('Hello, {username}!', { username: 'exampleUser' });
|
||||
*
|
||||
* @param {string} message - The message containing placeholders.
|
||||
* @param {Object} placeholders - A mapping of placeholder names to their replacement values.
|
||||
* @returns {string} The message with placeholders replaced by actual values.
|
||||
*/
|
||||
private static replacePlaceholders(message: string, placeholders: { [key: string]: string }): string {
|
||||
return Object.keys(placeholders).reduce((acc, key) => {
|
||||
const regex = new RegExp(`{${key}}`, 'g');
|
||||
return acc.replace(regex, placeholders[key]);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import dotenv from 'dotenv';
|
||||
import { RedditService } from '../reddit/services/Reddit';
|
||||
import { DatabaseService } from '../db/services/Database';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
export async function shouldNotifyUser(username: string, redditService: RedditService): Promise<boolean> {
|
||||
const userInfo = await redditService.getUserInfo(username);
|
||||
if (!userInfo) return false;
|
||||
|
||||
const { is_mod, is_employee, accept_pms, total_karma } = userInfo.data;
|
||||
|
||||
const excludeMods = process.env.EXCLUDE_MODS !== 'false'; // Defaults to true unless explicitly set to 'false'
|
||||
const excludeEmployees = process.env.EXCLUDE_EMPLOYEES !== 'false'; // Defaults to true unless explicitly set to 'false'
|
||||
const notifyAcceptPms = accept_pms !== false; // Notify if accept_pms is true or undefined
|
||||
const karmaThreshold = parseInt(process.env.KARMA_THRESHOLD || '100000', 10);
|
||||
const hasBeenNotifiedBefore = await DatabaseService.userMentionExists(username);
|
||||
|
||||
const meetsCriteria =
|
||||
(!excludeMods || !is_mod) && // Notify unless we're excluding mods and the user is a mod
|
||||
(!excludeEmployees || !is_employee) && // Notify unless we're excluding employees and the user is an employee
|
||||
notifyAcceptPms &&
|
||||
total_karma < karmaThreshold &&
|
||||
!hasBeenNotifiedBefore;
|
||||
|
||||
return meetsCriteria;
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
import { CommentProcessor } from "../rdrama/services/CommentProcessor";
|
||||
import { CommentParser } from "../rdrama/services/CommentParser";
|
||||
import { CommentPoster } from "../rdrama/services/CommentPoster";
|
||||
import { MessageService } from "../utils/MessageService";
|
||||
import { DatabaseService } from "../db/services/Database";
|
||||
import { RedditService } from "../reddit/services/Reddit";
|
||||
import RedditSessionManager from "../reddit/session/SessionManager";
|
||||
import { shouldNotifyUser } from "../utils/ShouldNotify";
|
||||
|
||||
class WorkflowOrchestrator {
|
||||
constructor(
|
||||
private commentProcessor: CommentProcessor,
|
||||
private commentParser: CommentParser,
|
||||
//private redditNotifier: RedditNotifier // Handles notifications to Reddit users
|
||||
private commentPoster: CommentPoster,
|
||||
) { }
|
||||
|
||||
/**
|
||||
|
@ -22,42 +28,47 @@ class WorkflowOrchestrator {
|
|||
const uniqueUsernames = [...new Set(allUsernames)];
|
||||
console.log(`Extracted ${uniqueUsernames.length} unique usernames`);
|
||||
|
||||
//// Query user information based on usernames
|
||||
//const userInfo = await this.databaseService.queryUsersInfo(uniqueUsernames);
|
||||
//console.log(`Queried information for ${userInfo.length} users`);
|
||||
//
|
||||
//// Filter users who should be notified
|
||||
//const usersToNotify = userInfo.filter(user => this.shouldNotifyUser(user));
|
||||
//console.log(`Identified ${usersToNotify.length} users to notify`);
|
||||
//
|
||||
//// Notify users
|
||||
//for (const user of usersToNotify) {
|
||||
// await this.redditNotifier.notifyUser(user);
|
||||
// console.log(`Notified user: ${user.username}`);
|
||||
//}
|
||||
for (const comment of comments) {
|
||||
const redditUsers = this.commentParser.extractUsernames(comment)
|
||||
if (redditUsers.length === 0) continue
|
||||
console.log('found:', redditUsers)
|
||||
const placeholdersRdrama = {
|
||||
author_name: comment.author_name,
|
||||
};
|
||||
for (const redditUser of redditUsers) {
|
||||
const userMentionExists = await DatabaseService.userMentionExists(redditUser)
|
||||
if (userMentionExists) continue
|
||||
const commentResponseRdrama = MessageService.getRandomRdramaMessage(placeholdersRdrama)
|
||||
if (!commentResponseRdrama) throw new Error('No comments for Rdrama found')
|
||||
const postedComment = await this.commentPoster.postComment(`c_${comment.id}`, `##### TEST MESSAGE NO REDDITOR PINGED (YET...)\n${commentResponseRdrama}`)
|
||||
//const postedComment = await this.commentPoster.postComment(`c_${comment.id}`, ${commentResponse}`) //TODO uncomment after golive
|
||||
console.log(`Sent Comment to`, JSON.stringify(postedComment, null, 4))
|
||||
const redditSession = await RedditSessionManager.getInstance()
|
||||
const redditService = new RedditService(redditSession)
|
||||
const resultshouldNotifyUser = await shouldNotifyUser(redditUser, redditService)
|
||||
if (!resultshouldNotifyUser) continue
|
||||
const placeholdersReddit = {
|
||||
author_name: comment.author_name,
|
||||
username: redditUser,
|
||||
permalink: comment.permalink
|
||||
};
|
||||
const redditMessage = MessageService.getRandomRedditMessage(placeholdersReddit)
|
||||
if (!redditMessage) throw new Error('No comments for Reddit found')
|
||||
await DatabaseService.insertUserMention({
|
||||
rdrama_comment_id: comment.id,
|
||||
username: redditUser,
|
||||
message: redditMessage,
|
||||
})
|
||||
await redditService.sendMessage(redditUser, 'Crosstalk PM Notification', redditMessage)
|
||||
return;
|
||||
|
||||
}
|
||||
}
|
||||
console.log('Workflow executed successfully.');
|
||||
} catch (error) {
|
||||
console.error('An error occurred during workflow execution:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a user should be notified based on certain criteria.
|
||||
*
|
||||
* @param user - The user information object.
|
||||
* @returns A boolean indicating whether the user should be notified.
|
||||
*/
|
||||
//private shouldNotifyUser(user: UserInfo): boolean {
|
||||
// // Placeholder for the actual logic to determine if a user should be notified.
|
||||
// // This could involve checking the last notification time against the current time,
|
||||
// // user preferences, or other criteria defined in the business logic.
|
||||
//
|
||||
// // Example logic (to be replaced with actual implementation):
|
||||
// const lastNotifiedTime = new Date(user.lastNotified); // Assuming 'lastNotified' is a Date or string.
|
||||
// const notificationThreshold = 24 * 60 * 60 * 1000; // 24 hours in milliseconds.
|
||||
// return (Date.now() - lastNotifiedTime.getTime()) > notificationThreshold;
|
||||
//}
|
||||
}
|
||||
|
||||
export default WorkflowOrchestrator;
|
Loading…
Reference in New Issue