├── index.js ├── .gitignore ├── package.json ├── test.js ├── src ├── request.js └── github.js ├── readme_zh.md └── readme.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./src/github'); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | node_modules 3 | package-lock.json 4 | _* -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-picbed", 3 | "version": "0.0.7", 4 | "description": "Upload file to github and Get Url", 5 | "keywords": [ 6 | "github", 7 | "picture", 8 | "upload", 9 | "pages" 10 | ], 11 | "main": "index.js", 12 | "scripts": { 13 | "test": "node test.js" 14 | }, 15 | "author": "Hancel.Lin", 16 | "license": "ISC", 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "tape": "~1.1.0" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/imlinhanchao/github-picbed.git" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/imlinhanchao/github-picbed/issues", 27 | "email": "imlinhanchao@gmail.com" 28 | }, 29 | "homepage": "https://github.com/imlinhanchao/github-picbed" 30 | } 31 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const path = require('path'); 3 | // config options first 4 | const options = { 5 | token: '', 6 | username: '', 7 | repository: '', 8 | httpProxy: '' 9 | }; 10 | 11 | if (options.token == '' || options.username == '' || options.repository == '') { 12 | throw new Error('Please Edit file to config options first.'); 13 | } 14 | 15 | function sleep (time) { 16 | return new Promise((resolve) => setTimeout(resolve, time)); 17 | } 18 | 19 | (async () => { 20 | 21 | const github = require('.')({ 22 | token: options.token, 23 | repository: `https://github.com/${options.username}/${options.repository}`, 24 | branch: options.branch, 25 | httpProxy: options.httpProxy 26 | }); 27 | 28 | console.log('wait for init.'); 29 | while(!github.isInitialized()) await sleep(100); 30 | 31 | test('Upload readme to repository', async function(assert) { 32 | assert.deepEqual(await github.upload({ 33 | data: path.resolve(__dirname, 'readme.md'), 34 | filename: 'readme.txt' 35 | }), {'filename': 'readme.txt', 'url':`https://cdn.jsdelivr.net/gh/${options.username}/${options.repository}@${options.branch}/readme.txt`}); 36 | assert.end() 37 | }) 38 | 39 | })() 40 | -------------------------------------------------------------------------------- /src/request.js: -------------------------------------------------------------------------------- 1 | const https = require('https'); 2 | 3 | module.exports = function ({ 4 | path, 5 | token, 6 | data = null, 7 | method, 8 | httpProxy = null 9 | }) { 10 | return new Promise((resolve, reject) => { 11 | 12 | var options = { 13 | hostname: 'api.github.com', 14 | path: path, 15 | method: method, 16 | headers: { 17 | 'Content-Type': 'application/json', 18 | 'Authorization': 'token ' + token, 19 | 'User-Agent': 'GitHub-PicBad-App' 20 | } 21 | }; 22 | 23 | if (httpProxy) { 24 | options.agent = new https.Agent({ proxy: httpProxy }); 25 | } 26 | 27 | let req = https.request(options, (res) => { 28 | let body = ''; 29 | 30 | res.setEncoding('utf8'); 31 | 32 | res.on('data', (data) => { 33 | body += data; 34 | }); 35 | 36 | res.on('end', () => { 37 | try { 38 | resolve(JSON.parse(body)) 39 | } catch (_) { 40 | resolve(body); 41 | } 42 | }); 43 | }); 44 | 45 | req.on('error', function (e) { 46 | reject(e); 47 | }); 48 | 49 | if (data) req.write(JSON.stringify(data)); 50 | 51 | req.end(); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /readme_zh.md: -------------------------------------------------------------------------------- 1 | # GitHub 图床 2 | 3 | 基于 GitHub 的图床。通过 Pages 提供 HTTP 文件服务。使用 GitHub API 上传图像。 4 | 5 | ## 安装 6 | 7 | ```bash 8 | npm install github-picbed 9 | ``` 10 | 11 | ## 用法 12 | 13 | ```javascript 14 | const github = require('github-picbed')({ 15 | token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 16 | repository: 'https://github.com/imlinhanchao/upload-file' 17 | }) 18 | const path = require('path'); 19 | 20 | router.post('/upload', async (req, res, next) => { 21 | let data = req.files[0].buffer; 22 | let extname = path.extname(req.files[0].originalname); 23 | let upload = await github.upload({ data, extname }) 24 | res.json(upload); 25 | }) 26 | ``` 27 | 28 | ## 准备 29 | 30 | 1. 您需要在 [GitHub](https://github.com/settings/tokens/new?scopes=repo&description=Picture%20Bed) 中创建访问令牌. 只需要开放 `repo` 权限即可。 31 | 2. 创建用于上传文件的存储库,并开通 Pages 服务。 32 | 33 | ## 函数 34 | 35 | ### 配置上传选项 36 | 37 | ```javascript 38 | async function config({ token, repository }); 39 | ``` 40 | 41 | #### 参数对象 42 | |键|描述| 43 | |--|--| 44 | |token|你创建的 GitHub 访问令牌。| 45 | |repository|你的用于上传文件存储库地址。| 46 | 47 | ### 检查初始化状态 48 | 49 | ```javascript 50 | async function isInitialized(); 51 | ``` 52 | 53 | #### 返回值 54 | **bool** - true 表示完成初始化。 55 | 56 | ### 上传文件 57 | 58 | ```javascript 59 | async function upload({ data, extname, filename }); 60 | ``` 61 | 62 | #### 参数对象 63 | |键|描述| 64 | |--|--| 65 | |data|您要上传的文件路径或文件内容 `Buffer` 对象。| 66 | |extname|文件的扩展名。如果 `data` 是 `Buffer` 对象,则必须设置这个值。| 67 | |filename|你要保存到仓库的文件名。(可选)| 68 | 69 | #### 返回值 70 | |键|描述| 71 | |--|--| 72 | |filename|最终上传的文件名。| 73 | |url|Web 访问地址。| 74 | 75 | ## 注意事项 76 | 77 | 配置 GitHub 存储库地址和访问令牌后,大约需要 1 秒钟来获取 GitHub Pages 的信息。因此,请不要在配置后立即上传。你可以使用 `isInitialized` 检查初始化是否已完成,或者使用`await` 等待配置完成。 78 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GitHub Picture Bed 2 | 3 | A picture bed based on GitHub. Provide an HTTP file service through Pages. Upload images using the GitHub API. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | npm install github-picbed 9 | ``` 10 | 11 | ## Usage 12 | 13 | ```javascript 14 | const github = require('github-picbed')({ 15 | token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 16 | repository: 'https://github.com/imlinhanchao/upload-file' 17 | }) 18 | const path = require('path'); 19 | 20 | router.post('/upload', async (req, res, next) => { 21 | let data = req.files[0].buffer; 22 | let extname = path.extname(req.files[0].originalname); 23 | let upload = await github.upload({ data, extname }) 24 | res.json(upload); 25 | }) 26 | ``` 27 | 28 | ## Preparation 29 | 30 | 1. You need to create an access token in [GitHub](https://github.com/settings/tokens). Select only `repo` for the `select scopes`. 31 | 2. Create a repository use to upload files. And then enable GitHub Pages. 32 | 33 | ## Functions 34 | 35 | ### Config Upload Options 36 | 37 | ```javascript 38 | async function config({ token, repository }); 39 | ``` 40 | 41 | #### Parameter Object 42 | |key|description| 43 | |--|--| 44 | |token|Your GitHub access token.| 45 | |repository|Your repository use to upload files.| 46 | 47 | ### Check Initialize State 48 | 49 | ```javascript 50 | async function isInitialized(); 51 | ``` 52 | 53 | #### Return Value 54 | **bool** - true means finish initialize. 55 | 56 | ### Upload file 57 | 58 | ```javascript 59 | async function upload({ data, extname, filename }); 60 | ``` 61 | 62 | #### Parameter Object 63 | |key|description| 64 | |--|--| 65 | |data|The file path that you want to upload or the buffer of file.| 66 | |extname|The extname of file. You must set this key if the data is buffer.| 67 | |filename|The filename that you want to upload.(Options)| 68 | 69 | #### Return Object 70 | |key|description| 71 | |--|--| 72 | |filename|The filename that was eventually uploaded.| 73 | |url|Access URL.| 74 | 75 | ## Notice 76 | 77 | After configuring the GitHub repository address and access token, it takes about 1 second to get the information of GitHub Pages. Therefore, please do not upload immediately after configuration. You can use `isInitialized` to check if initialization has been completed. Or use `await` to wait for the configuration to complete. 78 | -------------------------------------------------------------------------------- /src/github.js: -------------------------------------------------------------------------------- 1 | const request = require('./request'); 2 | const crypto = require('crypto'); 3 | const fs = require('fs'); 4 | 5 | let _options = {}; 6 | 7 | module.exports = function ({ 8 | token = null, 9 | repository = null, 10 | branch = null, 11 | httpProxy = null 12 | }) { 13 | let isPage = false; 14 | async function getOptions({ token, repository, branch, httpProxy }) { 15 | repository = repository.replace(/http(s|):\/\/github.com\//, ''); 16 | if (repository.split('/').length != 2) throw new Error('Not a invaild repository url.'); 17 | 18 | let username = repository.split('/')[0]; 19 | repository = repository.split('/')[1]; 20 | let options; 21 | if (!branch) { 22 | let pagesInfo = await getPages({ username, repository, token }); 23 | options = { 24 | domain: pagesInfo.domain, 25 | path: pagesInfo.path, 26 | branch: pagesInfo.branch 27 | } 28 | isPage = true; 29 | } 30 | else { 31 | options = { 32 | domain: `https://cdn.jsdelivr.net/gh/${username}/${repository}@${branch}/`, 33 | path: '/', 34 | branch 35 | } 36 | isPage = false; 37 | } 38 | 39 | return { 40 | token, 41 | username, 42 | repository, 43 | httpProxy, 44 | ...options 45 | }; 46 | } 47 | 48 | async function getPages({ username, repository, token }) { 49 | let rsp = await request({ 50 | path: `/repos/${username}/${repository}/pages`, 51 | token, 52 | method: 'GET', 53 | httpProxy 54 | }); 55 | 56 | if (!rsp.html_url) { 57 | return null; 58 | } 59 | 60 | return { 61 | domain: rsp.html_url, 62 | path: rsp.source.path, 63 | branch: rsp.source.branch 64 | } 65 | } 66 | 67 | (async () =>{ 68 | try { 69 | if (!token && !repository) return; 70 | _options = await getOptions({ token, repository, branch, httpProxy }); 71 | console.dir(_options); 72 | } catch (error) { 73 | console.error(error); 74 | } 75 | })(); 76 | 77 | return { 78 | async upload({ 79 | data, 80 | extname = null, 81 | filename = null 82 | }) { 83 | if (typeof data == 'string') { 84 | data = load(data); 85 | } 86 | 87 | if (!(data instanceof Buffer)) { 88 | throw new TypeError('The data must be String or Buffer.') 89 | } 90 | 91 | let fileHash = hash(data); 92 | filename = filename || fileHash + (extname || ''); 93 | let uploadname = filename.split('/').map(f => encodeURI(f)).join('/') 94 | 95 | let rsp = await request({ 96 | path: `/repos/${_options.username}/${_options.repository}/contents${_options.path}${uploadname}?ref=${_options.branch}`, 97 | token: _options.token, 98 | method: 'GET', 99 | httpProxy: _options.httpProxy 100 | }); 101 | 102 | if (!rsp.content) { 103 | let rsp = await request({ 104 | path: `/repos/${_options.username}/${_options.repository}/contents${_options.path}${uploadname}`, 105 | token: _options.token, 106 | method: 'PUT', 107 | httpProxy: _options.httpProxy, 108 | data: { 109 | message: `Upload file ${filename}`, 110 | content: data.toString("base64"), 111 | sha: fileHash, 112 | branch: _options.branch 113 | } 114 | }); 115 | if (!rsp.content) { 116 | throw new Error(`Upload file failed: ${rsp.message || 'Unknown Error'}.`); 117 | } 118 | } 119 | 120 | return { 121 | filename, 122 | url: `${_options.domain}${filename}` 123 | }; 124 | }, 125 | async config({ 126 | token, 127 | repository, 128 | branch, 129 | httpProxy 130 | }) { 131 | _options = await getOptions({ token, repository, branch, httpProxy }); 132 | }, 133 | get options() { 134 | return _options; 135 | }, 136 | isInitialized() { 137 | return !!_options.domain 138 | } 139 | } 140 | } 141 | 142 | 143 | function hash(buffer) { 144 | let sha256 = crypto.createHash('sha256'); 145 | let hash = sha256.update(buffer).digest('hex'); 146 | return hash; 147 | } 148 | 149 | function load(pathname) { 150 | return fs.readFileSync(pathname); 151 | } 152 | --------------------------------------------------------------------------------