开发工具 · Tool

Onedrive分享型网盘搭建 – FODI

小编 · 12月27日 · 2020年

前言

本人有两个onedrive账号,一个自用,另外一个是前几天蹭的edu账号,之前一直想用onedrive来分享文件(毕竟容量是真的大),就在放寒假前,我发现了github的一个项目:FODI,虽说这UI不是很好看,但是不用服务器(嗯,让白嫖党有点快乐了),所以就动起了手……

官方教程:https://logi.im/front-end/scf-fodi.html

当我正在按照官方教程搭建的时候,在获取refresh_token这一步,它居然给我报错了???

官方教程中,是要求进行登录后,把第一个?删掉,把第一个&改成?,然后就给我弹出以下错误:

Message
{
    "error": "invalid_grant",
    "error_description": "AADSTS54005: OAuth2 Authorization code was already redeemed, please retry with a new valid code or use an existing refresh token.\r\nTrace ID: b0425d60-48d1-4006-aec5-76d97732cd00\r\nCorrelation ID: 0d390346-7cfa-4284-9518-67444dca1511\r\nTimestamp: 2020-02-09 02:08:21Z",
    "error_codes": [
        54005
    ],
    "timestamp": "2020-02-09 02:08:21Z",
    "trace_id": "b0425d60-48d1-4006-aec5-76d97732cd00",
    "correlation_id": "0d390346-7cfa-4284-9518-67444dca1511"
}

我的内心是崩溃的……好了不干了!

Onedrive分享型网盘搭建 – FODI-字节智造
咕咕咕

开始操作

回归正题,干还是要干的,要么握着5T的onedrive没意思是吧……

然后我在Github又发现了一个新的项目——OneManager

Onedrive分享型网盘搭建 – FODI-字节智造
嘤嘤嘤

打算部署这个玩意,这时候就有人问了:诶不是,你这标题不是写的FODI?怎么变成OneManager了?

别急,这玩意自然有它利用的价值

Onedrive分享型网盘搭建 – FODI-字节智造

点击这个紫色的按钮,就会进入heroku的部署界面(啥?你说你不会用?那没事了),将此项目部署后进行安装,会要求输入heroku的api和管理员密码,这些就按要求填写就好了

接着就到了比较重要的一步:获取refresh_token(没错你知道我要干嘛了)

Onedrive分享型网盘搭建 – FODI-字节智造
版本选择

第一步安装的时候选择MS(中国版选择CN,如果你喜欢搞事情,你也可以选择MSC自己申请api,我就是懒,就不自己申请api了)

然后登陆自己的微软账户,接着就会弹出自己的refresh_token,这不就搞定了refresh_token啦?!

Onedrive分享型网盘搭建 – FODI-字节智造
refresh_token获取

如果你没有及时复制你自己的refresh_token,你也可以到你自己heroku项目的变量下复制

Onedrive分享型网盘搭建 – FODI-字节智造
refresh_token in Heroku

接着我们返回FODI,将自己的refresh_token贴近官方给的模板的对应位置,接着将代码复制到cloudflare的workers里面(你又问我workers是什么?看这里啦!),然后保存即可!

Onedrive分享型网盘搭建 – FODI-字节智造
cloudflare editor

官方模板:

/**
 * IS_CN: 如果为世纪互联版本,请将 0 改为 1
 * EXPOSE_PATH:暴露路径,如全盘展示请留空,否则按 '/媒体/音乐' 的格式填写
 * ONEDRIVE_REFRESHTOKEN: refresh_token
 */
const IS_CN = 0;
const EXPOSE_PATH = ""
const ONEDRIVE_REFRESHTOKEN = ""


async function handleRequest(request) {
  let requestPath
  let querySplited
  let queryString = request.url.split('?')[1]
  if (queryString) {
    querySplited = queryString.split('=')
  }
  if (querySplited && querySplited[0] === 'file') {
    const file = querySplited[1]
    const fileName = file.split('/').pop();
    requestPath = file.replace('/' + fileName, '')
    const url = await fetchFiles(requestPath, fileName)
    return Response.redirect(url, 302)
  } else {
    const { headers } = request
    const contentType = headers.get('content-type')
    let body={}
    if (contentType && contentType.includes('form')) {
      const formData = await request.formData()
      for (let entry of formData.entries()) {
        body[entry[0]] = entry[1]
      }
    }
    requestPath = body ? body['?path'] : '';
    const files = await fetchFiles(requestPath, null, body.passwd);
    return new Response(files, {
      headers: {
        'content-type': 'application/json; charset=utf-8',
        'Access-Control-Allow-Origin': '*'
      }
    })
  }
}

addEventListener('fetch', event => {
  return event.respondWith(handleRequest(event.request))
})


const clientId = [
  '4da3e7f2-bf6d-467c-aaf0-578078f0bf7c',
  '04c3ca0b-8d07-4773-85ad-98b037d25631'

]
const clientSecret = [
  '7/+ykq2xkfx:.DWjacuIRojIaaWL0QI6',
  'h8@B7kFVOmj0+8HKBWeNTgl@pU/z4yLB'
]

const oauthHost = [
  'https://login.microsoftonline.com',
  'https://login.partner.microsoftonline.cn'
]

const apiHost = [
  'https://graph.microsoft.com',
  'https://microsoftgraph.chinacloudapi.cn'
]

const OAUTH = {
  'redirectUri': 'https://scfonedrive.github.io',
  'refreshToken': ONEDRIVE_REFRESHTOKEN,
  'clientId': clientId[IS_CN],
  'clientSecret': clientSecret[IS_CN],
  'oauthUrl': oauthHost[IS_CN] + '/common/oauth2/v2.0/',
  'apiUrl': apiHost[IS_CN] + '/v1.0/me/drive/root',
  'scope': apiHost[IS_CN] + '/Files.ReadWrite.All offline_access'
}

