commit c8f1067c770dde207c8394bacc5a45d146f454e9 Author: neri Date: Fri Sep 23 12:05:34 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f23b25f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM node:18.0-slim +COPY index.js package.json yarn.lock ./ +RUN yarn +CMD [ "node", "index.js" ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..76ccc45 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# WikiJs Metabot + +Ein Skript was die Page-Listings auf [https://wiki.ctdo.de](https://wiki.ctdo.de) +automatisch aktuell hält + +## Verwendung + +Um auf einer Wiki-Seite ein Page-Listing hinzuzufügen muss der Seite das `metapage`-Tag hinzugefügt +werden und an der gewünschten Stelle ein Kommentar folgender Form eingefügt werden: + +```markdown + +``` + +Dabei muss `QUERY` eine Parameterlist für die `PageQuery::list`-Methode der WikiJs-GraphQL-API sein. +Folgende Parameter werden unterstützt: + +```text +limit: Int +orderBy: Enum CREATED | ID | PATH | TITLE | UPDATED +orderByDirection: Enum ASC | DESC +tags: [String] +locale: String +creatorId: Int +authorId: Int +``` + +### Beispiele + +Liste aller Seiten die mit den Tags `top` und `new` versehen sind: + +```markdown + +``` + +Liste der 10 zuletzt bearbeiteten Seiten: + +```markdown + +``` + +### Details + +- Mehrere Parameter müssen mit einem Komma getrennt werden +- Mehrere Tags im `tags`-Parameter müssen ebenfalls mit Komma getrennt werden +- Parameter vom Typ `String` müssen in doppelte Anführungszeichen `"` eingeschlossen werden +- Parameter vom Typ `Int` und vom Typ `Enum` müssen ohne Anführungszeichen angegeben werden diff --git a/index.js b/index.js new file mode 100644 index 0000000..e83b99b --- /dev/null +++ b/index.js @@ -0,0 +1,138 @@ +import WebSocket from 'ws'; + +process.on('SIGINT', () => process.exit(0)); + +const apiKey = process.env['CTDO_WIKIJS_API_KEY']; + +async function graphql(query) { + const res = await fetch('https://wiki.ctdo.de/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ query }), + }); + return await res.json(); +} + +function graphqlSubscribe(query, callback) { + const ws = new WebSocket( + 'wss://wiki.ctdo.de/graphql-subscriptions', + 'graphql-ws' + ); + ws.onmessage = (event) => callback(JSON.parse(event.data)); + ws.onopen = () => { + ws.send(JSON.stringify({ type: 'connection_init' })); + ws.send( + JSON.stringify({ + id: '1', + type: 'start', + payload: { query: `subscription ${query}` }, + }) + ); + }; +} + +async function update() { + const pageIds = ( + await graphql('query { pages { list(tags: ["metapage"]) { id } } }') + ).data.pages.list.map((page) => page.id); + console.log('checking metapages', { pageIds }); + pageIds.forEach((pageId) => updatePage(pageId)); +} + +async function updatePage(pageId) { + console.log('checking page', { pageId }); + + const page = ( + await graphql(`query { pages { single(id: ${pageId}) { + id + content + description + editor + isPrivate + isPublished + locale + path + publishEndDate + publishStartDate + scriptCss + scriptJs + tags { tag } + title + } } }`) + ).data.pages.single; + page.tags = page.tags.map((tag) => tag.tag); + + let contentLines = page.content.split('\n'); + const pagelistConfigs = contentLines.filter((line) => + line.match(/^\s*\s*$/) + ); + + for (const pagelistConfig of pagelistConfigs) { + await updatePagelist(pagelistConfig, contentLines); + } + + const updatedContent = contentLines.join('\n'); + + if (page.content !== updatedContent) { + page.content = updatedContent; + console.log('updating page'); + console.log({ page }); + const mutation = Object.entries(page) + .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) + .join(', '); + const result = await graphql( + `mutation { pages { update(${mutation}) { responseResult { succeeded, message, errorCode } } } }` + ); + console.log({ result: JSON.stringify(result) }); + } +} + +async function updatePagelist(pagelistConfig, contentLines) { + const query = pagelistConfig + .replace(/^\s*\s*$/, ''); + + const result = ( + await graphql(`query { pages { list(${query}) { path, title, tags } }}`) + ).data.pages.list + .filter((page) => !page.path.includes('/_')) + .filter((page) => !page.tags.includes('metapage')) + .map((page) => `- [${page.title}](${page.path})`); + + console.log({ pagelistConfig, query, result }); + + const startIndex = contentLines.findIndex((line) => line == pagelistConfig); + let endIndex = startIndex + 1; + while ( + endIndex < contentLines.length && + contentLines[endIndex].startsWith('- ') + ) { + endIndex++; + } + contentLines.splice( + startIndex, + endIndex - startIndex, + ``, + ...result + ); +} + +async function main() { + await update(); + + graphqlSubscribe('{ loggingLiveTrail { output } }', async (message) => { + if ( + message.type === 'data' && + message.payload.data.loggingLiveTrail.output.includes( + 'Committing updated file' + ) + ) { + await update(); + } + }); +} + +main(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..03d82b8 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "wikijs-metabot", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "AGPL-3.0-or-later", + "dependencies": { + "ws": "^8.9.0" + }, + "type": "module" +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..61bd46b --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ws@^8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" + integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg==