├── run.sh ├── config.json ├── package.json ├── .gitignore ├── LICENSE ├── helper.js ├── README.md ├── index.js └── yarn.lock /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | nohup node /Users/gaopeng/PicBed4MWeb/index.js & -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "repo": "gaopeng-hz/images", 3 | "token": "87a488ba20e1cbaefd9eb0302ab791e82e9f1a17", 4 | "port": 8081, 5 | "url": "/upload" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GithubPicBed", 3 | "version": "1.0.0", 4 | "description": "github picture hosting api", 5 | "main": "index.js", 6 | "author": "gaopeng", 7 | "license": "MIT", 8 | "dependencies": { 9 | "axios": "^0.21.1", 10 | "formidable": "^1.1.1", 11 | "log4js": "^1.1.1", 12 | "moment": "^2.24.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 gaopeng 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 | -------------------------------------------------------------------------------- /helper.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const fs = require('fs') 3 | const config = require('./config.json') 4 | const {repo, token} = config 5 | const moment = require('moment'); 6 | moment.locale('zh-cn'); 7 | 8 | var log4js = require('log4js') 9 | log4js.configure({ 10 | appenders: [ 11 | { type: 'console' }, 12 | { type: 'file', filename: 'app.log', category: 'GithubPicBed' } 13 | ] 14 | }) 15 | var log = log4js.getLogger('GithubPicBed') 16 | 17 | /** 18 | * @param {[String]} file {picture path} 19 | * @return {[String]} {picture pid} 20 | */ 21 | async function getImgUrl(file){ 22 | if (repo.length == 0 || repo.length == 0) 23 | throw 'config error' 24 | try{ 25 | let bitmap = fs.readFileSync(file) 26 | let base64Img = Buffer.from(bitmap).toString('base64') 27 | let timestamp = moment().format('YYYYMMDDHHmmss')+'.jpg' 28 | let imageUrl = 'https://api.github.com/repos/'+repo+'/contents/'+timestamp 29 | let body = { 30 | 'branch': 'master', 31 | 'message': 'upload image', 32 | 'content': base64Img, 33 | 'path': timestamp, 34 | } 35 | let upImgResp = await axios.put(imageUrl, body, { 36 | headers: { 37 | 'Authorization':'token '+token, 38 | 'Content-Type': 'application/json; charset=utf-8', 39 | } 40 | }) 41 | imgUrl = upImgResp.data['content']['download_url'] 42 | if (imgUrl) { 43 | log.info('success upload a pic to: '+imgUrl) 44 | return imgUrl 45 | } else { 46 | throw 'no img url ' 47 | } 48 | } 49 | catch(e){ 50 | log.error('upload failed with error: '+e) 51 | throw 'no img url ' 52 | } 53 | } 54 | 55 | module.exports = getImgUrl -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MWeb自定义GitHub图床 2 | 3 | ## 设计初衷 && 用途: 4 | 5 | 在Mac上一直用MWeb记录一些东西,使用的是markdown格式,里面有一些图片,默认是在本地的media目录下,如果要发布,图片需要发布到公网上。 6 | MWeb支持的图床有`imgur`、`七牛云存储`、`又拍云`、`腾讯云`,后面三个需要购买服务和域名。之前写过[一片文章](https://www.jianshu.com/p/980fcf97ddea)可以使用Github当图床。 7 | 8 | Github需要用PUT方法提交文件,而MWeb只能使用POST方法,所以就有了这个项目,本地启动一个服务,接受请求,转换后转发给github。 9 | 10 | ## 用法 11 | 12 | 1. `git clone https://github.com/gaopeng-hz/PicBed4MWeb.git ` 克隆项目 13 | 2. `yarn(or npm install)` 安装依赖 14 | 3. 修改config.json 15 | 4. `node index.js` 运行server 16 | 5. 在MWeb中添加发布服务(or 其他用途) 17 | 18 | ## `config.json` 说明 19 | 20 | ```json 21 | { 22 | "repo": "gaopeng-hz/images", // 仓库名称 23 | "token": "xxxx", // token,不能公开,获取方式参考上面那篇文章 24 | "port": 8081, // node服务器监听端口,默认8080 25 | "url": "/upload" // 服务上传url,默认/upload 26 | } 27 | ``` 28 | 29 | ## 服务管理 30 | 31 | `node index.js`可以启动服务进行调试,关闭终端后服务就停了,我想要一种`关闭终端后服务依然可以运行的方式`,这就是`nohup`,使用`nohup node index.js &`这个命令就可以保证服务在后台运行。这种方式启动服务后是不可以通过`CTRL+C`的方式关闭服务的,需要通过`ps | grep index.js`来查找服务的pid,输入的内容第一列就是pid,使用`kill -9`命令关闭服务。 32 | 33 | ![](https://raw.githubusercontent.com/gaopeng-hz/images/master/20190725145219.jpg) 34 | 35 | ## MWeb中的配置 36 | 37 | 在MWeb的偏好设置中,选择发布服务页面,在下方的图片上传服务中选择自定义,新弹出的配置页面中,名称自己定,API地址根据`config.json`中的配置,前面加上本地地址,POST文件名和图片URL路径固定为`file`和`url`。 38 | 39 | ![](https://raw.githubusercontent.com/gaopeng-hz/images/master/20190725135134.jpg) 40 | 41 | ## 开机启动 42 | 43 | 所有的调试都完成了之后我希望把这个服务加入到开机启动,新增一个文件`run.sh`,内容如下 44 | 45 | ```bash 46 | #!/usr/bin/env bash 47 | 48 | # 修改成自己的目录 49 | nohup node /Users/gaopeng/PicBed4MWeb/index.js & 50 | ``` 51 | 52 | 为文件增加权限 53 | 54 | ```bash 55 | sudo chmod 777 run.sh 56 | ``` 57 | 58 | 修改文件打开方式为终端 59 | 60 | ![](https://raw.githubusercontent.com/gaopeng-hz/images/master/20190725144711.jpg) 61 | 62 | 打开Mac的系统偏好设置,进入用户与群组的登录项Tab,添加`run.sh` 63 | 64 | ![](https://raw.githubusercontent.com/gaopeng-hz/images/master/20190725144716.jpg) 65 | 66 | 重启后可以直接使用MWeb上传图片了。 67 | 68 | ## 参考 69 | 70 | [sinaPicHostingApi](https://github.com/J3n5en/sinaPicHostingApi) 71 | 72 | [利用 github 和 python3 以及 MWeb 打造自己的博文图床](https://blog.csdn.net/FungLeo/article/details/80706829) 73 | 74 | 75 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const formidable = require('formidable') 3 | const config = require('./config.json') 4 | const getImgUrl = require('./helper.js') 5 | var port = config.port || 8080 6 | const url = config.url || '/upload' 7 | var log4js = require('log4js') 8 | log4js.configure({ 9 | appenders: [ 10 | { type: 'console' }, 11 | { type: 'file', filename: 'app.log', category: 'GithubPicBed' } 12 | ] 13 | }) 14 | var log = log4js.getLogger('GithubPicBed') 15 | 16 | var tasks = [] 17 | function addTask(file, response) { 18 | tasks.push({ 19 | "file":file, 20 | "response":response 21 | }) 22 | 23 | function uploadFile() { 24 | if(tasks.length) { 25 | let file = tasks[0]["file"] 26 | let response = tasks[0]["response"] 27 | getImgUrl(file).then((result)=>{ 28 | // 处理结果 29 | if (result) { 30 | response.writeHead(200, {"Content-Type": "text/json"}) 31 | response.write(JSON.stringify({ 32 | status:'success', 33 | url:result 34 | })) 35 | } else { 36 | response.writeHead(500, {"Content-Type": "text/json"}) 37 | response.write(JSON.stringify({ 38 | status:'false' 39 | })) 40 | } 41 | response.end() 42 | 43 | // 第一个任务退出 44 | tasks.shift() 45 | // 自动执行队列中的任务 46 | uploadFile() 47 | }) 48 | } 49 | } 50 | 51 | // 第一个任务手动执行 52 | if(tasks.length==1) { 53 | uploadFile() 54 | } 55 | } 56 | http.createServer(function (req, res) { 57 | var content = "" 58 | if (req.url == url) { 59 | var form = new formidable.IncomingForm() 60 | form.parse(req, function (err, fields, files) { 61 | if(err) { 62 | res.send(err) 63 | return 64 | } 65 | if (!files.file) { 66 | res.writeHead(500, {"Content-Type": "text/json"}) 67 | log.error('no file found') 68 | res.write(JSON.stringify({ 69 | status: 'false', 70 | info: 'plz upload a pic' 71 | })) 72 | res.end() 73 | return 74 | } 75 | 76 | addTask(files.file.path, res) 77 | }) 78 | } else { 79 | res.writeHead(404, {"Content-Type": "text/plain"}) 80 | res.write('not fonud') 81 | res.end() 82 | } 83 | }).listen(port) 84 | log.info(`app run at ${port}`) -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | axios@^0.21.1: 6 | version "0.21.1" 7 | resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" 8 | dependencies: 9 | follow-redirects "^1.10.0" 10 | 11 | core-util-is@~1.0.0: 12 | version "1.0.2" 13 | resolved "https://registry.npm.taobao.org/core-util-is/download/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 14 | 15 | date-format@^0.0.0: 16 | version "0.0.0" 17 | resolved "https://registry.npm.taobao.org/date-format/download/date-format-0.0.0.tgz#09206863ab070eb459acea5542cbd856b11966b3" 18 | 19 | debug@^0.7.2: 20 | version "0.7.4" 21 | resolved "https://registry.npm.taobao.org/debug/download/debug-0.7.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" 22 | 23 | debug@^2.2.0: 24 | version "2.6.9" 25 | resolved "https://registry.npm.taobao.org/debug/download/debug-2.6.9.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdebug%2Fdownload%2Fdebug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 26 | dependencies: 27 | ms "2.0.0" 28 | 29 | follow-redirects@^1.10.0: 30 | version "1.13.1" 31 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7" 32 | 33 | formidable@^1.1.1: 34 | version "1.2.1" 35 | resolved "https://registry.npm.taobao.org/formidable/download/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659" 36 | 37 | inherits@~2.0.1: 38 | version "2.0.4" 39 | resolved "https://registry.npm.taobao.org/inherits/download/inherits-2.0.4.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Finherits%2Fdownload%2Finherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 40 | 41 | isarray@0.0.1: 42 | version "0.0.1" 43 | resolved "https://registry.npm.taobao.org/isarray/download/isarray-0.0.1.tgz?cache=0&sync_timestamp=1562592096220&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fisarray%2Fdownload%2Fisarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 44 | 45 | log4js@^1.1.1: 46 | version "1.1.1" 47 | resolved "https://registry.npm.taobao.org/log4js/download/log4js-1.1.1.tgz#c21d29c7604089e4f255833e7f94b3461de1ff43" 48 | dependencies: 49 | debug "^2.2.0" 50 | semver "^5.3.0" 51 | streamroller "^0.4.0" 52 | 53 | minimist@0.0.8: 54 | version "0.0.8" 55 | resolved "https://registry.npm.taobao.org/minimist/download/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 56 | 57 | mkdirp@^0.5.1: 58 | version "0.5.1" 59 | resolved "https://registry.npm.taobao.org/mkdirp/download/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 60 | dependencies: 61 | minimist "0.0.8" 62 | 63 | moment@^2.24.0: 64 | version "2.24.0" 65 | resolved "https://registry.npm.taobao.org/moment/download/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" 66 | 67 | ms@2.0.0: 68 | version "2.0.0" 69 | resolved "https://registry.npm.taobao.org/ms/download/ms-2.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fms%2Fdownload%2Fms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 70 | 71 | readable-stream@^1.1.7: 72 | version "1.1.14" 73 | resolved "https://registry.npm.taobao.org/readable-stream/download/readable-stream-1.1.14.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freadable-stream%2Fdownload%2Freadable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" 74 | dependencies: 75 | core-util-is "~1.0.0" 76 | inherits "~2.0.1" 77 | isarray "0.0.1" 78 | string_decoder "~0.10.x" 79 | 80 | semver@^5.3.0: 81 | version "5.7.0" 82 | resolved "https://registry.npm.taobao.org/semver/download/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" 83 | 84 | streamroller@^0.4.0: 85 | version "0.4.1" 86 | resolved "https://registry.npm.taobao.org/streamroller/download/streamroller-0.4.1.tgz#d435bd5974373abd9bd9068359513085106cc05f" 87 | dependencies: 88 | date-format "^0.0.0" 89 | debug "^0.7.2" 90 | mkdirp "^0.5.1" 91 | readable-stream "^1.1.7" 92 | 93 | string_decoder@~0.10.x: 94 | version "0.10.31" 95 | resolved "https://registry.npm.taobao.org/string_decoder/download/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 96 | --------------------------------------------------------------------------------