├── platforms ├── vercel │ └── sosf │ │ ├── .gitignore │ │ ├── package.json │ │ ├── vercel.json │ │ └── api │ │ └── index.ts ├── cloudbase │ └── sosf │ │ ├── package.json │ │ └── index.js └── template.js ├── renovate.json ├── .editorconfig ├── sor ├── graph │ ├── endpoint.d.ts │ └── endpoint.js ├── package.json ├── README.md ├── LICENSE ├── index.js └── index.d.ts ├── package.json ├── dprint.json ├── .github └── workflows │ └── sosf.yml ├── .gitignore ├── LICENSE ├── cloudbaserc.json ├── auth-cli └── auth.js └── README.md /platforms/vercel/sosf/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [!{node_modules}/**] 4 | end_of_line = lf 5 | charset = utf-8 6 | 7 | [{*.js,*.ts,*.html,*.gql,*.graqhql}] 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /sor/graph/endpoint.d.ts: -------------------------------------------------------------------------------- 1 | export function getItem(strs: string, ...parmas: string[]): string 2 | export function listChildren(strs: string, ...parmas: string[]): string 3 | export function listRoot(strs: string, ...parmas: string[]): string 4 | -------------------------------------------------------------------------------- /platforms/vercel/sosf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sosf-vercel", 3 | "version": "1.0.0", 4 | "description": "sosf entity", 5 | "main": "index.js", 6 | "scripts": {}, 7 | "author": "", 8 | "license": "ISC", 9 | "dependencies": { 10 | "@beetcb/sor": "^1.0.3" 11 | }, 12 | "devDependencies": {} 13 | } -------------------------------------------------------------------------------- /platforms/cloudbase/sosf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sosf-tcb", 3 | "version": "1.0.0", 4 | "description": "sosf entity", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@beetcb/sor": "^1.0.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /sor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@beetcb/sor", 3 | "version": "1.0.3", 4 | "description": "SOR makes it easy to access sharepoint & onedrive resource API", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": {}, 8 | "keywords": [], 9 | "author": "beetcb", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@beetcb/sstore": "^0.1.1", 13 | "dotenv": "^10.0.0", 14 | "node-fetch": "^2.6.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sor/README.md: -------------------------------------------------------------------------------- 1 | ### SOR makes it easy to access sharepoint & onedrive resource API 2 | 3 | Just get started directly with the beautiful DTS prompt 4 | 5 | ### How to authenticate ms graph? 6 | 7 | Plz make sure the following environment variables key is set: 8 | 9 | ```ts 10 | export interface GraphAuthEnv { 11 | [ 12 | (key in 'client_id') 13 | | 'client_secret' 14 | | 'refresh_token' 15 | | 'redirect_uri' 16 | | 'auth_endpoint' 17 | ]: string 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sosf", 3 | "version": "1.0.0", 4 | "description": "serverless onedrive & sharepoint function", 5 | "main": "index.js", 6 | "scripts": { 7 | "auth": "node auth-cli/auth.js", 8 | "fmt": "dprint fmt" 9 | }, 10 | "prettier": { 11 | "semi": false, 12 | "singleQuote": true, 13 | "printWidth": 80 14 | }, 15 | "author": "", 16 | "license": "ISC", 17 | "dependencies": { 18 | "inquirer": "^8.0.0", 19 | "node-fetch": "^2.6.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://dprint.dev/schemas/v0.json", 3 | "projectType": "openSource", 4 | "indentWidth": 2, 5 | "lineWidth": 80, 6 | "incremental": true, 7 | "typescript": { 8 | "semiColons": "asi", 9 | "quoteStyle": "preferSingle" 10 | }, 11 | "json": {}, 12 | "markdown": {}, 13 | "includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs,json,md}"], 14 | "excludes": ["**/node_modules", "**/*-lock.json", "**/docs"], 15 | "plugins": [ 16 | "https://plugins.dprint.dev/typescript-0.46.0.wasm", 17 | "https://plugins.dprint.dev/json-0.11.0.wasm", 18 | "https://plugins.dprint.dev/markdown-0.7.1.wasm" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/sosf.yml: -------------------------------------------------------------------------------- 1 | name: sosf 2 | 3 | on: [push] 4 | 5 | jobs: 6 | deploy: 7 | runs-on: ubuntu-latest 8 | name: sosf deploy 9 | env: 10 | dotenv: | 11 | ${{secrets.dotenv}} 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Create dotenv file 17 | run: echo -e "$dotenv" > platforms/cloudbase/sosf/.env 18 | 19 | - name: Deploy to Tencent CloudBase 20 | uses: TencentCloudBase/cloudbase-action@v2 21 | with: 22 | secretId: ${{secrets.secretId}} 23 | secretKey: ${{secrets.secretKey}} 24 | envId: ${{secrets.envId}} 25 | -------------------------------------------------------------------------------- /platforms/vercel/sosf/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "api/index.ts": { 4 | "memory": 128, 5 | "maxDuration": 10, 6 | "includeFiles": "/tmp/conf/*" 7 | } 8 | }, 9 | "cleanUrls": true, 10 | "trailingSlash": false, 11 | "headers": [ 12 | { 13 | "source": "/", 14 | "headers": [ 15 | { 16 | "key": "Cache-Control", 17 | "value": "s-maxage=3600" 18 | } 19 | ] 20 | } 21 | ], 22 | "rewrites": [ 23 | { 24 | "source": "/", 25 | "destination": "/api" 26 | } 27 | ], 28 | "build": { 29 | "env": { 30 | "NODEJS_AWS_HANDLER_NAME": "default" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sor/graph/endpoint.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | 3 | const parseStrs = (strs, parmas) => 4 | [ 5 | strs.indexOf('drive'), 6 | strs.indexOf('id'), 7 | strs.indexOf('path'), 8 | strs.indexOf('select'), 9 | ].map((idx) => parmas[idx]) 10 | 11 | exports.getItem = (strs, ...parmas) => { 12 | const [drive, id, path, select] = parseStrs(strs, parmas) 13 | if (id) { 14 | return `${drive}/items/${id}?$select=${select}` 15 | } else { 16 | return `${drive}/root:${join(...path)}?$select=${select}` 17 | } 18 | } 19 | 20 | exports.listChildren = (strs, ...parmas) => { 21 | const [drive, id, path, select] = parseStrs(strs, parmas) 22 | if (id) { 23 | return `${drive}/items/${id}/children` 24 | } else { 25 | return `${drive}/root:${ 26 | join(...path).slice( 27 | 0, 28 | -1, 29 | ) 30 | }:/children?$select=${select}` 31 | } 32 | } 33 | 34 | exports.listRoot = (strs, ...parmas) => { 35 | const [drive, _, __, select] = parseStrs(strs, parmas) 36 | return `${drive}/root/children?$select=${select}` 37 | } 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | 38 | # Optional npm cache directory 39 | .npm 40 | 41 | .env* 42 | 43 | # Optional eslint cache 44 | .eslintcache 45 | 46 | # Optional REPL history 47 | .node_repl_history 48 | 49 | # Output of 'npm pack' 50 | *.zip 51 | 52 | # Yarn Integrity file 53 | .yarn-integrity 54 | 55 | package-lock.json 56 | 57 | .vercel/ 58 | 59 | .leancloud/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 beet 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 | -------------------------------------------------------------------------------- /sor/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 beet 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 | -------------------------------------------------------------------------------- /cloudbaserc.json: -------------------------------------------------------------------------------- 1 | { 2 | "envId": "{{env.ENV_ID}}", 3 | "version": "2.0", 4 | "$schema": "https://framework-1258016615.tcloudbaseapp.com/schema/latest.json", 5 | "functions": [ 6 | { 7 | "name": "sosf", 8 | "memorySize": 128, 9 | "timeout": 5, 10 | "runtime": "Nodejs10.15", 11 | "installDependency": true, 12 | "handler": "index.main" 13 | } 14 | ], 15 | "framework": { 16 | "name": "sosf", 17 | "plugins": { 18 | "func": { 19 | "use": "@cloudbase/framework-plugin-function", 20 | "inputs": { 21 | "servicePaths": { 22 | "sosf": "/" 23 | }, 24 | "functionRootPath": "./platforms/cloudbase", 25 | "functions": [ 26 | { 27 | "name": "sosf", 28 | "memorySize": 128, 29 | "timeout": 5, 30 | "runtime": "Nodejs10.15", 31 | "installDependency": true, 32 | "envVariables": { 33 | "ENV_ID": "{{env.ENV_ID}}" 34 | }, 35 | "handler": "index.main" 36 | } 37 | ] 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /platforms/vercel/sosf/api/index.ts: -------------------------------------------------------------------------------- 1 | import { getItem, getToken, listChildren } from '@beetcb/sor' 2 | 3 | export default async function handler({ 4 | _, 5 | queryStringParameters, 6 | headers, 7 | }) { 8 | const { id, key, type, path = '/' } = queryStringParameters 9 | const { access_key } = process.env 10 | 11 | const isReqFolder = path.endsWith('/') && type !== 'file' 12 | 13 | if (path === '/favicon.ico' || (isReqFolder && access_key != key)) { 14 | return null 15 | } 16 | 17 | const access_token = await getToken() 18 | 19 | if (!access_token) { 20 | return {} 21 | } 22 | 23 | if (isReqFolder && type !== 'file') { 24 | // Render folder 25 | const isReturnJson = type === 'json' 26 | || (headers['content-type'] && headers['content-type'].includes('json')) 27 | 28 | // Render html first 29 | if (!isReturnJson) { 30 | return { 31 | isBase64Encoded: false, 32 | statusCode: 200, 33 | headers: { 34 | 'content-type': 'text/html', 35 | }, 36 | body: ` 37 | 38 | 39 |
40 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | `, 55 | } 56 | } else { 57 | const data = await listChildren(path, access_token, id, key) 58 | if (data) { 59 | const itemTable = data.value.reduce((arr, ele) => { 60 | arr.push({ 61 | name: `${ele.name}${ele.file ? '' : '/'}`, 62 | params: '?' 63 | + new URLSearchParams( 64 | `${ele.id ? `&id=${ele.id}` : ''}${ 65 | key && !ele.file ? `&key=${key}` : '' 66 | }${ele.file ? '&type=file' : ''}`, 67 | ).toString(), 68 | }) 69 | return arr 70 | }, []) 71 | return { 72 | statusCode: 200, 73 | headers: { 74 | 'content-type': 'application/json', 75 | }, 76 | body: JSON.stringify(itemTable), 77 | } 78 | } 79 | } 80 | } else { 81 | // Render file 82 | const data = await getItem(path, access_token, id) 83 | if (data) { 84 | return { 85 | statusCode: 308, 86 | headers: { Location: data['@microsoft.graph.downloadUrl'].slice(6) }, 87 | } 88 | } else return {} 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /platforms/cloudbase/sosf/index.js: -------------------------------------------------------------------------------- 1 | const { getToken, getItem, listChildren } = require('@beetcb/sor') 2 | 3 | async function handler({ path, queryStringParameters, headers }) { 4 | const { id, key, type } = queryStringParameters 5 | const { access_key } = process.env 6 | const isReqFolder = path.endsWith('/') && type !== 'file' 7 | 8 | if (path === '/favicon.ico' || (isReqFolder && access_key != key)) { 9 | return null 10 | } 11 | 12 | const access_token = await getToken() 13 | 14 | if (!access_token) { 15 | return null 16 | } 17 | 18 | if (isReqFolder && type !== 'file') { 19 | // Render folder 20 | const isReturnJson = type === 'json' 21 | || (headers['content-type'] && headers['content-type'].includes('json')) 22 | 23 | // Render html first 24 | if (!isReturnJson) { 25 | return { 26 | isBase64Encoded: false, 27 | statusCode: 200, 28 | headers: { 29 | 'content-type': 'text/html', 30 | }, 31 | body: ` 32 | 33 | 34 | 35 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | `, 50 | } 51 | } else { 52 | const data = await listChildren(path, access_token, id, key) 53 | if (data) { 54 | const itemTable = data.value.reduce((arr, ele) => { 55 | arr.push({ 56 | name: `${ele.name}${ele.file ? '' : '/'}`, 57 | params: '?' 58 | + new URLSearchParams( 59 | `${ele.id ? `&id=${ele.id}` : ''}${ 60 | key && !ele.file ? `&key=${key}` : '' 61 | }${ele.file ? '&type=file' : ''}`, 62 | ).toString(), 63 | }) 64 | return arr 65 | }, []) 66 | 67 | return { 68 | isBase64Encoded: false, 69 | statusCode: 200, 70 | headers: { 71 | 'content-type': 'application/json', 72 | }, 73 | body: itemTable, 74 | } 75 | } 76 | } 77 | } else { 78 | // Render file 79 | const data = await getItem(path, access_token, id) 80 | if (data) { 81 | return { 82 | isBase64Encoded: false, 83 | statusCode: 307, 84 | headers: { Location: data['@microsoft.graph.downloadUrl'].slice(6) }, 85 | body: null, 86 | } 87 | } else return 'Resource not found' 88 | } 89 | } 90 | 91 | exports.main = handler 92 | -------------------------------------------------------------------------------- /sor/index.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch') 2 | const sstore = require('@beetcb/sstore') 3 | 4 | const { getItem, listChildren, listRoot } = require('./graph/endpoint') 5 | 6 | require('dotenv').config() 7 | 8 | // Get & Store access_token from/to db 9 | // Using tcb-sstore as fake db 10 | function db(token) { 11 | return token ? sstore.set('token', token) : sstore.get('token') 12 | } 13 | 14 | const checkExpired = (token) => { 15 | const { expires_at } = token 16 | if (timestamp() > expires_at) { 17 | console.log('Token expired') 18 | return true 19 | } 20 | } 21 | 22 | const timestamp = () => (Date.now() / 1000) | 0 23 | 24 | const getFetchOpts = (a_t) => { 25 | const opts = { 26 | headers: { 27 | Authorization: `bearer ${a_t}`, 28 | }, 29 | compress: false, 30 | } 31 | return opts 32 | } 33 | 34 | async function acquireToken() { 35 | const { 36 | client_id, 37 | client_secret, 38 | refresh_token, 39 | redirect_uri, 40 | auth_endpoint, 41 | } = process.env 42 | 43 | try { 44 | console.log(auth_endpoint) 45 | const res = await fetch(`${auth_endpoint}/token`, { 46 | method: 'POST', 47 | body: `${ 48 | new URLSearchParams({ 49 | grant_type: 'refresh_token', 50 | client_id, 51 | client_secret, 52 | refresh_token, 53 | }).toString() 54 | }&redirect_uri=${redirect_uri}`, 55 | headers: { 56 | 'content-type': 'application/x-www-form-urlencoded', 57 | }, 58 | }) 59 | return await storeToken(res) 60 | } catch (e) { 61 | console.warn(e) 62 | } 63 | } 64 | 65 | async function storeToken(res) { 66 | const { expires_in, access_token, refresh_token } = await res.json() 67 | const expires_at = timestamp() + expires_in 68 | const token = { expires_at, access_token, refresh_token } 69 | return db(token) 70 | } 71 | 72 | exports.getToken = async () => { 73 | // Grab access token 74 | let token = db() 75 | if (!token || checkExpired(token)) { 76 | token = await acquireToken() 77 | } else { 78 | console.log('Grab token from sstore!') 79 | } 80 | sstore.close() 81 | return token.access_token 82 | } 83 | 84 | exports.getItem = async (path, access_token, item_id = '') => { 85 | const base_dir = process.env.base_dir || '' 86 | const graph = getItem `drive${process.env.drive_api}id${item_id}path${[ 87 | base_dir, 88 | path, 89 | ]}select${`@microsoft.graph.downloadUrl`}` 90 | 91 | const res = await fetch(graph, getFetchOpts(access_token)) 92 | if (res.ok) { 93 | return await res.json() 94 | } else { 95 | console.error(res.statusText) 96 | return null 97 | } 98 | } 99 | 100 | exports.listChildren = async (path, access_token, item_id = '') => { 101 | const { base_dir } = process.env 102 | const graph = path === '/' && !item_id 103 | ? listRoot `drive${process.env.drive_api}select${`id,name,file`}` 104 | : listChildren `drive${process.env.drive_api}id${item_id}path${[ 105 | base_dir, 106 | path, 107 | ]}select${`id,name,file`}` 108 | 109 | const res = await fetch(graph, getFetchOpts(access_token)) 110 | if (res.ok) { 111 | return await res.json() 112 | } else { 113 | console.error(res.statusText) 114 | return null 115 | } 116 | } 117 | 118 | exports.drive_api = process.env.drive_api 119 | -------------------------------------------------------------------------------- /platforms/template.js: -------------------------------------------------------------------------------- 1 | const { h, html } = gridjs 2 | 3 | function copyText(data) { 4 | navigator.clipboard.writeText(data) 5 | } 6 | 7 | function initDocument() { 8 | document.body.innerHTML = ` 9 | 10 |15 | Usage: Type a keyword below to search for your files 16 |
17 | 18 | 26 |