├── .env.example ├── .gitignore ├── README.md ├── api.js ├── articles └── .gitkeep ├── gen.js ├── index.js ├── logger.js ├── package-lock.json ├── package.json └── serp.js /.env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY="example-openai-key" 2 | OPENAI_MODEL="example-model-id" 3 | ENABLE_WORDPRESS=true 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | articles 2 | !articles/.gitkeep 3 | node_modules 4 | .env 5 | .DS_Store 6 | keywords 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Article Generation Tool 2 | 3 | (README generated with AI. This is an old project I'm putting up for posterity in case anyone finds it useful 👍) 4 | 5 | This is a Node.js application that generates articles based on a given keyword. It queries a search engine (DuckDuckGo), summarizes the content of the search results, and generates a draft article using the summaries with AI. The generated article can be published as a WordPress draft or saved as a Markdown file. 6 | 7 | ## Getting Started 8 | 9 | 1. Install the required dependencies: `npm install` 10 | 2. Create a `.env` file and provide the necessary environment variables (e.g., API keys, credentials): `cp .env.example .env` and fill it out 11 | 3. Add your keywords to the `./keywords` directory, one keyword per file 12 | 4. Run the application: `node index.js` 13 | 14 | ## Usage 15 | 16 | The application can be run in two modes: 17 | 18 | 1. **Command Line**: The `main` function accepts a keyword as an argument and an optional `skipPublish` flag to skip publishing the article to WordPress. 19 | 20 | 2. **HTTP Server**: The application runs an HTTP server that listens for GET requests at the `/` endpoint. The keyword can be provided as a query parameter (`?keyword=`), and the `skip_publish` query parameter can be used to skip publishing the article to WordPress. 21 | 22 | ## Example 23 | 24 | ``` 25 | curl http://localhost:5139/?keyword=ai&skip_publish=true 26 | ``` 27 | 28 | This will generate an article based on the keyword "ai", but skip publishing it to WordPress. The response will be a JSON object containing the generated titles and article content. 29 | -------------------------------------------------------------------------------- /api.js: -------------------------------------------------------------------------------- 1 | const openai = require("openai"); 2 | const openaiClient = new openai.OpenAI(process.env.OPENAI_API_KEY); 3 | const model = process.env.OPENAI_MODEL || "gpt-4-1106-preview"; 4 | 5 | const createWordpressDraft = async (title, content) => { 6 | const wordpressApiUrl = "https://7.dev/wp-json/wp/v2/posts"; 7 | const username = process.env.WP_USERNAME; 8 | const password = process.env.WP_PASSWORD; 9 | 10 | const token = Buffer.from(`${username}:${password}`).toString("base64"); 11 | 12 | try { 13 | const response = await fetch(wordpressApiUrl, { 14 | method: "POST", 15 | headers: { 16 | "Authorization": `Basic ${token}`, 17 | "Content-Type": "application/json", 18 | }, 19 | body: JSON.stringify({ 20 | title: title, 21 | content: content, 22 | status: "draft", 23 | }), 24 | }); 25 | 26 | const data = await response.json(); 27 | console.log("Draft created:", data); 28 | } catch (error) { 29 | console.error("Error creating draft:", error); 30 | } 31 | }; 32 | 33 | module.exports = { 34 | createWordpressDraft, 35 | openaiClient, 36 | model, 37 | }; 38 | -------------------------------------------------------------------------------- /articles/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kristianfreeman/aiwriter/c43a290471e3f9063076b2c430126dde59edad38/articles/.gitkeep -------------------------------------------------------------------------------- /gen.js: -------------------------------------------------------------------------------- 1 | const { model, openaiClient } = require("./api"); 2 | const { logger } = require("./logger") 3 | 4 | const wordcount = require("word-count"); 5 | 6 | const generateTitles = async (keyword) => { 7 | const content = ` 8 | Generate 10 SEO-optimized titles for the following keyword: ${keyword}. 9 | `; 10 | 11 | const response = await openaiClient.chat.completions.create({ 12 | model, 13 | messages: [ 14 | { role: "user", content }, 15 | ], 16 | }); 17 | 18 | const message = response.choices[0].message; 19 | return message.content; 20 | }; 21 | 22 | const generateArticle = async (topic, summaries) => { 23 | const summariesAsText = summaries.filter((s) => !!s).map((summary) => 24 | summary.content 25 | ); 26 | 27 | const currentToken = 0; 28 | 29 | const minimumWordCount = 1700; 30 | 31 | const content = ` 32 | Write an article about the following topic: ${topic}. 33 | 34 | Use the following contents of similar articles to construct the article. Do not repeat the same content. Use a tone that is appropriate for a blog post. The article should be SEO-optimized. The article must be more than ${minimumWordCount} words. It must be formatted as Markdown. 35 | 36 | In the introductory paragraph, introduce the topic and the main points of the article. Also, declaratively answer any questions that the reader may have in a format that is Google SEO-friendly. Generate Markdown links as often as possible for anything that should be linked to. You must generate at least one Markdown link in the first two paragraphs linking to the concept or project. 37 | 38 | You should provide the reader with all the information they need to solve the problem/question or otherwise give them a solution. 39 | 40 | In the body, elaborate on the main points. Each section indicated by an h2 header must contain at least 100 words. In the conclusion, summarize the main points and provide a call to action. 41 | 42 | The blog post will be longer than you are able to generate in a single request. Generate the article in increments of 1000 tokens. Start at token ${currentToken}. When you are done, add the text "$COMPLETED" to the end of the article. 43 | 44 | ${summariesAsText.join("\n")} 45 | 46 | Do not repeat any of the content you have already generated. If you do, you will be penalized. Don't output any information about the articles that you are referencing either, or promote them in any way (do not generate links to them). 47 | `; 48 | 49 | let finalContent = ""; 50 | 51 | while (wordcount(finalContent) < minimumWordCount) { 52 | const response = await openaiClient.chat.completions.create({ 53 | model, 54 | messages: [ 55 | { 56 | role: "system", 57 | content: 58 | `Act as a technical writer creating SEO-optimized articles for a blog. Use very plain language and focus on the information you are trying to present. You must generate at least ${ 59 | minimumWordCount - wordcount(finalContent) 60 | } more words.`, 61 | }, 62 | { role: "user", content }, 63 | ], 64 | }); 65 | 66 | const message = response.choices[0].message; 67 | 68 | finalContent += message.content; 69 | 70 | if (message.content.includes("$COMPLETED")) { 71 | break; 72 | } 73 | 74 | logger.info(`${wordcount(finalContent)} words generated, continuing...`); 75 | } 76 | 77 | finalContent = finalContent.replace("$COMPLETED", ""); 78 | 79 | return finalContent; 80 | }; 81 | 82 | module.exports = { 83 | generateTitles, 84 | generateArticle, 85 | }; 86 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { execSync } = require("child_process"); 2 | const fs = require("fs"); 3 | 4 | require("dotenv").config(); 5 | 6 | const wordcount = require("word-count"); 7 | 8 | const { createWordpressDraft } = require("./api"); 9 | const { generateArticle, generateTitles } = require("./gen"); 10 | const { logger } = require("./logger") 11 | const { queryDdg, summarizeContent } = require("./serp"); 12 | 13 | const { serve } = require("@hono/node-server"); 14 | const { Hono } = require("hono"); 15 | 16 | const readKeywords = () => { 17 | const files = fs.readdirSync("./keywords"); 18 | return files; 19 | }; 20 | 21 | const main = async (keyword, skipPublish = false) => { 22 | if (!keyword) { 23 | logger.info("Please provide a keyword"); 24 | return; 25 | } 26 | 27 | logger.info(`Processing keyword: ${keyword}`); 28 | 29 | // const results = await queryGoogle(keyword); 30 | const results = await queryDdg(keyword); 31 | 32 | if (results.length === 0) { 33 | logger.info( 34 | "No results found. Something might be wrong with this keyword. Skipping...", 35 | ); 36 | return; 37 | } 38 | 39 | let summaries = []; 40 | 41 | for (const result of results) { 42 | logger.info(`PROCESSING "${result.title} - ${result.url}"`); 43 | const summary = await summarizeContent(result.url); 44 | summaries.push(summary); 45 | } 46 | logger.info(`Summarized ${summaries.length} articles. Generating article...`); 47 | 48 | const article = await generateArticle(keyword, summaries); 49 | logger.info(`Article generated. ${wordcount(article)} words`); 50 | 51 | const titles = await generateTitles(keyword); 52 | logger.info(`Titles generated: \n${titles}`); 53 | 54 | const fileContent = ` 55 | ${titles} 56 | 57 | ${article} 58 | `; 59 | 60 | const enableWordpress = process.env.ENABLE_WORDPRESS; 61 | if (!skipPublish && enableWordpress) { 62 | logger.info(`Creating Wordpress draft with title: ${keyword}`); 63 | await createWordpressDraft(keyword, fileContent); 64 | logger.info(`Wordpress draft created`); 65 | } 66 | 67 | fs.writeFileSync(`./articles/${keyword}.md`, fileContent); 68 | logger.info(`Article saved to ./articles/${keyword}.md`); 69 | 70 | // logger.info(`Deleting keyword file`); 71 | // fs.unlinkSync(`./keywords/${keyword}`); 72 | 73 | logger.info("DONE"); 74 | return { keyword, titles, article }; 75 | }; 76 | 77 | const run = async () => { 78 | const keywords = readKeywords(); 79 | await main(keywords[0]); 80 | execSync("sleep 5"); 81 | run(); 82 | }; 83 | 84 | const app = new Hono(); 85 | app.get("/", async (c) => { 86 | const keyword = c.req.query("keyword"); 87 | const skipPublish = !!c.req.query("skip_publish") || false; 88 | if (!keyword) { 89 | return c.throw(400, "Please provide a keyword"); 90 | } 91 | 92 | const result = await main(keyword, skipPublish); 93 | return c.json(result); 94 | }); 95 | 96 | const port = process.env.PORT || "5139"; 97 | 98 | serve({ 99 | fetch: app.fetch, 100 | port, 101 | }); 102 | 103 | console.log(`Listening on port ${port}`); 104 | 105 | module.exports = { logger } 106 | -------------------------------------------------------------------------------- /logger.js: -------------------------------------------------------------------------------- 1 | const logger = require("pino")(); 2 | module.exports = { logger } 3 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aiwriter", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "aiwriter", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@hono/node-server": "^1.3.2", 13 | "cheerio": "^1.0.0-rc.12", 14 | "dotenv": "^16.3.1", 15 | "googlethis": "github:timsu/google-this", 16 | "hono": "^3.11.4", 17 | "openai": "^4.3.0", 18 | "pino": "^8.15.0", 19 | "word-count": "^0.2.2" 20 | } 21 | }, 22 | "node_modules/@hono/node-server": { 23 | "version": "1.3.2", 24 | "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.3.2.tgz", 25 | "integrity": "sha512-SS/uR/2aEt+mQpQS3OBjDmB/s4ju9inAFoTXf7c62HqLdDt1Jx72E59BnOl1fl8B/OTli+TS334fxS04WKtA/w==", 26 | "engines": { 27 | "node": ">=18.14.1" 28 | } 29 | }, 30 | "node_modules/@types/node": { 31 | "version": "18.19.3", 32 | "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.3.tgz", 33 | "integrity": "sha512-k5fggr14DwAytoA/t8rPrIz++lXK7/DqckthCmoZOKNsEbJkId4Z//BqgApXBUGrGddrigYa1oqheo/7YmW4rg==", 34 | "dependencies": { 35 | "undici-types": "~5.26.4" 36 | } 37 | }, 38 | "node_modules/@types/node-fetch": { 39 | "version": "2.6.9", 40 | "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.9.tgz", 41 | "integrity": "sha512-bQVlnMLFJ2d35DkPNjEPmd9ueO/rh5EiaZt2bhqiSarPjZIuIV6bPQVqcrEyvNo+AfTrRGVazle1tl597w3gfA==", 42 | "dependencies": { 43 | "@types/node": "*", 44 | "form-data": "^4.0.0" 45 | } 46 | }, 47 | "node_modules/abort-controller": { 48 | "version": "3.0.0", 49 | "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", 50 | "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 51 | "dependencies": { 52 | "event-target-shim": "^5.0.0" 53 | }, 54 | "engines": { 55 | "node": ">=6.5" 56 | } 57 | }, 58 | "node_modules/agentkeepalive": { 59 | "version": "4.5.0", 60 | "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", 61 | "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", 62 | "dependencies": { 63 | "humanize-ms": "^1.2.1" 64 | }, 65 | "engines": { 66 | "node": ">= 8.0.0" 67 | } 68 | }, 69 | "node_modules/asynckit": { 70 | "version": "0.4.0", 71 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 72 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 73 | }, 74 | "node_modules/atomic-sleep": { 75 | "version": "1.0.0", 76 | "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", 77 | "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", 78 | "engines": { 79 | "node": ">=8.0.0" 80 | } 81 | }, 82 | "node_modules/axios": { 83 | "version": "0.21.4", 84 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", 85 | "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", 86 | "dependencies": { 87 | "follow-redirects": "^1.14.0" 88 | } 89 | }, 90 | "node_modules/base-64": { 91 | "version": "0.1.0", 92 | "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", 93 | "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" 94 | }, 95 | "node_modules/base64-js": { 96 | "version": "1.5.1", 97 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 98 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 99 | "funding": [ 100 | { 101 | "type": "github", 102 | "url": "https://github.com/sponsors/feross" 103 | }, 104 | { 105 | "type": "patreon", 106 | "url": "https://www.patreon.com/feross" 107 | }, 108 | { 109 | "type": "consulting", 110 | "url": "https://feross.org/support" 111 | } 112 | ] 113 | }, 114 | "node_modules/boolbase": { 115 | "version": "1.0.0", 116 | "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", 117 | "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" 118 | }, 119 | "node_modules/buffer": { 120 | "version": "6.0.3", 121 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", 122 | "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", 123 | "funding": [ 124 | { 125 | "type": "github", 126 | "url": "https://github.com/sponsors/feross" 127 | }, 128 | { 129 | "type": "patreon", 130 | "url": "https://www.patreon.com/feross" 131 | }, 132 | { 133 | "type": "consulting", 134 | "url": "https://feross.org/support" 135 | } 136 | ], 137 | "dependencies": { 138 | "base64-js": "^1.3.1", 139 | "ieee754": "^1.2.1" 140 | } 141 | }, 142 | "node_modules/charenc": { 143 | "version": "0.0.2", 144 | "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", 145 | "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", 146 | "engines": { 147 | "node": "*" 148 | } 149 | }, 150 | "node_modules/cheerio": { 151 | "version": "1.0.0-rc.12", 152 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", 153 | "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", 154 | "dependencies": { 155 | "cheerio-select": "^2.1.0", 156 | "dom-serializer": "^2.0.0", 157 | "domhandler": "^5.0.3", 158 | "domutils": "^3.0.1", 159 | "htmlparser2": "^8.0.1", 160 | "parse5": "^7.0.0", 161 | "parse5-htmlparser2-tree-adapter": "^7.0.0" 162 | }, 163 | "engines": { 164 | "node": ">= 6" 165 | }, 166 | "funding": { 167 | "url": "https://github.com/cheeriojs/cheerio?sponsor=1" 168 | } 169 | }, 170 | "node_modules/cheerio-select": { 171 | "version": "2.1.0", 172 | "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", 173 | "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", 174 | "dependencies": { 175 | "boolbase": "^1.0.0", 176 | "css-select": "^5.1.0", 177 | "css-what": "^6.1.0", 178 | "domelementtype": "^2.3.0", 179 | "domhandler": "^5.0.3", 180 | "domutils": "^3.0.1" 181 | }, 182 | "funding": { 183 | "url": "https://github.com/sponsors/fb55" 184 | } 185 | }, 186 | "node_modules/combined-stream": { 187 | "version": "1.0.8", 188 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 189 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 190 | "dependencies": { 191 | "delayed-stream": "~1.0.0" 192 | }, 193 | "engines": { 194 | "node": ">= 0.8" 195 | } 196 | }, 197 | "node_modules/crypt": { 198 | "version": "0.0.2", 199 | "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", 200 | "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", 201 | "engines": { 202 | "node": "*" 203 | } 204 | }, 205 | "node_modules/css-select": { 206 | "version": "5.1.0", 207 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", 208 | "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", 209 | "dependencies": { 210 | "boolbase": "^1.0.0", 211 | "css-what": "^6.1.0", 212 | "domhandler": "^5.0.2", 213 | "domutils": "^3.0.1", 214 | "nth-check": "^2.0.1" 215 | }, 216 | "funding": { 217 | "url": "https://github.com/sponsors/fb55" 218 | } 219 | }, 220 | "node_modules/css-what": { 221 | "version": "6.1.0", 222 | "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", 223 | "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", 224 | "engines": { 225 | "node": ">= 6" 226 | }, 227 | "funding": { 228 | "url": "https://github.com/sponsors/fb55" 229 | } 230 | }, 231 | "node_modules/delayed-stream": { 232 | "version": "1.0.0", 233 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 234 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 235 | "engines": { 236 | "node": ">=0.4.0" 237 | } 238 | }, 239 | "node_modules/digest-fetch": { 240 | "version": "1.3.0", 241 | "resolved": "https://registry.npmjs.org/digest-fetch/-/digest-fetch-1.3.0.tgz", 242 | "integrity": "sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==", 243 | "dependencies": { 244 | "base-64": "^0.1.0", 245 | "md5": "^2.3.0" 246 | } 247 | }, 248 | "node_modules/dom-serializer": { 249 | "version": "2.0.0", 250 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", 251 | "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 252 | "dependencies": { 253 | "domelementtype": "^2.3.0", 254 | "domhandler": "^5.0.2", 255 | "entities": "^4.2.0" 256 | }, 257 | "funding": { 258 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 259 | } 260 | }, 261 | "node_modules/domelementtype": { 262 | "version": "2.3.0", 263 | "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", 264 | "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", 265 | "funding": [ 266 | { 267 | "type": "github", 268 | "url": "https://github.com/sponsors/fb55" 269 | } 270 | ] 271 | }, 272 | "node_modules/domhandler": { 273 | "version": "5.0.3", 274 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", 275 | "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 276 | "dependencies": { 277 | "domelementtype": "^2.3.0" 278 | }, 279 | "engines": { 280 | "node": ">= 4" 281 | }, 282 | "funding": { 283 | "url": "https://github.com/fb55/domhandler?sponsor=1" 284 | } 285 | }, 286 | "node_modules/domutils": { 287 | "version": "3.1.0", 288 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", 289 | "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", 290 | "dependencies": { 291 | "dom-serializer": "^2.0.0", 292 | "domelementtype": "^2.3.0", 293 | "domhandler": "^5.0.3" 294 | }, 295 | "funding": { 296 | "url": "https://github.com/fb55/domutils?sponsor=1" 297 | } 298 | }, 299 | "node_modules/dotenv": { 300 | "version": "16.3.1", 301 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", 302 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", 303 | "engines": { 304 | "node": ">=12" 305 | }, 306 | "funding": { 307 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 308 | } 309 | }, 310 | "node_modules/entities": { 311 | "version": "4.5.0", 312 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 313 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 314 | "engines": { 315 | "node": ">=0.12" 316 | }, 317 | "funding": { 318 | "url": "https://github.com/fb55/entities?sponsor=1" 319 | } 320 | }, 321 | "node_modules/event-target-shim": { 322 | "version": "5.0.1", 323 | "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", 324 | "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", 325 | "engines": { 326 | "node": ">=6" 327 | } 328 | }, 329 | "node_modules/events": { 330 | "version": "3.3.0", 331 | "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", 332 | "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", 333 | "engines": { 334 | "node": ">=0.8.x" 335 | } 336 | }, 337 | "node_modules/fast-redact": { 338 | "version": "3.3.0", 339 | "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", 340 | "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", 341 | "engines": { 342 | "node": ">=6" 343 | } 344 | }, 345 | "node_modules/follow-redirects": { 346 | "version": "1.15.3", 347 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", 348 | "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", 349 | "funding": [ 350 | { 351 | "type": "individual", 352 | "url": "https://github.com/sponsors/RubenVerborgh" 353 | } 354 | ], 355 | "engines": { 356 | "node": ">=4.0" 357 | }, 358 | "peerDependenciesMeta": { 359 | "debug": { 360 | "optional": true 361 | } 362 | } 363 | }, 364 | "node_modules/form-data": { 365 | "version": "4.0.0", 366 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 367 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 368 | "dependencies": { 369 | "asynckit": "^0.4.0", 370 | "combined-stream": "^1.0.8", 371 | "mime-types": "^2.1.12" 372 | }, 373 | "engines": { 374 | "node": ">= 6" 375 | } 376 | }, 377 | "node_modules/form-data-encoder": { 378 | "version": "1.7.2", 379 | "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", 380 | "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" 381 | }, 382 | "node_modules/formdata-node": { 383 | "version": "4.4.1", 384 | "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", 385 | "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", 386 | "dependencies": { 387 | "node-domexception": "1.0.0", 388 | "web-streams-polyfill": "4.0.0-beta.3" 389 | }, 390 | "engines": { 391 | "node": ">= 12.20" 392 | } 393 | }, 394 | "node_modules/formdata-node/node_modules/web-streams-polyfill": { 395 | "version": "4.0.0-beta.3", 396 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", 397 | "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", 398 | "engines": { 399 | "node": ">= 14" 400 | } 401 | }, 402 | "node_modules/googlethis": { 403 | "version": "1.8.0", 404 | "resolved": "git+ssh://git@github.com/timsu/google-this.git#a11ff2cdc70d4d7cbb9ec1585acc3cbc373cf964", 405 | "license": "MIT", 406 | "dependencies": { 407 | "axios": "^0.21.1", 408 | "cheerio": "1.0.0-rc.10", 409 | "form-data": "^4.0.0", 410 | "unraw": "^2.0.1" 411 | }, 412 | "engines": { 413 | "node": ">=14" 414 | }, 415 | "funding": { 416 | "url": "https://github.com/sponsors/LuanRT" 417 | } 418 | }, 419 | "node_modules/googlethis/node_modules/cheerio": { 420 | "version": "1.0.0-rc.10", 421 | "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.10.tgz", 422 | "integrity": "sha512-g0J0q/O6mW8z5zxQ3A8E8J1hUgp4SMOvEoW/x84OwyHKe/Zccz83PVT4y5Crcr530FV6NgmKI1qvGTKVl9XXVw==", 423 | "dependencies": { 424 | "cheerio-select": "^1.5.0", 425 | "dom-serializer": "^1.3.2", 426 | "domhandler": "^4.2.0", 427 | "htmlparser2": "^6.1.0", 428 | "parse5": "^6.0.1", 429 | "parse5-htmlparser2-tree-adapter": "^6.0.1", 430 | "tslib": "^2.2.0" 431 | }, 432 | "engines": { 433 | "node": ">= 6" 434 | }, 435 | "funding": { 436 | "url": "https://github.com/cheeriojs/cheerio?sponsor=1" 437 | } 438 | }, 439 | "node_modules/googlethis/node_modules/cheerio-select": { 440 | "version": "1.6.0", 441 | "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.6.0.tgz", 442 | "integrity": "sha512-eq0GdBvxVFbqWgmCm7M3XGs1I8oLy/nExUnh6oLqmBditPO9AqQJrkslDpMun/hZ0yyTs8L0m85OHp4ho6Qm9g==", 443 | "dependencies": { 444 | "css-select": "^4.3.0", 445 | "css-what": "^6.0.1", 446 | "domelementtype": "^2.2.0", 447 | "domhandler": "^4.3.1", 448 | "domutils": "^2.8.0" 449 | }, 450 | "funding": { 451 | "url": "https://github.com/sponsors/fb55" 452 | } 453 | }, 454 | "node_modules/googlethis/node_modules/css-select": { 455 | "version": "4.3.0", 456 | "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", 457 | "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", 458 | "dependencies": { 459 | "boolbase": "^1.0.0", 460 | "css-what": "^6.0.1", 461 | "domhandler": "^4.3.1", 462 | "domutils": "^2.8.0", 463 | "nth-check": "^2.0.1" 464 | }, 465 | "funding": { 466 | "url": "https://github.com/sponsors/fb55" 467 | } 468 | }, 469 | "node_modules/googlethis/node_modules/dom-serializer": { 470 | "version": "1.4.1", 471 | "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", 472 | "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", 473 | "dependencies": { 474 | "domelementtype": "^2.0.1", 475 | "domhandler": "^4.2.0", 476 | "entities": "^2.0.0" 477 | }, 478 | "funding": { 479 | "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" 480 | } 481 | }, 482 | "node_modules/googlethis/node_modules/domhandler": { 483 | "version": "4.3.1", 484 | "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", 485 | "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", 486 | "dependencies": { 487 | "domelementtype": "^2.2.0" 488 | }, 489 | "engines": { 490 | "node": ">= 4" 491 | }, 492 | "funding": { 493 | "url": "https://github.com/fb55/domhandler?sponsor=1" 494 | } 495 | }, 496 | "node_modules/googlethis/node_modules/domutils": { 497 | "version": "2.8.0", 498 | "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", 499 | "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", 500 | "dependencies": { 501 | "dom-serializer": "^1.0.1", 502 | "domelementtype": "^2.2.0", 503 | "domhandler": "^4.2.0" 504 | }, 505 | "funding": { 506 | "url": "https://github.com/fb55/domutils?sponsor=1" 507 | } 508 | }, 509 | "node_modules/googlethis/node_modules/entities": { 510 | "version": "2.2.0", 511 | "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", 512 | "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", 513 | "funding": { 514 | "url": "https://github.com/fb55/entities?sponsor=1" 515 | } 516 | }, 517 | "node_modules/googlethis/node_modules/htmlparser2": { 518 | "version": "6.1.0", 519 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", 520 | "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", 521 | "funding": [ 522 | "https://github.com/fb55/htmlparser2?sponsor=1", 523 | { 524 | "type": "github", 525 | "url": "https://github.com/sponsors/fb55" 526 | } 527 | ], 528 | "dependencies": { 529 | "domelementtype": "^2.0.1", 530 | "domhandler": "^4.0.0", 531 | "domutils": "^2.5.2", 532 | "entities": "^2.0.0" 533 | } 534 | }, 535 | "node_modules/googlethis/node_modules/parse5": { 536 | "version": "6.0.1", 537 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", 538 | "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" 539 | }, 540 | "node_modules/googlethis/node_modules/parse5-htmlparser2-tree-adapter": { 541 | "version": "6.0.1", 542 | "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", 543 | "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", 544 | "dependencies": { 545 | "parse5": "^6.0.1" 546 | } 547 | }, 548 | "node_modules/hono": { 549 | "version": "3.11.4", 550 | "resolved": "https://registry.npmjs.org/hono/-/hono-3.11.4.tgz", 551 | "integrity": "sha512-lU8Et1Nkpa4RqgBvjGUGyu8/2QNdAOR9EqFzNCoP7wM+xtcq2v/DHi9CMJ/ixVxRU9Q2uAh0G3xIKTVYzzC4dg==", 552 | "engines": { 553 | "node": ">=16.0.0" 554 | } 555 | }, 556 | "node_modules/htmlparser2": { 557 | "version": "8.0.2", 558 | "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", 559 | "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", 560 | "funding": [ 561 | "https://github.com/fb55/htmlparser2?sponsor=1", 562 | { 563 | "type": "github", 564 | "url": "https://github.com/sponsors/fb55" 565 | } 566 | ], 567 | "dependencies": { 568 | "domelementtype": "^2.3.0", 569 | "domhandler": "^5.0.3", 570 | "domutils": "^3.0.1", 571 | "entities": "^4.4.0" 572 | } 573 | }, 574 | "node_modules/humanize-ms": { 575 | "version": "1.2.1", 576 | "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", 577 | "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", 578 | "dependencies": { 579 | "ms": "^2.0.0" 580 | } 581 | }, 582 | "node_modules/ieee754": { 583 | "version": "1.2.1", 584 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 585 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 586 | "funding": [ 587 | { 588 | "type": "github", 589 | "url": "https://github.com/sponsors/feross" 590 | }, 591 | { 592 | "type": "patreon", 593 | "url": "https://www.patreon.com/feross" 594 | }, 595 | { 596 | "type": "consulting", 597 | "url": "https://feross.org/support" 598 | } 599 | ] 600 | }, 601 | "node_modules/is-buffer": { 602 | "version": "1.1.6", 603 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 604 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 605 | }, 606 | "node_modules/md5": { 607 | "version": "2.3.0", 608 | "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", 609 | "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", 610 | "dependencies": { 611 | "charenc": "0.0.2", 612 | "crypt": "0.0.2", 613 | "is-buffer": "~1.1.6" 614 | } 615 | }, 616 | "node_modules/mime-db": { 617 | "version": "1.52.0", 618 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 619 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 620 | "engines": { 621 | "node": ">= 0.6" 622 | } 623 | }, 624 | "node_modules/mime-types": { 625 | "version": "2.1.35", 626 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 627 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 628 | "dependencies": { 629 | "mime-db": "1.52.0" 630 | }, 631 | "engines": { 632 | "node": ">= 0.6" 633 | } 634 | }, 635 | "node_modules/ms": { 636 | "version": "2.1.3", 637 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 638 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 639 | }, 640 | "node_modules/node-domexception": { 641 | "version": "1.0.0", 642 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 643 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 644 | "funding": [ 645 | { 646 | "type": "github", 647 | "url": "https://github.com/sponsors/jimmywarting" 648 | }, 649 | { 650 | "type": "github", 651 | "url": "https://paypal.me/jimmywarting" 652 | } 653 | ], 654 | "engines": { 655 | "node": ">=10.5.0" 656 | } 657 | }, 658 | "node_modules/node-fetch": { 659 | "version": "2.7.0", 660 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 661 | "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 662 | "dependencies": { 663 | "whatwg-url": "^5.0.0" 664 | }, 665 | "engines": { 666 | "node": "4.x || >=6.0.0" 667 | }, 668 | "peerDependencies": { 669 | "encoding": "^0.1.0" 670 | }, 671 | "peerDependenciesMeta": { 672 | "encoding": { 673 | "optional": true 674 | } 675 | } 676 | }, 677 | "node_modules/nth-check": { 678 | "version": "2.1.1", 679 | "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", 680 | "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", 681 | "dependencies": { 682 | "boolbase": "^1.0.0" 683 | }, 684 | "funding": { 685 | "url": "https://github.com/fb55/nth-check?sponsor=1" 686 | } 687 | }, 688 | "node_modules/on-exit-leak-free": { 689 | "version": "2.1.2", 690 | "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", 691 | "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", 692 | "engines": { 693 | "node": ">=14.0.0" 694 | } 695 | }, 696 | "node_modules/openai": { 697 | "version": "4.20.1", 698 | "resolved": "https://registry.npmjs.org/openai/-/openai-4.20.1.tgz", 699 | "integrity": "sha512-Dd3q8EvINfganZFtg6V36HjrMaihqRgIcKiHua4Nq9aw/PxOP48dhbsk8x5klrxajt5Lpnc1KTOG5i1S6BKAJA==", 700 | "dependencies": { 701 | "@types/node": "^18.11.18", 702 | "@types/node-fetch": "^2.6.4", 703 | "abort-controller": "^3.0.0", 704 | "agentkeepalive": "^4.2.1", 705 | "digest-fetch": "^1.3.0", 706 | "form-data-encoder": "1.7.2", 707 | "formdata-node": "^4.3.2", 708 | "node-fetch": "^2.6.7", 709 | "web-streams-polyfill": "^3.2.1" 710 | }, 711 | "bin": { 712 | "openai": "bin/cli" 713 | } 714 | }, 715 | "node_modules/parse5": { 716 | "version": "7.1.2", 717 | "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", 718 | "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", 719 | "dependencies": { 720 | "entities": "^4.4.0" 721 | }, 722 | "funding": { 723 | "url": "https://github.com/inikulin/parse5?sponsor=1" 724 | } 725 | }, 726 | "node_modules/parse5-htmlparser2-tree-adapter": { 727 | "version": "7.0.0", 728 | "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", 729 | "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", 730 | "dependencies": { 731 | "domhandler": "^5.0.2", 732 | "parse5": "^7.0.0" 733 | }, 734 | "funding": { 735 | "url": "https://github.com/inikulin/parse5?sponsor=1" 736 | } 737 | }, 738 | "node_modules/pino": { 739 | "version": "8.16.2", 740 | "resolved": "https://registry.npmjs.org/pino/-/pino-8.16.2.tgz", 741 | "integrity": "sha512-2advCDGVEvkKu9TTVSa/kWW7Z3htI/sBKEZpqiHk6ive0i/7f5b1rsU8jn0aimxqfnSz5bj/nOYkwhBUn5xxvg==", 742 | "dependencies": { 743 | "atomic-sleep": "^1.0.0", 744 | "fast-redact": "^3.1.1", 745 | "on-exit-leak-free": "^2.1.0", 746 | "pino-abstract-transport": "v1.1.0", 747 | "pino-std-serializers": "^6.0.0", 748 | "process-warning": "^2.0.0", 749 | "quick-format-unescaped": "^4.0.3", 750 | "real-require": "^0.2.0", 751 | "safe-stable-stringify": "^2.3.1", 752 | "sonic-boom": "^3.7.0", 753 | "thread-stream": "^2.0.0" 754 | }, 755 | "bin": { 756 | "pino": "bin.js" 757 | } 758 | }, 759 | "node_modules/pino-abstract-transport": { 760 | "version": "1.1.0", 761 | "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", 762 | "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", 763 | "dependencies": { 764 | "readable-stream": "^4.0.0", 765 | "split2": "^4.0.0" 766 | } 767 | }, 768 | "node_modules/pino-std-serializers": { 769 | "version": "6.2.2", 770 | "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", 771 | "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" 772 | }, 773 | "node_modules/process": { 774 | "version": "0.11.10", 775 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", 776 | "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", 777 | "engines": { 778 | "node": ">= 0.6.0" 779 | } 780 | }, 781 | "node_modules/process-warning": { 782 | "version": "2.3.2", 783 | "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.2.tgz", 784 | "integrity": "sha512-n9wh8tvBe5sFmsqlg+XQhaQLumwpqoAUruLwjCopgTmUBjJ/fjtBsJzKleCaIGBOMXYEhp1YfKl4d7rJ5ZKJGA==" 785 | }, 786 | "node_modules/quick-format-unescaped": { 787 | "version": "4.0.4", 788 | "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", 789 | "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" 790 | }, 791 | "node_modules/readable-stream": { 792 | "version": "4.4.2", 793 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", 794 | "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", 795 | "dependencies": { 796 | "abort-controller": "^3.0.0", 797 | "buffer": "^6.0.3", 798 | "events": "^3.3.0", 799 | "process": "^0.11.10", 800 | "string_decoder": "^1.3.0" 801 | }, 802 | "engines": { 803 | "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 804 | } 805 | }, 806 | "node_modules/real-require": { 807 | "version": "0.2.0", 808 | "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", 809 | "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", 810 | "engines": { 811 | "node": ">= 12.13.0" 812 | } 813 | }, 814 | "node_modules/safe-buffer": { 815 | "version": "5.2.1", 816 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 817 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 818 | "funding": [ 819 | { 820 | "type": "github", 821 | "url": "https://github.com/sponsors/feross" 822 | }, 823 | { 824 | "type": "patreon", 825 | "url": "https://www.patreon.com/feross" 826 | }, 827 | { 828 | "type": "consulting", 829 | "url": "https://feross.org/support" 830 | } 831 | ] 832 | }, 833 | "node_modules/safe-stable-stringify": { 834 | "version": "2.4.3", 835 | "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", 836 | "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", 837 | "engines": { 838 | "node": ">=10" 839 | } 840 | }, 841 | "node_modules/sonic-boom": { 842 | "version": "3.7.0", 843 | "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", 844 | "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", 845 | "dependencies": { 846 | "atomic-sleep": "^1.0.0" 847 | } 848 | }, 849 | "node_modules/split2": { 850 | "version": "4.2.0", 851 | "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", 852 | "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", 853 | "engines": { 854 | "node": ">= 10.x" 855 | } 856 | }, 857 | "node_modules/string_decoder": { 858 | "version": "1.3.0", 859 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 860 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 861 | "dependencies": { 862 | "safe-buffer": "~5.2.0" 863 | } 864 | }, 865 | "node_modules/thread-stream": { 866 | "version": "2.4.1", 867 | "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", 868 | "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", 869 | "dependencies": { 870 | "real-require": "^0.2.0" 871 | } 872 | }, 873 | "node_modules/tr46": { 874 | "version": "0.0.3", 875 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 876 | "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" 877 | }, 878 | "node_modules/tslib": { 879 | "version": "2.6.2", 880 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", 881 | "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" 882 | }, 883 | "node_modules/undici-types": { 884 | "version": "5.26.5", 885 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 886 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 887 | }, 888 | "node_modules/unraw": { 889 | "version": "2.0.1", 890 | "resolved": "https://registry.npmjs.org/unraw/-/unraw-2.0.1.tgz", 891 | "integrity": "sha512-tdOvLfRzHolwYcHS6HIX860MkK9LQ4+oLuNwFYL7bpgTEO64PZrcQxkisgwJYCfF8sKiWLwwu1c83DvMkbefIQ==" 892 | }, 893 | "node_modules/web-streams-polyfill": { 894 | "version": "3.2.1", 895 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", 896 | "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", 897 | "engines": { 898 | "node": ">= 8" 899 | } 900 | }, 901 | "node_modules/webidl-conversions": { 902 | "version": "3.0.1", 903 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 904 | "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" 905 | }, 906 | "node_modules/whatwg-url": { 907 | "version": "5.0.0", 908 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 909 | "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 910 | "dependencies": { 911 | "tr46": "~0.0.3", 912 | "webidl-conversions": "^3.0.0" 913 | } 914 | }, 915 | "node_modules/word-count": { 916 | "version": "0.2.2", 917 | "resolved": "https://registry.npmjs.org/word-count/-/word-count-0.2.2.tgz", 918 | "integrity": "sha512-tPRTbQ+nTCPY3F0z1f/y0PX22ScE6l/4/8j9KqA3h77JhlZ/w6cbVS8LIO5Pq/aV96SWBOoiE2IEgzxF0Cn+kA==" 919 | } 920 | } 921 | } 922 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aiwriter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "fstart": "npm start | pino-pretty", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@hono/node-server": "^1.3.2", 16 | "cheerio": "^1.0.0-rc.12", 17 | "dotenv": "^16.3.1", 18 | "googlethis": "github:timsu/google-this", 19 | "hono": "^3.11.4", 20 | "openai": "^4.3.0", 21 | "pino": "^8.15.0", 22 | "word-count": "^0.2.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /serp.js: -------------------------------------------------------------------------------- 1 | const { model, openaiClient } = require("./api"); 2 | const { logger } = require("./logger") 3 | const cheerio = require("cheerio"); 4 | const google = require("googlethis"); 5 | 6 | const queryDdg = async (keyword) => { 7 | const url = `https://html.duckduckgo.com/html/?q=${keyword}`; 8 | const html = await fetch(url); 9 | const text = await html.text(); 10 | const $ = cheerio.load(text); 11 | const weirdUrls = $(".result__a").slice(0, 5).map((i, el) => { 12 | return $(el); 13 | }).toArray(); 14 | 15 | const urls = weirdUrls.map((cheerioEl) => { 16 | const url = cheerioEl.attr("href"); 17 | const urlObj = new URL(`https:` + url); 18 | return { 19 | title: cheerioEl.text(), 20 | url: urlObj.searchParams.get("uddg"), 21 | }; 22 | }); 23 | 24 | return urls; 25 | }; 26 | 27 | const queryGoogle = async (keyword) => { 28 | const search = await google.search(keyword); 29 | return search.results.slice(0, 5); 30 | }; 31 | 32 | const summarizeContent = async (url) => { 33 | let html; 34 | try { 35 | html = await fetch(url); 36 | } catch (e) { 37 | logger.info(`Error fetching ${url}. Skipping...`); 38 | return; 39 | } 40 | 41 | const text = await html.text(); 42 | 43 | const $ = cheerio.load(text); 44 | const body = $("h1, h2, h3, h4, h5, h6, p"); 45 | 46 | const content = ` 47 | Helpfully summarize the following content: 48 | 49 | ${body.text().slice(0, 14000)} 50 | `; 51 | 52 | const messages = [ 53 | { 54 | role: "system", 55 | content: 56 | "Act as a helpful program that can summarize the content of an article. You should be detailed as possible.", 57 | }, 58 | { role: "user", content }, 59 | ]; 60 | 61 | const response = await openaiClient.chat.completions.create({ 62 | model: "gpt-3.5-turbo-16k", 63 | messages, 64 | }); 65 | 66 | return response.choices[0].message; 67 | }; 68 | 69 | module.exports = { 70 | queryGoogle, 71 | queryDdg, 72 | summarizeContent, 73 | }; 74 | --------------------------------------------------------------------------------