async function gatherResponse(response) {
  const { headers } = response
  const contentType = headers.get('content-type')
  if (contentType.includes('application/json')) {
    return await response.json()
  } else if (contentType.includes('application/text')) {
    return await response.text()
  } else if (contentType.includes('text/html')) {
    return await response.text()
  } else {
    return await response.text()
  }
}

async function getContent(url) {
  const response = await fetch(url)
  const result = await gatherResponse(response)
  return result
}

async function getContentWithHeaders(url, headers) {
  const response = await fetch(url, { headers: headers })
  const result = await gatherResponse(response)
  return result
}

async function fetchFormData(url, data) {
  const formdata = new FormData();
  for (const key in data) {
    if (data.hasOwnProperty(key)) {
      formdata.append(key, data[key])
    }
  }
  const requestOptions = {
    method: 'POST',
    body: formdata
  };
  const response = await fetch(url, requestOptions)
  const result = await gatherResponse(response)
  return result
}

async function fetchAccessToken() {
  url = OAUTH['oauthUrl'] + 'token'
  data = {
    'client_id': OAUTH['clientId'],
    'client_secret': OAUTH['clientSecret'],
    'grant_type': 'refresh_token',
    'requested_token_use': 'on_behalf_of',
    'refresh_token': OAUTH['refreshToken']
  }
  const result = await fetchFormData(url, data)
  return result.access_token
}

async function fetchFiles(path, fileName, passwd) {
  if (!path || path === '/') {
    if (EXPOSE_PATH === '') {
      path = ''
    } else {
      path = ':' + EXPOSE_PATH
    }
  } else {
    if (EXPOSE_PATH === '') {
      path = ':' + path
    } else {
      path = ':' + EXPOSE_PATH + path
    }
  }

  const accessToken = await fetchAccessToken()
  const uri = OAUTH.apiUrl + encodeURI(path) + '?expand=children(select=name,size,parentReference,lastModifiedDateTime,@microsoft.graph.downloadUrl)'

  const body = await getContentWithHeaders(uri, {
    Authorization: 'Bearer ' + accessToken
  })
  if (fileName) {
    let thisFile = null
    body.children.forEach(file => {
      if (file.name === decodeURIComponent(fileName)) {
        thisFile = file['@microsoft.graph.downloadUrl']
        return
      }
    })
    return thisFile
  } else {
    let files = []
    let encrypted = false
    for (let i = 0; i < body.children.length; i++) {
      const file = body.children[i]
      if (file.name === '.password') {
        const PASSWD = await getContent(file['@microsoft.graph.downloadUrl'])
        if (PASSWD !== passwd) {
          encrypted = true;
          break
        } else {
          continue
        }
      }
      files.push({
        name: file.name,
        size: file.size,
        time: file.lastModifiedDateTime,
        url: file['@microsoft.graph.downloadUrl']
      })
    }
    let parent
    if (body.children.length) {
      parent = body.children[0].parentReference.path
    } else {
      parent = body.parentReference.path
    }
    parent = parent.split(':').pop().replace(EXPOSE_PATH, '') || '/'
    parent = decodeURIComponent(parent)
    if (encrypted) {
      return JSON.stringify({ parent: parent, files: [], encrypted: true })
    } else {
      return JSON.stringify({ parent: parent, files: files })
    }
  }
}

接着,我们打开官方给的html文件,将自己的workers链接贴到对应的位置,部署到github即可!

html文件备份:(可以直接复制)

<!DOCTYPE html>

<head>
    <script>
        /**
        * SCF_GATEWAY:SCF 云函数网关地址
        * SITE_NAME:站点名称
        */
        window.GLOBAL_CONFIG = {
            SCF_GATEWAY: "",
            SITE_NAME: "FODI",
            IS_CF: true
        };
        if (window.GLOBAL_CONFIG.SCF_GATEWAY.indexOf('workers') === -1) {
            window.GLOBAL_CONFIG.SCF_GATEWAY += '/fodi/';
            window.GLOBAL_CONFIG.IS_CF = false;
        }
        // if (location.protocol === 'http:') {
        //     location.href = location.href.replace(/http/, 'https');
        // }
    </script>
    <meta charset="utf-8">
    <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no" name="viewport">
    <script src="//s0.pstatp.com/cdn/expire-1-M/ionicons/4.5.6/ionicons.js"></script>
    <script src="//s0.pstatp.com/cdn/expire-1-M/marked/0.6.2/marked.min.js"></script>
    <script src="//s0.pstatp.com/cdn/expire-1-M/highlight.js/9.15.6/highlight.min.js"></script>
    <link href="//s0.pstatp.com/cdn/expire-1-M/highlight.js/9.15.6/styles/github.min.css" rel="stylesheet" />
    <link href="//s0.pstatp.com/cdn/expire-1-M/github-markdown-css/3.0.1/github-markdown.min.css" rel="stylesheet" />
    <script src="//s0.pstatp.com/cdn/expire-1-M/jquery/3.4.0/jquery.min.js"></script>
    <script src="//s0.pstatp.com/cdn/expire-1-M/fancybox/3.5.7/jquery.fancybox.min.js"></script>
    <link href="//s0.pstatp.com/cdn/expire-1-M/fancybox/3.5.7/jquery.fancybox.min.css" rel="stylesheet" />
    <style>
        .password-wrapper {
            display: flex;
            align-items: center;
        }

        .password {
            margin: 0 auto;
            padding-top: 1em;
            display: none;
        }

        .password input {
            height: 2em;
            outline: none;
            border: solid rgb(218, 215, 215) 1px;
        }

        .password button {
            background: white;
            height: 2em;
            outline: none;
            border: solid rgb(218, 215, 215) 1px;
        }

        .password button:hover {
            color: white;
            background: rgb(218, 215, 215);
        }

        pre * {
            font-family: Courier New;
        }

        .preview {
            display: none;
            font-size: .8em;
        }

        .content {
            clear: both;
            padding: 0 1em;
            margin: 0 auto;
            text-align: center;
        }

        .file-name {
            line-height: 1em;
            padding: 1em 1em 0;
            text-align: center;
            white-space: nowrap;
            overflow: hidden;
        }

        .btn {
            float: right;
            text-align: center;
            border: solid rgb(218, 215, 215) 1px;
            border-radius: 1em;
            margin: 1em .2em;
            width: 4em;
            height: 2em;
            line-height: 2em;
            user-select: none;
            -moz-user-select: none;
            -o-user-select: none;
            -khtml-user-select: none;
            -webkit-user-select: none;
            -ms-user-select: none;
        }

        .btn:hover {
            color: white;
            background: rgb(218, 215, 215);
        }

        .btn.download {
            margin-right: 1em;
        }

        #arrow-back,
        #arrow-forward {
            color: rgb(218, 215, 215);
        }

        .loading-wrapper {
            display: none;
            position: fixed;
            height: 2em;
            line-height: 2em;
            margin-top: .5em;
            width: 100%;
            z-index: 1;
        }

        .loading {
            color: white;
            background: rgb(218, 215, 215);
            height: 100%;
            width: 8em;
            margin: 0 auto;
            text-align: center;
            border-radius: 1em;
        }

        ion-icon {
            font-size: 1.5em;
        }

        * {
            box-sizing: border-box;
            font-family: serif;
        }

        .markdown-body {
            min-width: 200px;
            margin: 0 auto;
            padding: .7em 1em;
            font-size: .8em;
        }

        .markdown-body h1,
        h2,
        h3,
        h4,
        h5,
        h6 {
            margin-top: 0;
        }

        .markdown-body img {
            max-width: 90%;
            max-height: 800px;
            width: auto;
            height: auto;
            display: block;
            margin: 0 auto;
        }

        body {
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }

        .header-wrapper {
            position: fixed;
            height: 3em;
            width: 100%;
            -moz-user-select: none;
            -o-user-select: none;
            -khtml-user-select: none;
            -webkit-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }

        .header {
            padding: 0 1.8em 0 1em;
            height: 100%;
            display: flex;
            align-items: center;
            border-bottom: solid rgb(218, 215, 215) 1px;
        }

        .logo {
            margin-right: .3em;
        }

        .site {
            white-space: nowrap;
            /* margin-left: auto;
                padding-left: 2em; */
        }

        .nav {
            width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }

        .nav-path,
        .nav-arr {
            font-size: 1em;
            height: 1.5em;
            margin-right: .3em;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            cursor: default;
        }

        #main-page:hover,
        .nav-path:hover,
        .tree-node:hover,
        .row.file-wrapper:hover {
            color: rgb(90, 101, 133);
            cursor: pointer;
        }


        .container {
            position: fixed;
            width: 100%;
            height: calc(100% - 3em);
            margin-top: 3em;
        }

        .main {
            position: relative;
            height: 100%;
            width: 100%;
        }

        .left {
            position: absolute;
            display: inline-grid;
            width: 20%;
            height: 100%;
            font-size: .8em;
            overflow: scroll;
        }

        .tree-node-wrapper {
            margin-left: 1.5em;
        }

        .tree-node {
            display: flex;
            align-items: center;
        }

        .tree-node-name {
            margin-left: .3em;
            white-space: nowrap;
        }

        .right {
            position: absolute;
            width: 80%;
            height: 100%;
            margin-left: 20%;
            overflow: scroll;
        }

        .row {
            height: 2.5em;
            padding: 0 .8em 0 1em;
            display: flex;
            align-items: center;
            border-bottom: solid rgb(218, 215, 215) 1px;
        }

        .row.file-wrapper {
            font-size: .8em;
            padding: 0 1em;
            height: 2em;
        }

        .file {
            width: 100%;
            display: flex;
            align-items: center;
        }

        .name {
            display: flex;
            align-items: center;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            width: 70%;
            padding-left: .3em;
        }

        .list-header .name {
            width: calc(70% + 1.1em);
            padding-left: 0;
        }

        .time {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            text-align: right;
            ;
            width: 133px;
        }

        .size {
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
            margin-left: auto;
        }

        @media screen and (max-width: 1000px) {
            .left {
                display: none;
            }

            .right {
                width: 100%;
                margin-left: initial;
            }
        }

        @media screen and (max-width: 800px) {
            .name {
                width: 60%;
            }

            .list-header .name {
                width: calc(60% + 1.1em);
            }

            .file-name {
                overflow-x: scroll;
                height: 100%;
            }
        }

        @media screen and (max-width: 600px) {
            .name {
                width: 75%;
            }

            .time {
                display: none;
            }

            .header {
                padding: 0 .3em;
            }

            .row {
                padding: 0 .3em;
            }

            .row.file-wrapper {
                padding: 0 .3em;
                height: 3em;
            }

            .markdown-body {
                padding: .6em .3em;
            }

            .file-name {
                padding: 1em .3em 0;
            }

            .content {
                padding: 0 .3em;
            }

            .btn.download {
                margin-right: .3em;
            }

            .logo {
                width: 2em;
                height: 2em;
            }


        }
    </style>
    <script>
        function createCORSRequest(method, url, timeout) {
            let xhr = new XMLHttpRequest();
            if ('withCredentials' in xhr) {
                xhr.open(method, url, true);
            } else if (typeof XDomainRequest !== 'undefined') {
                xhr = new XDomainRequest();
                xhr.open(method, url);
            } else {
                xhr = null;
            }
            if (xhr) {
                xhr.timeout = timeout;
            }
            return xhr;
        }

        function sendRequest(method, url, data, headers, callback, error, times) {
            let xhr = createCORSRequest(method, url, 2500);
            xhr.onreadystatechange = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    callback(xhr.responseText);
                }
            };
            xhr.timeout = xhr.onerror = () => {
                if (!times) {
                    times = 0;
                }
                console.log({
                    url: url,
                    data: data,
                    times: times
                })
                if (times < 1) {
                    sendRequest(method, url, data, headers, callback, error, times + 1);
                } else if (typeof error === 'function') {
                    error();
                }
            }
            if (headers) {
                for (key in headers) {
                    if (headers.hasOwnProperty(key)) {
                        xhr.setRequestHeader(key, headers[key]);
                    }
                }
            }
            if (data) {
                xhr.send(data);
            } else {
                xhr.send();
            }
        }

        function renderPage(data, cache) {
            let files;
            if (data) {
                files = JSON.parse(data);
                window.fileCache.set(files.parent, files);
                preCache(files, 0);
            } else {
                files = cache;
            }
            if (files.parent === window.backFordwardCache.current) {
                renderPath(files.parent);
                if (files.encrypted) {
                    handleEncryptedFolder(files);
                } else {
                    renderFileList(files);
                }
                renderTreeNode(files);
            }
            if (document.body.getAttribute('hidden')) {
                document.body.removeAttribute('hidden');
            }
            document.querySelector('.loading-wrapper').style.display = 'none';
        }

        function renderPath(path) {
            const createPathSpan = (text, path) => {
                let pathSpan = document.createElement('span');
                pathSpan.innerHTML = text.length > 20 ? text.substring(0, 20) + '..' : text;
                pathSpan.className = text === '/' ? 'nav-arr' : 'nav-path';
                if (path) {
                    addPathListener(pathSpan, path);
                }
                return pathSpan;
            };

            const paths = path.split('/');
            let pathSpanWrapper = document.getElementById('path');
            pathSpanWrapper.innerHTML = '';
            pathSpanWrapper.appendChild(createPathSpan(window.api.root));
            let continualPath = '/';
            for (let i = 1; i < paths.length - 1; i++) {
                continualPath += paths[i];
                pathSpanWrapper.appendChild(createPathSpan(paths[i], continualPath));
                pathSpanWrapper.appendChild(createPathSpan('/'));
                continualPath += '/';
            }
            pathSpanWrapper.appendChild(createPathSpan(paths[paths.length - 1]));
        }

        function renderFileList(files) {
            switchRightDisplay();

            const createFileWrapper = (type, name, time, size, path, url) => {
                let fileWrapper = document.getElementById('file-wrapper-templete').content.cloneNode(true);
                fileWrapper.querySelector('ion-icon').setAttribute('name', type);
                fileWrapper.querySelector('.name').innerHTML = name;
                fileWrapper.querySelector('.time').innerHTML = time;
                fileWrapper.querySelector('.size').innerHTML = size;
                addFileListLineListener(fileWrapper.querySelector('.row.file-wrapper'), path, url, size);
                return fileWrapper;
            };

            const formatDate = date => {
                const addZero = num => num > 9 ? num : '0' + num;
                date = new Date(date);
                const year = date.getFullYear();
                const month = addZero(date.getMonth() + 1);
                const day = addZero(date.getDate());
                const hour = addZero(date.getHours());
                const minute = addZero(date.getMinutes());
                const second = addZero(date.getSeconds());
                return 'yyyy-MM-dd HH:mm:ss'
                    .replace('yyyy', year)
                    .replace('MM', month)
                    .replace('dd', day)
                    .replace('HH', hour)
                    .replace('mm', minute)
                    .replace('ss', second);
            };

            const formatSize = size => {
                let count = 0;
                while (size >= 1024) {
                    size /= 1024;
                    count++;
                }
                size = size.toFixed(2);
                switch (count) {
                    case 1:
                        size += ' KB';
                        break;
                    case 2:
                        size += ' MB';
                        break;
                    case 3:
                        size += ' GB';
                        break;
                    case 4:
                        size += ' TB';
                        break;
                    case 5:
                        size += ' PB';
                        break;
                    default:
                        size += ' B';
                }
                return size;
            };

            let fileList = document.getElementById('file-list');
            fileList.innerHTML = '';
            files.files.forEach(file => {
                if (file.name.split('.').pop() === 'md') {
                    if (file.url) {
                        renderReadme(files.parent + '/' + file.name, file.url);
                    }
                } else {
                    const parent = files.parent === window.api.root ? '' : files.parent;
                    fileList.appendChild(createFileWrapper(
                        file.url ? 'document' : 'folder',
                        file.name,
                        formatDate(file.time),
                        formatSize(file.size),
                        parent + '/' + file.name,
                        file.url
                    ));
                }
            });
        }

        async function renderTreeNode(files) {
            const createTreeNodeWrapper = (array, type, name, path) => {
                let treeNodeWrapper = document.getElementById('tree-node-wrapper-template').content
                    .cloneNode(true);
                let icons = treeNodeWrapper.querySelectorAll('ion-icon');
                icons[0].setAttribute('name', array);
                icons[1].setAttribute('name', type);
                treeNodeWrapper.querySelector('.tree-node-name').innerText = name;
                treeNodeWrapper.appendNode = node => treeNodeWrapper.querySelector('.tree-node-wrapper').append(
                    node);
                addTreeNodeListener(treeNodeWrapper.querySelector('.tree-node'), path);
                return treeNodeWrapper;
            }

            const paths = files.parent.split('/');
            let absolutePath = max => {
                let absolutePath = '';
                for (let j = 1; j <= max; j++) {
                    absolutePath += '/' + paths[j];
                }
                return absolutePath;
            };
            let maxIndex = paths.length - 1;
            let currentTreeNode = createTreeNodeWrapper('arrow-dropdown',
                'folder-open',
                paths[maxIndex],
                absolutePath(maxIndex)
            );
            files.files.forEach(file => {
                if (!file.url) {
                    currentTreeNode.appendNode(createTreeNodeWrapper('arrow-dropright',
                        'folder',
                        file.name,
                        files.parent + '/' + file.name
                    ));
                }
            });

            for (let i = maxIndex - 1; i > 0; i--) {
                const currentTreeNodeParentAbsolutePath = absolutePath(i);
                let currentTreeNodeParent = createTreeNodeWrapper('arrow-dropdown',
                    'folder',
                    paths[i],
                    currentTreeNodeParentAbsolutePath
                );
                let cache = window.fileCache.get(currentTreeNodeParentAbsolutePath);
                if (cache) {
                    cache.files.forEach(file => {
                        if (!file.url) {
                            if (file.name === paths[i + 1]) {
                                currentTreeNodeParent.appendNode(currentTreeNode);
                            } else {
                                currentTreeNodeParent.appendNode(createTreeNodeWrapper(
                                    'arrow-dropright',
                                    'folder',
                                    file.name,
                                    currentTreeNodeParentAbsolutePath + '/' + file.name
                                ));
                            }
                        }
                    });
                } else {
                    currentTreeNodeParent.appendNode(currentTreeNode);
                }
                currentTreeNode = currentTreeNodeParent;
            }

            const treeRoot = document.getElementById('tree-root');
            treeRoot.innerHTML = '';
            const cache = window.fileCache.get(window.api.root);
            const currentNodeName = currentTreeNode.querySelector('.tree-node-name').innerText;
            if (cache) {
                cache.files.forEach(file => {
                    if (!file.url) {
                        if (file.name === currentNodeName) {
                            treeRoot.append(currentTreeNode);
                        } else {
                            treeRoot.append(createTreeNodeWrapper(
                                'arrow-dropright',
                                'folder',
                                file.name,
                                window.api.root + file.name
                            ));
                        }
                    }
                });
            } else {
                treeRoot.append(currentTreeNode);
            }
        }

        async function renderReadme(path, url) {
            const render = text => {
                let markedText;
                try {
                    markedText = marked(text, {
                        gfm: true,
                        highlight: (code, lang, callback) => {
                            return hljs.highlight(lang, code).value;
                        }
                    });
                } catch (e) {
                    markedText = marked(text, {
                        gfm: true,
                        highlight: (code, lang, callback) => {
                            return hljs.highlight('bash', code).value;
                        }
                    });
                }
                if (window.backFordwardCache.current + '/README.md' === path) {
                    if (!window.backFordwardCache.preview) {
                        document.getElementById('readme').innerHTML = markedText;
                        document.querySelector('.markdown-body').style.display = 'block';
                    }
                }
                let cache = window.fileCache.get(path);
                if (!cache || cache === true) {
                    window.fileCache.set(path, text);
                }
            };
            let text = window.fileCache.get(path);
            if (text === true) {
                let cacheWaitReadmeFetch = setInterval(() => {
                    text = window.fileCache.get(path);
                    if (typeof text === 'object') {
                        render(text, path);
                        clearInterval(cacheWaitReadmeFetch);
                    } else if (text === false) {
                        clearInterval(cacheWaitReadmeFetch);
                    }
                }, 100);
            } else if (text) {
                render(text, path);
            } else {
                window.fileCache.set(path, true);
                sendRequest('GET', url, null, null, text => render(text, path), () => window.fileCache.set(path, false));
            }
        }

        function handleEncryptedFolder(files) {
            switchRightDisplay('encrypted');
            const password = document.querySelector('.password');
            const input = password.querySelector('input');
            const button = password.querySelector('button');
            const buttonParent = button.parentElement;
            const buttonClone = button.cloneNode(true);
            buttonParent.replaceChild(buttonClone, button);
            input.placeholder = '请输入密码';
            buttonClone.addEventListener('click', event => {
                const passwd = input.value;
                if (!input.value) {
                    return;
                }
                input.value = '';
                input.placeholder = '正在验证..';
                sendRequest(window.api.method,
                    window.api.url,
                    window.api.formatPayload(files.parent, passwd),
                    window.api.headers,
                    data => {
                        const newFiles = JSON.parse(data);
                        if (newFiles.encrypted) {
                            input.placeholder = '密码错误';
                        } else {
                            window.fileCache.set(newFiles.parent, newFiles);
                            fetchFileList(newFiles.parent);
                        }
                    },
                    () => window.fileCache.set(newFiles.parent, false)
                );
            });
        }

        function addPathListener(elem, path) {
            elem.addEventListener('click', event => {
                fetchFileList(path);
                switchBackForwardStatus(path);
            });
        }

        function addTreeNodeListener(elem, path) {
            elem.addEventListener('click', event => {
                fetchFileList(path);
                switchBackForwardStatus(path);
            });
        }

        function addFileListLineListener(elem, path, url, size) {
            if (url) {
                elem.addEventListener('click', event => {
                    window.backFordwardCache.preview = true;
                    const previewHandler = {
                        copyTextContent: (source, text) => {
                            let result = false;
                            let target = document.createElement('pre');
                            target.style.opacity = '0';
                            target.textContent = text || source.textContent;
                            document.body.appendChild(target);
                            try {
                                let range = document.createRange();
                                range.selectNode(target);
                                window.getSelection().removeAllRanges();
                                window.getSelection().addRange(range);
                                document.execCommand('copy');
                                window.getSelection().removeAllRanges();
                                result = true;
                            } catch (e) { }
                            document.body.removeChild(target);
                            return result;
                        },
                        fileType: suffix => {
                            Array.prototype.contains = function (search) {
                                const object = this;
                                for (const key in object) {
                                    if (object.hasOwnProperty(key)) {
                                        if ((eval('/' + search + '/i')).test(object[key])) {
                                            return true;
                                        }
                                    }
                                }
                                return false;
                            };
                            if (['bmp', 'jpg', 'png', 'svg', 'webp', 'gif'].contains(suffix)) {
                                return 'image';
                            } else if (['mp3', 'flac', 'wav'].contains(suffix)) {
                                return 'audio';
                            } else if (['mp4', 'avi', 'mkv', 'flv', 'm3u8'].contains(suffix)) {
                                return 'video';
                            } else if (
                                [
                                    'txt', 'js', 'json', 'css', 'html', 'java', 'c', 'cpp', 'php',
                                    'cmd', 'ps1',
                                    'bat', 'sh', 'py', 'go', 'asp',
                                ].contains(suffix)
                            ) {
                                return 'text';
                            } else if (
                                ['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx', 'mpp', 'rtf', 'vsd', 'vsdx'].contains(suffix)
                            ) {
                                return 'office';
                            }
                        },
                        loadResource: (resource, callback) => {
                            let type;
                            switch (resource.split('.').pop()) {
                                case 'css':
                                    type = 'link';
                                    break;
                                case 'js':
                                    type = 'script';
                                    break;
                            }
                            let element = document.createElement(type);
                            let loaded = false;
                            if (typeof callback === 'function') {
                                element.onload = element.onreadystatechange = () => {
                                    if (!loaded && (!element.readyState || /loaded|complete/.test(
                                        element.readyState))) {
                                        element.onload = element.onreadystatechange = null;
                                        loaded = true;
                                        callback();
                                    }
                                }
                            }
                            if (type === 'link') {
                                element.href = resource;
                                element.rel = 'stylesheet';
                            } else {
                                element.src = resource;
                            }
                            document.getElementsByTagName('head')[0].appendChild(element);
                        },

                        createDplayer: (video, type, elem) => {
                            const host = '//s0.pstatp.com/cdn/expire-1-M';
                            const resources = [
                                '/dplayer/1.25.0/DPlayer.min.css',
                                '/dplayer/1.25.0/DPlayer.min.js',
                                '/hls.js/0.12.4/hls.light.min.js',
                                '/flv.js/1.5.0/flv.min.js'
                            ];
                            let unloadedResourceCount = resources.length;
                            resources.forEach(resource => {
                                previewHandler.loadResource(host + resource, () => {
                                    if (!--unloadedResourceCount) {
                                        let option = {
                                            url: video
                                        }
                                        if (type === 'flv') {
                                            option.type = 'flv';
                                        }
                                        new DPlayer({
                                            container: elem,
                                            screenshot: true,
                                            video: option
                                        });
                                    }
                                })
                            });
                        }
                    }
                    const suffix = path.split('.').pop();
                    let content = document.querySelector('.content');
                    switch (previewHandler.fileType(suffix)) {
                        case 'image':
                            let img = new Image();
                            img.style.maxWidth = '100%';
                            img.src = url;
                            let fancy = document.createElement('a');
                            fancy.setAttribute('data-fancybox', 'image');
                            fancy.href = img.src;
                            fancy.append(img);
                            content.innerHTML = '';
                            content.append(fancy);
                            break;
                        case 'audio':
                            let audio = new Audio();
                            audio.style.outline = 'none';
                            audio.preload = 'auto';
                            audio.controls = 'controls';
                            audio.style.width = '100%';
                            audio.src = url;
                            content.innerHTML = '';
                            content.append(audio);
                            break;
                        case 'video':
                            let video = document.createElement('div');
                            previewHandler.createDplayer(url, suffix, video);
                            content.innerHTML = '';
                            content.append(video);
                            break;
                        case 'text':
                            let pre = document.createElement('pre');
                            let code = document.createElement('code');
                            pre.append(code);
                            pre.style.background = 'rgb(245,245,245)';
                            pre.style['overflow-x'] = 'scroll';
                            pre.classList.add(suffix);
                            content.style['text-align'] = 'initial';
                            content.innerHTML = '';
                            content.append(pre);
                            sendRequest('GET', url, null, null, data => {
                                code.textContent = data;
                                if (size.indexOf(' B') >= 0 || size.indexOf(' KB') &&
                                    size.split(' ')[0] < 100
                                ) {
                                    hljs.highlightBlock(pre);
                                }
                            });
                            break;
                        case 'office':
                            const officeOnline = '//view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(url);
                            let div = document.createElement('div');
                            div.style.lineHeight = '2em';
                            div.style.background = 'rgba(218, 215, 215, 0.21)';
                            div.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
                            div.style.cursor = 'pointer';
                            div.innerHTML = '新窗口打开';
                            div.addEventListener('click', () => window.open(officeOnline));
                            content.innerHTML = '';
                            content.appendChild(div);
                            if (document.body.clientWidth >= 480) {
                                let iframe = document.createElement('iframe');
                                iframe.width = '100%';
                                iframe.style.height = '41em';
                                iframe.style.border = '0';
                                iframe.src = officeOnline;
                                content.appendChild(iframe);
                            }
                            break;
                        default:
                            content.style['text-align'] = 'center';
                            content.innerHTML = '该文件不支持预览';
                            break;

                    }
                    document.querySelector('.file-name').innerHTML = path;
                    document.querySelector('.btn.download').addEventListener('click',
                        () => location.href = url
                    );
                    document.querySelector('.btn.quote').addEventListener('click',
                        event => {
                            previewHandler.copyTextContent(null, window.api.url + '?file=' + path);
                            const btn = document.querySelector('.btn.quote');
                            btn.innerHTML = '已复制';
                            setTimeout(() => btn.innerHTML = '引用', 250);
                        }
                    );
                    document.querySelector('.btn.share').addEventListener('click',
                        event => {
                            const sharePath = () => {
                                let arr = window.backFordwardCache.current.split('/');
                                let r = '';
                                for (let i = 1; i < arr.length; i++) {
                                    r += '/' + arr[i];
                                }
                                return r;
                            }
                            previewHandler.copyTextContent(null,
                                window.location.origin +
                                window.location.pathname +
                                '?path=' + sharePath());
                            const btn = document.querySelector('.btn.share');
                            btn.innerHTML = '已复制';
                            setTimeout(() => btn.innerHTML = '分享', 250);
                        }
                    );
                    switchRightDisplay('preview');

                    let start = null;
                    let right = document.querySelector('.right');
                    const scrollToBottom = (timestamp) => {
                        if (!start) start = timestamp;
                        let progress = timestamp - start;
                        let last = right.scrollTop;
                        right.scrollTo(0, right.scrollTop + 14);
                        if (right.scrollTop !== last && progress < 1000 * 2) {
                            window.requestAnimationFrame(scrollToBottom);
                        }
                    };
                    window.requestAnimationFrame(scrollToBottom);
                });
            } else {
                elem.addEventListener('click', event => {
                    fetchFileList(path);
                    switchBackForwardStatus(path);
                });
            }
        }

        function addBackForwardListener() {
            document.getElementById('arrow-back').addEventListener('click', back);
            document.getElementById('arrow-forward').addEventListener('click', forward);
            document.querySelector('#main-page').addEventListener('click', () => {
                fetchFileList(window.api.root);
                switchBackForwardStatus(window.api.root);
            });
        }

        function switchRightDisplay(display) {
            if (display === 'preview') {
                document.querySelector('.list-header').style.display = 'none';
                document.querySelector('#file-list').style.display = 'none';
                document.querySelector('.markdown-body').style.display = 'none';
                document.querySelector('.password').style.display = 'none';
                document.querySelector('.preview').style.display = 'initial'
            } else if (display === 'encrypted') {
                document.querySelector('.list-header').style.display = 'none';
                document.querySelector('#file-list').style.display = 'none';
                document.querySelector('.markdown-body').style.display = 'none';
                document.querySelector('.preview').style.display = 'none';
                document.querySelector('.password').style.display = 'initial';
                document.querySelector('#readme').innerHTML = '';
                let content = document.querySelector('.preview .content');
                if (content) {
                    document.querySelector('.preview .content').innerHTML = '';
                }
            } else {
                document.querySelector('.list-header').style.display = 'initial';
                document.querySelector('#file-list').style.display = 'initial';
                document.querySelector('.markdown-body').style.display = 'none'
                document.querySelector('.preview').style.display = 'none';
                document.querySelector('.password').style.display = 'none';
                document.querySelector('#readme').innerHTML = '';
                let content = document.querySelector('.preview .content');
                if (content) {
                    document.querySelector('.preview .content').innerHTML = '';
                }
            }
        }

        function switchBackForwardStatus(path) {
            if (path) {
                window.backFordwardCache.deepest = path;
            }
            if (window.backFordwardCache.root !== window.backFordwardCache.current) {
                window.backFordwardCache.backable = true;
                document.getElementById('arrow-back').style.color = 'black';
            } else {
                window.backFordwardCache.backable = false;
                document.getElementById('arrow-back').style.color = 'rgb(218, 215, 215)';
            }
            if (window.backFordwardCache.deepest !== window.backFordwardCache.current) {
                window.backFordwardCache.forwardable = true;
                document.getElementById('arrow-forward').style.color = 'black';
            } else {
                window.backFordwardCache.forwardable = false;
                document.getElementById('arrow-forward').style.color = 'rgb(218, 215, 215)';
            }
        }

        function back() {
            if (!window.backFordwardCache.backable) {
                return;
            }
            if (window.backFordwardCache.preview) {
                fetchFileList(window.backFordwardCache.current);
            } else {
                let former = (() => {
                    let formerEndIndex = window.backFordwardCache.current.lastIndexOf('/');
                    return window.backFordwardCache.current.substring(0, formerEndIndex);
                })();
                former = former || window.api.root;
                fetchFileList(former);
                switchBackForwardStatus();
            }
            // console.log(window.backFordwardCache);
        }

        function forward() {
            if (!window.backFordwardCache.forwardable) {
                return
            }
            const current = window.backFordwardCache.current === window.api.root ? '' : window.backFordwardCache.current
            const subLength = current ? current.length : 0;
            const later = current + '/' +
                window.backFordwardCache.deepest.substring(subLength).split('/')[1];
            fetchFileList(later);
            switchBackForwardStatus();
            // console.log(window.backFordwardCache);
        }

        async function preCache(files, level) {
            if (level > 2) return;
            files.files.forEach(file => {
                const parent = files.parent === '/' ? '' : files.parent
                const path = parent + '/' + file.name;
                if (!file.url) {
                    // console.log('caching ' + path + ', level ' + level);
                    window.fileCache.set(path, true);
                    sendRequest(window.api.method,
                        window.api.url,
                        window.api.formatPayload(path),
                        window.api.headers,
                        data => {
                            const files = JSON.parse(data);
                            window.fileCache.set(path, files);
                            preCache(files, level + 1);
                        },
                        () => window.fileCache.set(path, false)
                    );
                } else if (file.name.split('.').pop() === 'md') {
                    // console.log('caching ' + path + ', level ' + level);
                    window.fileCache.set(path, true);
                    sendRequest('GET', file.url, null, null, text => window.fileCache.set(path, text), () => window.fileCache.set(path, false));
                }
            });
        }

        async function preCacheCheck(cache, path) {
            cache.files.forEach(file => {
                const prefix = path === window.api.root ? '' : path;
                const nextPath = prefix + '/' + file.name;
                const pathCache = window.fileCache.get(nextPath);
                if (!file.url) {
                    if (!pathCache && pathCache !== true) {
                        // console.log('inner caching ' + nextPath);
                        window.fileCache.set(nextPath, true);
                        sendRequest(window.api.method,
                            window.api.url,
                            window.api.formatPayload(nextPath),
                            window.api.headers,
                            data => {
                                const files = JSON.parse(data);
                                window.fileCache.set(nextPath, files);
                                preCache(files, 0);
                            },
                            () => window.fileCache.set(nextPath, false)
                        );
                    }
                } else if (file.name.split('.').pop() === 'md') {
                    if (!pathCache && pathCache !== true) {
                        // console.log('inner caching ' + nextPath);
                        window.fileCache.set(nextPath, true);
                        sendRequest('GET', file.url, null, null, text => window.fileCache.set(nextPath,
                            text), () => window.fileCache.set(nextPath,
                                false));
                    }
                }
            });
        }

        function fetchFileList(path) {
            // console.log('fetching ' + path);
            let loading = document.querySelector('.loading-wrapper');
            loading.style.display = 'initial';
            window.backFordwardCache.preview = false;
            window.backFordwardCache.current = path;
            let cache = window.fileCache.get(path);
            if (cache === true) {
                let cacheWaitFileListFetch = setInterval(() => {
                    cache = window.fileCache.get(path);
                    if (typeof cache === 'object') {
                        renderPage(null, cache);
                        preCacheCheck(cache, path);
                        clearInterval(cacheWaitFileListFetch);
                    } else if (cache === false) {
                        clearInterval(cacheWaitFileListFetch);
                        loading.style.color = 'red';
                        loading.innerText = 'Failed!';
                        setTimeout(() => {
                            loading.style.display = 'none';
                            loading.style.color = 'white';
                            loading.innerText = 'Loading..';
                        }, 2000);
                    }
                }, 100);
            } else if (cache) {
                renderPage(null, cache);
                preCacheCheck(cache, path);
            } else {
                window.fileCache.set(path, true);
                sendRequest(window.api.method,
                    window.api.url,
                    window.api.formatPayload(path),
                    window.api.headers,
                    renderPage
                );
            }
        }

        document.addEventListener('DOMContentLoaded', () => {
            document.title = window.GLOBAL_CONFIG.SITE_NAME;
            document.querySelector('.site').textContent = window.GLOBAL_CONFIG.SITE_NAME;
            window.api = {
                root: '/',
                url: window.GLOBAL_CONFIG.SCF_GATEWAY,
                method: 'POST',
                formatPayload: (path, passwd) => {
                    return '?path=' + encodeURIComponent(path) +
                        '&encrypted=' + window.api.accessToken.encrypted +
                        '&plain=' + window.api.accessToken.plain +
                        '&passwd=' + passwd;
                },
                headers: {
                    'Content-type': 'application/x-www-form-urlencoded'
                }
            }
            window.backFordwardCache = {
                root: window.api.root,
                deepest: window.api.root,
                current: window.api.root,
                backable: false,
                forwardable: false,
                preview: false
            }
            window.fileCache = new Map();
            const initialPath = new URLSearchParams(window.location.search).get('path') || window.api.root;
            if (window.GLOBAL_CONFIG.IS_CF) {
                window.api.accessToken = {
                    encrypted: '',
                    plain: ''
                };
                fetchFileList(initialPath);
                addBackForwardListener();
            } else {
                sendRequest(window.api.method,
                    window.api.url + '?accessToken',
                    null,
                    window.api.headers,
                    data => {
                        const accessToken = JSON.parse(data);
                        window.api.accessToken = {
                            encrypted: accessToken.encrypted,
                            plain: accessToken.plain
                        };
                        fetchFileList(initialPath);
                        addBackForwardListener();
                    }
                );
            }
        });
    </script>
