前言
本人有两个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"
}
我的内心是崩溃的……好了不干了!
开始操作
回归正题,干还是要干的,要么握着5T的onedrive没意思是吧……
然后我在Github又发现了一个新的项目——OneManager
打算部署这个玩意,这时候就有人问了:诶不是,你这标题不是写的FODI?怎么变成OneManager了?
别急,这玩意自然有它利用的价值
点击这个紫色的按钮,就会进入heroku的部署界面(啥?你说你不会用?那没事了),将此项目部署后进行安装,会要求输入heroku的api和管理员密码,这些就按要求填写就好了
接着就到了比较重要的一步:获取refresh_token(没错你知道我要干嘛了)
第一步安装的时候选择MS(中国版选择CN,如果你喜欢搞事情,你也可以选择MSC自己申请api,我就是懒,就不自己申请api了)
然后登陆自己的微软账户,接着就会弹出自己的refresh_token,这不就搞定了refresh_token啦?!
如果你没有及时复制你自己的refresh_token,你也可以到你自己heroku项目的变量下复制
接着我们返回FODI,将自己的refresh_token贴近官方给的模板的对应位置,接着将代码复制到cloudflare的workers里面(你又问我workers是什么?看这里啦!),然后保存即可!
官方模板:
/**
* 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>
完成界面:
题外话
那个获取refresh_token的网页并不是不可用,但是成功率巨低,我试了前前后后三十多次才一次成功……
heroku的登录需要科学上网,怎么科学上网就不要问我了,Google上大把……
我自己部署的heroku项目不会开放给大家获取refresh_token,因为获取完一次要到后台删除refresh_token才能进行下一次的获取
必须 注册 为本站用户, 登录 后才可以发表评论!