// ==UserScript==
// @name 一键添加阿里云共享到AList
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 方便快捷的一键添加阿里云共享到AList
// @author chaos
// @match https://www.aliyundrive.com/s/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=aliyundrive.com
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.3/jquery.min.js
// @require https://cdn.bootcdn.net/ajax/libs/jqueryui/1.13.2/jquery-ui.min.js
// @resource http://i.stack.imgur.com/FhHRx.gif
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_listValues
// @grant GM_openInTab
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/460279/%E4%B8%80%E9%94%AE%E6%B7%BB%E5%8A%A0%E9%98%BF%E9%87%8C%E4%BA%91%E5%85%B1%E4%BA%AB%E5%88%B0AList.user.js
// @updateURL https://update.greasyfork.icu/scripts/460279/%E4%B8%80%E9%94%AE%E6%B7%BB%E5%8A%A0%E9%98%BF%E9%87%8C%E4%BA%91%E5%85%B1%E4%BA%AB%E5%88%B0AList.meta.js
// ==/UserScript==
/* globals jQuery, $, waitForKeyElements */
/*
https://gist.githubusercontent.com/BrockA/2625891/raw/9c97aa67ff9c5d56be34a55ad6c18a314e5eb548/waitForKeyElements.js
--- waitForKeyElements(): A utility function, for Greasemonkey scripts,
that detects and handles AJAXed content.
Usage example:
waitForKeyElements (
"div.comments"
, commentCallbackFunction
);
//--- Page-specific function to do what we want when the node is found.
function commentCallbackFunction (jNode) {
jNode.text ("This comment changed by waitForKeyElements().");
}
IMPORTANT: This function requires your script to have loaded jQuery.
*/
function waitForKeyElements (
selectorTxt, /* Required: The jQuery selector string that
specifies the desired element(s).
*/
actionFunction, /* Required: The code to run when elements are
found. It is passed a jNode to the matched
element.
*/
bWaitOnce, /* Optional: If false, will continue to scan for
new elements even after the first match is
found.
*/
iframeSelector /* Optional: If set, identifies the iframe to
search.
*/
) {
var targetNodes, btargetsFound;
if (typeof iframeSelector == "undefined")
targetNodes = $(selectorTxt);
else
targetNodes = $(iframeSelector).contents ()
.find (selectorTxt);
if (targetNodes && targetNodes.length > 0) {
btargetsFound = true;
/*--- Found target node(s). Go through each and act if they
are new.
*/
targetNodes.each ( function () {
var jThis = $(this);
var alreadyFound = jThis.data ('alreadyFound') || false;
if (!alreadyFound) {
//--- Call the payload function.
var cancelFound = actionFunction (jThis);
if (cancelFound)
btargetsFound = false;
else
jThis.data ('alreadyFound', true);
}
} );
}
else {
btargetsFound = false;
}
//--- Get the timer-control variable for this selector.
var controlObj = waitForKeyElements.controlObj || {};
var controlKey = selectorTxt.replace (/[^\w]/g, "_");
var timeControl = controlObj [controlKey];
//--- Now set or clear the timer as appropriate.
if (btargetsFound && bWaitOnce && timeControl) {
//--- The only condition where we need to clear the timer.
clearInterval (timeControl);
delete controlObj [controlKey]
}
else {
//--- Set a timer, if needed.
if ( ! timeControl) {
timeControl = setInterval ( function () {
waitForKeyElements ( selectorTxt,
actionFunction,
bWaitOnce,
iframeSelector
);
},
300
);
controlObj [controlKey] = timeControl;
}
}
waitForKeyElements.controlObj = controlObj;
}
// todo: cache AList auth token
(async function() {
'use strict';
console.log('start user script...')
// Your code here...
injectStyleFile('https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css')
injectStyle(`
.alist-config {
display: none;
}
.alist-config lable,input {
display: block;
width: 100%;
}
.saveToAList {
cursor: pointer;
color: var(--basic_white);
background-color: rgb(185, 158, 29);
border-radius: 10px;
margin: 0px 5px;
padding: 1px 10px;
height: 36px;
font-size: 14px;
line-height: 1.5;
display: inline-flex;
align-items: center;
justify-content: center;
}
.share-name {
color: red;
}
.xhr-progress {
display: none;
position: fixed;
z-index: 1000;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba( 255, 255, 255, .8 )
url('http://i.stack.imgur.com/FhHRx.gif')
50% 50%
no-repeat;
}
body.loading .xhr-progress {
overflow: hidden;
display: block;
}
`)
const config = await loadAListConfig()
console.log('AList config', config)
createConfigModal(config)
createAddToAListResultModal()
createXhrProgressModal()
waitForKeyElements('div[class^="info-wrapper--"]', $node => {
console.log( "DOM document change!", $node )
const addedEleClass = 'saveToAList'
$node.after('
保存到AList
配置AList
')
const $saveToAList = $('#saveToAList')
const $configAList = $('#configAList')
const $dialog = $('.alist-config').dialog({
title: '配置AList',
autoOpen: false,
modal: true,
position: {my: "center", at: "center", of: $configAList},
buttons: {
"保存配置": function() {
saveAListConfig()
$dialog.dialog('close')
},
'取消': function() {
$dialog.dialog('close')
}
},
})
$saveToAList.click(function(){
console.log($(this))
getAliyunShareInfo()
//addAliyunShareToAList()
})
$configAList.css({
'background-color': '#3e3e3e',
})
$configAList.click(function(){
console.log($(this))
$dialog.dialog('open')
})
})
/*
function defines here
*/
function createConfigModal(config) {
const html = `
`
$('body').append(html)
}
function createXhrProgressModal() {
const html = `
`
$('body').append(html)
}
function createAddToAListResultModal() {
const html = `
`
$('body').append(html)
}
function injectScriptFile(path) {
const s = document.createElement('script')
s.type = 'text/javascript'
s.src = path
$('body').append(s)
}
function injectScript(code) {
const s = document.createElement('script')
s.type = 'text/javascript'
s.textContent = code
$('body').append(s)
}
function injectStyleFile(path) {
const s = document.createElement('link')
s.rel = 'stylesheet'
s.href = path
$('head').append(s)
}
function injectStyle(code) {
const s = document.createElement('style')
s.textContent = code
$('head').append(s)
}
async function saveAListConfig() {
console.log('save alist config')
const url = $('#url').val()
const username = $('#username').val()
const password = $('#password').val()
config.url = url
config.username = username
config.password = password
delete config.ok
await GM.setValue('config', JSON.stringify(config))
}
function valideteString(s) {
return s != undefined && s.length > 0
}
async function loadAListConfig() {
const configString = await GM.getValue('config', '{}')
let config = JSON.parse(configString)
if (valideteString(config.url) && valideteString(config.username) && valideteString(config.password) && valideteString(config.token)) {
config.ok = true
} else {
console.log('config not set')
config = {
url: 'http://localhost:5244',
username: 'admin',
password: '',
token: '',
ok: false,
}
}
return config
}
function onProgress(response, req) {
console.log('onprogress', req, response)
if (response.lengthComputable) {
console.log(`${req}: ${response.loaded}/${response.total}`)
if (response.loaded < response.total) {
//$("body").addClass('loading')
} else {
//$("body").removeClass('loading')
}
}
}
function getAliyunShareInfo() {
const shareUrl = location.href
const regex = /https:\/\/www.aliyundrive.com\/s\/([^/]+)/
const m = shareUrl.match(regex)
if (m) {
const shareId = m[1]
// get aliyundrive share token
const requestData = {
share_id: shareId,
share_pwd: '',
}
$("body").addClass('loading')
GM_xmlhttpRequest({
method: 'POST',
url: 'https://api.aliyundrive.com/v2/share_link/get_share_token',
headers: {
'Content-Type': 'application/json',
},
data: JSON.stringify(requestData),
onload: function(response) {
$("body").removeClass('loading')
//console.log(response.responseText)
const tokenObj = JSON.parse(response.responseText)
console.log('1. get aliyundrive share token', tokenObj)
// get share info for AList
const requestData = {
share_id: shareId,
limit: 20,
order_by: 'name',
order_direction: 'DESC',
parent_file_id: 'root',
image_url_process: 'image/resize,w_1920/format,jpeg/interlace,1',
image_thumbnail_process: 'image/resize,w_256/format,jpeg',
video_thumbnail_process: 'video/snapshot,t_1000,f_jpg,ar_auto,w_256',
}
$("body").addClass('loading')
GM_xmlhttpRequest({
method: 'POST',
url: 'https://api.aliyundrive.com/adrive/v2/file/list_by_share',
headers: {
'Content-Type': 'application/json',
'x-share-token': tokenObj.share_token,
},
data: JSON.stringify(requestData),
onload: function(response) {
$("body").removeClass('loading')
//console.log(response.responseText)
const shareInfoObj = JSON.parse(response.responseText)
console.log('2. list aliyundrive share', shareInfoObj)
const shareInfo = {
shareId: shareId,
root: 'root',
name: `阿里云共享${Date.now()}`,
}
if (shareInfoObj.items.length === 1) {
shareInfo.root = shareInfoObj.items[0].file_id
shareInfo.name = shareInfoObj.items[0].name
}
// add share to AList
console.log(shareInfo)
addAliyunShareToAList(shareInfo)
},
onprogress: function(response) {onProgress(response, 1)},
})
},
onprogress: function(response) {onProgress(response, 2)},
})
}
}
function addAliyunShareToAList(shareInfo) {
if (!valideteString(shareInfo.shareId) || !valideteString(shareInfo.root) || !valideteString(shareInfo.name)) {
console.log('shareInfo not complete', shareInfo)
return
}
const requestData = {
username: config.username,
password: config.password,
otp_code: '',
}
$("body").addClass('loading')
GM_xmlhttpRequest({
method: 'POST',
url: `${config.url}/api/auth/login`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
data: JSON.stringify(requestData),
onload: function(response) {
$("body").removeClass('loading')
//console.log(response.responseText)
const tokenObj = JSON.parse(response.responseText)
console.log('3. get AList login token', tokenObj)
if (tokenObj.code !== 200) {
alert('AList用户名或密码配置不正确,请重新配置')
return
}
const additionData = {
refresh_token: config.token,
share_id: shareInfo.shareId,
share_pwd: '',
root_folder_id: shareInfo.root,
order_by: '',
order_direction: '',
}
const requestData = {
driver: 'AliyundriveShare',
mount_path: shareInfo.name,
extract_folder: '',
remark: '',
order: 0,
cache_expiration: 30,
down_proxy_url: '',
web_proxy: false,
webdav_policy: '302_redirect',
addition: JSON.stringify(additionData),
}
$("body").addClass('loading')
GM_xmlhttpRequest({
method: 'POST',
url: `${config.url}/api/admin/storage/create`,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': tokenObj.data.token,
},
data: JSON.stringify(requestData),
onload: function(response) {
$("body").removeClass('loading')
//console.log(response.responseText)
const resObj = JSON.parse(response.responseText)
console.log('4. create AList storage', resObj)
if (resObj.code !== 200) {
alert(`添加失败:${resObj.message}`)
} else {
$('.share-name').text(shareInfo.name)
$('.alist-add-result').dialog({
buttons: {
'查看AList': function() {
$(this).dialog("close")
GM.openInTab(`${config.url}/${shareInfo.name}`, false)
},
'取消': function() {
$(this).dialog("close")
}
}
});
}
},
onprogress: function(response) {onProgress(response, 3)},
})
},
onprogress: function(response) {onProgress(response, 4)},
})
}
})();