├── .eslintignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── SUGGESTION_CN.md │ ├── SUGGESTION_EN.md │ ├── REPORT_PROBLEM_CN.md │ └── REPORT_PROBLEM_EN.md └── PULL_REQUEST_TEMPLATE │ ├── CN.md │ └── EN.md ├── .npmignore ├── .eslintrc.json ├── .editorconfig ├── tsconfig.eslint.json ├── tsconfig.json ├── src ├── index.ts ├── utils │ ├── interface.ts │ ├── deployer.ts │ ├── utils.ts │ └── hexo.d.ts ├── generator.ts ├── baidu_deployer.ts ├── shenma_deployer.ts ├── bing_deployer.ts └── google_deployer.ts ├── .gitignore ├── LICENSE ├── package.json ├── README.md └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | tmp/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: [] 2 | github: [abnerwei] 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | .travis.yml 3 | *.tsbuildinfo 4 | src/ 5 | .idea -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "hexo", 3 | "root": true 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.{js,ts}] 4 | indent_size = 2 5 | indent_style = tab -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src/**/*.ts", 5 | ".eslintrc.js" 6 | ], 7 | "exclude": [ 8 | "node_modules", 9 | "dist" 10 | ] 11 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUGGESTION_CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交建议(中文) 3 | about: 用中文提交建议,按“Get started”按钮开始提交此类issue 4 | title: "[建议] 你的建议" 5 | labels: suggestion 6 | assignees: '' 7 | 8 | --- 9 | ## 建议 10 | 14 | **答:** 15 | 16 | 17 | --- 18 | 19 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/SUGGESTION_EN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Submit a suggestion(English) 3 | about: Submit suggestions in English, press the "Get started" button to start submitting such issues 4 | title: "[suggestion] Your suggestions" 5 | labels: suggestion 6 | assignees: '' 7 | 8 | --- 9 | ## suggestion 10 | 14 | 15 | 16 | 17 | --- 18 | 19 | 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "moduleResolution": "node", 6 | "outDir": "lib", 7 | "rootDir": "src", 8 | "sourceMap": true, 9 | "removeComments": true, 10 | "esModuleInterop": true, 11 | "incremental": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src"], 17 | "exclude": [ 18 | "node_modules", 19 | ".vscode-test", 20 | "__tests__" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Hexo from "./utils/hexo"; 2 | 3 | import deployer from "./utils/deployer"; 4 | import generator from './generator'; 5 | 6 | hexo.config.url_submission = Object.assign({ 7 | enable: true, 8 | type: 'all', 9 | prefix: ['post'], 10 | ignore: [], 11 | channels: {}, 12 | count: 100, 13 | proxy: '', 14 | urls_path: 'submit_url.txt', 15 | sitemap: 'sitemap.xml' 16 | }, hexo.config.url_submission) 17 | 18 | const pluginConfig = hexo.config.url_submission 19 | 20 | if (pluginConfig?.enable) { 21 | hexo.extend.generator.register('submission_generator', (locals: Hexo.Site) => { 22 | return generator(locals, hexo) 23 | }) // generator 24 | hexo.extend.deployer.register('url_submission', async (args: Hexo.extend.Deployer.Config) => { 25 | await deployer(hexo) 26 | }) 27 | } -------------------------------------------------------------------------------- /src/utils/interface.ts: -------------------------------------------------------------------------------- 1 | 2 | interface ChannelConfig { 3 | count: string, 4 | user: string, 5 | token: string, 6 | key: string 7 | } 8 | 9 | interface Channel { 10 | [key: string]: ChannelConfig | undefined 11 | } 12 | 13 | export interface GoogleKeys { 14 | type: string, 15 | project_id: string, 16 | private_key_id: string, 17 | private_key: string, 18 | client_email: string, 19 | client_id: string, 20 | auth_uri: string, 21 | token_uri: string, 22 | auth_provider_x509_cert_url: string, 23 | client_x509_cert_url: string 24 | } 25 | 26 | export interface UrlSubmission { 27 | enable: boolean, 28 | type: string 29 | prefix: Array, 30 | ignore: Array, 31 | channels: Channel, 32 | count: number, 33 | proxy: string, 34 | urls_path: string, 35 | sitemap: string 36 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | # intellij 40 | 41 | .idea 42 | lib/* 43 | tsconfig.tsbuildinfo 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 提交 Pull requests(中文) 3 | about: 用中文提交 Pull requests,按 “Get started” 按钮开始 4 | --- 5 | ## 摘要 6 | 7 | 8 | 9 | ## 清单 10 | 13 | 14 | - [ ] 我已经阅读了有关提交 `PULL_REQUEST` 的说明 15 | - [ ] 我已经签署了CLA(如果这个项目有) 16 | - [ ] 更改在本地通过测试 17 | - [ ] 我添加了一些测试来证明我的解决方案是有效或功能正常 18 | - [ ] 我添加了必要的文档(如果适用) 19 | - [ ] 任何相关更改已合并并发布在下游模块中 20 | - [ ] 我确认 **避免** 破坏持续集成构建 21 | 22 | 23 | ## 更改类型 24 | 28 | - [ ] 错误修正(解决问题的不间断更改) 29 | - [ ] 新功能(增加功能的不间断更改) 30 | - [ ] 重大更改(不向下兼容,可能引起现有功能无法按预期运行) 31 | - [ ] 代码样式更新(对原有代码格式化,重命名) 32 | - [ ] 重构(优化原有代码,无功能更改,无API更改) 33 | - [ ] 文档更新(一般,如果没有其他选择) 34 | - [ ] 其他(请描述): 35 | 36 | 37 | ## 进一步的注释说明 38 | 41 | 42 | 43 | 44 | ## 最终确认 45 | 46 | - [ ] 提交的PR没有问题,完全符合您的预期 47 | 48 | 49 | 52 | -------------------------------------------------------------------------------- /src/utils/deployer.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | 3 | import Hexo from './hexo' 4 | import { readFileSync, projectPrefix } from './utils' 5 | 6 | export default async (hexo: Hexo) => { 7 | let pluginConfig = hexo.config.url_submission 8 | // extract url file 9 | if (Object.keys(pluginConfig?.channels).length !== 0) { 10 | try { 11 | pluginConfig.urlList = readFileSync(hexo.public_dir, pluginConfig?.urls_path) 12 | pluginConfig.urlArr = pluginConfig.urlList.split(/[(\r\n)\r\n]+/) 13 | } catch (error) { 14 | hexo.log.error(projectPrefix.concat('Extract url file failed. Cancel inclusion submission...')) 15 | return 16 | } 17 | } 18 | 19 | for (let channel in pluginConfig?.channels) { 20 | let filePath = `./../${channel}_deployer.js` 21 | try { 22 | await (await import(filePath)).deployer(hexo) 23 | } catch (error) { 24 | hexo.log.error(projectPrefix.concat(`\x1b[31mParsing error, submission channel named \x1b[1m${channel}\x1b[22m does not support.\x1b[39m`)) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/REPORT_PROBLEM_CN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 报告问题(中文) 3 | about: 用中文汇报在使用此项目过程中遇到的问题,按“Get started”按钮开始提交此类issue 4 | title: "[报告问题] 你遇到的问题" 5 | labels: problems 6 | assignees: '' 7 | 8 | --- 9 | 10 | 21 | ## Information 信息 22 | 23 | 24 | **Version/版本:** 25 | 26 | 27 | 28 | **Platform/操作系统:** 29 | 30 | 31 | **npm版本:** 32 | 33 | 34 | **hexo版本:** 35 | 36 | 37 | **科学上网方式:** 38 | 39 | 40 | ## Expected behaviour 预期行为 41 | **答:** 42 | 43 | ## Actual behaviour 实际行为 44 | 47 | **答:** 48 | 49 | ## Steps to reproduce the behaviour 重现步骤 50 | **答:** 51 | 52 | ## Feature Request 功能方面的建议 53 | 56 | **答:** 57 | 58 | 59 | --- 60 | 61 | 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 abnerwei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-url-submission", 3 | "version": "2.0.3", 4 | "description": "Submit website url to search engines.", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "build": "tsc -p tsconfig.json", 8 | "eslint": "eslint .", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/abnerwei/hexo-url-submission.git" 14 | }, 15 | "keywords": [ 16 | "seo", 17 | "bing", 18 | "google", 19 | "baidu", 20 | "hexo", 21 | "abnerwei", 22 | "deployer", 23 | "hexo", 24 | "url", 25 | "submission" 26 | ], 27 | "author": { 28 | "name": "Doctor.Wei", 29 | "email": "abnerwei@vip.qq.com" 30 | }, 31 | "maintainers": [ 32 | "Doctor.Wei " 33 | ], 34 | "license": "MIT", 35 | "dependencies": { 36 | "axios": "^1.2.0", 37 | "google-auth-library": "^9.0.0", 38 | "hexo-fs": "^5.0.0", 39 | "https-proxy-agent": "^7.0.0" 40 | }, 41 | "devDependencies": { 42 | "@types/node": "^18.11.10" 43 | }, 44 | "engines": { 45 | "node": ">=10.13.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/REPORT_PROBLEM_EN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report problems (English) 3 | about: Report problems encountered during use in English, press the "Get started" button to start submitting issues 4 | title: "[Report problems] problems" 5 | labels: problems 6 | assignees: '' 7 | 8 | --- 9 | 10 | 22 | ## Information 信息 23 | 24 | 25 | **Version:** 26 | 27 | 28 | **Platform:** 29 | 30 | **npm Version:** 31 | 32 | **hexo Version:** 33 | 34 | 35 | ## Expected behaviour 36 | 37 | 38 | ## Actual behaviour 39 | 42 | 43 | 44 | ## Steps to reproduce the behaviour 45 | 46 | 47 | ## Feature Request 48 | 49 | 50 | 53 | 54 | --- 55 | 56 | 59 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/EN.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Submit Pull requests(English) 3 | about: Submit Pull requests in English, press the "Get started" button to start submitting. 4 | --- 5 | ## Summary 6 | 7 | 8 | 9 | ## List 10 | 16 | 17 | -[ ] I have read the instructions for submitting `PULL_REQUEST` 18 | -[ ] I have signed the CLA (if this project has one) 19 | -[ ] Changes passed the test locally 20 | -[ ] I added some tests to prove that my solution is effective or functional 21 | -[ ] I added the necessary documents (if applicable) 22 | -[ ] Any related changes have been merged and published in downstream modules 23 | -[ ] I confirm **Avoid** breaking the continuous integration build 24 | 25 | 26 | ## Change type 27 | 31 | -[ ] Bug fixes (continuous changes to solve problems) 32 | -[ ] New features (continuous changes to add features) 33 | -[ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 34 | -[ ] Code style update (format and rename the original code) 35 | -[ ] Refactoring (no functional changes, no api changes) 36 | -[ ] Documentation update (generally, if there are no other options) 37 | -[ ] Other (please describe): 38 | 39 | 40 | ## Further notes 41 | 44 | 45 | 46 | 47 | ## Final confirmation 48 | 49 | -[ ] There is no problem with the PR submitted, and it fully meets your expectations 50 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /src/generator.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import pathFn from 'path'; 4 | import fs from 'fs'; 5 | 6 | import Hexo from "./utils/hexo"; 7 | import { projectPrefix, isMatchUrl } from './utils/utils' 8 | import { UrlSubmission } from './utils/interface'; 9 | 10 | export default (locals: Hexo.Site, hexo: Hexo): Hexo.extend.Generator.Return => { 11 | const { config, log, source_dir } = hexo; 12 | const url_submission: UrlSubmission = config.url_submission; 13 | const { type: generatorType, count, urls_path, prefix, ignore } = url_submission; 14 | log.info(projectPrefix.concat("Start generating url list...")) 15 | let pages = locals.pages.toArray().concat(...locals.posts.toArray() as Hexo.Locals.Page[]) 16 | let urls = pages.map((post: Hexo.Locals.Page) => { 17 | return { 18 | "date": post.updated || post.date, 19 | "permalink": post.permalink, 20 | "source": post.source 21 | } 22 | }).sort(function (a: any, b: any) { 23 | return b.date - a.date 24 | }).slice(0, count).filter((post) => { // filter matching prefix 25 | const url = new URL(post.permalink) 26 | return (prefix.length === 0 ? true : prefix.filter(k => url.pathname.startsWith(k)).length > 0) 27 | && (ignore.length === 0 ? true : ignore.filter(k => isMatchUrl(url.pathname, k)).length === 0) 28 | }) 29 | 30 | if ('latest' === generatorType) { 31 | try { 32 | urls = urls.filter((post) => { 33 | const pageFile = fs.statSync(pathFn.join(source_dir, post.source)) 34 | return new Date(pageFile.mtime).setHours(0, 0, 0, 0) === new Date().setHours(0, 0, 0, 0) 35 | }) 36 | } catch (error: any) { 37 | log.error(projectPrefix.concat("Read file meta failed, error is: ", error.message)) 38 | return { path: '', data: '' } 39 | } 40 | } 41 | let urlsMap = urls.map(post => post.permalink).join('\n') 42 | 43 | if (urls.length > 0) { 44 | log.info(projectPrefix.concat("Page urls will generate into file named \x1b[90m" + urls_path + "\x1b[39m")) 45 | } else { 46 | log.info(projectPrefix.concat("No matching pages found!")) 47 | return { path: '', data: '' } 48 | } 49 | return { 50 | path: urls_path, 51 | data: urlsMap 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/baidu_deployer.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import Hexo from './utils/hexo' 4 | import { projectPrefix, defaultTimeOut } from './utils/utils' 5 | 6 | export const deployer = async (args: Hexo) => { 7 | const { config, log } = args 8 | const { url_submission, url } = config 9 | const { urlList, urlArr, count: baseCount } = url_submission 10 | let { token, count } = url_submission.channels?.baidu 11 | token = token || process.env.BAIDU_TOKEN 12 | const logPrefix = projectPrefix.concat('(\x1b[3mbaidu\x1b[23m) ') 13 | axios.defaults.timeout = defaultTimeOut 14 | 15 | if (count === undefined) { 16 | log.info(logPrefix.concat("The number of submitted entries for Baidu Search is not set, and the default value will be used for submission.")) 17 | } 18 | count = Math.min(count || baseCount, urlArr.length) 19 | log.warn(logPrefix.concat('The number of entries submitted by Baidu Search has been set to \x1b[1m', String(count), '\x1b[22m')) 20 | 21 | log.info(logPrefix.concat("Start submit urlList to baidu engine...")) 22 | 23 | const target = "/urls?site=" + url + "&token=" + token 24 | // Success 25 | /* 26 | * { 27 | "remain":99998, 28 | "success":2, 29 | "not_same_site":[], 30 | "not_valid":[] 31 | } 32 | */ 33 | // Failed 34 | /* 35 | * { 36 | "error":401, 37 | "message":"token is not valid" 38 | } 39 | */ 40 | try { 41 | let response = await axios.create().request({ 42 | url: target, 43 | method: 'POST', 44 | baseURL: 'http://data.zz.baidu.com', 45 | headers: { 46 | 'Content-type': 'text/plain' 47 | }, 48 | data: urlList 49 | }) 50 | 51 | let message = '' 52 | const respJson = response.data 53 | if (response.status === 200) { 54 | const { remain } = respJson 55 | let quota = ''.concat(`\x1b[3${remain >= urlArr.length ? '2' : '1'}m`, remain, '\x1b[39m') 56 | message = message.concat('success: [success: \x1b[32m', respJson.success, '\x1b[39m, remain: ', quota) 57 | } else { 58 | message = message.concat('failed: [', respJson.message) 59 | } 60 | log.info(logPrefix.concat("Submit to baidu engine ", message, ']')) 61 | } catch (error: any) { 62 | log.error(logPrefix.concat('Submit to baidu engine error: [', error.message, ']')) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/shenma_deployer.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import urlParser from 'url' 3 | 4 | import Hexo from './utils/hexo' 5 | import { projectPrefix, defaultTimeOut } from './utils/utils' 6 | 7 | export const deployer = async (args: Hexo) => { 8 | const { config, log } = args 9 | const { url_submission, url } = config 10 | const { urlList, urlArr, count: baseCount } = url_submission 11 | let { user, token, count } = url_submission.channels?.shenma 12 | token = token || process.env.SHENMA_TOKEN 13 | const logPrefix = projectPrefix.concat('(\x1b[3mshenma\x1b[23m) ') 14 | axios.defaults.timeout = defaultTimeOut 15 | 16 | if (count === undefined) { 17 | log.info(logPrefix.concat("The number of submitted entries for ShenMa Search is not set, and the default value will be used for submission.")) 18 | } 19 | user = user || process.env.SHENMA_USER 20 | count = Math.min(count || baseCount, urlArr.length) 21 | log.warn(logPrefix.concat('The number of entries submitted by ShenMa Search has been set to \x1b[1m', String(count), '\x1b[22m')) 22 | 23 | if (typeof (user) !== 'string' || typeof (token) !== 'string') { 24 | log.warn(logPrefix.concat("Shenme engine config check invalid, cancel submission.")) 25 | return 26 | } 27 | 28 | log.info(logPrefix.concat("Start submit urlList to shenma engine...")) 29 | 30 | const target = "/push?site=" + urlParser.parse(url).host + "&user_name=" + user + "&resource_name=mip_add&token=" + token 31 | // Success 32 | /* 33 | * { 34 | "returnCode" : 200, //接收成功,但需要进一步校验提交的内容是否正确 35 | "errorMsg" : "" 36 | } 37 | */ 38 | // Failed 39 | /* 40 | * { 41 | "returnCode" : 201, // 201: token不合法 202: 当日流量已用完 400: 请求参数有误 500: 服务器内部错误 42 | "errorMsg" : "" 43 | } 44 | */ 45 | try { 46 | let response = await axios.create().request({ 47 | url: target, 48 | baseURL: 'https://data.zhanzhang.sm.cn', 49 | method: 'POST', 50 | headers: { 51 | 'Content-type': 'text/plain' 52 | }, 53 | data: urlList 54 | }) 55 | let message = '' 56 | const respJson = response.data 57 | if (response.status === 200) { 58 | switch (respJson.returnCode) { 59 | case 200: 60 | message = message.concat('success.') 61 | break 62 | default: 63 | message = message.concat('failed: [\x1b[31m', respJson.errorMsg, '\x1b[39m]') 64 | } 65 | } else { 66 | message = message.concat('failed: [\x1b[31m shenme submission server is down! \x1b[39m]') 67 | } 68 | log.info(logPrefix.concat("Submit to shenma engine ", message)) 69 | } catch (error: any) { 70 | log.error(logPrefix.concat('Submit to shenma engine error: [\x1b[31m', error, '\x1b[39m]')) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import pathFn from 'path' 2 | import fs from 'fs' 3 | 4 | export const projectPrefix = 'url_submission: ' 5 | export const defaultTimeOut = 2000 6 | 7 | export const readFileSync = (publicDir: string, filePath: string) => { 8 | return fs.readFileSync(pathFn.join(publicDir, filePath), 'utf8') 9 | } 10 | 11 | export const isMatchUrl = (s: string, p: string): boolean => { 12 | if (s === p) return true; 13 | 14 | const words = p.split(/\*+/g); 15 | 16 | if (words.length === 2 && words[0] === "" && words[1] === "") { 17 | return true; 18 | } 19 | if (words.length === 1 || (words.length === 2 && (words[0] === "" || words[1] === ""))) { 20 | return _check_regular(s, p); 21 | } 22 | if (!_check_includes(s, p)) return false; 23 | if (words.length >= 2) { 24 | return _check_fixs(s, words); 25 | } 26 | 27 | return false; 28 | } 29 | 30 | const _check_includes = (s: string, p: string) => { 31 | const words = Array.from(new Set(p.split(/\?|\*+/g).filter(Boolean))) 32 | if ( 33 | words.some((word) => { 34 | return !s.includes(word); 35 | }) 36 | ) { 37 | return false; 38 | } 39 | return true; 40 | } 41 | const _check_fixs = (s: string, words: string[]): boolean => { 42 | if (words.length >= 2) { 43 | const prefix = words[0]; 44 | const suffix = words[words.length - 1]; 45 | let str = s; 46 | if (suffix) { 47 | const matched = str.match( 48 | //@ts-ignore 49 | new RegExp(`^(.*?)${suffix.replaceAll("?", ".")}$`), 50 | ); 51 | if (!matched) return false; 52 | str = matched[1]; 53 | } 54 | if (prefix) { 55 | const matched = str.match( 56 | //@ts-ignore 57 | new RegExp(`^${prefix.replaceAll("?", ".")}(.*?)$`), 58 | ); 59 | if (!matched) return false; 60 | str = matched[1]; 61 | } 62 | const rest = words.slice(1, words.length - 1); 63 | return _check_words(str, rest); 64 | } 65 | return false; 66 | } 67 | const _check_regular = (s: string, p: string): boolean => { 68 | return new RegExp( 69 | //@ts-ignore 70 | "^" + p.replaceAll("?", ".").replaceAll(/\*+/g, '.*') + "$", 71 | "g", 72 | ).test(s); 73 | } 74 | const _check_words = (s: string, words: string[]): boolean => { 75 | if (words.length === 0) return true; 76 | const mid_index = words.reduce( 77 | (a, v, i) => (v.length > words[a].length ? i : a), 78 | Math.floor(words.length / 2), 79 | ); 80 | const middle = words[mid_index]; 81 | const matched_array = Array.from( 82 | //@ts-ignore 83 | s.matchAll(new RegExp(`${middle.replaceAll("?", ".")}`, "g")), 84 | ); 85 | 86 | if (!matched_array.length) return false; 87 | matched_array.sort(() => Math.random() - 0.5); 88 | const first_half = words.slice(0, mid_index); 89 | const second_half = words.slice(mid_index + 1); 90 | return matched_array.some((matched) => { 91 | const length = matched[0].length; 92 | if ("number" !== typeof matched.index) return false; 93 | const left = s.slice(0, matched.index); 94 | const right = s.slice(matched.index + length); 95 | return _check_words(left, first_half) && _check_words(right, second_half); 96 | }); 97 | } 98 | 99 | export const queue = (arr: any) => { 100 | let sequence = Promise.resolve() 101 | arr.forEach((item: any) => { 102 | sequence = sequence.then(item) 103 | }) 104 | return sequence 105 | } -------------------------------------------------------------------------------- /src/bing_deployer.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import Hexo from './utils/hexo' 4 | import { projectPrefix, defaultTimeOut } from './utils/utils' 5 | 6 | export const deployer = async (args: Hexo) => { 7 | const { config, log } = args 8 | const { url_submission, url } = config 9 | const { urlArr, count: baseCount } = url_submission 10 | let { token, count } = url_submission.channels?.bing 11 | token = token || process.env.BING_TOKEN 12 | const defaultQuota = 500 13 | axios.defaults.timeout = defaultTimeOut 14 | const logPrefix = projectPrefix.concat('(\x1b[3mbing\x1b[23m) ') 15 | 16 | if (count === undefined) { 17 | log.info(logPrefix.concat("The number of submitted entries for Bing Search is not set. Continue to detect the remaining quota or the default value will be used for submission.")) 18 | } 19 | count = count || baseCount 20 | 21 | axios.defaults.withCredentials = true 22 | // auto set count 23 | try { 24 | let response = await axios.create().request({ 25 | url: "/webmaster/api.svc/json/GetUrlSubmissionQuota?siteUrl=" + url + "&apikey=" + token, 26 | baseURL: 'https://ssl.bing.com', 27 | method: 'GET', 28 | validateStatus: (status) => { return status === 200 } 29 | }) 30 | const respJson = response.data 31 | const { DailyQuota, MonthlyQuota } = respJson?.d 32 | let daliyQuota = ''.concat(`\x1b[3${DailyQuota >= urlArr.length ? '2' : '1'}m`, DailyQuota, '\x1b[39m') 33 | let monthlyQuota = ''.concat(`\x1b[3${MonthlyQuota >= urlArr.length ? '2' : '1'}m`, MonthlyQuota, '\x1b[39m') 34 | log.info(logPrefix.concat(`Get bing engine remain, [daliy: ${daliyQuota}, monthly: ${monthlyQuota}]`)) 35 | count = Math.min(count, baseCount, DailyQuota, urlArr.length, defaultQuota) 36 | } catch (error: any) { 37 | log.error(logPrefix.concat('Get remain for bing engine error: \x1b[31m', error, '\x1b[39m')) 38 | } 39 | 40 | if (count === 0) { 41 | log.warn(logPrefix.concat('The submission quota of this website has been exhausted, cancel submission.')) 42 | return 43 | } 44 | log.warn(logPrefix.concat('The number of entries submitted by Bing Search has been set to \x1b[1m', count, '\x1b[22m')) 45 | 46 | let urlArray = urlArr.slice(0, count) 47 | 48 | log.info(logPrefix.concat("Start submit urlList to bing engine...")) 49 | 50 | try { 51 | // Success 200 52 | /* 53 | * { 54 | "d": null 55 | } 56 | */ 57 | // Failed 400 58 | /* 59 | * { 60 | ErrorCode: 2, 61 | Message: 'ERROR!!! Quota remaining for today: 10, Submitted: 32' 62 | } 63 | */ 64 | let response = await axios.create().request({ 65 | url: "/webmaster/api.svc/json/SubmitUrlbatch?apikey=" + token, 66 | baseURL: 'https://ssl.bing.com', 67 | method: 'POST', 68 | headers: { 'Content-Type': 'application/json' }, 69 | data: { 70 | "siteUrl": url, 71 | "urlList": urlArray 72 | }, 73 | validateStatus: function (status) { 74 | return status <= 400 75 | } 76 | }) 77 | 78 | let message = '' 79 | const respJson = response.data 80 | if (response.status === 200) { 81 | message = message.concat('success.') 82 | } else { 83 | message = message.concat('failed: \x1b[31m', respJson?.Message, '\x1b[39m') 84 | } 85 | log.info(logPrefix.concat("Submit to bing engine ", message)) 86 | } catch (error: any) { 87 | log.error(logPrefix.concat('Submit to bing engine error: \x1b[31m', error.message, '\x1b[39m')) 88 | } 89 | } 90 | 91 | 92 | const formatError = (errorCode: number): string => { 93 | switch (errorCode) { 94 | case 12: 95 | return 'AlreadyExists' 96 | case 16: 97 | return 'Deprecated' 98 | case 1: 99 | return 'InternalError' 100 | case 3: 101 | return 'InvalidApiKey' 102 | case 8: 103 | return 'InvalidParameter' 104 | case 7: 105 | return 'InvalidUrl' 106 | case 0: 107 | return 'None' 108 | case 13: 109 | return 'NotAllowed' 110 | case 14: 111 | return 'NotAuthorized' 112 | case 11: 113 | return 'NotFound' 114 | case 5: 115 | return 'ThrottleHost' 116 | case 4: 117 | return 'ThrottleUser' 118 | case 9: 119 | return 'TooManySites' 120 | case 15: 121 | return 'UnexpectedState' 122 | case 6: 123 | return 'UserBlocked' 124 | case 10: 125 | return 'UserNotFound' 126 | default: 127 | case 2: 128 | return 'UnknownError' 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/google_deployer.ts: -------------------------------------------------------------------------------- 1 | import pathFn from 'path' 2 | import { JWT as googleOauth, Credentials as googleAuthCredentials } from 'google-auth-library' 3 | import Axios from 'axios' 4 | import { HttpsProxyAgent } from "https-proxy-agent" 5 | import * as fs from "hexo-fs" 6 | 7 | import { GoogleKeys } from './utils/interface' 8 | import Hexo from './utils/hexo' 9 | import { projectPrefix, defaultTimeOut } from './utils/utils' 10 | 11 | export const deployer = async (args: Hexo) => { 12 | const { config, log, base_dir } = args 13 | const { url_submission, url } = config 14 | const { urlArr, count: baseCount, sitemap, proxy } = url_submission 15 | let { key, count }: { key: string, count: number } = url_submission.channels?.google 16 | Axios.defaults.timeout = defaultTimeOut 17 | 18 | const logPrefix = projectPrefix.concat('(\x1b[3mgoogle\x1b[23m) ') 19 | 20 | if (count === undefined) { 21 | log.info(logPrefix.concat("The number of submitted entries for Google Search is not set, and the default value will be used for submission.")) 22 | } 23 | count = Math.min(count || baseCount, urlArr.length) 24 | log.warn(logPrefix.concat('The number of entries submitted by Google Search has been set to \x1b[1m', String(count), '\x1b[22m')) 25 | 26 | let axios = Axios.create() 27 | let parsedGoogleKey: GoogleKeys 28 | try { 29 | let keyPath = pathFn.join(base_dir, key) 30 | if (fs.existsSync(keyPath) && fs.statSync(keyPath).isFile()) { 31 | parsedGoogleKey = JSON.parse(fs.readFileSync(keyPath)) 32 | } else { 33 | parsedGoogleKey = JSON.parse(process.env.GOOGLE_KEY || '{}') 34 | } 35 | } catch (error: any) { 36 | log.error(logPrefix.concat('Google key file not exist, cancel submission. ')) 37 | log.error(logPrefix.concat('Error: \x1b[31m', error.message, '\x1b[39m')) 38 | return 39 | } 40 | 41 | if (proxy !== '') { 42 | let httpsAgent = new HttpsProxyAgent(proxy) 43 | axios = Axios.create({ 44 | proxy: false, 45 | httpsAgent 46 | }) 47 | process.env.HTTPS_PROXY = proxy 48 | process.env.HTTP_PROXY = proxy 49 | } 50 | 51 | log.info(logPrefix.concat("Start submit urlList to google engine...")) 52 | 53 | const boundary = '===============' + randomRangeNumber(1000000000, 9999999999) + '==' 54 | let data = '' 55 | urlArr.slice(0, count).forEach((line: string) => { 56 | let body = JSON.stringify({ 57 | url: line, 58 | type: 'URL_UPDATED', 59 | }) 60 | data += '\r\n' + 61 | '--' + boundary + '\n' + 62 | 'Content-Type: application/http \n' + 63 | 'Content-Transfer-Encoding: binary \n' + 64 | '\r\n' + 65 | 'POST /v3/urlNotifications:publish \n' + 66 | 'Content-Type: application/json \n' + 67 | 'accept: application/json \n' + 68 | 'Content-Length: ' + body.length + '\n' + 69 | '\r\n' + 70 | body 71 | }) 72 | 73 | let tokens: googleAuthCredentials | undefined = {} 74 | 75 | try { 76 | // Part.1 Indexing API 77 | const jwtClient = new googleOauth( 78 | parsedGoogleKey.client_email, 79 | undefined, 80 | parsedGoogleKey.private_key, 81 | ["https://www.googleapis.com/auth/indexing"], 82 | undefined 83 | ) 84 | tokens = await authorize(jwtClient) 85 | } catch (error: any) { 86 | log.error(logPrefix.concat('Submit to google engine authorize error: \x1b[31m', error.message, '\x1b[39m')) 87 | return 88 | } 89 | 90 | try { 91 | let options = { 92 | url: 'https://indexing.googleapis.com/batch', 93 | method: "POST", 94 | headers: { 95 | 'Content-Type': 'multipart/mixed; boundary="' + boundary + '"', 96 | 'Authorization': 'bearer ' + tokens?.access_token 97 | }, 98 | data: data, 99 | validateStatus: (status: number) => { 100 | return status <= 400 101 | } 102 | } 103 | let response = await axios.request(options) 104 | let message = '' 105 | if (response.status === 200) { 106 | message = message.concat('success') 107 | } else { 108 | message = message.concat('failed: [\x1b[31m', response.data.error.message, '\x1b[39m]') 109 | } 110 | log.info(logPrefix.concat("Submit to google engine ", message)) 111 | } catch (error: any) { 112 | log.error(logPrefix.concat('Submit to google engine error: \x1b[31m', error, '\x1b[39m')) 113 | } 114 | } 115 | 116 | const randomRangeNumber = (minNumber: number, maxNumber: number) => { 117 | let range = maxNumber - minNumber 118 | let random = Math.random() 119 | return minNumber + Math.round(random * range) 120 | } 121 | 122 | const authorize = (jwtClient: googleOauth) => { 123 | return new Promise((resolve, reject) => { 124 | jwtClient.authorize((err: Error | null, tokens?: googleAuthCredentials) => { 125 | if (err !== null) return reject(err) 126 | resolve(tokens) 127 | }) 128 | }) 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hexo-url-submission 2 | 3 | `Welcome to make valuable comments and Star` 4 | 5 | [![GitHub stars](https://img.shields.io/github/stars/abnerwei/hexo-url-submission.svg?style=social)](https://github.com/abnerwei/hexo-url-submission/stargazers) [![GitHub forks](https://img.shields.io/github/forks/abnerwei/hexo-url-submission.svg?style=social)](https://github.com/abnerwei/hexo-url-submission/network/members) 6 | 7 | 8 | [![NPM version](https://badge.fury.io/js/hexo-url-submission.svg)](https://www.npmjs.com/package/hexo-url-submission) 9 | ![GitHub top language](https://img.shields.io/github/languages/top/abnerwei/hexo-url-submission.svg) 10 | ![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/abnerwei/hexo-url-submission.svg) 11 | ![GitHub repo size](https://img.shields.io/github/repo-size/abnerwei/hexo-url-submission.svg) 12 | ![GitHub](https://img.shields.io/github/license/abnerwei/hexo-url-submission.svg) 13 | ![platforms](https://img.shields.io/badge/platform-win32%20%7C%20win64%20%7C%20linux%20%7C%20osx-brightgreen.svg) 14 | [![GitHub issues](https://img.shields.io/github/issues/abnerwei/hexo-url-submission.svg)](https://github.com/abnerwei/hexo-url-submission/issues) 15 | [![GitHub closed issues](https://img.shields.io/github/issues-closed/abnerwei/hexo-url-submission.svg)](https://github.com/abnerwei/hexo-url-submission/issues?q=is%3Aissue+is%3Aclosed) 16 | ![Libraries.io dependency status for GitHub repo](https://img.shields.io/librariesio/github/abnerwei/hexo-url-submission.svg) 17 | ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/abnerwei/hexo-url-submission.svg) 18 | ![GitHub contributors](https://img.shields.io/github/contributors/abnerwei/hexo-url-submission.svg) 19 | 20 | [![NPM](https://nodei.co/npm/hexo-url-submission.png)](https://nodei.co/npm/hexo-url-submission/) 21 | 22 | ## Roadmap 23 | [Roadmap, plan, milestone](https://github.com/abnerwei/hexo-url-submission/projects/1) 24 | 25 | ## Donate 26 | 27 | Hexo-URL-Submssion is used to submit site URLs to major search engines, including Google, Bing, ShenMa and Baidu, to improve the speed and quality of sites included in search engines. 28 | 29 | These three major search engines have occupied 98% of the global search engine market share (except Yandex Ru). Later, I will support api submission for more search engines. 30 | 31 | As of August 2021, Google: 92.05%, Bing: 2.45%, Yahoo!: 1.5%, Baidu: 1.39%, Yandex: 1.21%, DuckDuckGo: 0.63%. 32 | 33 | In China, [Baidu](https://baidu.com), [360](https://so.com), [Shenma](https://m.sm.cn/) (only app), [Toutiao](https://www.toutiao.com/), [Sogou](https://www.sogou.com/) and other search engines occupy a dominant position 34 | 35 | 36 | ## Version record 37 | - v1.0.0 feat: Support Baidu, Google, Bing url batch submission 38 | - v1.0.1 fix(bing_deployer): local variables overwrite global variables and cause data errors 39 | - v1.0.2 feat(shenma): Support ShenMa Search Engine 40 | - v1.0.3 improve(dep): improve package dep 41 | - v1.0.4 improve(dep): remove deprecated dep 42 | - v1.0.5 optimize(deployer): optimized deployer 43 | - v1.0.6 optimize(dep): replace `request(deprecated)` 44 | - v1.0.7 fix: Nodejs16+ DEP API & hexo 6.1 bug 45 | - v1.0.9 optimize(google/bing): optimized google deployer 400, fixed bing deployer quote 46 | - ⭐️v2.0.0 refactor: Added support for `ignore` parameters and wildcards, Channel parameters under independent control[#7](https://github.com/abnerwei/hexo-url-submission/issues/7) 47 | - v2.0.1 fix: use hexo-fs to read google key file instead of require [#18](https://github.com/abnerwei/hexo-url-submission/pull/18) 48 | 49 | ## Quick start 50 | 51 | ### 1. Install 52 | ``` 53 | npm install --save hexo-url-submission 54 | ``` 55 | 56 | or 57 | 58 | ``` 59 | yarn add hexo-url-submission 60 | ``` 61 | 62 | ### 2. Edit hexo _config.yml 63 | #### (1) hexo-url-submission 64 | 65 | > You can use environment variables in your local or CI/CD tools to safely store tokens 66 | 67 | ```yaml 68 | url_submission: 69 | enable: true 70 | type: 'latest' # latest or all( latest: modified pages; all: posts & pages) 71 | channels: # included channels are `baidu`, `google`, `bing`, `shenma` 72 | baidu: 73 | token: "" # Baidu Private Token 74 | count: 10 # Optional 75 | bing: 76 | token: "" # Bing Access Token 77 | count: 10 # Optional 78 | google: 79 | key: "google.json" # Google key path (e.g. `google_key.json` or `data/google_key.json`) 80 | count: 10 # Optional 81 | shenma: 82 | count: 10 # Optional 83 | user: "" # Username used when registering 84 | token: "" # ShenMa Private Key 85 | prefix: ['/post', '/wiki'] # URL prefix 86 | ignore: ["/post/a*", "/post/a?c"] # URL addresses that do not need to be submitted (wildcards are supported) 87 | count: 10 # Submit limit 88 | urls_path: 'submit_url.txt' # URL list file path 89 | sitemap: '' # Sitemap path(e.g. the url is like this https://abnerwei.com/baidusitemap.xml, you can fill in `baidusitemap.xml`) 90 | ``` 91 | 92 | #### (2) deploy 93 | ```yaml 94 | deploy: 95 | - type: url_submission 96 | ``` 97 | 98 | ### 3. good job 99 | Run: 100 | ```shell 101 | hexo clean && hexo g && hexo d 102 | ``` 103 | enjoy it! 104 | 105 | success response: 106 | ```shell 107 | INFO Deploying: url_submission 108 | WARN url_submission: (baidu) The number of submitted entries for Baidu Search is not set, and the default value will be used for submission. 109 | WARN url_submission: (baidu) The number of entries submitted by Baidu Search has been set to 37 110 | INFO url_submission: (baidu) Start submit urlList to baidu engine... 111 | INFO url_submission: (baidu) Submit to baidu engine success: [success: 37, remain: 2963] 112 | WARN url_submission: (bing) The number of submitted entries for Bing Search is not set. Continue to detect the remaining quota or the default value will be used for submission. 113 | WARN url_submission: (bing) The number of entries submitted by Bing Search has been set to 37 114 | INFO url_submission: (bing) Get bing engine remain, [daliy: 100, monthly: 2800] 115 | INFO url_submission: (bing) Start submit urlList to bing engine... 116 | INFO url_submission: (bing) Submit to bing engine success. 117 | WARN url_submission: (google) The number of submitted entries for Google Search is not set, and the default value will be used for submission. 118 | WARN url_submission: (google) The number of entries submitted by Google Search has been set to 37 119 | INFO url_submission: (google) Start submit urlList to google engine... 120 | INFO url_submission: (google) Submit to google engine success 121 | WARN url_submission: (shenma) The number of submitted entries for ShenMa Search is not set, and the default value will be used for submission. 122 | WARN url_submission: (shenma) The number of entries submitted by ShenMa Search has been set to 37 123 | INFO url_submission: (shenma) Start submit urlList to shenma engine... 124 | INFO url_submission: (shenma) Submit to shenma engine failed: [token illegal] 125 | INFO Deploy done: url_submission 126 | ``` 127 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^18.11.10": 6 | version "18.19.71" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.71.tgz#96d4f0a0be735ead6c8998c62a4b2c0012a5d09a" 8 | integrity sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw== 9 | dependencies: 10 | undici-types "~5.26.4" 11 | 12 | agent-base@^7.1.2: 13 | version "7.1.3" 14 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" 15 | integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== 16 | 17 | asynckit@^0.4.0: 18 | version "0.4.0" 19 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 20 | integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== 21 | 22 | axios@^1.2.0: 23 | version "1.7.9" 24 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a" 25 | integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== 26 | dependencies: 27 | follow-redirects "^1.15.6" 28 | form-data "^4.0.0" 29 | proxy-from-env "^1.1.0" 30 | 31 | base64-js@^1.3.0: 32 | version "1.5.1" 33 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 34 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 35 | 36 | bignumber.js@^9.0.0: 37 | version "9.1.2" 38 | resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" 39 | integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== 40 | 41 | bluebird@^3.7.2: 42 | version "3.7.2" 43 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" 44 | integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== 45 | 46 | buffer-equal-constant-time@1.0.1: 47 | version "1.0.1" 48 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" 49 | integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== 50 | 51 | camel-case@^4.1.2: 52 | version "4.1.2" 53 | resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" 54 | integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== 55 | dependencies: 56 | pascal-case "^3.1.2" 57 | tslib "^2.0.3" 58 | 59 | chokidar@^4.0.3: 60 | version "4.0.3" 61 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30" 62 | integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA== 63 | dependencies: 64 | readdirp "^4.0.1" 65 | 66 | combined-stream@^1.0.8: 67 | version "1.0.8" 68 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 69 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 70 | dependencies: 71 | delayed-stream "~1.0.0" 72 | 73 | cross-spawn@^7.0.3: 74 | version "7.0.6" 75 | resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" 76 | integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== 77 | dependencies: 78 | path-key "^3.1.0" 79 | shebang-command "^2.0.0" 80 | which "^2.0.1" 81 | 82 | debug@4: 83 | version "4.4.0" 84 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" 85 | integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== 86 | dependencies: 87 | ms "^2.1.3" 88 | 89 | deepmerge@^4.2.2: 90 | version "4.3.1" 91 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" 92 | integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== 93 | 94 | delayed-stream@~1.0.0: 95 | version "1.0.0" 96 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 97 | integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== 98 | 99 | dom-serializer@^2.0.0: 100 | version "2.0.0" 101 | resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" 102 | integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== 103 | dependencies: 104 | domelementtype "^2.3.0" 105 | domhandler "^5.0.2" 106 | entities "^4.2.0" 107 | 108 | domelementtype@^2.3.0: 109 | version "2.3.0" 110 | resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" 111 | integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== 112 | 113 | domhandler@^5.0.2, domhandler@^5.0.3: 114 | version "5.0.3" 115 | resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" 116 | integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== 117 | dependencies: 118 | domelementtype "^2.3.0" 119 | 120 | domutils@^3.1.0: 121 | version "3.2.2" 122 | resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" 123 | integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== 124 | dependencies: 125 | dom-serializer "^2.0.0" 126 | domelementtype "^2.3.0" 127 | domhandler "^5.0.3" 128 | 129 | ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: 130 | version "1.0.11" 131 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" 132 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== 133 | dependencies: 134 | safe-buffer "^5.0.1" 135 | 136 | entities@^4.2.0, entities@^4.5.0: 137 | version "4.5.0" 138 | resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" 139 | integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== 140 | 141 | extend@^3.0.2: 142 | version "3.0.2" 143 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 144 | integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== 145 | 146 | follow-redirects@^1.15.6: 147 | version "1.15.9" 148 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" 149 | integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== 150 | 151 | form-data@^4.0.0: 152 | version "4.0.1" 153 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.1.tgz#ba1076daaaa5bfd7e99c1a6cb02aa0a5cff90d48" 154 | integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== 155 | dependencies: 156 | asynckit "^0.4.0" 157 | combined-stream "^1.0.8" 158 | mime-types "^2.1.12" 159 | 160 | gaxios@^6.0.0, gaxios@^6.1.1: 161 | version "6.7.1" 162 | resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.1.tgz#ebd9f7093ede3ba502685e73390248bb5b7f71fb" 163 | integrity sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ== 164 | dependencies: 165 | extend "^3.0.2" 166 | https-proxy-agent "^7.0.1" 167 | is-stream "^2.0.0" 168 | node-fetch "^2.6.9" 169 | uuid "^9.0.1" 170 | 171 | gcp-metadata@^6.1.0: 172 | version "6.1.0" 173 | resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-6.1.0.tgz#9b0dd2b2445258e7597f2024332d20611cbd6b8c" 174 | integrity sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg== 175 | dependencies: 176 | gaxios "^6.0.0" 177 | json-bigint "^1.0.0" 178 | 179 | google-auth-library@^9.0.0: 180 | version "9.15.0" 181 | resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-9.15.0.tgz#1b009c08557929c881d72f953f17e839e91b009b" 182 | integrity sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ== 183 | dependencies: 184 | base64-js "^1.3.0" 185 | ecdsa-sig-formatter "^1.0.11" 186 | gaxios "^6.1.1" 187 | gcp-metadata "^6.1.0" 188 | gtoken "^7.0.0" 189 | jws "^4.0.0" 190 | 191 | graceful-fs@^4.2.10: 192 | version "4.2.11" 193 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" 194 | integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== 195 | 196 | gtoken@^7.0.0: 197 | version "7.1.0" 198 | resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-7.1.0.tgz#d61b4ebd10132222817f7222b1e6064bd463fc26" 199 | integrity sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw== 200 | dependencies: 201 | gaxios "^6.0.0" 202 | jws "^4.0.0" 203 | 204 | hexo-fs@^5.0.0: 205 | version "5.0.0" 206 | resolved "https://registry.yarnpkg.com/hexo-fs/-/hexo-fs-5.0.0.tgz#c6999cc823dfa3b9dd1d6254b5d777f8d24e765d" 207 | integrity sha512-oBkg1QgXyb4JxipaH+yCV/7E7XOUASTK6x1pocWvLw6U2mB6VfLENgYg61ft9Qn6JCiKSzs7FU4vyTUy6gIPvQ== 208 | dependencies: 209 | bluebird "^3.7.2" 210 | chokidar "^4.0.3" 211 | graceful-fs "^4.2.10" 212 | hexo-util "^3.3.0" 213 | 214 | hexo-util@^3.3.0: 215 | version "3.3.0" 216 | resolved "https://registry.yarnpkg.com/hexo-util/-/hexo-util-3.3.0.tgz#448927fb22e167f2159306666cc2c7e82c777513" 217 | integrity sha512-YvGngXijE2muEh5L/VI4Fmjqb+/yAkmY+VuyhWVoRwQu1X7bmWodsfYRXX7CUYhi5LqsvH8FAe/yBW1+f6ZX4Q== 218 | dependencies: 219 | camel-case "^4.1.2" 220 | cross-spawn "^7.0.3" 221 | deepmerge "^4.2.2" 222 | highlight.js "^11.6.0" 223 | htmlparser2 "^9.0.0" 224 | prismjs "^1.29.0" 225 | strip-indent "^3.0.0" 226 | 227 | highlight.js@^11.6.0: 228 | version "11.11.1" 229 | resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585" 230 | integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== 231 | 232 | htmlparser2@^9.0.0: 233 | version "9.1.0" 234 | resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23" 235 | integrity sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ== 236 | dependencies: 237 | domelementtype "^2.3.0" 238 | domhandler "^5.0.3" 239 | domutils "^3.1.0" 240 | entities "^4.5.0" 241 | 242 | https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1: 243 | version "7.0.6" 244 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" 245 | integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== 246 | dependencies: 247 | agent-base "^7.1.2" 248 | debug "4" 249 | 250 | is-stream@^2.0.0: 251 | version "2.0.1" 252 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" 253 | integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== 254 | 255 | isexe@^2.0.0: 256 | version "2.0.0" 257 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 258 | integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== 259 | 260 | json-bigint@^1.0.0: 261 | version "1.0.0" 262 | resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" 263 | integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== 264 | dependencies: 265 | bignumber.js "^9.0.0" 266 | 267 | jwa@^2.0.0: 268 | version "2.0.0" 269 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" 270 | integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== 271 | dependencies: 272 | buffer-equal-constant-time "1.0.1" 273 | ecdsa-sig-formatter "1.0.11" 274 | safe-buffer "^5.0.1" 275 | 276 | jws@^4.0.0: 277 | version "4.0.0" 278 | resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" 279 | integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== 280 | dependencies: 281 | jwa "^2.0.0" 282 | safe-buffer "^5.0.1" 283 | 284 | lower-case@^2.0.2: 285 | version "2.0.2" 286 | resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" 287 | integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== 288 | dependencies: 289 | tslib "^2.0.3" 290 | 291 | mime-db@1.52.0: 292 | version "1.52.0" 293 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 294 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 295 | 296 | mime-types@^2.1.12: 297 | version "2.1.35" 298 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 299 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 300 | dependencies: 301 | mime-db "1.52.0" 302 | 303 | min-indent@^1.0.0: 304 | version "1.0.1" 305 | resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" 306 | integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== 307 | 308 | ms@^2.1.3: 309 | version "2.1.3" 310 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 311 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 312 | 313 | no-case@^3.0.4: 314 | version "3.0.4" 315 | resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" 316 | integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== 317 | dependencies: 318 | lower-case "^2.0.2" 319 | tslib "^2.0.3" 320 | 321 | node-fetch@^2.6.9: 322 | version "2.7.0" 323 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" 324 | integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== 325 | dependencies: 326 | whatwg-url "^5.0.0" 327 | 328 | pascal-case@^3.1.2: 329 | version "3.1.2" 330 | resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" 331 | integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== 332 | dependencies: 333 | no-case "^3.0.4" 334 | tslib "^2.0.3" 335 | 336 | path-key@^3.1.0: 337 | version "3.1.1" 338 | resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" 339 | integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== 340 | 341 | prismjs@^1.29.0: 342 | version "1.29.0" 343 | resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.29.0.tgz#f113555a8fa9b57c35e637bba27509dcf802dd12" 344 | integrity sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q== 345 | 346 | proxy-from-env@^1.1.0: 347 | version "1.1.0" 348 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 349 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 350 | 351 | readdirp@^4.0.1: 352 | version "4.1.1" 353 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.1.tgz#bd115327129672dc47f87408f05df9bd9ca3ef55" 354 | integrity sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw== 355 | 356 | safe-buffer@^5.0.1: 357 | version "5.2.1" 358 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 359 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 360 | 361 | shebang-command@^2.0.0: 362 | version "2.0.0" 363 | resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" 364 | integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== 365 | dependencies: 366 | shebang-regex "^3.0.0" 367 | 368 | shebang-regex@^3.0.0: 369 | version "3.0.0" 370 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" 371 | integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== 372 | 373 | strip-indent@^3.0.0: 374 | version "3.0.0" 375 | resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" 376 | integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== 377 | dependencies: 378 | min-indent "^1.0.0" 379 | 380 | tr46@~0.0.3: 381 | version "0.0.3" 382 | resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" 383 | integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== 384 | 385 | tslib@^2.0.3: 386 | version "2.8.1" 387 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" 388 | integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== 389 | 390 | undici-types@~5.26.4: 391 | version "5.26.5" 392 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" 393 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== 394 | 395 | uuid@^9.0.1: 396 | version "9.0.1" 397 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" 398 | integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== 399 | 400 | webidl-conversions@^3.0.0: 401 | version "3.0.1" 402 | resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" 403 | integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== 404 | 405 | whatwg-url@^5.0.0: 406 | version "5.0.0" 407 | resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" 408 | integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== 409 | dependencies: 410 | tr46 "~0.0.3" 411 | webidl-conversions "^3.0.0" 412 | 413 | which@^2.0.1: 414 | version "2.0.2" 415 | resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" 416 | integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== 417 | dependencies: 418 | isexe "^2.0.0" 419 | -------------------------------------------------------------------------------- /src/utils/hexo.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for hexo 3.8 2 | // Project: https://hexo.io/ 3 | // Origin Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 4 | // Optimized By Abnerwei 5 | // TypeScript Version: 3.0 6 | 7 | import { EventEmitter } from 'events'; 8 | import moment = require('moment'); 9 | import { ParsedArgs } from 'minimist'; 10 | import Logger = require('bunyan'); 11 | import underscore = require('underscore'); 12 | import connect = require('connect'); 13 | import Stream = require('stream'); 14 | import util = require('hexo-util'); 15 | import fs = require('fs'); 16 | import Bluebird = require('bluebird'); 17 | 18 | declare global { 19 | const hexo: Hexo; 20 | } 21 | 22 | interface HexoConfig { 23 | [key: string]: any; 24 | /** 25 | * The title of your website 26 | */ 27 | readonly title: string; 28 | 29 | /** 30 | * The subtitle of your website 31 | */ 32 | readonly subtitle: string; 33 | 34 | /** 35 | * The description of your website 36 | */ 37 | readonly description: string; 38 | 39 | /* 40 | * Your name 41 | */ 42 | readonly author: string; 43 | 44 | /** 45 | * The language of your website. Use a 2-lettter ISO-639-1 code. Default is en. 46 | */ 47 | readonly language: string; 48 | 49 | /** 50 | * The timezone of your website. Hexo uses the setting on your computer by default. 51 | * You can find the list of available timezones [here]{@link https://en.wikipedia.org/wiki/List_of_tz_database_time_zones} . 52 | * Some examples are `America/New_York` , `Japan` , and `UTC` . 53 | */ 54 | readonly timezone: string; 55 | 56 | /* 57 | * The URL of your website 58 | */ 59 | readonly url: string; 60 | 61 | /** 62 | * The root directory of your website 63 | */ 64 | readonly root: string; 65 | 66 | /** 67 | * The permalink format of articles 68 | */ 69 | readonly permalink: string; 70 | 71 | /** 72 | * Default values of each segment in permalink 73 | */ 74 | readonly permalink_defaults: string | null; 75 | 76 | /** 77 | * Source folder. Where your content is stored 78 | */ 79 | readonly source_dir: string; 80 | 81 | /** 82 | * Public folder. Where the static site will be generated 83 | */ 84 | readonly public_dir: string; 85 | 86 | /** 87 | * Tag directory 88 | */ 89 | readonly tag_dir: string; 90 | 91 | /** 92 | * Archive directory 93 | */ 94 | readonly archive_dir: string; 95 | 96 | /** 97 | * Category directory 98 | */ 99 | readonly category_dir: string; 100 | 101 | /** 102 | * Include code directory (subdirectory of source_dir) 103 | */ 104 | readonly code_dir: string; 105 | 106 | /** 107 | * i18n directory 108 | */ 109 | readonly i18n_dir: string; 110 | 111 | /** 112 | * Paths that will be copied to public raw, without being rendered. You can use glob expressions for path matching. 113 | */ 114 | readonly skip_render: string | string[] | null; 115 | 116 | /** 117 | * The filename format for new posts 118 | */ 119 | readonly new_post_name: string; 120 | 121 | /** 122 | * Default layout 123 | */ 124 | readonly default_layout: string; 125 | 126 | /** 127 | * Transform titles into title case? 128 | */ 129 | readonly titlecase: boolean; 130 | 131 | /** 132 | * Open external links in a new tab? 133 | */ 134 | readonly external_link: boolean; 135 | 136 | /** 137 | * Transform filenames to 1 lower case; 2 upper case 138 | */ 139 | readonly filename_case: number; 140 | 141 | /** 142 | * Display drafts? 143 | */ 144 | readonly render_drafts: boolean; 145 | 146 | /** 147 | * Enable the Asset Folder? 148 | */ 149 | readonly post_asset_folder: boolean; 150 | 151 | /** 152 | * Make links relative to the root folder? 153 | */ 154 | readonly relative_link: boolean; 155 | 156 | /** 157 | * Display future posts? 158 | */ 159 | readonly future: boolean; 160 | 161 | /** 162 | * Code block settings 163 | */ 164 | readonly highlight: { 165 | readonly enable: boolean; 166 | readonly line_number: boolean; 167 | readonly auto_detect: boolean; 168 | readonly tab_replace: string | null; 169 | }; 170 | 171 | /** 172 | * Default category 173 | */ 174 | readonly default_category: string; 175 | 176 | /** 177 | * Category slugs 178 | */ 179 | readonly category_map: { [key: string]: string | number }; 180 | 181 | /** 182 | * Tag slugs 183 | */ 184 | readonly tag_map: { [key: string]: string | number }; 185 | 186 | /** 187 | * Date format 188 | * https://momentjs.com/ 189 | */ 190 | readonly date_format: string; 191 | 192 | /** 193 | * Time format 194 | * https://momentjs.com/ 195 | */ 196 | readonly time_format: string; 197 | 198 | /** 199 | * The amount of posts displayed on a single page. 0 disables pagination 200 | */ 201 | readonly per_page: number; 202 | 203 | /** 204 | * Pagination directory 205 | */ 206 | readonly pagination_dir: string; 207 | 208 | /** 209 | * Theme name. false disables theming 210 | */ 211 | readonly theme: string | false; 212 | 213 | /** 214 | * Theme configuration. Include any custom theme settings under this key to override theme defaults. 215 | */ 216 | readonly theme_config: { [key: string]: string | number }; 217 | 218 | /** 219 | * Deployment settings 220 | */ 221 | readonly deploy: Hexo.extend.Deployer.Config | Hexo.extend.Deployer.Config | null; 222 | 223 | /** 224 | * Hexo by default ignores hidden files and folders, but setting this field will make Hexo process them 225 | */ 226 | readonly include?: string[] | undefined; 227 | 228 | /** 229 | * Hexo process will ignore files list under this field 230 | */ 231 | readonly exclude?: string[] | undefined; 232 | readonly ignore: string[]; 233 | } 234 | 235 | interface Model { 236 | /** 237 | * Warehouse method 238 | * https://hexojs.github.io/warehouse/ 239 | */ 240 | toArray(): T[]; 241 | /** 242 | * Warehouse method 243 | * https://hexojs.github.io/warehouse/ 244 | */ 245 | count(): number; 246 | /** 247 | * Warehouse method 248 | * https://hexojs.github.io/warehouse/ 249 | */ 250 | forEach(fn: (v: T, i: number) => void): void; 251 | /** 252 | * Warehouse method 253 | * https://hexojs.github.io/warehouse/ 254 | */ 255 | filter(fn: (v: T, i: number) => boolean): Model; 256 | /** 257 | * Warehouse method 258 | * https://hexojs.github.io/warehouse/ 259 | */ 260 | map(fn: (v: T, i: number) => U): U[]; 261 | } 262 | 263 | declare class Hexo extends EventEmitter { 264 | /** 265 | * Create a Hexo instance. 266 | * @param base the root directory of the website, `base_dir` . 267 | * @param args an object containing the initialization options. 268 | */ 269 | constructor(base?: string, args?: Hexo.InstanceOptions); 270 | 271 | /** 272 | * Load configuration and plugins. 273 | */ 274 | init(): Promise; 275 | 276 | /** 277 | * Loading all files in the `source` folder as well as the theme data. 278 | */ 279 | load(fn?: (err: any, value: any) => void): Promise; 280 | 281 | /** 282 | * The same things `load` does, but will also start watching for file changes continuously. 283 | */ 284 | watch(fn?: (err: any, value: any) => void): Promise; 285 | 286 | unwatch(): void; 287 | 288 | /** 289 | * Any console command can be called explicitly using the call method on the Hexo instance. 290 | */ 291 | call(name: string, args?: any, fn?: (err: any, value: any) => void): Promise; 292 | call(name: string, fn?: (err: any, value: any) => void): Promise; 293 | 294 | /** 295 | * You should call the `exit` method upon successful or unsuccessful completion of a console command. 296 | * This allows Hexo to exit gracefully and finish up important things such as saving the database. 297 | */ 298 | exit(err?: any): Promise; 299 | 300 | /** 301 | * Site settings in `_config.yml` 302 | */ 303 | readonly config: HexoConfig; 304 | 305 | readonly theme: Hexo.Theme; 306 | readonly source: Hexo.Box; 307 | readonly post: Hexo.Post; 308 | readonly render: Hexo.Render; 309 | 310 | /** 311 | * Local variables are used for template rendering, which is the `site` variable in templates. 312 | * https://hexo.io/api/locals 313 | */ 314 | readonly locals: Hexo.Locals; 315 | 316 | readonly base_dir: string; 317 | 318 | /** 319 | * Public folder. Where the static site will be generated 320 | */ 321 | readonly public_dir: string; 322 | 323 | /** 324 | * Source folder. Where your content is stored 325 | */ 326 | readonly source_dir: string; 327 | 328 | readonly plugin_dir: string; 329 | readonly script_dir: string; 330 | readonly scaffold_dir: string; 331 | readonly theme_dir: string; 332 | readonly theme_script_dir: string; 333 | readonly config_path: string; 334 | readonly env: { 335 | readonly args: ParsedArgs; 336 | readonly debug: boolean; 337 | readonly safe: boolean; 338 | readonly silent: boolean; 339 | readonly env: string; 340 | readonly version: string; 341 | readonly init: boolean; 342 | }; 343 | 344 | /** 345 | * Logger object 346 | * https://www.npmjs.com/package/bunyan 347 | */ 348 | readonly log: Logger 349 | 350 | readonly extend: { 351 | /** 352 | * The console forms the bridge between Hexo and its users. It registers and describes the available console commands. 353 | */ 354 | readonly console: Hexo.extend.Console; 355 | /** 356 | * A deployer helps users quickly deploy their site to a remote server without complicated commands. 357 | */ 358 | readonly deployer: Hexo.extend.Deployer; 359 | /** 360 | * A filter is used to modify some specified data. Hexo passes data to filters in sequence and the filters then modify the data one after the other. 361 | */ 362 | readonly filter: Hexo.extend.Filter; 363 | /** 364 | * A generator builds routes based on processed files. 365 | */ 366 | readonly generator: Hexo.extend.Generator; 367 | /** 368 | * A helper makes it easy to quickly add snippets to your templates. We recommend using helpers instead of templates when you’re dealing with more complicated code. 369 | */ 370 | readonly helper: Hexo.extend.Helper; 371 | /** 372 | * A migrator helps users migrate from other systems to Hexo. 373 | */ 374 | readonly migrator: Hexo.extend.Migrator; 375 | /** 376 | * A processor is used to process source files in the source folder. 377 | */ 378 | readonly processor: Hexo.extend.Processor; 379 | /** 380 | * A renderer is used to render content. 381 | */ 382 | readonly renderer: Hexo.extend.Renderer; 383 | /** 384 | * A tag allows users to quickly and easily insert snippets into their posts. 385 | */ 386 | readonly tag: Hexo.extend.Tag; 387 | }; 388 | 389 | readonly route: Hexo.Router; 390 | readonly scaffold: Hexo.Scaffold; 391 | 392 | /** 393 | * Emitted before deployment begins. 394 | */ 395 | on(ev: 'deployBefore', fn: () => void): this; 396 | 397 | /** 398 | * Emitted after deployment finishes. 399 | */ 400 | on(ev: 'deployAfter', fn: () => void): this; 401 | 402 | /** 403 | * Emitted before Hexo exits. 404 | */ 405 | on(ev: 'exit', fn: (err: any) => void): this; 406 | 407 | /** 408 | * Emitted before generation begins. 409 | */ 410 | on(ev: 'generateBefore', fn: () => void): this; 411 | 412 | /** 413 | * Emitted after generation finishes. 414 | */ 415 | on(ev: 'generateAfter', fn: () => void): this; 416 | 417 | /** 418 | * Emitted after a new post has been created. This event returns the post data: 419 | */ 420 | on(ev: 'new', fn: (post: { path: string; content: string }) => void): this; 421 | 422 | /** 423 | * Emitted before processing begins. This event returns a path representing the root directory of the box. 424 | */ 425 | on(ev: 'processBefore', fn: (type: Hexo.Box.File['type'], path: string) => void): this; 426 | 427 | /** 428 | * Emitted after processing finishes. This event returns a path representing the root directory of the box. 429 | */ 430 | on(ev: 'processAfter', fn: (type: Hexo.Box.File['type'], path: string) => void): this; 431 | 432 | /** 433 | * Emitted after initialization finishes. 434 | */ 435 | on(ev: 'ready', fn: () => void): this; 436 | } 437 | 438 | declare namespace Hexo { 439 | interface InstanceOptions { 440 | debug?: boolean | undefined; 441 | safe?: boolean | undefined; 442 | silent?: boolean | undefined; 443 | config?: string | undefined; 444 | draft?: boolean | undefined; 445 | drafts?: boolean | undefined; 446 | } 447 | 448 | interface Locals { 449 | get(type: 'posts'): Model; 450 | get(type: 'pages'): Model; 451 | get(type: 'categories'): Model; 452 | get(type: 'tags'): Model; 453 | /** 454 | * Get a Variable 455 | */ 456 | get(type: string): any; 457 | 458 | /** 459 | * Set a Variable 460 | */ 461 | set(type: string, fn: () => any): this; 462 | 463 | /** 464 | * Remove a Variable 465 | */ 466 | remove(type: string): this; 467 | 468 | /** 469 | * Get All Variable 470 | */ 471 | toObject(): any; 472 | 473 | /** 474 | * Invalidate the cache 475 | */ 476 | invalidate(): this; 477 | } 478 | namespace Locals { 479 | interface Page { 480 | title: string; 481 | date: moment.Moment; 482 | updated?: moment.Moment | undefined; 483 | comments: boolean; 484 | layout: string; 485 | content: string; 486 | excerpt?: string | undefined; 487 | more?: string | undefined; 488 | source: string; 489 | full_source: string; 490 | path: string; 491 | permalink: string; 492 | prev?: null | Page | undefined; 493 | next?: null | Page | undefined; 494 | raw?: string | undefined; 495 | photos?: string[] | undefined; 496 | link?: string | undefined; 497 | [key: string]: any; 498 | } 499 | 500 | interface Post extends Page { 501 | published?: boolean | undefined; 502 | categories?: string[] | undefined; 503 | tags: string[]; 504 | } 505 | 506 | interface Tag { 507 | name: string; 508 | slug: string; 509 | path: string; 510 | permalink: string; 511 | posts: Model; 512 | length: number; 513 | } 514 | interface Category extends Tag { 515 | parent: string; 516 | } 517 | } 518 | 519 | namespace extend { 520 | interface Console { 521 | register(name: string, desc: string, options: Console.Options, fn: (args: ParsedArgs) => void): void; 522 | register(name: string, options: Console.Options, fn: (args: ParsedArgs) => void): void; 523 | register(name: string, desc: string, fn: (args: ParsedArgs) => void): void; 524 | register(name: string, fn: (args: ParsedArgs) => void): void; 525 | } 526 | namespace Console { 527 | interface Options { 528 | /** 529 | * The usage of a console command. 530 | */ 531 | usage?: string | undefined; 532 | 533 | /** 534 | * The description of each argument of a console command. 535 | */ 536 | arguments?: Array<{ name: string; desc: string }> | undefined; 537 | 538 | /** 539 | * The description of each option of a console command. 540 | */ 541 | options?: Array<{ name: string; desc: string }> | undefined; 542 | 543 | /** 544 | * More detailed information about a console command. 545 | */ 546 | desc?: string | undefined; 547 | } 548 | } 549 | 550 | interface Deployer { 551 | register(name: string, fn: (args: Deployer.Config) => void): void; 552 | } 553 | namespace Deployer { 554 | interface Config { 555 | readonly type: string | undefined; 556 | readonly [key: string]: any; 557 | } 558 | } 559 | 560 | interface Filter { 561 | register(type: string, fn: (data: any, ...args: any[]) => any, priority?: number): void; 562 | 563 | /** 564 | * Executed before a post is rendered. Refer to post rendering to learn the execution steps. 565 | */ 566 | register( 567 | type: 'before_post_render', 568 | fn: (data: { content: string; [key: string]: any }) => { content: string; [key: string]: any } | void, 569 | priority?: number, 570 | ): void; 571 | 572 | /** 573 | * Executed after a post is rendered. Refer to post rendering to learn the execution steps. 574 | */ 575 | register( 576 | type: 'after_post_render', 577 | fn: (data: { content: string; [key: string]: any }) => { content: string; [key: string]: any } | void, 578 | priority?: number, 579 | ): void; 580 | 581 | /** 582 | * Executed before Hexo is about to exit – this will run right after `hexo.exit` is called. 583 | */ 584 | register(type: 'before_exit', fn: () => void, priority?: number): void; 585 | 586 | /** 587 | * Executed before generation begins. 588 | */ 589 | register(type: 'before_generate', fn: (data: any) => any, priority?: number): void; 590 | 591 | /** 592 | * Executed after generation finishes. 593 | */ 594 | register(type: 'after_generate', fn: () => void, priority?: number): void; 595 | 596 | /** 597 | * Modify [local variables](https://hexo.io/docs/variables) in templates. 598 | */ 599 | register( 600 | type: 'template_locals', 601 | fn: (locals: TemplateLocals) => TemplateLocals | void, 602 | priority?: number, 603 | ): void; 604 | 605 | /** 606 | * Executed after Hexo is initialized – this will run right after `hexo.init` completes. 607 | */ 608 | register(type: 'after_init', fn: () => void, priority?: number): void; 609 | 610 | /** 611 | * Executed when creating a post to determine the path of new posts. 612 | */ 613 | register( 614 | type: 'new_post_path', 615 | fn: (data: Post.Data, replace: boolean | undefined) => void, 616 | priority?: number, 617 | ): void; 618 | 619 | /** 620 | * Used to determine the permalink of posts. 621 | */ 622 | register(type: 'post_permalink', fn: (permalink: string) => string, priority?: number): void; 623 | 624 | /** 625 | * Executed after rendering finishes. You can see rendering for more info. 626 | */ 627 | register( 628 | type: 'after_render:html', 629 | fn: (result: string, data: { path: string; text: string; [key: string]: any }) => string | void, 630 | priority?: number, 631 | ): void; 632 | 633 | /** 634 | * Executed after generated files and cache are removed with hexo clean command. 635 | */ 636 | register(type: 'after_clean', fn: () => void, priority?: number): void; 637 | 638 | /** 639 | * Add middleware to the server. app is a Connect instance. 640 | */ 641 | register( 642 | type: 'server_middleware', 643 | fn: (app: connect.Server) => connect.Server | void, 644 | priority?: number, 645 | ): void; 646 | 647 | unregister(type: string, fn: (...args: any[]) => any): void; 648 | exec(type: string, data?: any, options?: Filter.Options): any; 649 | execSync(type: string, data?: any, options?: Filter.Options): any; 650 | } 651 | namespace Filter { 652 | interface Options { 653 | /** 654 | * `hexo` object. 655 | */ 656 | context?: Hexo | undefined; 657 | /** 658 | * Arguments. This must be an array. 659 | */ 660 | args?: any[] | undefined; 661 | } 662 | } 663 | 664 | interface Generator { 665 | register( 666 | name: string, 667 | fn: ( 668 | locals: Site, 669 | ) => Generator.Return | Generator.Return[] | Bluebird | Bluebird, 670 | ): void; 671 | } 672 | namespace Generator { 673 | interface Return { 674 | /** 675 | * Path not including the prefixing `/` . 676 | */ 677 | path: string; 678 | 679 | /** 680 | * Layout. Specify the layouts for rendering. The value can be a string or an array. If it’s ignored then the route will return data directly. 681 | */ 682 | layout?: string | string[]; 683 | 684 | data: any; 685 | } 686 | } 687 | 688 | interface Helper { 689 | register(name: string, fn: (...args: any[]) => any): void; 690 | list(): { [name: string]: (...args: any[]) => any }; 691 | get(name: string): ((...args: any[]) => any) | undefined; 692 | } 693 | 694 | interface Migrator { 695 | register(name: string, fn: (args: ParsedArgs, fn: (err: any) => void) => void): void; 696 | } 697 | 698 | interface Processor { 699 | register(pattern: RegExp | string | ((str: string) => any), fn: (file: Box.File) => void): void; 700 | register(fn: (file: Box.File) => void): void; 701 | } 702 | 703 | interface Renderer { 704 | register( 705 | srcExt: string, 706 | outExt: string, 707 | fn: (this: Hexo, data: RendererData, options: any) => string, 708 | sync: true, 709 | ): void; 710 | register( 711 | srcExt: string, 712 | outExt: string, 713 | fn: (this: Hexo, data: RendererData, options: any) => Promise, 714 | sync?: false, 715 | ): void; 716 | register( 717 | srcExt: string, 718 | outExt: string, 719 | fn: (this: Hexo, data: RendererData, options: any) => Promise, 720 | ): void; 721 | } 722 | 723 | interface RendererData { 724 | /** 725 | * File content. 726 | */ 727 | readonly text: string; 728 | /** 729 | * File path. 730 | */ 731 | readonly path?: string | undefined; 732 | } 733 | 734 | interface Tag { 735 | register( 736 | name: string, 737 | fn: (args: string[], content: string | undefined) => string, 738 | options?: Tag.Options, 739 | ): void; 740 | } 741 | namespace Tag { 742 | interface Options { 743 | ends?: boolean | undefined; 744 | async?: boolean | undefined; 745 | } 746 | } 747 | } 748 | 749 | interface Router { 750 | /** 751 | * The `get` method returns a `Stream`. 752 | */ 753 | get(path: string): Router.RouteStream | undefined; 754 | 755 | /** 756 | * The `set` method takes a string, a `Buffer` or a function. 757 | */ 758 | set(path: string, data: string | Buffer | util.Pattern | Router.Data): this; 759 | 760 | /** 761 | * Remove a Path 762 | */ 763 | remove(path: string): this; 764 | 765 | /** 766 | * Get the List of Routes 767 | */ 768 | list(): string[]; 769 | 770 | /** 771 | * The `format` method transforms a string to a valid path. 772 | */ 773 | format(path: string): string; 774 | } 775 | namespace Router { 776 | interface Data { 777 | data: string | Buffer | Callback; 778 | modified: boolean; 779 | } 780 | 781 | interface RouteStream extends Stream.Readable { 782 | readonly modified: boolean; 783 | } 784 | 785 | type Callback = ((err: any, result: string) => void) | (() => Promise); 786 | } 787 | 788 | interface Scaffold { 789 | /** 790 | * Get a Scaffold 791 | */ 792 | get(name: string, fn?: (err: any, result: string) => void): Promise; 793 | /** 794 | * Set a Scaffold 795 | */ 796 | set(name: string, content: string, fn?: (err: any) => void): Promise; 797 | /** 798 | * Remove a Scaffold 799 | */ 800 | remove(name: string, fn?: (err: any) => void): Promise; 801 | } 802 | 803 | interface Box extends EventEmitter { 804 | /** 805 | * Loads all files in the folder. 806 | */ 807 | process(fn: (err: any) => void): Promise; 808 | /** 809 | * Loads all files in the folder and start watching for file changes. 810 | */ 811 | watch(fn?: (err: any) => void): Promise; 812 | /** 813 | * Stop watching. 814 | */ 815 | unwatch(): void; 816 | 817 | /** 818 | * A processor is an essential element of `Box` and is used to process files. 819 | * You can use path matching as described above to restrict what exactly the processor should process. 820 | * Register a new processor with the `addProcessor` method. 821 | */ 822 | addProcessor(pattern: string | RegExp | util.Pattern, fn: (file: Box.File) => void): void; 823 | } 824 | namespace Box { 825 | interface File { 826 | /** 827 | * Full path of the file 828 | */ 829 | readonly source: string; 830 | 831 | /** 832 | * Relative path to the box of the file 833 | */ 834 | readonly path: string; 835 | 836 | /** 837 | * File type. The value can be `create` , `update` , `skip`, `delete` . 838 | */ 839 | readonly type: 'create' | 'update' | 'skip' | 'delete'; 840 | 841 | /** 842 | * The information from path matching. 843 | */ 844 | readonly params: any; 845 | 846 | /** 847 | * Read a file 848 | */ 849 | read( 850 | option?: { encoding?: string | null | undefined; flag?: string | undefined }, 851 | fn?: (err: any, result: string | Buffer) => void, 852 | ): Promise; 853 | read(fn?: (err: any, result: string | Buffer) => void): Promise; 854 | 855 | /** 856 | * Read a file synchronously 857 | */ 858 | readSync(option?: { encoding?: string | null | undefined; flag?: string | undefined }): string | Buffer; 859 | 860 | /** 861 | * Read the status of a file 862 | */ 863 | stat(fn?: (err: any, result: fs.Stats) => void): Promise; 864 | 865 | /** 866 | * Read the status of a file synchronously 867 | */ 868 | statSync(): fs.Stats; 869 | 870 | /** 871 | * Render a file 872 | */ 873 | render(fn?: (err: any, result: string) => void): Promise; 874 | render(option?: any, fn?: (err: any, result: string) => void): Promise; 875 | 876 | /** 877 | * Render a file synchronously 878 | */ 879 | renderSync(option?: any): string; 880 | } 881 | } 882 | 883 | interface Render { 884 | render(data: Render.Data, option?: any, fn?: (err: any, result: string) => void): Promise; 885 | render(data: Render.Data, fn?: (err: any, result: string) => void): Promise; 886 | renderSync(data: Render.Data, option?: any): string; 887 | 888 | /** 889 | * Check whether a file is renderable synchronously. 890 | */ 891 | isRenderable(path: string): boolean; 892 | 893 | /** 894 | * Check whether a file is renderable. 895 | */ 896 | isRenderableSync(path: string): boolean; 897 | 898 | /** 899 | * Get the Output Extension 900 | */ 901 | getOutput(path: string): string; 902 | } 903 | namespace Render { 904 | interface Data { 905 | text?: string | undefined; 906 | engine?: string | undefined; 907 | path?: string | undefined; 908 | } 909 | } 910 | 911 | interface Post { 912 | /** 913 | * Create a Post 914 | */ 915 | create(data: Post.Data, replace?: boolean, fn?: (err: any) => void): Promise; 916 | create(data: Post.Data, fn?: (err: any) => void): Promise; 917 | 918 | /** 919 | * Publish a Draft 920 | */ 921 | publish(data: Post.Data, replace?: boolean, fn?: (err: any) => void): Promise; 922 | publish(data: Post.Data, fn?: (err: any) => void): Promise; 923 | 924 | render(source: string | null | undefined, data: Post.RenderData, fn: (err: any) => void): Promise; 925 | } 926 | namespace Post { 927 | interface Data { 928 | title?: string | undefined; 929 | slug?: string | undefined; 930 | layout?: string | undefined; 931 | path?: string | undefined; 932 | date?: moment.MomentInput | undefined; 933 | } 934 | interface RenderData { 935 | engine?: string | undefined; 936 | content?: string | undefined; 937 | } 938 | } 939 | 940 | interface Theme extends Box { 941 | config: HexoConfig; 942 | 943 | /** 944 | * Get a View 945 | */ 946 | getView(path: string): View | undefined; 947 | 948 | /** 949 | * Set a View 950 | */ 951 | setView(path: string, data: any): void; 952 | 953 | /** 954 | * Remove a View 955 | */ 956 | removeView(path: string): void; 957 | } 958 | 959 | interface View { 960 | readonly path: string; 961 | readonly source: string; 962 | 963 | /** 964 | * Remove a View 965 | */ 966 | render(options?: any, fn?: (err: any, result: string) => void): Promise; 967 | render(fn: (err: any, result: string) => void): Promise; 968 | 969 | /** 970 | * Remove a View synchronously. 971 | */ 972 | renderSync(options?: any): string; 973 | } 974 | 975 | interface Site { 976 | posts: Model; 977 | pages: Model; 978 | categories: Model; 979 | tags: Model; 980 | data: { [key: string]: any }; 981 | } 982 | } 983 | 984 | interface TemplateLocals { 985 | /** 986 | * Underscore object 987 | */ 988 | _: underscore.UnderscoreStatic; 989 | page: 990 | | Hexo.Locals.Post 991 | | Hexo.Locals.Page 992 | | Hexo.Locals.Category 993 | | Hexo.Locals.Tag 994 | | IndexPage 995 | | ArchivePage 996 | | CategoryPage 997 | | TagPage; 998 | path: string; 999 | url: string; 1000 | 1001 | /** 1002 | * Site settings in `_config.yml` 1003 | */ 1004 | config: HexoConfig; 1005 | theme: HexoConfig; 1006 | env: Hexo['env']; 1007 | layout: string; 1008 | view_dir: string; 1009 | site: any; 1010 | } 1011 | 1012 | interface IndexPage { 1013 | per_page?: number | undefined; 1014 | total?: number | undefined; 1015 | current?: number | undefined; 1016 | current_url?: string | undefined; 1017 | posts?: object | undefined; 1018 | prev?: number | undefined; 1019 | prev_link?: string | undefined; 1020 | next?: number | undefined; 1021 | next_link?: string | undefined; 1022 | path?: string | undefined; 1023 | } 1024 | 1025 | interface ArchivePage extends IndexPage { 1026 | archive?: boolean | undefined; 1027 | year?: number | undefined; 1028 | month?: number | undefined; 1029 | } 1030 | 1031 | interface CategoryPage extends IndexPage { 1032 | category: string; 1033 | } 1034 | 1035 | interface TagPage extends IndexPage { 1036 | tag: string; 1037 | } 1038 | 1039 | export = Hexo; 1040 | --------------------------------------------------------------------------------