From d723d4264ab593b2544e75b2f50f8e33fcad92fe Mon Sep 17 00:00:00 2001 From: Max Nuding Date: Wed, 5 Apr 2023 16:21:43 +0200 Subject: [PATCH] Generate Atom feed --- .env.EXAMPLE | 1 + package-lock.json | 349 +++++++++++++++++++++++++++++++++++-- package.json | 5 +- src/hooks.server.ts | 18 +- src/lib/server/db.ts | 165 ++++++++++-------- src/lib/server/rss.ts | 45 +++++ src/lib/server/timeline.ts | 9 +- 7 files changed, 496 insertions(+), 96 deletions(-) create mode 100644 src/lib/server/rss.ts diff --git a/.env.EXAMPLE b/.env.EXAMPLE index 938d3e8..d9d5761 100644 --- a/.env.EXAMPLE +++ b/.env.EXAMPLE @@ -2,6 +2,7 @@ HASHTAG_FILTER = ichlausche,music,musik,nowplaying,tunetuesday,nowlistening URL_FILTER = song.link,album.link,spotify.com,music.apple.com,bandcamp.com YOUTUBE_API_KEY = CHANGE_ME MASTODON_INSTANCE = 'metalhead.club' +BASE_URL = 'https://moshingmammut.phlaym.net' VERBOSE = false PUBLIC_REFRESH_INTERVAL = 10000 diff --git a/package-lock.json b/package-lock.json index 4797168..a9916a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,15 +8,17 @@ "name": "moshing-mammut", "version": "0.0.1", "dependencies": { - "@types/sqlite3": "^3.1.8", - "@types/ws": "^8.5.4", "dotenv": "^16.0.3", + "feed": "^4.2.2", + "feeder": "git+ssh://git@phlaym.net:phlaym/feeder.git", "sqlite3": "^5.1.6", "ws": "^8.13.0" }, "devDependencies": { "@sveltejs/adapter-node": "^1.2.3", "@sveltejs/kit": "^1.5.0", + "@types/sqlite3": "^3.1.8", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", @@ -31,6 +33,102 @@ "vite": "^4.0.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@esbuild/android-arm": { "version": "0.17.14", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.14.tgz", @@ -841,7 +939,8 @@ "node_modules/@types/node": { "version": "18.15.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" + "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==", + "dev": true }, "node_modules/@types/pug": { "version": "2.0.6", @@ -865,6 +964,7 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/@types/sqlite3/-/sqlite3-3.1.8.tgz", "integrity": "sha512-sQMt/qnyUWnqiTcJXm5ZfNPIBeJ/DVvJDwxw+0tAxPJvadzfiP1QhryO1JOR6t1yfb8NpzQb/Rud06mob5laIA==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -873,6 +973,7 @@ "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz", "integrity": "sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -1429,6 +1530,11 @@ "color-support": "bin.js" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1536,6 +1642,14 @@ "integrity": "sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==", "dev": true }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1801,6 +1915,18 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -1922,6 +2048,38 @@ "reusify": "^1.0.4" } }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/feeder": { + "version": "1.0.0", + "resolved": "git+ssh://git@phlaym.net:phlaym/feeder.git#9543c65bd79b056202cb5649b08858e90085da41", + "license": "ISC", + "dependencies": { + "tslint": "^6.1.3", + "typescript": "^5.0.3" + } + }, + "node_modules/feeder/node_modules/typescript": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.3.tgz", + "integrity": "sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=12.20" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2014,8 +2172,7 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/gauge": { "version": "3.0.2", @@ -2130,7 +2287,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -2305,7 +2461,6 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, "dependencies": { "has": "^1.0.3" }, @@ -2397,6 +2552,11 @@ "url": "https://opencollective.com/js-sdsl" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2594,7 +2754,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2691,7 +2850,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, "dependencies": { "minimist": "^1.2.6" }, @@ -3009,8 +3167,7 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-type": { "version": "4.0.0", @@ -3174,7 +3331,6 @@ "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, "dependencies": { "is-core-module": "^2.9.0", "path-parse": "^1.0.7", @@ -3329,6 +3485,11 @@ "rimraf": "bin.js" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -3465,6 +3626,11 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "node_modules/sqlite3": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.6.tgz", @@ -3580,7 +3746,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3788,6 +3953,152 @@ "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", "dev": true }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/tslint/node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/tslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/tslint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tslint/node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, "node_modules/tsutils": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", @@ -3837,7 +4148,6 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4024,6 +4334,17 @@ } } }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 15b2a0a..6302745 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "devDependencies": { "@sveltejs/adapter-node": "^1.2.3", "@sveltejs/kit": "^1.5.0", + "@types/sqlite3": "^3.1.8", + "@types/ws": "^8.5.4", "@typescript-eslint/eslint-plugin": "^5.45.0", "@typescript-eslint/parser": "^5.45.0", "eslint": "^8.28.0", @@ -29,9 +31,8 @@ }, "type": "module", "dependencies": { - "@types/sqlite3": "^3.1.8", - "@types/ws": "^8.5.4", "dotenv": "^16.0.3", + "feed": "^4.2.2", "sqlite3": "^5.1.6", "ws": "^8.13.0" } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 89f47a2..1ee1dda 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,5 +1,6 @@ import { TimelineReader } from '$lib/server/timeline'; import type { HandleServerError } from '@sveltejs/kit'; +import fs from 'fs/promises'; TimelineReader.init(); @@ -12,4 +13,19 @@ export const handleError = (({ error }) => { message: 'Whoops!', code: (error as any)?.code ?? 'UNKNOWN' }; -}) satisfies HandleServerError; \ No newline at end of file +}) satisfies HandleServerError; + +import type { Handle } from '@sveltejs/kit'; + +export const handle = (async ({ event, resolve }) => { + if (event.url.pathname === '/feed.atom') { + const f = await fs.readFile('feed.atom', { encoding: 'utf8' }); + return new Response( + f, + { headers: [['Content-Type', 'application/atom+xml']] } + ); + } + + const response = await resolve(event); + return response; +}) satisfies Handle; \ No newline at end of file diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index 042b983..02bd876 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -92,84 +92,97 @@ function getMigrations(): Migration[] { }]; } -export function savePost(post: Post): void { - console.debug(`Saving post ${post.url}`); - const account = post.account; - db.run(` - INSERT INTO accounts (id, acct, username, display_name, url, avatar, avatar_static) - VALUES(?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(id) - DO UPDATE SET - acct=excluded.acct, - username=excluded.username, - display_name=excluded.display_name, - url=excluded.url, - avatar=excluded.avatar, - avatar_static=excluded.avatar_static;`, - [ - account.id, - account.acct, - account.username, - account.display_name, - account.url, - account.avatar, - account.avatar_static - ], - (err) => { - if (err !== null) { - console.error(`Could not insert/update account ${account.id}`, err); - return; - } - db.run(` - INSERT INTO posts (id, content, created_at, url, account_id) - VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET - content=excluded.content, - created_at=excluded.created_at, - url=excluded.url, - account_id=excluded.account_id;`, - [ - post.id, - post.content, - post.created_at, - post.url, - post.account.id - ], - (postErr) => { - if (postErr !== null) { - console.error(`Could not insert post ${post.url}`, postErr); - return; - } - - db.parallelize(() => { - for (let tag of post.tags) { - db.run(` - INSERT INTO tags (url, tag) VALUES (?, ?) - ON CONFLICT(url) DO UPDATE SET - tag=excluded.tag;`, - [ - tag.url, - tag.name - ], - (tagErr) => { - if (tagErr !== null) { - console.error(`Could not insert/update tag ${tag.url}`, tagErr); - return; - } - db.run('INSERT INTO poststags (post_id, tag_url) VALUES (?, ?)', - [post.id, tag.url], - (posttagserr) => { - if (posttagserr !== null) { - console.error(`Could not insert poststags ${tag.url}, ${post.url}`, posttagserr); - return; - } - } - ); - } - ); +export async function savePost(post: Post): Promise { + return new Promise((resolve, reject) => { + console.debug(`Saving post ${post.url}`); + const account = post.account; + db.run(` + INSERT INTO accounts (id, acct, username, display_name, url, avatar, avatar_static) + VALUES(?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(id) + DO UPDATE SET + acct=excluded.acct, + username=excluded.username, + display_name=excluded.display_name, + url=excluded.url, + avatar=excluded.avatar, + avatar_static=excluded.avatar_static;`, + [ + account.id, + account.acct, + account.username, + account.display_name, + account.url, + account.avatar, + account.avatar_static + ], + (err) => { + if (err !== null) { + console.error(`Could not insert/update account ${account.id}`, err); + reject(err); + return; + } + db.run(` + INSERT INTO posts (id, content, created_at, url, account_id) + VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET + content=excluded.content, + created_at=excluded.created_at, + url=excluded.url, + account_id=excluded.account_id;`, + [ + post.id, + post.content, + post.created_at, + post.url, + post.account.id + ], + (postErr) => { + if (postErr !== null) { + console.error(`Could not insert post ${post.url}`, postErr); + reject(postErr); + return; } + + db.parallelize(() => { + let remaining = post.tags.length; + for (let tag of post.tags) { + db.run(` + INSERT INTO tags (url, tag) VALUES (?, ?) + ON CONFLICT(url) DO UPDATE SET + tag=excluded.tag;`, + [ + tag.url, + tag.name + ], + (tagErr) => { + if (tagErr !== null) { + console.error(`Could not insert/update tag ${tag.url}`, tagErr); + reject(tagErr); + return; + } + db.run('INSERT INTO poststags (post_id, tag_url) VALUES (?, ?)', + [post.id, tag.url], + (posttagserr) => { + if (posttagserr !== null) { + console.error(`Could not insert poststags ${tag.url}, ${post.url}`, posttagserr); + reject(posttagserr); + return; + } + // Don't decrease on fail + remaining--; + // Only resolve after all have been inserted + if (remaining === 0) { + resolve(undefined); + } + } + ); + } + ); + } + }); }); - }); - }); + }); + }); } export async function getPosts(since: string | null, before: string | null, limit: number) { diff --git a/src/lib/server/rss.ts b/src/lib/server/rss.ts new file mode 100644 index 0000000..cbf5b3b --- /dev/null +++ b/src/lib/server/rss.ts @@ -0,0 +1,45 @@ +import { BASE_URL } from '$env/static/private'; +import { PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME } from '$env/static/public'; +import type { Post } from '$lib//mastodon/response'; +import { Feed } from 'feed'; +import fs from 'fs/promises'; + +export function createFeed(posts: Post[]): Feed { + const baseUrl = BASE_URL.endsWith('/') ? BASE_URL : BASE_URL + '/'; + const feed = new Feed({ + title: `${PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME} music feed`, + description: `Posts about music on ${PUBLIC_MASTODON_INSTANCE_DISPLAY_NAME}`, + id: baseUrl, + link: baseUrl, + language: 'en', + //image: "http://example.com/image.png", + //favicon: "http://example.com/favicon.ico", + copyright: '', + generator: 'moshing-mamut', + feedLinks: { + atom: `${BASE_URL}/feed.atom` + }, + author: { + name: '@aymm', + link: 'https://metalhead.club/@aymm' + }, + }); + posts.forEach(p => { + feed.addItem({ + title: p.content, + id: p.url, + link: p.url, + content: p.content, + author: [{ + name: p.account.acct, + link: p.account.url + }], + date: new Date(p.created_at) + }) + }); + feed.addCategory('Music'); + return feed; +} +export async function saveAtomFeed(feed: Feed) { + await fs.writeFile('feed.atom', feed.atom1(), { encoding: 'utf8' }); +} \ No newline at end of file diff --git a/src/lib/server/timeline.ts b/src/lib/server/timeline.ts index 3bd7962..515c12e 100644 --- a/src/lib/server/timeline.ts +++ b/src/lib/server/timeline.ts @@ -1,6 +1,7 @@ import { HASHTAG_FILTER, MASTODON_INSTANCE, URL_FILTER, YOUTUBE_API_KEY } from '$env/static/private'; import type { Post, Tag, TimelineEvent } from '$lib/mastodon/response'; -import { savePost } from '$lib/server/db'; +import { getPosts, savePost } from '$lib/server/db'; +import { createFeed, saveAtomFeed } from '$lib/server/rss'; import { WebSocket } from "ws"; const YOUTUBE_REGEX = new RegExp(/https?:\/\/(www\.)?youtu((be.com\/.*?v=)|(\.be\/))(?[a-zA-Z_0-9-]+)/gm); @@ -77,9 +78,11 @@ export class TimelineReader { if (found_urls.length === 0 && found_tags.length === 0 && !await TimelineReader.checkYoutubeMatches(post.content)) { - return; + //return; } - savePost(post); + await savePost(post); + const posts = await getPosts(null, null, 100); + await saveAtomFeed(createFeed(posts)); } catch (e) { console.error("error message", event, event.data, e)