</head>

<body hidden="hidden">
    <template id="tree-node-wrapper-template">
        <div class="tree-node-wrapper">
            <div class="tree-node">
                <ion-icon></ion-icon>
                <ion-icon></ion-icon>
                <div class="tree-node-name"></div>
            </div>
        </div>
    </template>
    <template id="file-wrapper-templete">
        <div class="row file-wrapper">
            <div class="file">
                <ion-icon></ion-icon>
                <span class="name"></span>
                <span class="time"></span>
                <span class="size"></span>
            </div>
        </div>
    </template>
    <div class="loading-wrapper">
        <div class="loading">Loading...</div>
    </div>
    <div class="header-wrapper">
        <div class="header">
            <ion-icon id="arrow-back" class="logo" name="arrow-back"></ion-icon>
            <ion-icon id="arrow-forward" class="logo" name="arrow-forward"></ion-icon>
            <ion-icon id="main-page" class="logo" name="folder"></ion-icon>
            <div class="nav">
                <span id="path">
                </span>
            </div>
            <span class="site" id="nav-site">ONEDRIVE</span>
        </div>
    </div>
    <div class="container">
        <div class="main">
            <div class="left">
                <div id="tree-root">
                </div>
            </div>
            <div class="right">
                <div class="list-header">
                    <div class="row">
                        <div class="file">
                            <span class="name">ITEMS</span>
                            <span class="time">TIME</span>
                            <span class="size">SIZE</span>
                        </div>
                    </div>
                </div>
                <div id="file-list">
                </div>
                <div class="markdown-body">
                    <div id="readme">
                    </div>
                </div>
                <div class="preview">
                    <div class="info">
                        <div class="file-name"></div>
                        <div class="btn download">下载</div>
                        <div class="btn quote">引用</div>
                        <div class="btn share">分享</div>
                    </div>
                    <div class="content"></div>
                </div>
                <div class="password-wrapper">
                    <div class="password">
                        <input type="password">
                        <button>提交</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
</body>

</html>

完成界面:

Onedrive分享型网盘搭建 – FODI-字节智造
cloudflare editor

题外话

那个获取refresh_token的网页并不是不可用,但是成功率巨低,我试了前前后后三十多次才一次成功……

heroku的登录需要科学上网,怎么科学上网就不要问我了,Google上大把……

我自己部署的heroku项目不会开放给大家获取refresh_token,因为获取完一次要到后台删除refresh_token才能进行下一次的获取

0 条回应

必须 注册 为本站用户, 登录 后才可以发表评论!