Compare commits

...

332 Commits

Author SHA1 Message Date
06f3618897 refactor: filesystem struct 2022-07-27 17:09:48 +08:00
b399c924b7 chore: slice convert util 2022-07-27 17:08:29 +08:00
e707d6b26e chore: change select values case 2022-07-27 15:49:18 +08:00
4ba04fa7db chore: rename main items 2022-07-27 11:43:49 +08:00
5166d73b4d chore: unified function name 2022-07-23 21:49:09 +08:00
826e4807dc chore: add current user log 2022-07-23 21:33:53 +08:00
4691142f80 fix: webdav_policy default value 2022-07-23 21:19:27 +08:00
9d92834ee3 chore: password can be empty when update me 2022-07-23 20:49:16 +08:00
4f3129ec28 feat: change current user's profile 2022-07-23 20:42:12 +08:00
fb65e98fa3 chore: add fuse package 2022-07-20 00:39:20 +08:00
90a5c175ed feat: virtual driver 2022-07-19 19:55:54 +08:00
872e7cf87b fix: virtual obj is a folder 2022-07-19 18:10:02 +08:00
638db77ca1 chore: rename local struct 2022-07-19 17:11:53 +08:00
fe94016289 chore: set default root folder in driver config 2022-07-19 17:07:12 +08:00
184b9d1e6c feat: get storage by id api 2022-07-18 23:02:14 +08:00
e08810a12f chore: fix test typo 2022-07-18 14:52:34 +08:00
303d245e0f docs: add sponsor 2022-07-18 00:48:55 +08:00
a16da3b45e chore: fix typo 2022-07-12 18:41:16 +08:00
2bff656f00 chore: rename VirtualPath to MountPath 2022-07-12 14:11:37 +08:00
fbc858b43c chore: optimize get settings 2022-07-12 14:03:03 +08:00
4ac312fd07 chore: add version to aria handle 2022-07-12 14:02:29 +08:00
b1d563c874 chore: add uuid to token 2022-07-12 14:01:43 +08:00
6ebb36b2eb chore: deprecated settings test data 2022-07-11 22:36:30 +08:00
3691ee5861 chore: use variable 2022-07-11 22:22:30 +08:00
dc38f21294 chore: rename controllers to handles 2022-07-11 17:12:50 +08:00
8971a924f1 fix: clear password while get current user 2022-07-10 17:09:03 +08:00
18b218c6c9 fix: the variable has the same name as the package 2022-07-10 16:39:55 +08:00
a25d76ef6e chore: fix typo 2022-07-10 16:20:13 +08:00
69d1287254 chore: remove wrapper of user 2022-07-10 15:47:09 +08:00
f102b130db chore: public settings no auth required 2022-07-10 15:23:08 +08:00
fc1204c914 chore: rename account to storage 2022-07-10 14:45:39 +08:00
efa20cc7bd feat: dirs api 2022-07-10 14:09:31 +08:00
e28c1e436d fix: only file have raw_url 2022-07-08 15:56:29 +08:00
90283ef29c chore: incorrect username retry count 2022-07-07 21:31:43 +08:00
156da2b794 fix: login don't need auth 2022-07-07 14:19:24 +08:00
9ba7cf0835 chore: add base path setting 2022-07-02 16:43:07 +08:00
fb23758d12 fix: empty public settings 2022-07-02 16:12:30 +08:00
8125fee3f9 feat: put directly api 2022-07-01 17:11:22 +08:00
e3891246b9 feat: post messenger 2022-07-01 16:53:01 +08:00
a6e5edcf53 chore: fix typo 2022-07-01 16:08:08 +08:00
4340a48633 fix: put as task from web 2022-07-01 15:11:18 +08:00
4d0ae6b1ef fix: webdav move contains rename 2022-06-30 22:55:23 +08:00
53416172e7 feat: clear cache after change 2022-06-30 22:51:49 +08:00
2b1726614b feat: webdav handle 2022-06-30 22:41:55 +08:00
dd013ac0b2 chore: add webdav package 2022-06-30 18:27:26 +08:00
3934d9029e feat: hide objects 2022-06-30 16:09:06 +08:00
fba96d024f feat: add write field to list resp 2022-06-30 15:53:57 +08:00
35b04ffa9c feat: add readme field to list resp 2022-06-30 15:41:58 +08:00
e614faa99b chore: cancel task while wait for worker 2022-06-29 22:06:56 +08:00
fd55f2cbfa chore: reduce query aria2 status interval 2022-06-29 20:32:45 +08:00
f54418bdae fix: serialize task info 2022-06-29 20:28:02 +08:00
786e44d1d2 fix: init aria2 client 2022-06-29 20:07:33 +08:00
58d153e5ff fix: task list method 2022-06-29 18:56:31 +08:00
0bf724f447 feat: task manage api 2022-06-29 18:36:14 +08:00
c88680b495 chore: aria2 task wait for transfer 2022-06-29 18:12:31 +08:00
d24e51bc86 chore: user permissions 2022-06-29 18:03:12 +08:00
3c7a2f78cf chore: init db and aria2 2022-06-29 17:37:40 +08:00
8abee6504f feat: set aria2 client and add url to aria2 api 2022-06-29 17:31:37 +08:00
a09a1b814b chore: change permission check 2022-06-29 17:08:31 +08:00
bf950ee6e1 feat: set raw url in get resp 2022-06-29 16:23:31 +08:00
40548926e6 feat: fs link api 2022-06-29 16:08:55 +08:00
f275f83de0 feat: fs manage api 2022-06-29 15:01:22 +08:00
8a0915ffb1 chore: don't and slash prefix just for windows abs path 2022-06-28 22:22:02 +08:00
505b126888 chore: optional get func for driver 2022-06-28 22:13:47 +08:00
96380a50da feat: file proxy handle 2022-06-28 21:58:46 +08:00
d1efec4539 chore: common err resp log 2022-06-28 18:12:53 +08:00
67bc66fedf feat: file down handle 2022-06-28 18:00:11 +08:00
d89ec89d51 feat: sign of file 2022-06-28 15:12:40 +08:00
5dbf5db4ff feat: token and reset 2022-06-28 14:18:10 +08:00
7903ed1f52 chore: change fs get and list resp 2022-06-27 21:34:13 +08:00
c8f10703b7 feat: obj get api 2022-06-27 21:15:39 +08:00
db6b5f8950 chore: path standardize 2022-06-27 20:56:17 +08:00
74973bc5b5 fix: local relative path 2022-06-27 20:37:05 +08:00
7c0b86a9cd feat: obj list api 2022-06-27 19:51:23 +08:00
c6007aa9e6 feat: sort obj list 2022-06-27 19:10:02 +08:00
f01a81ee9c chore: settings util 2022-06-27 17:25:19 +08:00
005ded41c3 feat: settings manage api 2022-06-27 17:06:10 +08:00
1a148eee7c feat: initial setting items 2022-06-27 15:51:02 +08:00
e4c3ef0262 feat: setting model 2022-06-27 14:51:48 +08:00
6bb2b76e25 chore: move item types 2022-06-27 14:32:21 +08:00
e71aff9d94 chore: keep guest in memory 2022-06-27 14:29:36 +08:00
490df4f5fe fix: typo of environment variable (close #1280) 2022-06-27 14:01:15 +08:00
087fae1b15 chore: webdav policy of account 2022-06-27 13:58:21 +08:00
2aff218356 fix: gin.Context nil pointer 2022-06-26 20:31:04 +08:00
b98cd915a4 feat: driver manage api 2022-06-26 20:25:02 +08:00
3349982312 fix(driver): additional items 2022-06-26 20:18:12 +08:00
5783aa99f1 feat: account manage api 2022-06-26 20:00:36 +08:00
cab498e376 feat: user manage api 2022-06-26 19:36:27 +08:00
6b9bca893b chore: change whether print log 2022-06-26 19:20:19 +08:00
c67f128f15 chore: move server package to root 2022-06-26 19:10:14 +08:00
4cef3adc90 feat: meta manage api 2022-06-26 19:09:28 +08:00
acd4083399 chore: ignore password for get current user 2022-06-26 16:55:37 +08:00
7cbfe93a02 chore: set guest while token is empty 2022-06-26 16:39:02 +08:00
54ca68e4b3 chore: init users 2022-06-25 22:05:02 +08:00
b474eefd87 chore: rename store to db 2022-06-25 21:36:35 +08:00
c5295f4d72 feat: user jwt login 2022-06-25 21:34:44 +08:00
306b90399c chore: move conf package 2022-06-25 20:38:02 +08:00
7dadab95b2 fix: missed mimetype of stream in aria2 monitor 2022-06-25 15:15:54 +08:00
ee2bc99e4c feat: cancel copy for upload 2022-06-25 15:14:03 +08:00
935416de45 chore: clear parent folder cache after upload 2022-06-24 14:24:39 +08:00
3f49271db6 feat(fs): add put return after finished 2022-06-24 14:21:28 +08:00
956a5ae906 perf: extract fs func and add error log 2022-06-23 23:03:11 +08:00
40b7ecc845 chore(aria2): export task manager 2022-06-23 21:24:23 +08:00
92983aa185 chore: get or remove by states 2022-06-23 21:19:01 +08:00
6c61f1d261 chore: add state for task 2022-06-23 21:09:54 +08:00
aedcae840d test(aria2): download and transfer file 2022-06-23 17:06:17 +08:00
ffdb198247 feat(local): basic function of driver 2022-06-23 17:06:07 +08:00
3a1fcbef1c chore: close stream after put 2022-06-23 17:05:03 +08:00
ffa0bc294a chore: optimize standardize path 2022-06-23 17:04:37 +08:00
a65dcb48b4 chore: use abs temp dir 2022-06-23 16:49:37 +08:00
b971b13362 feat: dir and file check 2022-06-23 16:09:22 +08:00
d77dea733f chore: rename errors 2022-06-23 16:03:27 +08:00
fd5c3e831d chore: change size of file to int64 2022-06-23 15:57:36 +08:00
c3040fdfc3 chore: move errors 2022-06-23 15:57:10 +08:00
2612cd7f1c test(aria2): init aria2 client 2022-06-22 19:36:49 +08:00
3fe0a7bf6b refactor(task): remove Data field 2022-06-22 19:28:41 +08:00
a6df492fff refactor(aria2): extract monitor 2022-06-22 15:16:13 +08:00
72208e052a chore(fs): rename some variable and param 2022-06-22 15:03:27 +08:00
f6242d46b1 feat: add uri to aria2 2022-06-21 17:37:02 +08:00
55c4a925ba chore(fs): rename some param 2022-06-21 16:37:51 +08:00
9633af4e25 fix: typo and error handle 2022-06-21 16:25:45 +08:00
55d6434daa refactor(task): generic task manager 2022-06-21 16:14:37 +08:00
1b3387ca1a chore: aria2 notifier 2022-06-20 22:29:52 +08:00
6c552a9d62 chore: aria2 related function 2022-06-20 20:34:58 +08:00
4db25605e7 fix(fs): typo 2022-06-20 19:50:59 +08:00
a61bb6ab1f chore: add is it support upload config for driver 2022-06-20 17:14:08 +08:00
31ff31d3dd chore: add callback for task 2022-06-20 17:13:19 +08:00
d665cce739 feat: add task work limit 2022-06-18 20:38:14 +08:00
dd46e99e66 chore: set addition type as text 2022-06-18 20:10:35 +08:00
adf0178bb7 feat: add progress for task 2022-06-18 20:06:45 +08:00
6ad2cf2003 test: add task manager test 2022-06-17 22:09:34 +08:00
68ca2abd0c chore: change task.ID to uint64 2022-06-17 21:52:31 +08:00
d73a9e4734 fix: format % is missing verb at end of string 2022-06-17 21:42:56 +08:00
73c0c0bf44 chore: export copy and upload task manager 2022-06-17 21:38:37 +08:00
72a76599e4 feat: add upload file to task manager 2022-06-17 21:35:46 +08:00
b9f9e5853e fix: copy task name 2022-06-17 21:30:16 +08:00
fa6e918fc7 feat: add copy to task manager 2022-06-17 21:23:44 +08:00
53e969e894 feat: task manager 2022-06-17 16:31:41 +08:00
6d0e54d87e chore: add driver for issue template 2022-06-17 16:31:31 +08:00
626e878861 chore: update issue template 2022-06-17 16:31:25 +08:00
52575f6ad6 feat: add meta model and test 2022-06-17 16:31:19 +08:00
ca13678105 fix: add where for get user by name 2022-06-17 16:31:19 +08:00
355db3ab9b feat: standardization virtual path while create and update 2022-06-17 16:31:19 +08:00
04f43cb684 fix: comment typo 2022-06-17 16:31:19 +08:00
52ab1310be feat: set path as ID if it's empty 2022-06-17 16:31:19 +08:00
56c95eadea feat: add user model 2022-06-17 16:30:49 +08:00
1df5472855 docs: add version explanation 2022-06-15 21:58:20 +08:00
9aa7074600 test: add get balanced account test 2022-06-15 21:52:31 +08:00
69647f73f0 chore: rename some symbols 2022-06-15 20:41:17 +08:00
09ef7c7106 refactor: change driver interface 2022-06-15 20:31:23 +08:00
d9eb188b7a feat: check parent dir before upload 2022-06-15 19:20:36 +08:00
083395ee53 feat: recursive create folder 2022-06-15 19:10:11 +08:00
2d60dab13c feat: copy files between 2 accounts 2022-06-15 18:58:26 +08:00
4fa7846f00 feat(local): check root folder while init 2022-06-15 18:48:30 +08:00
9fcdbec5c9 feat: get file stream from link 2022-06-15 18:08:13 +08:00
979f8383d8 chore: move some types to model 2022-06-15 18:06:42 +08:00
2cddd3cf2b chore: add aria2 rpc package 2022-06-15 17:15:22 +08:00
c65a9b3001 fix: typo 2022-06-15 14:57:13 +08:00
066ddd3e09 chore: create temp file util 2022-06-15 14:56:43 +08:00
6cdd85283b chore: reduce cache shards 2022-06-14 22:37:41 +08:00
5780d9d834 test: add GetAccountVirtualFilesByPath test 2022-06-14 22:23:33 +08:00
097b516dc5 fix: wrong virtual file name 2022-06-14 22:23:10 +08:00
b73dbee7e6 chore: don't export func GetAccountsByPath 2022-06-14 19:49:17 +08:00
b8e4a2e7c0 test: add driver and account test 2022-06-14 19:44:25 +08:00
0d4542a3f1 fix: delete account driver after get 2022-06-14 19:16:27 +08:00
7c4d28d55a feat: replace with generic_sync.MapOf 2022-06-14 19:09:54 +08:00
1143331b4d chore: task and message package 2022-06-14 17:19:43 +08:00
e4b956b091 chore: set log structure first 2022-06-14 17:18:58 +08:00
e3d2e6dd64 fix(local): local storage should haven't cache 2022-06-14 17:18:11 +08:00
6accc2eff6 feat: add NoCache config for driver 2022-06-13 21:15:58 +08:00
c525406516 feat: add cache for list files 2022-06-13 21:14:01 +08:00
6056fdbddc feat: use singleflight to prevent cache breakdown 2022-06-13 20:24:13 +08:00
2f52b5d354 feat: link cache 2022-06-13 19:56:33 +08:00
e16ab876aa feat: add expiration field for Link 2022-06-13 15:39:47 +08:00
3e8f36e9f3 feat: get root folder file 2022-06-13 14:53:44 +08:00
3135775250 fix: composite literal uses unkeyed fields 2022-06-11 19:01:20 +08:00
77b0c69112 feat: extract get function 2022-06-11 14:43:03 +08:00
ec89bb70c7 feat: fs and operations 2022-06-10 21:00:51 +08:00
cd7e9974df feat: add root prefix before operate 2022-06-10 20:20:45 +08:00
354dee67dc feat(fs): get file object 2022-06-10 17:26:43 +08:00
122b7baa73 feat(fs): list files 2022-06-10 17:18:27 +08:00
c5e5666b64 feat: set account modified time 2022-06-10 16:51:20 +08:00
7b6f11fa52 feat: get account by path 2022-06-10 16:49:52 +08:00
2481676c46 feat: get account files by path 2022-06-09 23:05:52 +08:00
164dab49ac feat: get accounts by path 2022-06-09 23:05:27 +08:00
e1a2ed0436 feat: driver and account operate 2022-06-09 17:11:46 +08:00
5b73b68eb5 feat: add log enable config 2022-06-09 15:12:34 +08:00
cd21f14106 fix: additional field type 2022-06-08 17:01:36 +08:00
65fba7936c chore: replace string with const 2022-06-08 16:42:06 +08:00
ba648fa10c feat: get type from field's type 2022-06-08 16:32:20 +08:00
ae755db2d2 feat: driver additional items parse 2022-06-08 16:20:58 +08:00
677047c80b feat: improve driver 2022-06-07 22:02:41 +08:00
0d93a6aa41 feat: driver manage 2022-06-07 18:13:55 +08:00
84eb978731 feat: sort and proxy config 2022-06-07 16:38:31 +08:00
ac0f984136 feat: driver config 2022-06-07 16:31:28 +08:00
79965ab4b3 feat(driver): add args to init and update func 2022-06-06 22:54:03 +08:00
492476dfe4 feat: additional info of account 2022-06-06 22:31:56 +08:00
62ac168226 chore: delete placeholder README 2022-06-06 22:08:39 +08:00
09616dbe25 feat: set gin log writer 2022-06-06 22:06:33 +08:00
fced60c2b5 feat: basic structure 2022-06-06 21:48:53 +08:00
b76060570e refactor: init v3 2022-06-06 16:28:37 +08:00
eb15bce24b ci: auto generate changelog 2022-06-06 16:22:12 +08:00
52814266b8 chore: Merge pull request #1200 from alist-org/all-contributors/add-XZB-1248
docs: add XZB-1248 as a contributor for code
2022-06-06 16:16:11 +08:00
f845ec05e0 docs: update .all-contributorsrc [skip ci] 2022-06-06 08:15:50 +00:00
29fb02c886 docs: update CONTRIBUTORS.md [skip ci] 2022-06-06 08:15:49 +00:00
072e854a71 chore: Merge pull request #1199 from alist-org/dev
Merge dev branch
2022-06-06 16:10:35 +08:00
cae0a5f603 chore: Merge pull request #1191 from XZB-1248/dev
fix: filename is urlencoded when using Safari
2022-06-03 21:57:42 +08:00
XZB
7c6d8ca222 fix(proxy): filename is urlencoded when using Safari 2022-06-02 18:13:17 +08:00
f6be50f15a fix(189): login and get link (close #1182) 2022-05-31 16:03:54 +08:00
c35d54d092 chore: Merge pull request #1167 from Xhofe/dev 2022-05-28 21:01:47 +08:00
323dad2a1c fix(sftp): infinite loop while remove file (close #1094) 2022-05-28 21:01:04 +08:00
62aefc4f68 fix(189): new resty client 2022-05-28 20:43:13 +08:00
6a7eb8b3eb fix: don't save search files of balance account (close #1125) 2022-05-21 22:12:18 +08:00
eb549f2631 feat: add pdf viewer url to settings (close #1109) 2022-05-19 15:31:47 +08:00
9207eb69ee feat: add m4v to default video types (close #1114) 2022-05-19 15:31:40 +08:00
866df0540b chore: Merge pull request #1110 from foxxorcat/dev
增加123流式上传选择
2022-05-17 12:49:22 +08:00
04e04a1aa6 fix(189pc): delete user-agent for upload 2022-05-16 23:33:12 +08:00
6a66e39d5b feat(123):add io stream upload 2022-05-16 21:03:00 +08:00
f2b2728be7 fix(123,189pc,alidriver,xunlei):tempfile remove 2022-05-16 09:48:33 +08:00
39b8f28fc4 fix: disable pprof while not debug 2022-05-15 16:17:52 +08:00
e1ccc0b215 chore: Merge pull request #1093 from Xhofe/dev
2.5.2
2022-05-13 17:39:26 +08:00
87e339850d fix(ftp): remove dir (#1082) 2022-05-13 17:38:22 +08:00
79c9b6ac77 chore: Merge pull request #1090 from foxxorcat/dev (#1090) 2022-05-13 13:54:04 +08:00
aeb2297f1f perf(123):file thumbnail 2022-05-12 22:27:32 +08:00
3b59bb5c09 perf(123):upload 2022-05-12 21:39:55 +08:00
bc4bac921f chore: Merge pull request #1089 from foxxorcat/dev
修复迅雷一些已知问题
2022-05-12 20:42:49 +08:00
f917882a84 perf(xunlei):upload 2022-05-12 19:18:28 +08:00
6a67d1cf69 fix(xunlei):check captchaToken 2022-05-12 19:15:39 +08:00
041b3587bf fix(xunlei):turn page 2022-05-12 13:27:49 +08:00
0eef7a129c fix(xunlei):the verification code cannot be obtained from the mobile phone number or email 2022-05-12 13:26:12 +08:00
4b635f06e3 fix(189pc): delete user-agent for upload 2022-05-11 20:01:15 +08:00
279111a8e2 chore: Merge pull request #1079 from foxxorcat/dev 2022-05-10 22:35:59 +08:00
67674835da fix(alidriver):fast upload file is not close 2022-05-10 21:54:44 +08:00
732e9eb1c3 feat:add pprof 2022-05-10 21:40:43 +08:00
b6af9aa587 fix(139,189,189pc,alidrive,onedrive,yandex):http response body is not close #1072 2022-05-10 21:37:48 +08:00
a9027c0f06 fix(baidu.photo):update download api 2022-05-10 20:35:19 +08:00
d780fa18a5 fix(sftp): error while has no files(close #1078) 2022-05-10 18:18:06 +08:00
d5626d6e2f fix: cancel QueryEscape Disposition (close #1074) 2022-05-10 18:16:32 +08:00
52dcbfe1a4 fix(xunlei):missing x-client-id error in some user requests 2022-05-09 18:14:52 +08:00
bf0ee3d315 refactor(baidu.photo): add a file api of download 2022-05-09 12:43:51 +08:00
0237e78c1e chore: Merge branch 'dev' into v2 2022-05-08 14:30:01 +08:00
44b8c6abf7 fix(189): typo 2022-05-08 14:28:26 +08:00
33e1acd344 chore: Merge pull request #1060 from Xhofe/dev
Dev 2.5.1
2022-05-08 14:26:27 +08:00
c54cb61f14 chore: add debug info 2022-05-08 14:25:37 +08:00
734b204709 chore: change ocr api 2022-05-08 14:22:07 +08:00
b7d9c5e4ff chore: Merge pull request #1059 from foxxorcat/dev (close #1053)
fix baidu.photo and xunlei
2022-05-08 13:59:58 +08:00
e698b457b9 fix(baidu.photo):windows path error 2022-05-08 13:37:56 +08:00
5258c21656 fix(xunlei):login error 2022-05-08 13:36:36 +08:00
1ca9a3d14e chore: Merge pull request #1052 from Xhofe/dev
docs: add baidu.photo
2022-05-07 16:47:22 +08:00
f23bec9a35 docs: add baidu.photo [skip ci] 2022-05-07 16:43:02 +08:00
62a1acd1f4 chore: Merge pull request #1051 from Xhofe/all-contributors/add-WntFlm
docs: add WntFlm as a contributor for code
2022-05-07 16:38:33 +08:00
fa6e3fe567 docs: update .all-contributorsrc [skip ci] 2022-05-07 08:38:10 +00:00
b71b62ee35 docs: update CONTRIBUTORS.md [skip ci] 2022-05-07 08:38:09 +00:00
410b4939a4 chore: Merge pull request #1050 from Xhofe/all-contributors/add-ericarena
docs: add ericarena as a contributor for code
2022-05-07 16:37:24 +08:00
62c0071f29 docs: update .all-contributorsrc [skip ci] 2022-05-07 08:36:58 +00:00
f043a41005 docs: update CONTRIBUTORS.md [skip ci] 2022-05-07 08:36:57 +00:00
2e9da57036 chore: Merge pull request #1048 from Xhofe/all-contributors/add-Windman1320
docs: add Windman1320 as a contributor for code
2022-05-07 16:36:17 +08:00
d83cd37984 docs: update .all-contributorsrc [skip ci] 2022-05-07 08:35:50 +00:00
bad8b0ebbb docs: update CONTRIBUTORS.md [skip ci] 2022-05-07 08:35:49 +00:00
4535e65948 chore: Merge pull request #1047 from Xhofe/dev
Dev 2.5.0
2022-05-07 16:29:10 +08:00
3b413c2ee2 chore: Merge pull request #1021 from WntFlm/mimefix
fix(webdav): empty mimeType
2022-05-01 19:13:29 +08:00
427ae56333 chore: Merge pull request #1020 from foxxorcat/dev
fix(xunlei):download link speed limit
2022-05-01 13:25:54 +08:00
658fd5ad6e fix(webdav): empty mimeType
Now mimeType will always be a non-empty string, by defaulting it to "application/octet-stream".
2022-05-01 09:42:25 +08:00
11830bb51c fix(xunlei):download link speed limit 2022-04-30 21:41:15 +08:00
75c98429bf fix(webdav): wrong MIMEType (close #1007) 2022-04-29 14:09:51 +08:00
f77ea1b3a5 chore: Merge pull request #1011 from foxxorcat/dev
增加一刻相册支持,优化迅雷代码
2022-04-29 14:08:04 +08:00
0a8bd96d33 feat: support baidu.photo 2022-04-28 23:44:22 +08:00
68f37fc11f refactor(xunlei): optimized code 2022-04-28 23:15:37 +08:00
d6775cda69 fix(123): can't delete folder (close #1009) 2022-04-28 21:17:11 +08:00
43c6e07bac feat: add aria2 download settings(#1000)
* feat: add aria2 support

在右键菜单中增加了使用aria2下载的item,可以直接发送选中的文件链接到aria2,省略复制再粘贴到aria2的步骤

* feat: set default value for `Aria2 RPC url`

Co-authored-by: Xhofe <i@nn.ci>
2022-04-28 18:05:07 +08:00
4901e9080c fix(quark): file size over i32 (close #997) 2022-04-26 15:22:39 +08:00
48049a5ea3 docs: upgrade golang version [skip ci] 2022-04-25 16:05:49 +08:00
bd7260f0ff chore: base for template 2022-04-24 21:22:24 +08:00
6c0d54394f chore: Merge pull request #992 from Xhofe/dev
Dev 2.4.3
2022-04-24 17:40:26 +08:00
ce5dacbf3f build: build musl first 2022-04-24 17:39:25 +08:00
08aaa5e2c0 build: rm .git before xgo 2022-04-24 16:53:47 +08:00
42c0e438d5 fix(webdav): sharepoint upload 2022-04-24 15:38:17 +08:00
e4df146043 fix(webdav): sharepoint repeat login 2022-04-24 15:37:59 +08:00
27b7dae113 feat(webdav): support range get 2022-04-23 22:43:02 +08:00
293d574ce7 build: specify xgo version 2022-04-23 16:53:26 +08:00
56b3b35556 chore: Merge pull request #984 from Xhofe/dev
2.4.2
2022-04-21 22:34:12 +08:00
a7a0e85a46 docs: update qq group 2022-04-21 22:31:17 +08:00
95c0106fdd feat(onedrive): default redirect_uri(close #967) 2022-04-20 15:21:48 +08:00
6612338fc1 fix(189pc): InvalidSessionKey (fix #920) 2022-04-20 15:16:30 +08:00
c276a1541f chore: delete useless comment 2022-04-18 18:32:29 +08:00
cc96a5bbdb chore: add windows bin to gitignore 2022-04-18 18:31:10 +08:00
0810561a8a fix(xunlei): check err prevent stack overflow 2022-04-18 18:29:21 +08:00
82a5c43b94 chore: Merge pull request #961 from Xhofe/dev
Dev v2.4.1
2022-04-17 23:12:02 +08:00
d38f36ef44 chore: delete useless test file 2022-04-17 22:50:54 +08:00
f9533440c7 build: cancel static link for glibc 2022-04-17 22:50:28 +08:00
41a186b051 fix(native): set size of folder to 0 2022-04-17 21:12:55 +08:00
4e6a44253c chore: Merge pull request #958 from Xhofe/dev
Dev 2.4.0
2022-04-17 17:29:02 +08:00
ebda77cd43 docs: add sharepoint to webdav 2022-04-17 17:16:39 +08:00
1a1e86521f fix(quark): lost files while number of files > 100 (fix #947) 2022-04-16 21:06:33 +08:00
1b4740dae3 fix: file deduplication (fix #941) 2022-04-16 17:28:16 +08:00
91fc8df84e build: cancel static link for darwin 2022-04-16 17:08:48 +08:00
e6ecf1fa30 feat(189): add tips get page 2022-04-16 17:08:21 +08:00
183a6f1b3a build: static link for compile 2022-04-16 16:55:55 +08:00
3c2d59e272 build: use crazymax/xgo 2022-04-16 16:43:21 +08:00
fd80e3eaf7 build: Use -buildvcs=false to disable VCS stamping 2022-04-16 15:23:21 +08:00
4928c331a8 build: upgrade go version 2022-04-16 15:04:28 +08:00
3ad75e54cb refactor(baidu): add a crack api of download
* 修复百度网盘API文件大于20M问题

* refactor: keep the official api

Co-authored-by: Xhofe <i@nn.ci>
2022-04-16 14:52:36 +08:00
a2cf3ab42e workflow: add checkboxes for issue template 2022-04-14 22:16:15 +08:00
e24814ee2f chore: Merge pull request #938 from Xhofe/feature/search
Feature/search
2022-04-13 21:57:59 +08:00
37b42e6e17 fix(sftp): add port 2022-04-12 20:22:09 +08:00
30ebb0f4d4 feat: support other region sharepoint with webdav 2022-04-12 09:54:22 +08:00
8e059c64b5 feat: webdav for sharepoint online (#460) 2022-04-11 21:32:38 +08:00
395de069c2 fix: extract_folder causes sorting confusion (close #929) 2022-04-11 16:50:47 +08:00
4c22f37d54 fix(search): file type 2022-04-08 22:47:04 +08:00
a73a40133d feat: search api 2022-04-08 22:03:26 +08:00
6591af58ea feat: store search file index 2022-04-08 21:51:21 +08:00
58568d4ef6 fix(189cloud): remove empty Authorization 2022-04-07 16:56:50 +08:00
5295593bf8 fix(189cloudpc): wrong modified time (close #910) 2022-04-06 17:59:45 +08:00
24d031d578 feat: clear temp file while start 2022-04-06 16:24:31 +08:00
7141bf0358 build: static compilation for musl 2022-04-06 16:19:15 +08:00
c5d707cf0a fix: multilevel virtual path (close #904) 2022-04-06 15:23:10 +08:00
dfcf66b43e fix(native): set size of folder to 0 2022-04-06 15:11:03 +08:00
fa6ee62cf0 feat: global readme url 2022-04-05 20:17:27 +08:00
1428d90361 feat: meta readme 2022-04-05 20:17:16 +08:00
c413c22201 fix(quark): denied by Referer ACL 2022-04-04 20:55:48 +08:00
9b6adecd62 feat: sharepoint webdav (unfinished) 2022-04-04 20:55:22 +08:00
b3540cf539 docs: add SFTP in readme [skip ci] 2022-04-03 18:20:33 +08:00
f8650c9c0b fix(webdav): remove default Authorization header (close #893) 2022-04-03 18:19:15 +08:00
bf2e5768d6 feat: add rapid upload switch for 189pc and alidrive (#892)
* 189PC增加快传开关

* alidrive增加快传开关
2022-04-03 17:56:21 +08:00
18c82e79b5 feat: sftp support 2022-04-02 19:28:43 +08:00
d69d24a5b2 fix(alidrive): judge status of delete folder (close #886) 2022-04-02 14:34:34 +08:00
342729179d chore: Merge pull request #884 from Xhofe/dev
fix: some issues of webdav due to virtual path
2022-04-01 22:02:39 +08:00
0537449335 fix(webdav): virtual path no account 2022-04-01 21:57:55 +08:00
df90311453 fix(webdav): alist path not found 2022-04-01 20:40:57 +08:00
876579ea3b chore: Merge pull request #874 from Xhofe/dev
support mount to root path
2022-04-01 09:42:37 +08:00
e83081380e workflow: cancel build docker for pr 2022-04-01 09:40:53 +08:00
9daeaf7562 fix: virtual path, support mount to root path 2022-04-01 09:40:08 +08:00
273 changed files with 15111 additions and 18944 deletions

View File

@ -51,11 +51,47 @@
"contributions": [
"doc"
]
},
{
"login": "Windman1320",
"name": "Windman",
"avatar_url": "https://avatars.githubusercontent.com/u/9999486?v=4",
"profile": "https://github.com/Windman1320",
"contributions": [
"code"
]
},
{
"login": "ericarena",
"name": "ericarena",
"avatar_url": "https://avatars.githubusercontent.com/u/4518927?v=4",
"profile": "https://github.com/ericarena",
"contributions": [
"code"
]
},
{
"login": "WntFlm",
"name": "WntFlm",
"avatar_url": "https://avatars.githubusercontent.com/u/34620278?v=4",
"profile": "https://github.com/WntFlm",
"contributions": [
"code"
]
},
{
"login": "XZB-1248",
"name": "XZB-1248",
"avatar_url": "https://avatars.githubusercontent.com/u/28593573?v=4",
"profile": "https://github.com/XZB-1248",
"contributions": [
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "alist",
"projectOwner": "Xhofe",
"projectOwner": "alist-org",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true

View File

@ -1,12 +1,20 @@
name: "Bug report"
description: Bug report
labels: [pending triage]
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this bug report, please **confirm that your issue is not a duplicate issue and not because of your operation or version issues**
感谢您花时间填写此错误报告,请**务必确认您的issue不是重复的且不是因为您的操作或版本问题**
- type: checkboxes
attributes:
label: Please make sure of the following things
description: You may select more than one, even select all.
options:
- label: I have read the [documentation](https://alist-doc.nn.ci).
- label: I'm sure there are no duplicate issues or discussions.
- label: I'm sure it's due to `alist` and not something else(such as `Dependencies` or `Operational`).
- type: input
id: version
attributes:
@ -15,6 +23,14 @@ body:
placeholder: v2.0.0
validations:
required: true
- type: input
id: driver
attributes:
label: Driver used / 使用的存储驱动
description: What storage driver are you using?
placeholder: "for example: Onedrive"
validations:
required: true
- type: textarea
id: bug-description
attributes:

View File

@ -1,7 +1,16 @@
name: "Feature request"
description: Feature request
labels: ["enhancement: pending triage"]
labels: [enhancement]
body:
- type: checkboxes
attributes:
label: Please make sure of the following things
description: You may select more than one, even select all.
options:
- label: I have read the [documentation](https://alist-doc.nn.ci).
- label: I'm sure there are no duplicate issues or discussions.
- label: I'm sure this feature is not implemented.
- label: I'm sure it's a reasonable and popular requirement.
- type: textarea
id: feature-description
attributes:

View File

@ -1,50 +0,0 @@
name: build
on:
push:
branches: [ '**' ]
pull_request:
branches: [ '**' ]
jobs:
build:
strategy:
matrix:
platform: [ubuntu-latest]
go-version: [1.17]
name: Build
runs-on: ${{ matrix.platform }}
steps:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Checkout
uses: actions/checkout@v2
with:
path: alist
- name: Install upx
run: |
docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest
sudo apt install upx
- name: Build
run: |
mv alist/build.sh .
bash build.sh web
mv dist/* alist/public
bash build.sh build
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: artifact
path: alist/build

View File

@ -1,46 +0,0 @@
name: build_docker
on:
push:
branches: [ v2 ]
pull_request:
branches: [ v2 ]
jobs:
build_docker:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: xhofe/alist
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Build web
run: |
bash build.sh web
mv dist/* public
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: xhofe
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x

View File

@ -1,46 +0,0 @@
name: release
on:
push:
tags:
- '*'
jobs:
release:
strategy:
matrix:
platform: [ubuntu-latest]
go-version: [1.17]
name: Release
runs-on: ${{ matrix.platform }}
steps:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout
uses: actions/checkout@v2
with:
ref: v2
path: alist
persist-credentials: false
fetch-depth: 0
- name: Install upx
run: |
docker pull techknowlogick/xgo:latest
go install src.techknowlogick.com/xgo@latest
sudo apt install upx
- name: Build
run: |
mv alist/build.sh .
bash build.sh cdn
mv dist/* alist/public
bash build.sh release
- name: Release
uses: softprops/action-gh-release@v1
with:
files: alist/build/compress/*

View File

@ -1,45 +0,0 @@
name: release_docker
on:
push:
tags:
- '*'
jobs:
docker_release:
name: Docker
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: xhofe/alist
- name: Set up Node
uses: actions/setup-node@v2
with:
node-version: '16'
- name: Build web
run: |
bash build.sh cdn
mv dist/* public
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: xhofe
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x

4
.gitignore vendored
View File

@ -22,8 +22,10 @@ dist/
# vendor/
bin/*
/alist
/alist.exe
*.json
public/*.html
public/assets/
public/public/
data/
/data
log/

View File

@ -7,7 +7,7 @@
Prerequisites:
- [git](https://nodejs.org/zh-cn/)
- [Go 1.17+](https://golang.org/doc/install)
- [Go 1.18+](https://golang.org/doc/install)
- [gcc](https://gcc.gnu.org/)
- [nodejs](https://nodejs.org/)
@ -17,12 +17,12 @@ Clone `alist` and `alist-web` anywhere:
$ git clone https://github.com/Xhofe/alist.git
$ git clone https://github.com/Xhofe/alist-web.git
```
You should switch to the dev branch for development.
You should switch to the `main` branch for development.
## Preview your change
### backend
```shell
$ go run alist.go
$ go run cmd/alist.go
```
### frontend
```shell
@ -102,4 +102,4 @@ The rest of the commit message is then used for this.
## Submit a pull request
Push your branch to your `alist` fork and open a pull request against the
`dev` branch.
`main` branch.

View File

@ -1,5 +1,5 @@
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors-)
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
## Contributors ✨
@ -11,11 +11,17 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/Xhofe/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=foxxorcat" title="Code">💻</a></td>
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=vg-land" title="Code">💻</a></td>
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/Xhofe/alist/commits?author=Clansty" title="Documentation">📖</a></td>
<td align="center"><a href="http://nn.ci"><img src="https://avatars.githubusercontent.com/u/36558727?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Xhofe</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Xhofe" title="Code">💻</a> <a href="#ideas-Xhofe" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/alist-org/alist/commits?author=Xhofe" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/foxxorcat"><img src="https://avatars.githubusercontent.com/u/95907542?v=4?s=100" width="100px;" alt=""/><br /><sub><b>foxxorcat</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=foxxorcat" title="Code">💻</a></td>
<td align="center"><a href="https://www.iflu.cf/"><img src="https://avatars.githubusercontent.com/u/63903027?v=4?s=100" width="100px;" alt=""/><br /><sub><b>道辰</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=DaoChen6" title="Documentation">📖</a></td>
<td align="center"><a href="https://vg-land.github.io/"><img src="https://avatars.githubusercontent.com/u/16739728?v=4?s=100" width="100px;" alt=""/><br /><sub><b>vg-land</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=vg-land" title="Code">💻</a></td>
<td align="center"><a href="https://c5y.moe"><img src="https://avatars.githubusercontent.com/u/18461360?v=4?s=100" width="100px;" alt=""/><br /><sub><b>凌莞~(=^▽^=)</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Clansty" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/Windman1320"><img src="https://avatars.githubusercontent.com/u/9999486?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Windman</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=Windman1320" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/ericarena"><img src="https://avatars.githubusercontent.com/u/4518927?v=4?s=100" width="100px;" alt=""/><br /><sub><b>ericarena</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=ericarena" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/WntFlm"><img src="https://avatars.githubusercontent.com/u/34620278?v=4?s=100" width="100px;" alt=""/><br /><sub><b>WntFlm</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=WntFlm" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/XZB-1248"><img src="https://avatars.githubusercontent.com/u/28593573?v=4?s=100" width="100px;" alt=""/><br /><sub><b>XZB-1248</b></sub></a><br /><a href="https://github.com/alist-org/alist/commits?author=XZB-1248" title="Code">💻</a></td>
</tr>
</table>

View File

@ -1,14 +0,0 @@
FROM alpine:edge as builder
LABEL stage=go-builder
WORKDIR /app/
COPY ./ ./
RUN apk add --no-cache bash git go gcc musl-dev; \
bash build.sh docker
FROM alpine:edge
LABEL MAINTAINER="i@nn.ci"
VOLUME /opt/alist/data/
WORKDIR /opt/alist/
COPY --from=builder /app/bin/alist ./
EXPOSE 5244
CMD [ "./alist", "-docker" ]

View File

@ -658,4 +658,4 @@ specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.

View File

@ -1,6 +1,6 @@
<div align="center">
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
<p><em>🗂Another file list program that supports multiple storage, powered by Gin and React.</em></p>
<p><em>🗂A file list program that supports multiple storage, powered by Gin and React.</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
@ -13,64 +13,17 @@
---
English | [中文](./README_cn.md) | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
[Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
## Features
- [x] Multiple storage
- [x] Local storage
- [x] [Aliyundrive](https://www.aliyundrive.com/)
- [x] OneDrive / Sharepoint ([global](https://www.office.com/), [cn](https://portal.partner.microsoftonline.cn),de,us)
- [x] [189cloud](https://cloud.189.cn) (Personal, Family)
- [x] [GoogleDrive](https://drive.google.com/)
- [x] [123pan](https://www.123pan.com/)
- [x] [Lanzou](https://pc.woozooo.com/)
- [x] [Alist](https://github.com/Xhofe/alist)
- [x] FTP
- [x] [PikPak](https://www.mypikpak.com/)
- [x] [ShandianPan](https://shandianpan.com/)
- [x] [S3](https://aws.amazon.com/s3/)
- [x] WebDav
- [x] Teambition([China](https://www.teambition.com/ ),[International](https://us.teambition.com/ ))
- [x] [Mediatrack](https://www.mediatrack.cn/)
- [x] [139yun](https://yun.139.com/) (Personal, Family)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [Baidu Disk](http://pan.baidu.com/)
- [x] [Quark](https://pan.quark.cn)
- [x] [XunleiCloud](https://pan.xunlei.com/)
- [x] Easy to deploy and out-of-the-box
- [x] File preview (PDF, markdown, code, plain text, ...)
- [x] Image preview in gallery mode
- [x] Video and audio preview (mp4, mp3, ...)
- [x] Office documents preview (docx, pptx, xlsx, ...)
- [x] `README.md` preview rendering
- [x] File permalink copy and direct file download
- [x] Dark mode
- [x] I18n
- [x] Protected routes (password protection and authentication)
- [x] WebDav (see https://alist-doc.nn.ci/en/docs/webdav for details)
- [x] [Docker Deploy](https://hub.docker.com/r/xhofe/alist)
- [x] Cloudflare workers proxy
- [x] File/Folder package download
- [x] Support video list playback and subtitles(ass,srt,vtt)
- [x] Web upload(Can allow visitors to upload), delete, mkdir, rename, move and copy
> ### New version is under development. Checkout [v2](https://github.com/alist-org/alist/tree/v2) branch for latest version.
## Discussion
Please go to our [discussion forum](https://github.com/Xhofe/alist/discussions) for general questions, **issues are for bug reports and feature request only.**
## Demo
Available at: <https://alist.nn.ci>.
![demo](https://store.heytapimage.com/cdo-portal/feedback/202202/20/b271627971e29f0c7c9d59935b6ef381.png)
## Document
<https://alist-doc.nn.ci/en/>
## Special sponsors
- [Find Resources - Aliyundrive Resource Search Engine](https://zhaoziyuan.la/)
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
- [KinhDown 百度云盘不限速下载永久免费已稳定运行3年非常可靠!](https://kinhdown.com/?Type=Tutorials)
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
## License
@ -86,4 +39,4 @@ The `AList` is open-source software licensed under the AGPL-3.0 license.
---
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=YJJj2Gwb)

View File

@ -1,89 +0,0 @@
<div align="center">
<a href="https://alist.nn.ci"><img height="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
<p><em>🗂️一个支持多存储的文件列表程序,使用 Gin 和 React 。</em></p>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/release/Xhofe/alist?style=flat-square" alt="latest version"></a>
<a href="https://github.com/Xhofe/alist/discussions"><img src="https://img.shields.io/github/discussions/Xhofe/alist?color=%23ED8936&style=flat-square" alt="discussions"></a>
<a href="https://github.com/Xhofe/alist/actions?query=workflow%3ABuild"><img src="https://img.shields.io/github/workflow/status/Xhofe/alist/build?style=flat-square" alt="Build status"></a>
<a href="https://github.com/Xhofe/alist/releases"><img src="https://img.shields.io/github/downloads/Xhofe/alist/total?style=flat-square&color=%239F7AEA" alt="Downloads"></a>
<a href="https://github.com/Xhofe/alist/blob/v2/LICENSE"><img src="https://img.shields.io/github/license/Xhofe/alist?style=flat-square" alt="License"></a>
<a href="https://pay.xhofe.top">
<img src="https://img.shields.io/badge/%24-donate-ff69b4.svg?style=flat-square" alt="donate">
</a>
</div>
---
[English](./README.md) | 中文 | [Contributors](./CONTRIBUTORS.md) | [Contributing](./CONTRIBUTING.md)
## 支持
- [x] 多种存储
- [x] 本地存储
- [x] [阿里云盘](https://www.aliyundrive.com/)
- [x] OneDrive / Sharepoint[国际版](https://www.office.com/), [世纪互联](https://portal.partner.microsoftonline.cn),de,us
- [x] [天翼云盘](https://cloud.189.cn) (个人云, 家庭云)
- [x] [GoogleDrive](https://drive.google.com/)
- [x] [123云盘](https://www.123pan.com/)
- [x] [蓝奏云](https://pc.woozooo.com/)
- [x] [Alist](https://github.com/Xhofe/alist)
- [x] FTP
- [x] [PikPak](https://www.mypikpak.com/)
- [x] [闪电盘](https://shandianpan.com/)
- [x] [S3](https://aws.amazon.com/cn/s3/)
- [x] WebDav
- [x] Teambition[中国](https://www.teambition.com/ )[国际](https://us.teambition.com/ )
- [x] [分秒帧](https://www.mediatrack.cn/)
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
- [x] [Yandex.Disk](https://disk.yandex.com/)
- [x] [百度网盘](http://pan.baidu.com/)
- [x] [夸克网盘](https://pan.quark.cn)
- [x] [迅雷云盘](https://pan.xunlei.com/)
- [x] 部署方便,开箱即用
- [x] 文件预览PDF、markdown、代码、纯文本……
- [x] 画廊模式下的图像预览
- [x] 视频和音频预览mp4、mp3 等)
- [x] Office 文档预览docx、pptx、xlsx、...
- [x] `README.md` 预览渲染
- [x] 文件永久链接复制和直接文件下载
- [x] 黑暗模式
- [x] 国际化
- [x] 受保护的路由(密码保护和身份验证)
- [x] WebDav具体见https://alist-doc.nn.ci/docs/webdav
- [x] [Docker 部署](https://hub.docker.com/r/xhofe/alist)
- [x] Cloudflare workers 中转
- [x] 文件/文件夹打包下载
- [x] 支持视频列表播放和字幕(ass,srt,vtt)
- [x] 网页上传(可以允许访客上传),删除,新建文件夹,重命名,移动,复制
## 讨论
一般问题请到[讨论论坛](https://github.com/Xhofe/alist/discussions) **issue仅针对错误报告和功能请求。**
## 演示
<https://alist.nn.ci>
![演示](https://store.heytapimage.com/cdo-portal/feedback/202202/20/b271627971e29f0c7c9d59935b6ef381.png)
## 文档
<https://alist-doc.nn.ci/>
## 特别赞助
- [找资源 - 阿里云盘资源搜索引擎](https://zhaoziyuan.la/)
- [JetBrains: Essential tools for software developers and teams](https://www.jetbrains.com/)
## 许可
`AList` 是在 AGPL-3.0 许可下许可的开源软件。
## 免责声明
- 本程序为免费开源项目旨在分享网盘文件方便下载以及学习golang使用时请遵守相关法律法规请勿滥用
- 本程序通过调用官方sdk/接口实现,无破坏官方接口行为;
- 本程序仅做302重定向/流量转发,不拦截、存储、篡改任何用户数据;
- 在使用本程序之前你应了解并承担相应的风险包括但不限于账号被ban下载限速等与本程序无关
- 如有侵权,请通过[邮件](mailto:i@nn.ci)与我联系,会及时处理。
---
> [@Blog](https://nn.ci/) · [@GitHub](https://github.com/Xhofe) · [@TelegramGroup](https://t.me/alist_chat) · [@QQGroup](https://jq.qq.com/?_wv=1027&k=OVPJcv2b)

View File

@ -1,60 +0,0 @@
package main
import (
"fmt"
"github.com/Xhofe/alist/bootstrap"
"github.com/Xhofe/alist/conf"
_ "github.com/Xhofe/alist/drivers"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/server"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func Init() bool {
//bootstrap.InitLog()
bootstrap.InitConf()
bootstrap.InitCron()
bootstrap.InitModel()
if conf.Password {
pass, err := model.GetSettingByKey("password")
if err != nil {
log.Errorf(err.Error())
return false
}
fmt.Printf("your password: %s\n", pass.Value)
return false
}
server.InitIndex()
bootstrap.InitSettings()
bootstrap.InitAccounts()
bootstrap.InitCache()
return true
}
func main() {
if conf.Version {
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\nWebVersion: %s\n",
conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.GitTag, conf.WebTag)
return
}
if !Init() {
return
}
if !conf.Debug {
gin.SetMode(gin.ReleaseMode)
}
r := gin.Default()
server.InitApiRouter(r)
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
log.Infof("start server @ %s", base)
var err error
if conf.Conf.Scheme.Https {
err = r.RunTLS(base, conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
} else {
err = r.Run(base)
}
if err != nil {
log.Errorf("failed to start: %s", err.Error())
}
}

View File

@ -1,12 +0,0 @@
package main
import (
"fmt"
"net/url"
"testing"
)
func TestUrl(t *testing.T) {
s,_ := url.QueryUnescape("/ali/%E7%8C%AA%E5%A4%B4%E7%9A%84%E6%96%87%E4%BB%B6%5B%E5%98%BF%E5%98%BF%5D/%E9%82%B9%E9%82%B9%E7%9A%84%E6%96%87%E4%BB%B6/%E6%A1%8C%E9%9D%A2%E5%A3%81%E7%BA%B8/v2-e8f266ba17ae387eefed1cb22b2b5e4e_r.jpg")
fmt.Print(s)
}

View File

@ -1,33 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/drivers/operate"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
)
func InitAccounts() {
log.Infof("init accounts...")
var accounts []model.Account
if err := conf.DB.Find(&accounts).Error; err != nil {
log.Fatalf("failed sync init accounts")
}
for i, account := range accounts {
model.RegisterAccount(account)
driver, ok := base.GetDriver(account.Type)
if !ok {
log.Errorf("no [%s] driver", account.Type)
} else {
log.Infof("start init account: [%s], type: [%s]", account.Name, account.Type)
//err := driver.Save(&accounts[i], nil)
err := operate.Save(driver, &accounts[i], nil)
if err != nil {
log.Errorf("init account [%s] error:[%s]", account.Name, err.Error())
} else {
log.Infof("success init account: %s, type: %s", account.Name, account.Type)
}
}
}
}

View File

@ -1,22 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/eko/gocache/v2/cache"
"github.com/eko/gocache/v2/store"
goCache "github.com/patrickmn/go-cache"
log "github.com/sirupsen/logrus"
"time"
)
// InitCache init cache
func InitCache() {
log.Infof("init cache...")
c := conf.Conf.Cache
if c.Expiration == 0 {
c.Expiration, c.CleanupInterval = 60, 120
}
goCacheClient := goCache.New(time.Duration(c.Expiration)*time.Minute, time.Duration(c.CleanupInterval)*time.Minute)
goCacheStore := store.NewGoCache(goCacheClient, nil)
conf.Cache = cache.New(goCacheStore)
}

View File

@ -1,66 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/utils"
"github.com/caarlos0/env/v6"
log "github.com/sirupsen/logrus"
"io/ioutil"
"os"
)
// InitConf init config
func InitConf() {
log.Infof("reading config file: %s", conf.ConfigFile)
if !utils.Exists(conf.ConfigFile) {
log.Infof("config file not exists, creating default config file")
_, err := utils.CreatNestedFile(conf.ConfigFile)
if err != nil {
log.Fatalf("failed to create config file")
}
conf.Conf = conf.DefaultConfig()
if !utils.WriteToJson(conf.ConfigFile, conf.Conf) {
log.Fatalf("failed to create default config file")
}
} else {
config, err := ioutil.ReadFile(conf.ConfigFile)
if err != nil {
log.Fatalf("reading config file error:%s", err.Error())
}
conf.Conf = conf.DefaultConfig()
err = utils.Json.Unmarshal(config, conf.Conf)
if err != nil {
log.Fatalf("load config error: %s", err.Error())
}
log.Debugf("config:%+v", conf.Conf)
// update config.json struct
confBody, err := utils.Json.MarshalIndent(conf.Conf, "", " ")
if err != nil {
log.Fatalf("marshal config error:%s", err.Error())
}
err = ioutil.WriteFile(conf.ConfigFile, confBody, 0777)
if err != nil {
log.Fatalf("update config struct error: %s", err.Error())
}
}
if !conf.Conf.Force {
confFromEnv()
}
err := os.MkdirAll(conf.Conf.TempDir, 0700)
if err != nil {
log.Fatalf("create temp dir error: %s", err.Error())
}
log.Debugf("config: %+v", conf.Conf)
}
func confFromEnv() {
prefix := "ALIST_"
if conf.Docker {
prefix = ""
}
if err := env.Parse(conf.Conf, env.Options{
Prefix: prefix,
}); err != nil {
log.Fatalf("load config from env error: %s", err.Error())
}
}

View File

@ -1,14 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
)
// InitCron init cron
func InitCron() {
log.Infof("init cron...")
conf.Cron = cron.New()
conf.Cron.Start()
}

View File

@ -1,36 +0,0 @@
package bootstrap
import (
"flag"
"github.com/Xhofe/alist/conf"
log "github.com/sirupsen/logrus"
)
// InitLog init log
func InitLog() {
if conf.Debug {
log.SetLevel(log.DebugLevel)
log.SetReportCaller(true)
}
if conf.Password || conf.Version {
log.SetLevel(log.WarnLevel)
}
log.SetFormatter(&log.TextFormatter{
//DisableColors: true,
ForceColors: true,
EnvironmentOverrideColors: true,
TimestampFormat: "2006-01-02 15:04:05",
FullTimestamp: true,
})
log.Infof("init log...")
}
func init() {
flag.StringVar(&conf.ConfigFile, "conf", "data/config.json", "config file")
flag.BoolVar(&conf.Debug, "debug", false, "start with debug mode")
flag.BoolVar(&conf.Version, "version", false, "print version info")
flag.BoolVar(&conf.Password, "password", false, "print current password")
flag.BoolVar(&conf.Docker, "docker", false, "is using docker")
flag.Parse()
InitLog()
}

View File

@ -1,84 +0,0 @@
package bootstrap
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
log "github.com/sirupsen/logrus"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
log2 "log"
"os"
"strings"
"time"
)
func InitModel() {
log.Infof("init model...")
var err error
databaseConfig := conf.Conf.Database
newLogger := logger.New(
log2.New(os.Stdout, "\r\n", log2.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Silent,
IgnoreRecordNotFoundError: true,
Colorful: true,
},
)
gormConfig := &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: databaseConfig.TablePrefix,
},
Logger: newLogger,
}
switch databaseConfig.Type {
case "sqlite3":
{
if !(strings.HasSuffix(databaseConfig.DBFile, ".db") && len(databaseConfig.DBFile) > 3) {
log.Fatalf("db name error.")
}
db, err := gorm.Open(sqlite.Open(databaseConfig.DBFile), gormConfig)
if err != nil {
log.Fatalf("failed to connect database:%s", err.Error())
}
conf.DB = db
}
case "mysql":
{
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&tls=%s",
databaseConfig.User, databaseConfig.Password, databaseConfig.Host, databaseConfig.Port, databaseConfig.Name, databaseConfig.SslMode)
db, err := gorm.Open(mysql.Open(dsn), gormConfig)
if err != nil {
log.Fatalf("failed to connect database:%s", err.Error())
}
conf.DB = db
}
case "postgres":
{
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=Asia/Shanghai",
databaseConfig.Host, databaseConfig.User, databaseConfig.Password, databaseConfig.Name, databaseConfig.Port, databaseConfig.SslMode)
db, err := gorm.Open(postgres.Open(dsn), gormConfig)
if err != nil {
log.Errorf("failed to connect database:%s", err.Error())
}
conf.DB = db
}
default:
log.Fatalf("not supported database type: %s", databaseConfig.Type)
}
log.Infof("auto migrate model...")
if databaseConfig.Type == "mysql" {
err = conf.DB.Set("gorm:table_options", "ENGINE=InnoDB CHARSET=utf8mb4").
AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
} else {
err = conf.DB.AutoMigrate(&model.SettingItem{}, &model.Account{}, &model.Meta{})
}
if err != nil {
log.Fatalf("failed to auto migrate: %s", err.Error())
}
}

View File

@ -1,292 +0,0 @@
package bootstrap
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"strings"
)
func InitSettings() {
log.Infof("init settings...")
err := model.SaveSetting(model.Version)
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}
settings := []model.SettingItem{
{
Key: "title",
Value: "Alist",
Description: "title",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "password",
Value: utils.RandomStr(8),
Description: "password",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "logo",
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/can_circle.svg",
Description: "logo",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "favicon",
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg",
Description: "favicon",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "icon color",
Value: "#1890ff",
Description: "icon's color",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "announcement",
Value: "This is a test announcement.",
Description: "announcement message (support markdown)",
Type: "text",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "text types",
Value: strings.Join(conf.TextTypes, ","),
Type: "string",
Description: "text type extensions",
Group: model.FRONT,
},
{
Key: "audio types",
Value: strings.Join(conf.AudioTypes, ","),
Type: "string",
Description: "audio type extensions",
Group: model.FRONT,
},
{
Key: "video types",
Value: strings.Join(conf.VideoTypes, ","),
Type: "string",
Description: "video type extensions",
Group: model.FRONT,
},
{
Key: "d_proxy types",
Value: strings.Join(conf.DProxyTypes, ","),
Type: "string",
Description: "/d but proxy",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "hide files",
Value: "/\\/README.md/i",
Type: "text",
Description: "hide files, support RegExp, one per line",
Group: model.FRONT,
},
{
Key: "music cover",
Value: "https://cdn.jsdelivr.net/gh/alist-org/logo@main/circle_center.svg",
Description: "music cover image",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "site beian",
Description: "chinese beian info",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "home readme url",
Description: "when have multiple, the readme file to show",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "autoplay video",
Value: "false",
Type: "bool",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "autoplay audio",
Value: "false",
Type: "bool",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "check parent folder",
Value: "false",
Type: "bool",
Description: "check parent folder password",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "customize head",
Value: "",
Type: "text",
Description: "Customize head, placed at the beginning of the head",
Access: model.PRIVATE,
Group: model.FRONT,
},
{
Key: "customize body",
Value: "",
Type: "text",
Description: "Customize script, placed at the end of the body",
Access: model.PRIVATE,
Group: model.FRONT,
},
{
Key: "home emoji",
Value: "🏠",
Type: "string",
Description: "emoji in front of home in nav",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "animation",
Value: "true",
Type: "bool",
Description: "when there are a lot of files, the animation will freeze when opening",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "check down link",
Value: "false",
Type: "bool",
Description: "check down link password, your link will be 'https://alist.com/d/filename?pw=xxx'",
Access: model.PUBLIC,
Group: model.BACK,
},
{
Key: "WebDAV username",
Value: "admin",
Description: "WebDAV username",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "WebDAV password",
Value: utils.RandomStr(8),
Description: "WebDAV password",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "artplayer whitelist",
Value: "*",
Description: "refer to https://artplayer.org/document/options#whitelist",
Type: "string",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "artplayer autoSize",
Value: "true",
Description: "refer to https://artplayer.org/document/options#autosize",
Type: "bool",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "Visitor WebDAV username",
Value: "guest",
Description: "Visitor WebDAV username",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "Visitor WebDAV password",
Value: "guest",
Description: "Visitor WebDAV password",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
{
Key: "load type",
Value: "all",
Type: "select",
Values: "all,load more,auto load more,pagination",
Description: "Not recommended to choose to auto load more, it has bugs now",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "default page size",
Value: "30",
Type: "number",
Access: model.PUBLIC,
Group: model.FRONT,
},
{
Key: "ocr api",
Value: "https://api.xhofe.top/ocr/file/json",
Description: "Used to identify verification codes",
Type: "string",
Access: model.PRIVATE,
Group: model.BACK,
},
}
for i, _ := range settings {
v := settings[i]
v.Version = conf.GitTag
o, err := model.GetSettingByKey(v.Key)
if err != nil {
if err == gorm.ErrRecordNotFound {
err = model.SaveSetting(v)
if v.Key == "password" {
log.Infof("Initial password: %s", conf.C.Sprintf(v.Value))
}
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}
} else {
log.Fatalf("can't get setting: %s", err.Error())
}
} else {
//o.Version = conf.GitTag
//err = model.SaveSetting(*o)
v.Value = o.Value
err = model.SaveSetting(v)
if err != nil {
log.Fatalf("failed write setting: %s", err.Error())
}
if v.Key == "password" {
log.Infof("Your password: %s", conf.C.Sprintf(v.Value))
}
}
}
model.LoadSettings()
}

156
build.sh
View File

@ -1,156 +0,0 @@
#!/bin/bash
# 构建前端,在当前目录产生一个dist文件夹
BUILD_WEB() {
git clone https://github.com/alist-org/alist-web.git
cd alist-web
yarn
yarn build
sed -i -e "s/\/CDN_URL\//\//g" dist/index.html
sed -i -e "s/assets/\/assets/g" dist/index.html
rm -f dist/index.html-e
mv dist ..
cd .. || exit
rm -rf alist-web
}
CDN_WEB() {
curl -L https://github.com/alist-org/alist-web/releases/latest/download/dist.tar.gz -o dist.tar.gz
tar -zxvf dist.tar.gz
rm -f dist.tar.gz
}
# 在DOCKER中构建
BUILD_DOCKER() {
appName="alist"
builtAt="$(date +'%F %T %z')"
goVersion=$(go version | sed 's/go version //')
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
ldflags="\
-w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
"
go build -o ./bin/alist -ldflags="$ldflags" -tags=jsoniter alist.go
}
BUILD() {
cd alist
appName="alist"
builtAt="$(date +'%F %T %z')"
goVersion=$(go version | sed 's/go version //')
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
echo "build version: $gitTag"
ldflags="\
-w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
"
if [ "$1" == "release" ]; then
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
else
xgo -targets=linux/amd64,windows/amd64,darwin/amd64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
fi
mkdir -p "build"
mv alist-* build
if [ "$1" != "release" ]; then
cd build
upx -9 ./alist-linux*
upx -9 ./alist-windows*
find . -type f -print0 | xargs -0 md5sum >md5.txt
cat md5.txt
cd .. || exit
fi
cd .. || exit
}
BUILD_MUSL() {
BASE="https://musl.cc/"
FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross arm-linux-musleabihf-cross mips-linux-musl-cross mips64-linux-musl-cross mips64el-linux-musl-cross mipsel-linux-musl-cross powerpc64le-linux-musl-cross s390x-linux-musl-cross)
for i in "${FILES[@]}"; do
url="${BASE}${i}.tgz"
curl -L -o "${i}.tgz" "${url}"
sudo tar xf "${i}.tgz" --strip-components 1 -C /usr/local
done
cd alist
appName="alist"
builtAt="$(date +'%F %T %z')"
goVersion=$(go version | sed 's/go version //')
gitAuthor=$(git show -s --format='format:%aN <%ae>' HEAD)
gitCommit=$(git log --pretty=format:"%h" -1)
gitTag=$(git describe --long --tags --dirty --always)
webTag=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist-web/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
ldflags="\
-w -s \
-X 'github.com/Xhofe/alist/conf.BuiltAt=$builtAt' \
-X 'github.com/Xhofe/alist/conf.GoVersion=$goVersion' \
-X 'github.com/Xhofe/alist/conf.GitAuthor=$gitAuthor' \
-X 'github.com/Xhofe/alist/conf.GitCommit=$gitCommit' \
-X 'github.com/Xhofe/alist/conf.GitTag=$gitTag' \
-X 'github.com/Xhofe/alist/conf.WebTag=$webTag' \
"
OS_ARCHES=(linux-musl-amd64 linux-musl-arm64 linux-musl-arm linux-musl-mips linux-musl-mips64 linux-musl-mips64le linux-musl-mipsle linux-musl-ppc64le linux-musl-s390x)
CGO_ARGS=(x86_64-linux-musl-gcc aarch64-linux-musl-gcc arm-linux-musleabihf-gcc mips-linux-musl-gcc mips64-linux-musl-gcc mips64el-linux-musl-gcc mipsel-linux-musl-gcc powerpc64le-linux-musl-gcc s390x-linux-musl-gcc)
for i in "${!OS_ARCHES[@]}"; do
os_arch=${OS_ARCHES[$i]}
cgo_cc=${CGO_ARGS[$i]}
echo building for ${os_arch}
export GOOS=${os_arch%%-*}
export GOARCH=${os_arch##*-}
export CC=${cgo_cc}
export CGO_ENABLED=1
go build -o ./build/$appName-$os_arch -ldflags="$ldflags" -tags=jsoniter alist.go
done
cd .. || exit
}
RELEASE() {
cd alist/build
upx -9 ./alist-linux-amd64
upx -9 ./alist-windows*
find . -type f -print0 | xargs -0 md5sum >md5.txt
cat md5.txt
mkdir compress
mv md5.txt compress
for i in $(find . -type f -name "$appName-linux-*"); do
tar -czvf compress/"$i".tar.gz "$i"
done
for i in $(find . -type f -name "$appName-darwin-*"); do
tar -czvf compress/"$i".tar.gz "$i"
done
for i in $(find . -type f -name "$appName-windows-*"); do
zip compress/$(echo $i | sed 's/\.[^.]*$//').zip "$i"
done
cd ../.. || exit
}
if [ "$1" = "web" ]; then
BUILD_WEB
elif [ "$1" = "cdn" ]; then
CDN_WEB
elif [ "$1" = "docker" ]; then
BUILD_DOCKER
elif [ "$1" = "build" ]; then
BUILD build
elif [ "$1" = "release" ]; then
BUILD release
BUILD_MUSL
RELEASE
else
echo -e "${RED_COLOR} Parameter error ${RES}"
fi

59
cmd/alist.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"flag"
"fmt"
bootstrap2 "github.com/alist-org/alist/v3/internal/bootstrap"
"github.com/alist-org/alist/v3/internal/bootstrap/data"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/server"
"os"
"github.com/alist-org/alist/v3/cmd/args"
_ "github.com/alist-org/alist/v3/drivers"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func init() {
flag.StringVar(&args.Config, "conf", "data/config.json", "config file")
flag.BoolVar(&args.Debug, "debug", false, "start with debug mode")
flag.BoolVar(&args.Version, "version", false, "print version info")
flag.BoolVar(&args.Password, "password", false, "print current password")
flag.BoolVar(&args.NoPrefix, "no-prefix", false, "disable env prefix")
flag.BoolVar(&args.Dev, "dev", false, "start with dev mode")
flag.Parse()
}
func Init() {
if args.Version {
fmt.Printf("Built At: %s\nGo Version: %s\nAuthor: %s\nCommit ID: %s\nVersion: %s\nWebVersion: %s\n",
conf.BuiltAt, conf.GoVersion, conf.GitAuthor, conf.GitCommit, conf.Version, conf.WebVersion)
os.Exit(0)
}
bootstrap2.InitConfig()
bootstrap2.Log()
bootstrap2.InitDB()
data.InitData()
bootstrap2.InitAria2()
}
func main() {
Init()
if !args.Debug && !args.Dev {
gin.SetMode(gin.ReleaseMode)
}
r := gin.New()
r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out))
server.Init(r)
base := fmt.Sprintf("%s:%d", conf.Conf.Address, conf.Conf.Port)
log.Infof("start server @ %s", base)
var err error
if conf.Conf.Scheme.Https {
err = r.RunTLS(base, conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
} else {
err = r.Run(base)
}
if err != nil {
log.Errorf("failed to start: %s", err.Error())
}
}

10
cmd/args/config.go Normal file
View File

@ -0,0 +1,10 @@
package args
var (
Config string // config file
Debug bool
Version bool
Password bool
NoPrefix bool
Dev bool
)

View File

@ -1,54 +0,0 @@
package conf
type Database struct {
Type string `json:"type" env:"DB_TYPE"`
Host string `json:"host" env:"DB_HOST"`
Port int `json:"port" env:"DB_PORT"`
User string `json:"user" env:"DB_USER"`
Password string `json:"password" env:"DB_PASS"`
Name string `json:"name" env:"DB_NAME"`
DBFile string `json:"db_file" env:"DB_FILE"`
TablePrefix string `json:"table_prefix" env:"DB_TABLE_PREFIX"`
SslMode string `json:"ssl_mode" env:"DB_SLL_MODE"`
}
type Scheme struct {
Https bool `json:"https" env:"HTTPS"`
CertFile string `json:"cert_file" env:"CERT_FILE"`
KeyFile string `json:"key_file" env:"KEY_FILE"`
}
type CacheConfig struct {
Expiration int64 `json:"expiration" env:"CACHE_EXPIRATION"`
CleanupInterval int64 `json:"cleanup_interval" env:"CLEANUP_INTERVAL"`
}
type Config struct {
Force bool `json:"force"`
Address string `json:"address" env:"ADDR"`
Port int `json:"port" env:"PORT"`
Assets string `json:"assets" env:"ASSETS"`
Database Database `json:"database"`
Scheme Scheme `json:"scheme"`
Cache CacheConfig `json:"cache"`
TempDir string `json:"temp_dir" env:"TEMP_DIR"`
}
func DefaultConfig() *Config {
return &Config{
Address: "0.0.0.0",
Port: 5244,
Assets: "https://npm.elemecdn.com/alist-web@$version/dist",
TempDir: "data/temp",
Database: Database{
Type: "sqlite3",
Port: 0,
TablePrefix: "x_",
DBFile: "data/data.db",
},
Cache: CacheConfig{
Expiration: 60,
CleanupInterval: 120,
},
}
}

View File

@ -1,11 +0,0 @@
package conf
const (
UNKNOWN = iota
FOLDER
OFFICE
VIDEO
AUDIO
TEXT
IMAGE
)

View File

@ -1,103 +0,0 @@
package conf
import (
"context"
"github.com/eko/gocache/v2/cache"
"github.com/fatih/color"
"github.com/robfig/cron/v3"
"gorm.io/gorm"
"strconv"
)
var (
BuiltAt string
GoVersion string
GitAuthor string
GitCommit string
GitTag string = "dev"
WebTag string
)
var (
ConfigFile string // config file
Conf *Config
Debug bool
Version bool
Password bool
Docker bool
DB *gorm.DB
Cache *cache.Cache
Ctx = context.TODO()
Cron *cron.Cron
C = color.New(color.FgHiBlue, color.Bold, color.BgHiWhite, color.Underline)
)
var (
TextTypes = []string{"txt", "htm", "html", "xml", "java", "properties", "sql",
"js", "md", "json", "conf", "ini", "vue", "php", "py", "bat", "gitignore", "yml",
"go", "sh", "c", "cpp", "h", "hpp", "tsx", "vtt", "srt", "ass"}
DProxyTypes = []string{"m3u8"}
OfficeTypes = []string{"doc", "docx", "xls", "xlsx", "ppt", "pptx", "pdf"}
VideoTypes = []string{"mp4", "mkv", "avi", "mov", "rmvb", "webm", "flv"}
AudioTypes = []string{"mp3", "flac", "ogg", "m4a", "wav", "opus"}
ImageTypes = []string{"jpg", "tiff", "jpeg", "png", "gif", "bmp", "svg", "ico", "swf", "webp"}
)
var settingsMap = make(map[string]string)
func Set(key string, value string) {
settingsMap[key] = value
}
func GetStr(key string) string {
value, ok := settingsMap[key]
if !ok {
return ""
}
return value
}
func GetBool(key string) bool {
value, ok := settingsMap[key]
if !ok {
return false
}
return value == "true"
}
func GetInt(key string, defaultV int) int {
value, ok := settingsMap[key]
if !ok {
return defaultV
}
v, err := strconv.Atoi(value)
if err != nil {
return defaultV
}
return v
}
var (
LoadSettings = []string{
"check parent folder", "check down link", "WebDAV username", "WebDAV password",
"Visitor WebDAV username", "Visitor WebDAV password",
"default page size", "load type",
"ocr api", "favicon",
}
)
var (
RawIndexHtml string
ManageHtml string
IndexHtml string
Token string
//CheckParent bool
//CheckDown bool
//DavUsername string
//DavPassword string
//VisitorDavUsername string
//VisitorDavPassword string
)

View File

@ -1,230 +0,0 @@
package _23
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
"time"
)
type BaseResp struct {
Code int `json:"code"`
Message string `json:"message"`
}
type Pan123TokenResp struct {
BaseResp
Data struct {
Token string `json:"token"`
} `json:"data"`
}
type Pan123File struct {
FileName string `json:"FileName"`
Size int64 `json:"Size"`
UpdateAt *time.Time `json:"UpdateAt"`
FileId int64 `json:"FileId"`
Type int `json:"Type"`
Etag string `json:"Etag"`
S3KeyFlag string `json:"S3KeyFlag"`
}
type Pan123Files struct {
BaseResp
Data struct {
InfoList []Pan123File `json:"InfoList"`
Next string `json:"Next"`
} `json:"data"`
}
type Pan123DownResp struct {
BaseResp
Data struct {
DownloadUrl string `json:"DownloadUrl"`
} `json:"data"`
}
type UploadResp struct {
BaseResp
Data struct {
AccessKeyId string `json:"AccessKeyId"`
Bucket string `json:"Bucket"`
Key string `json:"Key"`
SecretAccessKey string `json:"SecretAccessKey"`
SessionToken string `json:"SessionToken"`
FileId int64 `json:"FileId"`
} `json:"data"`
}
func (driver Pan123) Login(account *model.Account) error {
url := "https://www.123pan.com/api/user/sign_in"
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
var resp Pan123TokenResp
_, err := base.RestyClient.R().
SetResult(&resp).
SetBody(base.Json{
"passport": account.Username,
"password": account.Password,
}).Post(url)
if err != nil {
return err
}
if resp.Code != 200 {
err = fmt.Errorf(resp.Message)
account.Status = resp.Message
} else {
account.Status = "work"
account.AccessToken = resp.Data.Token
}
_ = model.SaveAccount(account)
return err
}
func (driver Pan123) FormatFile(file *Pan123File) *model.File {
f := &model.File{
Id: strconv.FormatInt(file.FileId, 10),
Name: file.FileName,
Size: file.Size,
Driver: driver.Config().Name,
UpdatedAt: file.UpdateAt,
}
if file.Type == 1 {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(file.FileName))
}
return f
}
func (driver Pan123) GetFiles(parentId string, account *model.Account) ([]Pan123File, error) {
next := "0"
res := make([]Pan123File, 0)
for next != "-1" {
var resp Pan123Files
query := map[string]string{
"driveId": "0",
"limit": "100",
"next": next,
"orderBy": account.OrderBy,
"orderDirection": account.OrderDirection,
"parentFileId": parentId,
"trashed": "false",
}
_, err := driver.Request("https://www.123pan.com/api/file/list",
base.Get, nil, query, nil, &resp, false, account)
if err != nil {
return nil, err
}
next = resp.Data.Next
res = append(res, resp.Data.InfoList...)
}
return res, nil
}
func (driver Pan123) Request(url string, method int, headers, query map[string]string, data *base.Json, resp interface{}, proxy bool, account *model.Account) ([]byte, error) {
rawUrl := url
if account.APIProxyUrl != "" && proxy {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debug(res.String())
body := res.Body()
code := jsoniter.Get(body, "code").ToInt()
if code != 0 {
if code == 401 {
err := driver.Login(account)
if err != nil {
return nil, err
}
return driver.Request(rawUrl, method, headers, query, data, resp, proxy, account)
}
return nil, errors.New(jsoniter.Get(body, "message").ToString())
}
return body, nil
}
//func (driver Pan123) Post(url string, data base.Json, account *model.Account) ([]byte, error) {
// res, err := pan123Client.R().
// SetHeader("authorization", "Bearer "+account.AccessToken).
// SetBody(data).Post(url)
// if err != nil {
// return nil, err
// }
// body := res.Body()
// if jsoniter.Get(body, "code").ToInt() != 0 {
// return nil, errors.New(jsoniter.Get(body, "message").ToString())
// }
// return body, nil
//}
func (driver Pan123) GetFile(path string, account *model.Account) (*Pan123File, error) {
dir, name := filepath.Split(path)
dir = utils.ParsePath(dir)
_, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]Pan123File)
for _, file := range parentFiles {
if file.FileName == name {
if file.Type != conf.FOLDER {
return &file, err
} else {
return nil, base.ErrNotFile
}
}
}
return nil, base.ErrPathNotFound
}
//func HMAC(message string, secret string) string {
// key := []byte(secret)
// h := hmac.New(sha256.New, key)
// h.Write([]byte(message))
// // fmt.Println(h.Sum(nil))
// //sha := hex.EncodeToString(h.Sum(nil))
// // fmt.Println(sha)
// //return sha
// return string(h.Sum(nil))
//}
func init() {
base.RegisterDriver(&Pan123{})
}

View File

@ -1,439 +0,0 @@
package _23
import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strconv"
)
type Pan123 struct{}
func (driver Pan123) Config() base.DriverConfig {
return base.DriverConfig{
Name: "123Pan",
}
}
func (driver Pan123) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "name,fileId,updateAt,createAt",
Required: true,
Default: "name",
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Required: true,
Default: "asc",
},
}
}
func (driver Pan123) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if account.RootFolder == "" {
account.RootFolder = "0"
}
err := driver.Login(account)
return err
}
func (driver Pan123) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Pan123) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Pan123File
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]Pan123File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver Pan123) Link(args base.Args, account *model.Account) (*base.Link, error) {
log.Debugf("%+v", args)
file, err := driver.GetFile(utils.ParsePath(args.Path), account)
if err != nil {
return nil, err
}
var resp Pan123DownResp
var headers map[string]string
if !utils.IsLocalIPAddr(args.IP) {
headers = map[string]string{
//"X-Real-IP": "1.1.1.1",
"X-Forwarded-For": args.IP,
}
}
data := base.Json{
"driveId": 0,
"etag": file.Etag,
"fileId": file.FileId,
"fileName": file.FileName,
"s3keyFlag": file.S3KeyFlag,
"size": file.Size,
"type": file.Type,
}
_, err = driver.Request("https://www.123pan.com/api/file/download_info",
base.Post, headers, nil, &data, &resp, false, account)
//_, err = pan123Client.R().SetResult(&resp).SetHeader("authorization", "Bearer "+account.AccessToken).
// SetBody().Post("https://www.123pan.com/api/file/download_info")
if err != nil {
return nil, err
}
u, err := url.Parse(resp.Data.DownloadUrl)
if err != nil {
return nil, err
}
u_ := fmt.Sprintf("https://%s%s", u.Host, u.Path)
res, err := base.NoRedirectClient.R().SetQueryParamsFromValues(u.Query()).Get(u_)
if err != nil {
return nil, err
}
log.Debug(res.String())
link := base.Link{
Url: resp.Data.DownloadUrl,
}
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
}
return &link, nil
}
func (driver Pan123) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("pan123 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Pan123) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("origin")
//}
func (driver Pan123) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Pan123) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
parentFileId, _ := strconv.Atoi(parentFile.Id)
data := base.Json{
"driveId": 0,
"etag": "",
"fileName": name,
"parentFileId": parentFileId,
"size": 0,
"type": 1,
}
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
base.Post, nil, nil, &data, nil, false, account)
return err
}
func (driver Pan123) Move(src string, dst string, account *model.Account) error {
dstDir, _ := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
fileId, _ := strconv.Atoi(srcFile.Id)
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
parentFileId, _ := strconv.Atoi(dstDirFile.Id)
data := base.Json{
"fileIdList": []base.Json{{"FileId": fileId}},
"parentFileId": parentFileId,
}
_, err = driver.Request("https://www.123pan.com/api/file/mod_pid",
base.Post, nil, nil, &data, nil, false, account)
return err
}
func (driver Pan123) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
fileId, _ := strconv.Atoi(srcFile.Id)
data := base.Json{
"driveId": 0,
"fileId": fileId,
"fileName": dstName,
}
_, err = driver.Request("https://www.123pan.com/api/file/rename",
base.Post, nil, nil, &data, nil, false, account)
return err
}
func (driver Pan123) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver Pan123) Delete(path string, account *model.Account) error {
file, err := driver.GetFile(path, account)
if err != nil {
return err
}
data := base.Json{
"driveId": 0,
"operation": true,
"fileTrashInfoList": file,
}
_, err = driver.Request("https://www.123pan.com/api/file/trash",
base.Post, nil, nil, &data, nil, false, account)
return err
}
func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
parentFileId, _ := strconv.Atoi(parentFile.Id)
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New()
_, err = io.Copy(h, tempFile)
if err != nil {
return err
}
etag := hex.EncodeToString(h.Sum(nil))
log.Debugln("md5:", etag)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
data := base.Json{
"driveId": 0,
"duplicate": true,
"etag": etag,
"fileName": file.GetFileName(),
"parentFileId": parentFileId,
"size": file.GetSize(),
"type": 0,
}
var resp UploadResp
_, err = driver.Request("https://www.123pan.com/api/file/upload_request",
base.Post, nil, nil, &data, &resp, false, account)
//res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
if err != nil {
return err
}
if resp.Data.Key == "" {
return nil
}
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(resp.Data.AccessKeyId, resp.Data.SecretAccessKey, resp.Data.SessionToken),
Region: aws.String("123pan"),
Endpoint: aws.String("file.123pan.com"),
S3ForcePathStyle: aws.Bool(true),
}
s, err := session.NewSession(cfg)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Key,
Body: tempFile,
}
_, err = uploader.Upload(input)
if err != nil {
return err
}
_, err = driver.Request("https://www.123pan.com/api/file/upload_complete", base.Post, nil, nil, &base.Json{
"fileId": resp.Data.FileId,
}, nil, false, account)
return err
}
//type UploadResp struct {
// XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
// Bucket string `xml:"Bucket"`
// Key string `xml:"Key"`
// UploadId string `xml:"UploadId"`
//}
// TODO unfinished
//func (driver Pan123) Upload(file *model.FileStream, account *model.Account) error {
// return base.ErrNotImplement
// parentFile, err := driver.File(file.ParentPath, account)
// if err != nil {
// return err
// }
// if !parentFile.IsDir() {
// return base.ErrNotFolder
// }
// parentFileId, _ := strconv.Atoi(parentFile.Id)
// data := base.Json{
// "driveId": 0,
// "duplicate": true,
// "etag": RandStr(32), //maybe file's md5
// "fileName": file.GetFileName(),
// "parentFileId": parentFileId,
// "size": file.GetSize(),
// "type": 0,
// }
// res, err := driver.Request("https://www.123pan.com/api/file/upload_request",
// base.Post, nil, nil, &data, nil, false, account)
// //res, err := driver.Post("https://www.123pan.com/api/file/upload_request", data, account)
// if err != nil {
// return err
// }
// baseUrl := fmt.Sprintf("https://file.123pan.com/%s/%s", jsoniter.Get(res, "data.Bucket").ToString(), jsoniter.Get(res, "data.Key").ToString())
// var resp UploadResp
// kSecret := jsoniter.Get(res, "data.SecretAccessKey").ToString()
// nowTimeStr := time.Now().String()
// Date := strings.ReplaceAll(strings.Split(nowTimeStr, "T")[0], "-", "")
//
// StringToSign := fmt.Sprintf("%s\n%s\n%s\n%s",
// "AWS4-HMAC-SHA256",
// nowTimeStr,
// fmt.Sprintf("%s/us-east-1/s3/aws4_request", Date),
// )
//
// kDate := HMAC("AWS4"+kSecret, Date)
// kRegion := HMAC(kDate, "us-east-1")
// kService := HMAC(kRegion, "s3")
// kSigning := HMAC(kService, "aws4_request")
// _, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
// "Authorization": fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-security-token;x-amz-user-agent, Signature=%s",
// jsoniter.Get(res, "data.AccessKeyId"),
// Date,
// hex.EncodeToString([]byte(HMAC(StringToSign, kSigning)))),
// "X-Amz-Content-Sha256": "UNSIGNED-PAYLOAD",
// "X-Amz-Date": nowTimeStr,
// "x-amz-security-token": jsoniter.Get(res, "data.SessionToken").ToString(),
// }).Post(fmt.Sprintf("%s?uploads", baseUrl))
// if err != nil {
// return err
// }
// return base.ErrNotImplement
//}
var _ base.Driver = (*Pan123)(nil)

View File

@ -1,175 +0,0 @@
package _39
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path"
"time"
)
func (driver Cloud139) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
url := "https://yun.139.com" + pathname
req := base.RestyClient.R()
randStr := utils.RandomStr(16)
ts := time.Now().Format("2006-01-02 15:04:05")
log.Debugf("%+v", data)
body, err := utils.Json.Marshal(data)
if err != nil {
return nil, err
}
sign := calSign(string(body), ts, randStr)
svcType := "1"
if isFamily(account) {
svcType = "2"
}
req.SetHeaders(map[string]string{
"Accept": "application/json, text/plain, */*",
"CMS-DEVICE": "default",
"Cookie": account.AccessToken,
"mcloud-channel": "1000101",
"mcloud-client": "10701",
//"mcloud-route": "001",
"mcloud-sign": fmt.Sprintf("%s,%s,%s", ts, randStr, sign),
//"mcloud-skey":"",
"mcloud-version": "6.6.0",
"Origin": "https://yun.139.com",
"Referer": "https://yun.139.com/w/",
"x-DeviceInfo": "||9|6.6.0|chrome|95.0.4638.69|uwIy75obnsRPIwlJSd7D9GhUvFwG96ce||macos 10.15.2||zh-CN|||",
"x-huawei-channelSrc": "10000034",
"x-inner-ntwk": "2",
"x-m4c-caller": "PC",
"x-m4c-src": "10002",
"x-SvcType": svcType,
})
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
var e BaseResp
//var err error
var res *resty.Response
req.SetResult(&e)
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
case base.Delete:
res, err = req.Delete(url)
case base.Patch:
res, err = req.Patch(url)
case base.Put:
res, err = req.Put(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debugln(res.String())
if !e.Success {
return nil, errors.New(e.Message)
}
if resp != nil {
err = utils.Json.Unmarshal(res.Body(), resp)
if err != nil {
return nil, err
}
}
return res.Body(), nil
}
func (driver Cloud139) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
}
func (driver Cloud139) GetFiles(catalogID string, account *model.Account) ([]model.File, error) {
start := 0
limit := 100
files := make([]model.File, 0)
for {
data := base.Json{
"catalogID": catalogID,
"sortDirection": 1,
"startNumber": start + 1,
"endNumber": start + limit,
"filterType": 0,
"catalogSortType": 0,
"contentSortType": 0,
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
}
var resp GetDiskResp
_, err := driver.Post("/orchestration/personalCloud/catalog/v1.0/getDisk", data, &resp, account)
if err != nil {
return nil, err
}
for _, catalog := range resp.Data.GetDiskResult.CatalogList {
f := model.File{
Id: catalog.CatalogID,
Name: catalog.CatalogName,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: getTime(catalog.UpdateTime),
}
files = append(files, f)
}
for _, content := range resp.Data.GetDiskResult.ContentList {
f := model.File{
Id: content.ContentID,
Name: content.ContentName,
Size: content.ContentSize,
Type: utils.GetFileType(path.Ext(content.ContentName)),
Driver: driver.Config().Name,
UpdatedAt: getTime(content.UpdateTime),
Thumbnail: content.ThumbnailURL,
//Thumbnail: content.BigthumbnailURL,
}
files = append(files, f)
}
if start+limit >= resp.Data.GetDiskResult.NodeCount {
break
}
start += limit
}
return files, nil
}
func (driver Cloud139) GetLink(contentId string, account *model.Account) (string, error) {
data := base.Json{
"appName": "",
"contentID": contentId,
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
}
res, err := driver.Post("/orchestration/personalCloud/uploadAndDownload/v1.0/downloadRequest",
data, nil, account)
if err != nil {
return "", err
}
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
}
func init() {
base.RegisterDriver(&Cloud139{})
}

View File

@ -1,456 +0,0 @@
package _39
import (
"bytes"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"path/filepath"
"strconv"
)
type Cloud139 struct{}
func (driver Cloud139) Config() base.DriverConfig {
return base.DriverConfig{
Name: "139Yun",
LocalSort: true,
}
}
func (driver Cloud139) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "phone",
Type: base.TypeString,
Required: true,
Description: "phone number",
},
{
Name: "access_token",
Label: "Cookie",
Type: base.TypeString,
Required: true,
Description: "Unknown expiration time",
},
{
Name: "internal_type",
Label: "139yun type",
Type: base.TypeSelect,
Required: true,
Values: "Personal,Family",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
{
Name: "site_id",
Label: "cloud_id",
Type: base.TypeString,
Required: false,
},
}
}
func (driver Cloud139) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
_, err := driver.Request("/orchestration/personalCloud/user/v1.0/qryUserExternInfo", base.Post, nil, nil, nil, base.Json{
"qryUserExternInfoReq": base.Json{
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
},
}, nil, account)
return err
}
func (driver Cloud139) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Cloud139) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
if isFamily(account) {
files, err = driver.familyGetFiles(file.Id, account)
} else {
files, err = driver.GetFiles(file.Id, account)
}
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver Cloud139) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
var u string
//if isFamily(account) {
// u, err = driver.familyLink(file.Id, account)
//} else {
u, err = driver.GetLink(file.Id, account)
//}
if err != nil {
return nil, err
}
return &base.Link{Url: u}, nil
}
func (driver Cloud139) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("139 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Cloud139) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Cloud139) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Cloud139) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := base.Json{
"createCatalogExtReq": base.Json{
"parentCatalogID": parentFile.Id,
"newCatalogName": utils.Base(path),
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
},
}
pathname := "/orchestration/personalCloud/catalog/v1.0/createCatalogExt"
if isFamily(account) {
data = base.Json{
"cloudID": account.SiteId,
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
"docLibName": utils.Base(path),
}
pathname = "/orchestration/familyCloud/cloudCatalog/v1.0/createCloudDoc"
}
_, err = driver.Post(pathname,
data, nil, account)
return err
}
func (driver Cloud139) Move(src string, dst string, account *model.Account) error {
if isFamily(account) {
return base.ErrNotSupport
}
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
var contentInfoList []string
var catalogInfoList []string
if srcFile.IsDir() {
catalogInfoList = append(catalogInfoList, srcFile.Id)
} else {
contentInfoList = append(contentInfoList, srcFile.Id)
}
data := base.Json{
"createBatchOprTaskReq": base.Json{
"taskType": 3,
"actionType": "304",
"taskInfo": base.Json{
"contentInfoList": contentInfoList,
"catalogInfoList": catalogInfoList,
"newCatalogID": dstParentFile.Id,
},
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
},
}
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
_, err = driver.Post(pathname, data, nil, account)
return err
}
func (driver Cloud139) Rename(src string, dst string, account *model.Account) error {
if isFamily(account) {
return base.ErrNotSupport
}
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
var data base.Json
var pathname string
if srcFile.IsDir() {
data = base.Json{
"catalogID": srcFile.Id,
"catalogName": utils.Base(dst),
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
}
pathname = "/orchestration/personalCloud/catalog/v1.0/updateCatalogInfo"
} else {
data = base.Json{
"contentID": srcFile.Id,
"contentName": utils.Base(dst),
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
}
pathname = "/orchestration/personalCloud/catalog/v1.0/updateContentInfo"
}
_, err = driver.Post(pathname, data, nil, account)
return err
}
func (driver Cloud139) Copy(src string, dst string, account *model.Account) error {
if isFamily(account) {
return base.ErrNotSupport
}
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
var contentInfoList []string
var catalogInfoList []string
if srcFile.IsDir() {
catalogInfoList = append(catalogInfoList, srcFile.Id)
} else {
contentInfoList = append(contentInfoList, srcFile.Id)
}
data := base.Json{
"createBatchOprTaskReq": base.Json{
"taskType": 3,
"actionType": 309,
"taskInfo": base.Json{
"contentInfoList": contentInfoList,
"catalogInfoList": catalogInfoList,
"newCatalogID": dstParentFile.Id,
},
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
},
}
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
_, err = driver.Post(pathname, data, nil, account)
return err
}
func (driver Cloud139) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
if err != nil {
return err
}
var contentInfoList []string
var catalogInfoList []string
if file.IsDir() {
catalogInfoList = append(catalogInfoList, file.Id)
} else {
contentInfoList = append(contentInfoList, file.Id)
}
data := base.Json{
"createBatchOprTaskReq": base.Json{
"taskType": 2,
"actionType": 201,
"taskInfo": base.Json{
"newCatalogID": "",
"contentInfoList": contentInfoList,
"catalogInfoList": catalogInfoList,
},
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
},
}
pathname := "/orchestration/personalCloud/batchOprTask/v1.0/createBatchOprTask"
if isFamily(account) {
data = base.Json{
"catalogList": catalogInfoList,
"contentList": contentInfoList,
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
"sourceCatalogType": 1002,
"taskType": 2,
}
pathname = "/orchestration/familyCloud/batchOprTask/v1.0/createBatchOprTask"
}
_, err = driver.Post(pathname, data, nil, account)
return err
}
func (driver Cloud139) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
data := base.Json{
"manualRename": 2,
"operation": 0,
"fileCount": 1,
"totalSize": file.Size,
"uploadContentList": []base.Json{{
"contentName": file.Name,
"contentSize": file.Size,
// "digest": "5a3231986ce7a6b46e408612d385bafa"
}},
"parentCatalogID": parentFile.Id,
"newCatalogName": "",
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
}
pathname := "/orchestration/personalCloud/uploadAndDownload/v1.0/pcUploadFileRequest"
if isFamily(account) {
data = newJson(base.Json{
"fileCount": 1,
"manualRename": 2,
"operation": 0,
"path": "",
"seqNo": "",
"totalSize": file.Size,
"uploadContentList": []base.Json{{
"contentName": file.Name,
"contentSize": file.Size,
// "digest": "5a3231986ce7a6b46e408612d385bafa"
}},
}, account)
pathname = "/orchestration/familyCloud/content/v1.0/getFileUploadURL"
return base.ErrNotSupport
}
var resp UploadResp
_, err = driver.Post(pathname, data, &resp, account)
if err != nil {
return err
}
var Default uint64 = 10485760
part := int(math.Ceil(float64(file.Size) / float64(Default)))
var start uint64 = 0
for i := 0; i < part; i++ {
byteSize := file.Size - start
if byteSize > Default {
byteSize = Default
}
byteData := make([]byte, byteSize)
_, err = io.ReadFull(file, byteData)
if err != nil {
return err
}
req, err := http.NewRequest("POST", resp.Data.UploadResult.RedirectionURL, bytes.NewBuffer(byteData))
if err != nil {
return err
}
headers := map[string]string{
"Accept": "*/*",
"Content-Type": "text/plain;name=" + unicode(file.Name),
"contentSize": strconv.FormatUint(file.Size, 10),
"range": fmt.Sprintf("bytes=%d-%d", start, start+byteSize-1),
"content-length": strconv.FormatUint(byteSize, 10),
"uploadtaskID": resp.Data.UploadResult.UploadTaskID,
"rangeType": "0",
"Referer": "https://yun.139.com/",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36 Edg/95.0.1020.44",
"x-SvcType": "1",
}
for k, v := range headers {
req.Header.Set(k, v)
}
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
log.Debugf("%+v", res)
start += byteSize
}
return nil
}
var _ base.Driver = (*Cloud139)(nil)

View File

@ -1,74 +0,0 @@
package _39
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
jsoniter "github.com/json-iterator/go"
"path"
)
func (driver Cloud139) familyGetFiles(catalogID string, account *model.Account) ([]model.File, error) {
pageNum := 1
files := make([]model.File, 0)
for {
data := newJson(base.Json{
"catalogID": catalogID,
"contentSortType": 0,
"pageInfo": base.Json{
"pageNum": pageNum,
"pageSize": 100,
},
"sortDirection": 1,
}, account)
var resp QueryContentListResp
_, err := driver.Post("/orchestration/familyCloud/content/v1.0/queryContentList", data, &resp, account)
if err != nil {
return nil, err
}
for _, catalog := range resp.Data.CloudCatalogList {
f := model.File{
Id: catalog.CatalogID,
Name: catalog.CatalogName,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: getTime(catalog.LastUpdateTime),
}
files = append(files, f)
}
for _, content := range resp.Data.CloudContentList {
f := model.File{
Id: content.ContentID,
Name: content.ContentName,
Size: content.ContentSize,
Type: utils.GetFileType(path.Ext(content.ContentName)),
Driver: driver.Config().Name,
UpdatedAt: getTime(content.LastUpdateTime),
Thumbnail: content.ThumbnailURL,
//Thumbnail: content.BigthumbnailURL,
}
files = append(files, f)
}
if 100*pageNum > resp.Data.TotalCount {
break
}
pageNum++
}
return files, nil
}
func (driver Cloud139) familyLink(contentId string, account *model.Account) (string, error) {
data := newJson(base.Json{
"contentID": contentId,
//"path":"",
}, account)
res, err := driver.Post("/orchestration/familyCloud/content/v1.0/getFileDownLoadURL",
data, nil, account)
if err != nil {
return "", err
}
return jsoniter.Get(res, "data", "downloadURL").ToString(), nil
}

View File

@ -1,187 +0,0 @@
package _39
type BaseResp struct {
Success bool `json:"success"`
Code string `json:"code"`
Message string `json:"message"`
}
type Catalog struct {
CatalogID string `json:"catalogID"`
CatalogName string `json:"catalogName"`
//CatalogType int `json:"catalogType"`
//CreateTime string `json:"createTime"`
UpdateTime string `json:"updateTime"`
//IsShared bool `json:"isShared"`
//CatalogLevel int `json:"catalogLevel"`
//ShareDoneeCount int `json:"shareDoneeCount"`
//OpenType int `json:"openType"`
//ParentCatalogID string `json:"parentCatalogId"`
//DirEtag int `json:"dirEtag"`
//Tombstoned int `json:"tombstoned"`
//ProxyID interface{} `json:"proxyID"`
//Moved int `json:"moved"`
//IsFixedDir int `json:"isFixedDir"`
//IsSynced interface{} `json:"isSynced"`
//Owner string `json:"owner"`
//Modifier interface{} `json:"modifier"`
//Path string `json:"path"`
//ShareType int `json:"shareType"`
//SoftLink interface{} `json:"softLink"`
//ExtProp1 interface{} `json:"extProp1"`
//ExtProp2 interface{} `json:"extProp2"`
//ExtProp3 interface{} `json:"extProp3"`
//ExtProp4 interface{} `json:"extProp4"`
//ExtProp5 interface{} `json:"extProp5"`
//ETagOprType int `json:"ETagOprType"`
}
type Content struct {
ContentID string `json:"contentID"`
ContentName string `json:"contentName"`
//ContentSuffix string `json:"contentSuffix"`
ContentSize int64 `json:"contentSize"`
//ContentDesc string `json:"contentDesc"`
//ContentType int `json:"contentType"`
//ContentOrigin int `json:"contentOrigin"`
UpdateTime string `json:"updateTime"`
//CommentCount int `json:"commentCount"`
ThumbnailURL string `json:"thumbnailURL"`
//BigthumbnailURL string `json:"bigthumbnailURL"`
//PresentURL string `json:"presentURL"`
//PresentLURL string `json:"presentLURL"`
//PresentHURL string `json:"presentHURL"`
//ContentTAGList interface{} `json:"contentTAGList"`
//ShareDoneeCount int `json:"shareDoneeCount"`
//Safestate int `json:"safestate"`
//Transferstate int `json:"transferstate"`
//IsFocusContent int `json:"isFocusContent"`
//UpdateShareTime interface{} `json:"updateShareTime"`
//UploadTime string `json:"uploadTime"`
//OpenType int `json:"openType"`
//AuditResult int `json:"auditResult"`
//ParentCatalogID string `json:"parentCatalogId"`
//Channel string `json:"channel"`
//GeoLocFlag string `json:"geoLocFlag"`
//Digest string `json:"digest"`
//Version string `json:"version"`
//FileEtag string `json:"fileEtag"`
//FileVersion string `json:"fileVersion"`
//Tombstoned int `json:"tombstoned"`
//ProxyID string `json:"proxyID"`
//Moved int `json:"moved"`
//MidthumbnailURL string `json:"midthumbnailURL"`
//Owner string `json:"owner"`
//Modifier string `json:"modifier"`
//ShareType int `json:"shareType"`
//ExtInfo struct {
// Uploader string `json:"uploader"`
// Address string `json:"address"`
//} `json:"extInfo"`
//Exif struct {
// CreateTime string `json:"createTime"`
// Longitude interface{} `json:"longitude"`
// Latitude interface{} `json:"latitude"`
// LocalSaveTime interface{} `json:"localSaveTime"`
//} `json:"exif"`
//CollectionFlag interface{} `json:"collectionFlag"`
//TreeInfo interface{} `json:"treeInfo"`
//IsShared bool `json:"isShared"`
//ETagOprType int `json:"ETagOprType"`
}
type GetDiskResp struct {
BaseResp
Data struct {
Result struct {
ResultCode string `json:"resultCode"`
ResultDesc interface{} `json:"resultDesc"`
} `json:"result"`
GetDiskResult struct {
ParentCatalogID string `json:"parentCatalogID"`
NodeCount int `json:"nodeCount"`
CatalogList []Catalog `json:"catalogList"`
ContentList []Content `json:"contentList"`
IsCompleted int `json:"isCompleted"`
} `json:"getDiskResult"`
} `json:"data"`
}
type UploadResp struct {
BaseResp
Data struct {
Result struct {
ResultCode string `json:"resultCode"`
ResultDesc interface{} `json:"resultDesc"`
} `json:"result"`
UploadResult struct {
UploadTaskID string `json:"uploadTaskID"`
RedirectionURL string `json:"redirectionUrl"`
NewContentIDList []struct {
ContentID string `json:"contentID"`
ContentName string `json:"contentName"`
IsNeedUpload string `json:"isNeedUpload"`
FileEtag int64 `json:"fileEtag"`
FileVersion int64 `json:"fileVersion"`
OverridenFlag int `json:"overridenFlag"`
} `json:"newContentIDList"`
CatalogIDList interface{} `json:"catalogIDList"`
IsSlice interface{} `json:"isSlice"`
} `json:"uploadResult"`
} `json:"data"`
}
type CloudContent struct {
ContentID string `json:"contentID"`
//Modifier string `json:"modifier"`
//Nickname string `json:"nickname"`
//CloudNickName string `json:"cloudNickName"`
ContentName string `json:"contentName"`
//ContentType int `json:"contentType"`
//ContentSuffix string `json:"contentSuffix"`
ContentSize int64 `json:"contentSize"`
//ContentDesc string `json:"contentDesc"`
//CreateTime string `json:"createTime"`
//Shottime interface{} `json:"shottime"`
LastUpdateTime string `json:"lastUpdateTime"`
ThumbnailURL string `json:"thumbnailURL"`
//MidthumbnailURL string `json:"midthumbnailURL"`
//BigthumbnailURL string `json:"bigthumbnailURL"`
//PresentURL string `json:"presentURL"`
//PresentLURL string `json:"presentLURL"`
//PresentHURL string `json:"presentHURL"`
//ParentCatalogID string `json:"parentCatalogID"`
//Uploader string `json:"uploader"`
//UploaderNickName string `json:"uploaderNickName"`
//TreeInfo interface{} `json:"treeInfo"`
//UpdateTime interface{} `json:"updateTime"`
//ExtInfo struct {
// Uploader string `json:"uploader"`
//} `json:"extInfo"`
//EtagOprType interface{} `json:"etagOprType"`
}
type CloudCatalog struct {
CatalogID string `json:"catalogID"`
CatalogName string `json:"catalogName"`
//CloudID string `json:"cloudID"`
//CreateTime string `json:"createTime"`
LastUpdateTime string `json:"lastUpdateTime"`
//Creator string `json:"creator"`
//CreatorNickname string `json:"creatorNickname"`
}
type QueryContentListResp struct {
BaseResp
Data struct {
Result struct {
ResultCode string `json:"resultCode"`
ResultDesc string `json:"resultDesc"`
} `json:"result"`
Path string `json:"path"`
CloudContentList []CloudContent `json:"cloudContentList"`
CloudCatalogList []CloudCatalog `json:"cloudCatalogList"`
TotalCount int `json:"totalCount"`
RecallContent interface{} `json:"recallContent"`
} `json:"data"`
}

View File

@ -1,70 +0,0 @@
package _39
import (
"encoding/base64"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
func encodeURIComponent(str string) string {
r := url.QueryEscape(str)
r = strings.Replace(r, "+", "%20", -1)
return r
}
func calSign(body, ts, randStr string) string {
body = strings.ReplaceAll(body, "\n", "")
body = strings.ReplaceAll(body, " ", "")
body = encodeURIComponent(body)
strs := strings.Split(body, "")
sort.Strings(strs)
body = strings.Join(strs, "")
body = base64.StdEncoding.EncodeToString([]byte(body))
res := utils.GetMD5Encode(body) + utils.GetMD5Encode(ts+":"+randStr)
res = strings.ToUpper(utils.GetMD5Encode(res))
return res
}
func getTime(t string) *time.Time {
stamp, _ := time.ParseInLocation("20060102150405", t, time.Local)
return &stamp
}
func isFamily(account *model.Account) bool {
return account.InternalType == "Family"
}
func unicode(str string) string {
textQuoted := strconv.QuoteToASCII(str)
textUnquoted := textQuoted[1 : len(textQuoted)-1]
return textUnquoted
}
func MergeMap(mObj ...map[string]interface{}) map[string]interface{} {
newObj := map[string]interface{}{}
for _, m := range mObj {
for k, v := range m {
newObj[k] = v
}
}
return newObj
}
func newJson(data map[string]interface{}, account *model.Account) map[string]interface{} {
common := map[string]interface{}{
"catalogType": 3,
"cloudID": account.SiteId,
"cloudType": 1,
"commonAccountInfo": base.Json{
"account": account.Username,
"accountType": 1,
},
}
return MergeMap(data, common)
}

View File

@ -1,625 +0,0 @@
package _89
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"io"
"math"
"net/http"
"net/http/cookiejar"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)
var client189Map map[string]*resty.Client
var infoMap = make(map[string]Rsa)
func (driver Cloud189) getClient(account *model.Account) (*resty.Client, error) {
client, ok := client189Map[account.Name]
if ok {
return client, nil
}
err := driver.Login(account)
if err != nil {
return nil, err
}
client, ok = client189Map[account.Name]
if !ok {
return nil, fmt.Errorf("can't find [%s] client", account.Name)
}
return client, nil
}
func (driver Cloud189) FormatFile(file *Cloud189File) *model.File {
f := &model.File{
Id: strconv.FormatInt(file.Id, 10),
Name: file.Name,
Size: file.Size,
Driver: driver.Config().Name,
UpdatedAt: nil,
Thumbnail: file.Icon.SmallUrl,
Url: file.Url,
}
loc, _ := time.LoadLocation("Local")
lastOpTime, err := time.ParseInLocation("2006-01-02 15:04:05", file.LastOpTime, loc)
if err == nil {
f.UpdatedAt = &lastOpTime
}
if file.Size == -1 {
f.Type = conf.FOLDER
f.Size = 0
} else {
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
//func (c Cloud189) GetFile(path string, account *model.Account) (*Cloud189File, error) {
// dir, name := filepath.Split(path)
// dir = utils.ParsePath(dir)
// _, _, err := c.ParentPath(dir, account)
// if err != nil {
// return nil, err
// }
// parentFiles_, _ := conf.Cache.Get(conf.Ctx, fmt.Sprintf("%s%s", account.Name, dir))
// parentFiles, _ := parentFiles_.([]Cloud189File)
// for _, file := range parentFiles {
// if file.Name == name {
// if file.Size != -1 {
// return &file, err
// } else {
// return nil, ErrNotFile
// }
// }
// }
// return nil, ErrPathNotFound
//}
type LoginResp struct {
Msg string `json:"msg"`
Result int `json:"result"`
ToUrl string `json:"toUrl"`
}
// Login refer to PanIndex
func (driver Cloud189) Login(account *model.Account) error {
client, ok := client189Map[account.Name]
if !ok {
//cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client = resty.New()
//client.SetCookieJar(cookieJar)
client.SetTimeout(base.DefaultTimeout)
client.SetRetryCount(3)
client.SetHeader("Referer", "https://cloud.189.cn/")
}
// clear cookie
jar, _ := cookiejar.New(nil)
client.SetCookieJar(jar)
url := "https://cloud.189.cn/api/portal/loginUrl.action?redirectURL=https%3A%2F%2Fcloud.189.cn%2Fmain.action"
b := ""
lt := ""
ltText := regexp.MustCompile(`lt = "(.+?)"`)
for i := 0; i < 3; i++ {
res, err := client.R().Get(url)
if err != nil {
return err
}
// 已经登陆
if res.RawResponse.Request.URL.String() == "https://cloud.189.cn/web/main" {
return nil
}
b = res.String()
ltTextArr := ltText.FindStringSubmatch(b)
if len(ltTextArr) > 0 {
lt = ltTextArr[1]
break
} else {
<-time.After(time.Second)
}
}
if lt == "" {
return fmt.Errorf("get empty login page")
}
captchaToken := regexp.MustCompile(`captchaToken' value='(.+?)'`).FindStringSubmatch(b)[1]
returnUrl := regexp.MustCompile(`returnUrl = '(.+?)'`).FindStringSubmatch(b)[1]
paramId := regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(b)[1]
//reqId := regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(b)[1]
jRsakey := regexp.MustCompile(`j_rsaKey" value="(\S+)"`).FindStringSubmatch(b)[1]
vCodeID := regexp.MustCompile(`picCaptcha\.do\?token\=([A-Za-z0-9\&\=]+)`).FindStringSubmatch(b)[1]
vCodeRS := ""
if vCodeID != "" {
// need ValidateCode
log.Debugf("try to identify verification codes")
timeStamp := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
u := "https://open.e.189.cn/api/logbox/oauth2/picCaptcha.do?token=" + vCodeID + timeStamp
imgRes, err := client.R().SetHeaders(map[string]string{
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
"Referer": "https://open.e.189.cn/api/logbox/oauth2/unifyAccountLogin.do",
"Sec-Fetch-Dest": "image",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
}).Get(u)
if err != nil {
return err
}
vRes, err := client.R().SetMultipartField(
"image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
Post(conf.GetStr("ocr api"))
if err != nil {
return err
}
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
}
vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", vCodeRS)
}
userRsa := RsaEncode([]byte(account.Username), jRsakey, true)
passwordRsa := RsaEncode([]byte(account.Password), jRsakey, true)
url = "https://open.e.189.cn/api/logbox/oauth2/loginSubmit.do"
var loginResp LoginResp
res, err := client.R().
SetHeaders(map[string]string{
"lt": lt,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36",
"Referer": "https://open.e.189.cn/",
"accept": "application/json;charset=UTF-8",
}).SetFormData(map[string]string{
"appKey": "cloud",
"accountType": "01",
"userName": "{RSA}" + userRsa,
"password": "{RSA}" + passwordRsa,
"validateCode": vCodeRS,
"captchaToken": captchaToken,
"returnUrl": returnUrl,
"mailSuffix": "@pan.cn",
"paramId": paramId,
"clientType": "10010",
"dynamicCheck": "FALSE",
"cb_SaveName": "1",
"isOauth2": "false",
}).Post(url)
if err != nil {
return err
}
err = utils.Json.Unmarshal(res.Body(), &loginResp)
if err != nil {
log.Error(err.Error())
return err
}
if loginResp.Result != 0 {
return fmt.Errorf(loginResp.Msg)
}
_, err = client.R().Get(loginResp.ToUrl)
if err != nil {
log.Errorf(err.Error())
return err
}
client189Map[account.Name] = client
return nil
}
func (driver Cloud189) isFamily(account *model.Account) bool {
return account.InternalType == "Family"
}
func (driver Cloud189) GetFiles(fileId string, account *model.Account) ([]Cloud189File, error) {
res := make([]Cloud189File, 0)
pageNum := 1
for {
var resp Cloud189Files
body, err := driver.Request("https://cloud.189.cn/api/open/file/listFiles.action", base.Get, map[string]string{
//"noCache": random(),
"pageSize": "60",
"pageNum": strconv.Itoa(pageNum),
"mediaType": "0",
"folderId": fileId,
"iconOption": "5",
"orderBy": "lastOpTime", //account.OrderBy
"descending": "true", //account.OrderDirection
}, nil, nil, account)
if err != nil {
return nil, err
}
err = utils.Json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
if resp.ResCode != 0 {
return nil, fmt.Errorf(resp.ResMessage)
}
if resp.FileListAO.Count == 0 {
break
}
for _, folder := range resp.FileListAO.FolderList {
res = append(res, Cloud189File{
Id: folder.Id,
LastOpTime: folder.LastOpTime,
Name: folder.Name,
Size: -1,
})
}
res = append(res, resp.FileListAO.FileList...)
pageNum++
}
return res, nil
}
func (driver Cloud189) Request(url string, method int, query, form map[string]string, headers map[string]string, account *model.Account) ([]byte, error) {
client, err := driver.getClient(account)
if err != nil {
return nil, err
}
//var resp base.Json
if driver.isFamily(account) {
url = strings.Replace(url, "/api/open", "/api/open/family", 1)
if query != nil {
query["familyId"] = account.SiteId
}
if form != nil {
form["familyId"] = account.SiteId
}
}
var e Cloud189Error
req := client.R().SetError(&e).
SetHeader("Accept", "application/json;charset=UTF-8").
SetQueryParams(map[string]string{
"noCache": random(),
})
if query != nil {
req = req.SetQueryParams(query)
}
if form != nil {
req = req.SetFormData(form)
}
if headers != nil {
req = req.SetHeaders(headers)
}
var res *resty.Response
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
if e.ErrorCode != "" {
if e.ErrorCode == "InvalidSessionKey" {
err = driver.Login(account)
if err != nil {
return nil, err
}
return driver.Request(url, method, query, form, nil, account)
}
}
if jsoniter.Get(res.Body(), "res_code").ToInt() != 0 {
err = errors.New(jsoniter.Get(res.Body(), "res_message").ToString())
}
return res.Body(), err
}
func (driver Cloud189) GetSessionKey(account *model.Account) (string, error) {
//info, ok := infoMap[account.Name]
//if !ok {
// info = Info{}
// infoMap[account.Name] = info
//} else {
// log.Debugf("hit")
//}
//if info.SessionKey != "" {
// return info.SessionKey, nil
//}
resp, err := driver.Request("https://cloud.189.cn/v2/getUserBriefInfo.action", base.Get, nil, nil, nil, account)
if err != nil {
return "", err
}
sessionKey := jsoniter.Get(resp, "sessionKey").ToString()
//info.SessionKey = sessionKey
return sessionKey, nil
}
func (driver Cloud189) GetResKey(account *model.Account) (string, string, error) {
rsa, ok := infoMap[account.Name]
if !ok {
rsa = Rsa{}
infoMap[account.Name] = rsa
}
now := time.Now().UnixMilli()
if rsa.Expire > now {
return rsa.PubKey, rsa.PkId, nil
}
resp, err := driver.Request("https://cloud.189.cn/api/security/generateRsaKey.action", base.Get, nil, nil, nil, account)
if err != nil {
return "", "", err
}
pubKey, pkId := jsoniter.Get(resp, "pubKey").ToString(), jsoniter.Get(resp, "pkId").ToString()
rsa.PubKey, rsa.PkId = pubKey, pkId
rsa.Expire = jsoniter.Get(resp, "expire").ToInt64()
return pubKey, pkId, nil
}
//func (driver Cloud189) UploadRequest1(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
// //sessionKey, err := driver.GetSessionKey(account)
// //if err != nil {
// // return nil, err
// //}
// sessionKey := account.DriveId
// pubKey, pkId, err := driver.GetResKey(account)
// log.Debugln(sessionKey, pubKey, pkId)
// if err != nil {
// return nil, err
// }
// xRId := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// pkey := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")[0 : 16+int(16*mathRand.Float32())]
// params := hex.EncodeToString(AesEncrypt([]byte(qs(form)), []byte(pkey[0:16])))
// date := strconv.FormatInt(time.Now().Unix(), 10)
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", date)
// a.Set("params", params)
// signature := hex.EncodeToString(SHA1(EncodeParam(a), pkey))
// encryptionText := RsaEncode([]byte(pkey), pubKey, false)
// headers := map[string]string{
// "signature": signature,
// "sessionKey": sessionKey,
// "encryptionText": encryptionText,
// "pkId": pkId,
// "x-request-id": xRId,
// "x-request-date": date,
// }
// req := base.RestyClient.R().SetHeaders(headers).SetQueryParam("params", params)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data := res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
//
//func (driver Cloud189) UploadRequest2(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
// c := strconv.FormatInt(time.Now().UnixMilli(), 10)
// r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
// l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
// l = l[0 : 16+int(16*mathRand.Float32())]
//
// e := qs(form)
// data := AesEncrypt([]byte(e), []byte(l[0:16]))
// h := hex.EncodeToString(data)
//
// sessionKey := account.DriveId
// a := make(url.Values)
// a.Set("SessionKey", sessionKey)
// a.Set("Operate", http.MethodGet)
// a.Set("RequestURI", uri)
// a.Set("Date", c)
// a.Set("params", h)
// g := SHA1(EncodeParam(a), l)
//
// pubKey, pkId, err := driver.GetResKey(account)
// if err != nil {
// return nil, err
// }
// b := RsaEncode([]byte(l), pubKey, false)
// client, err := driver.getClient(account)
// if err != nil {
// return nil, err
// }
// req := client.R()
// req.Header.Set("accept", "application/json;charset=UTF-8")
// req.Header.Set("SessionKey", sessionKey)
// req.Header.Set("Signature", hex.EncodeToString(g))
// req.Header.Set("X-Request-Date", c)
// req.Header.Set("X-Request-ID", r)
// req.Header.Set("EncryptionText", b)
// req.Header.Set("PkId", pkId)
// if resp != nil {
// req.SetResult(resp)
// }
// res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
// if err != nil {
// return nil, err
// }
// //log.Debug(res.String())
// data = res.Body()
// if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
// return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
// }
// return data, nil
//}
func (driver Cloud189) UploadRequest(uri string, form map[string]string, account *model.Account, resp interface{}) ([]byte, error) {
c := strconv.FormatInt(time.Now().UnixMilli(), 10)
r := Random("xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx")
l := Random("xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx")
l = l[0 : 16+int(16*utils.Rand.Float32())]
e := qs(form)
data := AesEncrypt([]byte(e), []byte(l[0:16]))
h := hex.EncodeToString(data)
sessionKey := account.DriveId
signature := hmacSha1(fmt.Sprintf("SessionKey=%s&Operate=GET&RequestURI=%s&Date=%s&params=%s", sessionKey, uri, c, h), l)
pubKey, pkId, err := driver.GetResKey(account)
if err != nil {
return nil, err
}
b := RsaEncode([]byte(l), pubKey, false)
client, err := driver.getClient(account)
if err != nil {
return nil, err
}
req := client.R()
req.Header.Set("accept", "application/json;charset=UTF-8")
req.Header.Set("SessionKey", sessionKey)
req.Header.Set("Signature", signature)
req.Header.Set("X-Request-Date", c)
req.Header.Set("X-Request-ID", r)
req.Header.Set("EncryptionText", b)
req.Header.Set("PkId", pkId)
if resp != nil {
req.SetResult(resp)
}
res, err := req.Get("https://upload.cloud.189.cn" + uri + "?params=" + h)
if err != nil {
return nil, err
}
//log.Debug(res.String())
data = res.Body()
if jsoniter.Get(data, "code").ToString() != "SUCCESS" {
return nil, errors.New(uri + "---" + jsoniter.Get(data, "msg").ToString())
}
return data, nil
}
// NewUpload Error: signature check false
func (driver Cloud189) NewUpload(file *model.FileStream, account *model.Account) error {
sessionKey, err := driver.GetSessionKey(account)
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
account.DriveId = sessionKey
}
_ = model.SaveAccount(account)
const DEFAULT uint64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
var finish uint64 = 0
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
res, err := driver.UploadRequest("/person/initMultiUpload", map[string]string{
"parentFolderId": parentFile.Id,
"fileName": encode(file.Name),
"fileSize": strconv.FormatInt(int64(file.Size), 10),
"sliceSize": strconv.FormatInt(int64(DEFAULT), 10),
"lazyCheck": "1",
}, account, nil)
if err != nil {
return err
}
uploadFileId := jsoniter.Get(res, "data", "uploadFileId").ToString()
//_, err = driver.UploadRequest("/person/getUploadedPartsInfo", map[string]string{
// "uploadFileId": uploadFileId,
//}, account, nil)
var i int64
var byteSize uint64
md5s := make([]string, 0)
md5Sum := md5.New()
for i = 1; i <= count; i++ {
byteSize = file.GetSize() - finish
if DEFAULT < byteSize {
byteSize = DEFAULT
}
//log.Debugf("%d,%d", byteSize, finish)
byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData)
//log.Debug(err, n)
if err != nil {
return err
}
finish += uint64(n)
md5Bytes := getMd5(byteData)
md5Hex := hex.EncodeToString(md5Bytes)
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
md5s = append(md5s, strings.ToUpper(md5Hex))
md5Sum.Write(byteData)
//log.Debugf("md5Bytes: %+v,md5Str:%s,md5Base64:%s", md5Bytes, md5Hex, md5Base64)
var resp UploadUrlsResp
res, err = driver.UploadRequest("/person/getMultiUploadUrls", map[string]string{
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
"uploadFileId": uploadFileId,
}, account, &resp)
if err != nil {
return err
}
uploadData := resp.UploadUrls["partNumber_"+strconv.FormatInt(i, 10)]
log.Debugf("uploadData: %+v", uploadData)
requestURL := uploadData.RequestURL
uploadHeaders := strings.Split(decodeURIComponent(uploadData.RequestHeader), "&")
req, _ := http.NewRequest(http.MethodPut, requestURL, bytes.NewReader(byteData))
for _, v := range uploadHeaders {
i := strings.Index(v, "=")
req.Header.Set(v[0:i], v[i+1:])
}
r, err := base.HttpClient.Do(req)
log.Debugf("%+v %+v", r, r.Request.Header)
if err != nil {
return err
}
}
fileMd5 := hex.EncodeToString(md5Sum.Sum(nil))
sliceMd5 := fileMd5
if file.GetSize() > DEFAULT {
sliceMd5 = utils.GetMD5Encode(strings.Join(md5s, "\n"))
}
res, err = driver.UploadRequest("/person/commitMultiUploadFile", map[string]string{
"uploadFileId": uploadFileId,
"fileMd5": fileMd5,
"sliceMd5": sliceMd5,
"lazyCheck": "1",
}, account, nil)
account.DriveId, _ = driver.GetSessionKey(account)
return err
}
func (driver Cloud189) OldUpload(file *model.FileStream, account *model.Account) error {
//return base.ErrNotImplement
client, err := driver.getClient(account)
if err != nil {
return err
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
// api refer to PanIndex
res, err := client.R().SetMultipartFormData(map[string]string{
"parentId": parentFile.Id,
"sessionKey": account.DriveId,
"opertype": "1",
"fname": file.GetFileName(),
}).SetMultipartField("Filedata", file.GetFileName(), file.GetMIMEType(), file).Post("https://hb02.upload.cloud.189.cn/v1/DCIWebUploadAction")
if err != nil {
return err
}
if jsoniter.Get(res.Body(), "MD5").ToString() != "" {
return nil
}
log.Debugf(res.String())
return errors.New(res.String())
}

View File

@ -1,377 +0,0 @@
package _89
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"path/filepath"
"strings"
)
type Cloud189 struct{}
func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{
Name: "189Cloud",
LocalSort: true,
}
}
func (driver Cloud189) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
//{
// Name: "internal_type",
// Label: "189cloud type",
// Type: base.TypeSelect,
// Required: true,
// Values: "Personal,Family",
//},
//{
// Name: "site_id",
// Label: "family id",
// Type: base.TypeString,
//},
//{
// Name: "order_by",
// Label: "order_by",
// Type: base.TypeSelect,
// Values: "name,size,lastOpTime,createdDate",
// Required: true,
//},
//{
// Name: "order_direction",
// Label: "desc",
// Type: base.TypeSelect,
// Values: "true,false",
// Required: true,
//},
}
}
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
if old != nil {
delete(client189Map, old.Name)
}
if account == nil {
return nil
}
if err := driver.Login(account); err != nil {
account.Status = err.Error()
_ = model.SaveAccount(account)
return err
}
sessionKey, err := driver.GetSessionKey(account)
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
account.DriveId = sessionKey
}
_ = model.SaveAccount(account)
return err
}
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []Cloud189File
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]Cloud189File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(utils.ParsePath(args.Path), account)
if err != nil {
return nil, err
}
if file.Type == conf.FOLDER {
return nil, base.ErrNotFile
}
var resp DownResp
u := "https://cloud.189.cn/api/portal/getFileInfo.action"
body, err := driver.Request(u, base.Get, map[string]string{
"fileId": file.Id,
}, nil, nil, account)
if err != nil {
return nil, err
}
log.Debugln(string(body))
err = utils.Json.Unmarshal(body, &resp)
if err != nil {
return nil, err
}
if resp.ResCode != 0 {
return nil, fmt.Errorf(resp.ResMessage)
}
client, err := driver.getClient(account)
if err != nil {
return nil, err
}
client = resty.NewWithClient(client.GetClient()).SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}))
res, err := client.R().Get("https:" + resp.FileDownloadUrl)
if err != nil {
return nil, err
}
log.Debugln(res.Status())
link := base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
{Name: "Authorization", Value: ""},
},
}
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
res, err = client.R().Get(link.Url)
if res.StatusCode() == 302 {
link.Url = res.Header().Get("location")
}
} else {
link.Url = resp.FileDownloadUrl
}
link.Url = strings.Replace(link.Url, "http://", "https://", 1)
return &link, nil
}
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("189 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Cloud189) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
//}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parent, err := driver.File(dir, account)
if err != nil {
return err
}
if !parent.IsDir() {
return base.ErrNotFolder
}
form := map[string]string{
"parentFolderId": parent.Id,
"folderName": name,
}
_, err = driver.Request("https://cloud.189.cn/api/open/file/createFolder.action", base.Post, nil, form, nil, account)
return err
}
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
dstDir, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
isFolder := 0
if srcFile.IsDir() {
isFolder = 1
}
taskInfos := []base.Json{
{
"fileId": srcFile.Id,
"fileName": dstName,
"isFolder": isFolder,
},
}
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
if err != nil {
return err
}
form := map[string]string{
"type": "MOVE",
"targetFolderId": dstDirFile.Id,
"taskInfos": string(taskInfosBytes),
}
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
return err
}
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
url := "https://cloud.189.cn/api/open/file/renameFile.action"
idKey := "fileId"
nameKey := "destFileName"
if srcFile.IsDir() {
url = "https://cloud.189.cn/api/open/file/renameFolder.action"
idKey = "folderId"
nameKey = "destFolderName"
}
form := map[string]string{
idKey: srcFile.Id,
nameKey: dstName,
}
_, err = driver.Request(url, base.Post, nil, form, nil, account)
return err
}
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
dstDir, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
isFolder := 0
if srcFile.IsDir() {
isFolder = 1
}
taskInfos := []base.Json{
{
"fileId": srcFile.Id,
"fileName": dstName,
"isFolder": isFolder,
},
}
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
if err != nil {
return err
}
form := map[string]string{
"type": "COPY",
"targetFolderId": dstDirFile.Id,
"taskInfos": string(taskInfosBytes),
}
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
return err
}
func (driver Cloud189) Delete(path string, account *model.Account) error {
path = utils.ParsePath(path)
file, err := driver.File(path, account)
if err != nil {
return err
}
isFolder := 0
if file.IsDir() {
isFolder = 1
}
taskInfos := []base.Json{
{
"fileId": file.Id,
"fileName": file.Name,
"isFolder": isFolder,
},
}
taskInfosBytes, err := utils.Json.Marshal(taskInfos)
if err != nil {
return err
}
form := map[string]string{
"type": "DELETE",
"targetFolderId": "",
"taskInfos": string(taskInfosBytes),
}
_, err = driver.Request("https://cloud.189.cn/api/open/batch/createBatchTask.action", base.Post, nil, form, nil, account)
return err
}
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
//return base.ErrNotImplement
if file == nil {
return base.ErrEmptyFile
}
return driver.NewUpload(file, account)
//return driver.OldUpload(file, account)
}
var _ base.Driver = (*Cloud189)(nil)

View File

@ -1,67 +0,0 @@
package _89
type Cloud189Error struct {
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
type Cloud189File struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
Size int64 `json:"size"`
Icon struct {
SmallUrl string `json:"smallUrl"`
//LargeUrl string `json:"largeUrl"`
} `json:"icon"`
Url string `json:"url"`
}
type Cloud189Folder struct {
Id int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Name string `json:"name"`
}
type Cloud189Files struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
type UploadUrlsResp struct {
Code string `json:"code"`
UploadUrls map[string]Part `json:"uploadUrls"`
}
type Part struct {
RequestURL string `json:"requestURL"`
RequestHeader string `json:"requestHeader"`
}
//type Info struct {
// SessionKey string
// Rsa Rsa
//}
type Rsa struct {
Expire int64 `json:"expire"`
PkId string `json:"pkId"`
PubKey string `json:"pubKey"`
}
type Cloud189Down struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileDownloadUrl string `json:"fileDownloadUrl"`
}
type DownResp struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
FileDownloadUrl string `json:"downloadUrl"`
}

View File

@ -1,199 +0,0 @@
package _89
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/url"
"regexp"
"strconv"
"strings"
)
func random() string {
return fmt.Sprintf("0.%17v", utils.Rand.Int63n(100000000000000000))
}
func RsaEncode(origData []byte, j_rsakey string, hex bool) string {
publicKey := []byte("-----BEGIN PUBLIC KEY-----\n" + j_rsakey + "\n-----END PUBLIC KEY-----")
block, _ := pem.Decode(publicKey)
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
pub := pubInterface.(*rsa.PublicKey)
b, err := rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
if err != nil {
log.Errorf("err: %s", err.Error())
}
res := base64.StdEncoding.EncodeToString(b)
if hex {
return b64tohex(res)
}
return res
}
var b64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"
func int2char(a int) string {
return strings.Split(BI_RM, "")[a]
}
func b64tohex(a string) string {
d := ""
e := 0
c := 0
for i := 0; i < len(a); i++ {
m := strings.Split(a, "")[i]
if m != "=" {
v := strings.Index(b64map, m)
if 0 == e {
e = 1
d += int2char(v >> 2)
c = 3 & v
} else if 1 == e {
e = 2
d += int2char(c<<2 | v>>4)
c = 15 & v
} else if 2 == e {
e = 3
d += int2char(c)
d += int2char(v >> 2)
c = 3 & v
} else {
e = 0
d += int2char(c<<2 | v>>4)
d += int2char(15 & v)
}
}
}
if e == 1 {
d += int2char(c << 2)
}
return d
}
func qs(form map[string]string) string {
f := make(url.Values)
for k, v := range form {
f.Set(k, v)
}
return EncodeParam(f)
//strList := make([]string, 0)
//for k, v := range form {
// strList = append(strList, fmt.Sprintf("%s=%s", k, url.QueryEscape(v)))
//}
//return strings.Join(strList, "&")
}
func EncodeParam(v url.Values) string {
if v == nil {
return ""
}
var buf strings.Builder
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
for _, k := range keys {
vs := v[k]
for _, v := range vs {
if buf.Len() > 0 {
buf.WriteByte('&')
}
buf.WriteString(k)
buf.WriteByte('=')
//if k == "fileName" {
// buf.WriteString(encode(v))
//} else {
buf.WriteString(v)
//}
}
}
return buf.String()
}
func encode(str string) string {
//str = strings.ReplaceAll(str, "%", "%25")
//str = strings.ReplaceAll(str, "&", "%26")
//str = strings.ReplaceAll(str, "+", "%2B")
//return str
return url.QueryEscape(str)
}
func AesEncrypt(data, key []byte) []byte {
block, _ := aes.NewCipher(key)
if block == nil {
return []byte{}
}
data = PKCS7Padding(data, block.BlockSize())
decrypted := make([]byte, len(data))
size := block.BlockSize()
for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
block.Encrypt(decrypted[bs:be], data[bs:be])
}
return decrypted
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func hmacSha1(data string, secret string) string {
h := hmac.New(sha1.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func getMd5(data []byte) []byte {
h := md5.New()
h.Write(data)
return h.Sum(nil)
}
func init() {
base.RegisterDriver(&Cloud189{})
client189Map = make(map[string]*resty.Client)
}
func decodeURIComponent(str string) string {
r, _ := url.PathUnescape(str)
//r = strings.ReplaceAll(r, " ", "+")
return r
}
func Random(v string) string {
reg := regexp.MustCompilePOSIX("[xy]")
data := reg.ReplaceAllFunc([]byte(v), func(msg []byte) []byte {
var i int64
t := int64(16 * utils.Rand.Float32())
if msg[0] == 120 {
i = t
} else {
i = 3&t | 8
}
return []byte(strconv.FormatInt(i, 16))
})
return string(data)
}
//func SHA1(v, l string) []byte {
// key := []byte(l)
// mac := hmac.New(sha1.New, key)
// mac.Write([]byte(v))
// return mac.Sum(nil)
//}

View File

@ -1,330 +0,0 @@
package _189
import (
"bytes"
"errors"
"fmt"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"sync"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
)
var userStateCache = struct {
sync.Mutex
States map[string]*State
}{States: make(map[string]*State)}
func GetState(account *model.Account) *State {
userStateCache.Lock()
defer userStateCache.Unlock()
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
return v
}
state := &State{client: resty.New().
SetHeaders(map[string]string{
"Accept": "application/json;charset=UTF-8",
"User-Agent": base.UserAgent,
}),
}
userStateCache.States[account.Username] = state
return state
}
type State struct {
sync.Mutex
client *resty.Client
RsaPublicKey string
SessionKey string
SessionSecret string
FamilySessionKey string
FamilySessionSecret string
AccessToken string
//怎么刷新的???
RefreshToken string
}
func (s *State) login(account *model.Account) error {
// 清除cookie
jar, _ := cookiejar.New(nil)
s.client.SetCookieJar(jar)
var err error
var res *resty.Response
defer func() {
account.Status = "work"
if err != nil {
account.Status = err.Error()
}
model.SaveAccount(account)
if res != nil {
log.Debug(res.String())
}
}()
var param *LoginParam
param, err = s.getLoginParam()
if err != nil {
return err
}
// 提交登录
s.RsaPublicKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", param.jRsaKey)
res, err = s.client.R().
SetHeaders(map[string]string{
"Referer": AUTH_URL,
"REQID": param.ReqId,
"lt": param.Lt,
}).
SetFormData(map[string]string{
"appKey": APP_ID,
"accountType": "02",
"userName": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Username),
"password": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Password),
"validateCode": param.vCodeRS,
"captchaToken": param.CaptchaToken,
"returnUrl": RETURN_URL,
"mailSuffix": "@189.cn",
"dynamicCheck": "FALSE",
"clientType": CLIENT_TYPE,
"cb_SaveName": "1",
"isOauth2": "false",
"state": "",
"paramId": param.ParamId,
}).
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
if err != nil {
return err
}
toUrl := utils.Json.Get(res.Body(), "toUrl").ToString()
if toUrl == "" {
log.Error(res.String())
return fmt.Errorf(res.String())
}
// 获取Session
var erron Erron
var sessionResp appSessionResp
res, err = s.client.R().
SetResult(&sessionResp).SetError(&erron).
SetQueryParams(clientSuffix()).
SetQueryParam("redirectURL", url.QueryEscape(toUrl)).
Post(API_URL + "/getSessionForPC.action")
if err != nil {
return err
}
if erron.ResCode != "" {
err = fmt.Errorf(erron.ResMessage)
return err
}
if sessionResp.ResCode != 0 {
err = fmt.Errorf(sessionResp.ResMessage)
return err
}
s.SessionKey = sessionResp.SessionKey
s.SessionSecret = sessionResp.SessionSecret
s.FamilySessionKey = sessionResp.FamilySessionKey
s.FamilySessionSecret = sessionResp.FamilySessionSecret
s.AccessToken = sessionResp.AccessToken
s.RefreshToken = sessionResp.RefreshToken
return err
}
func (s *State) getLoginParam() (*LoginParam, error) {
res, err := s.client.R().
SetQueryParams(map[string]string{
"appId": APP_ID,
"clientType": CLIENT_TYPE,
"returnURL": RETURN_URL,
"timeStamp": fmt.Sprint(timestamp()),
}).
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
if err != nil {
return nil, err
}
log.Debug(res.String())
param := &LoginParam{
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
vCodeID: regexp.MustCompile(`token=([A-Za-z0-9&=]+)`).FindStringSubmatch(res.String())[1],
}
imgRes, err := s.client.R().Get(fmt.Sprint(AUTH_URL, "/api/logbox/oauth2/picCaptcha.do?token=", param.vCodeID, timestamp()))
if err != nil {
return nil, err
}
if len(imgRes.Body()) > 0 {
vRes, err := resty.New().R().
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
Post(conf.GetStr("ocr api"))
if err != nil {
return nil, err
}
if utils.Json.Get(vRes.Body(), "status").ToInt() != 200 {
return nil, errors.New("ocr error:" + utils.Json.Get(vRes.Body(), "msg").ToString())
}
param.vCodeRS = utils.Json.Get(vRes.Body(), "result").ToString()
log.Debugln("code: ", param.vCodeRS)
}
return param, nil
}
func (s *State) refreshSession(account *model.Account) error {
var erron Erron
var userSessionResp UserSessionResp
res, err := s.client.R().
SetResult(&userSessionResp).SetError(&erron).
SetQueryParams(clientSuffix()).
SetQueryParams(map[string]string{
"appId": APP_ID,
"accessToken": s.AccessToken,
}).
SetHeader("X-Request-ID", uuid.NewString()).
Get("https://api.cloud.189.cn/getSessionForPC.action")
if err != nil {
return err
}
log.Debug(res.String())
if erron.ResCode != "" {
return fmt.Errorf(erron.ResMessage)
}
switch userSessionResp.ResCode {
case 0:
s.SessionKey = userSessionResp.SessionKey
s.SessionSecret = userSessionResp.SessionSecret
s.FamilySessionKey = userSessionResp.FamilySessionKey
s.FamilySessionSecret = userSessionResp.FamilySessionSecret
case 11, 18:
return s.login(account)
default:
account.Status = userSessionResp.ResMessage
_ = model.SaveAccount(account)
return fmt.Errorf(userSessionResp.ResMessage)
}
return nil
}
func (s *State) IsLogin() bool {
_, err := s.Request("GET", API_URL+"/getUserInfo.action", nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
}, nil)
return err == nil
}
func (s *State) Login(account *model.Account) error {
s.Lock()
defer s.Unlock()
return s.login(account)
}
func (s *State) RefreshSession(account *model.Account) error {
s.Lock()
defer s.Unlock()
return s.refreshSession(account)
}
func (s *State) Request(method string, fullUrl string, params url.Values, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
s.Lock()
dateOfGmt := getHttpDateStr()
sessionKey := s.SessionKey
sessionSecret := s.SessionSecret
if account != nil && isFamily(account) {
sessionKey = s.FamilySessionKey
sessionSecret = s.FamilySessionSecret
}
req := s.client.R()
req.SetHeaders(map[string]string{
"Date": dateOfGmt,
"SessionKey": sessionKey,
"X-Request-ID": uuid.NewString(),
})
// 设置params
var paramsData string
if params != nil {
paramsData = AesECBEncrypt(params.Encode(), s.SessionSecret[:16])
req.SetQueryParam("params", paramsData)
}
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, fullUrl, dateOfGmt, paramsData))
callback(req)
s.Unlock()
var err error
var res *resty.Response
switch method {
case "GET":
res, err = req.Get(fullUrl)
case "POST":
res, err = req.Post(fullUrl)
case "DELETE":
res, err = req.Delete(fullUrl)
case "PATCH":
res, err = req.Patch(fullUrl)
case "PUT":
res, err = req.Put(fullUrl)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debug(res.String())
var erron Erron
utils.Json.Unmarshal(res.Body(), &erron)
if erron.ResCode != "" {
return nil, fmt.Errorf(erron.ResMessage)
}
if erron.Code != "" && erron.Code != "SUCCESS" {
if erron.Msg == "" {
return nil, fmt.Errorf(erron.Message)
}
return nil, fmt.Errorf(erron.Msg)
}
if erron.ErrorCode != "" {
return nil, fmt.Errorf(erron.ErrorMsg)
}
if account != nil {
switch utils.Json.Get(res.Body(), "res_code").ToInt64() {
case 11, 18:
if err := s.RefreshSession(account); err != nil {
return nil, err
}
return s.Request(method, fullUrl, params, callback, account)
case 0:
if res.StatusCode() == http.StatusOK {
return res, nil
}
fallthrough
default:
return nil, fmt.Errorf(res.String())
}
}
if utils.Json.Get(res.Body(), "res_code").ToInt64() != 0 {
return res, fmt.Errorf(utils.Json.Get(res.Body(), "res_message").ToString())
}
return res, nil
}

View File

@ -1,831 +0,0 @@
package _189
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
)
func init() {
base.RegisterDriver(new(Cloud189))
}
type Cloud189 struct {
}
func (driver Cloud189) Config() base.DriverConfig {
return base.DriverConfig{
Name: "189CloudPC",
}
}
func (driver Cloud189) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
{
Name: "internal_type",
Label: "189cloud type",
Type: base.TypeSelect,
Required: true,
Values: "Personal,Family",
},
{
Name: "site_id",
Label: "family id",
Type: base.TypeString,
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "filename,filesize,lastOpTime",
Required: true,
},
{
Name: "order_direction",
Label: "desc",
Type: base.TypeSelect,
Values: "true,false",
Required: true,
},
}
}
func (driver Cloud189) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if !isFamily(account) && account.RootFolder == "" {
account.RootFolder = "-11"
}
state := GetState(account)
if !state.IsLogin() {
if err := state.Login(account); err != nil {
return err
}
}
if isFamily(account) {
list, err := driver.getFamilyInfoList(account)
if err != nil {
return err
}
for _, l := range list {
if account.SiteId == "" {
account.SiteId = fmt.Sprint(l.FamilyID)
}
log.Infof("天翼家庭云 用户名:%s FamilyID %d\n", l.RemarkName, l.FamilyID)
}
}
account.Status = "work"
model.SaveAccount(account)
return nil
}
func (driver Cloud189) getFamilyInfoList(account *model.Account) ([]FamilyInfoResp, error) {
var resp FamilyInfoListResp
_, err := GetState(account).Request("GET", API_URL+"/family/manage/getFamilyList.action", nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetResult(&resp)
}, account)
if err != nil {
return nil, err
}
return resp.FamilyInfoResp, nil
}
func (driver Cloud189) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Cloud189) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file"
}
fullUrl += "/listFiles.action"
files := make([]model.File, 0)
client := GetState(account)
for pageNum := 1; ; pageNum++ {
var resp Cloud189FilesResp
queryparam := map[string]string{
"folderId": file.Id,
"fileType": "0",
"mediaAttr": "0",
"iconOption": "5",
"pageNum": fmt.Sprint(pageNum),
"pageSize": "130",
}
_, err = client.Request("GET", fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix()).SetQueryParams(queryparam)
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
"orderBy": toFamilyOrderBy(account.OrderBy),
"descending": account.OrderDirection,
})
} else {
r.SetQueryParams(map[string]string{
"recursive": "0",
"orderBy": account.OrderBy,
"descending": account.OrderDirection,
})
}
r.SetResult(&resp)
}, account)
if err != nil {
return nil, err
}
// 获取完毕跳出
if resp.FileListAO.Count == 0 {
break
}
mustTime := func(str string) *time.Time {
time, _ := http.ParseTime(str)
return &time
}
for _, folder := range resp.FileListAO.FolderList {
files = append(files, model.File{
Id: fmt.Sprint(folder.ID),
Name: folder.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: mustTime(folder.CreateDate),
})
}
for _, file := range resp.FileListAO.FileList {
files = append(files, model.File{
Id: fmt.Sprint(file.ID),
Name: file.Name,
Size: file.Size,
Type: utils.GetFileType(filepath.Ext(file.Name)),
Driver: driver.Config().Name,
UpdatedAt: mustTime(file.CreateDate),
Thumbnail: file.Icon.SmallUrl,
})
}
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Cloud189) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("189PC path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Cloud189) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(utils.ParsePath(args.Path), account)
if err != nil {
return nil, err
}
if file.Type == conf.FOLDER {
return nil, base.ErrNotFile
}
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file"
}
fullUrl += "/getFileDownloadUrl.action"
var downloadUrl struct {
URL string `json:"fileDownloadUrl"`
}
_, err = GetState(account).Request("GET", fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix()).SetQueryParam("fileId", file.Id)
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
})
} else {
r.SetQueryParams(map[string]string{
"dt": "3",
"flag": "1",
})
}
r.SetResult(&downloadUrl)
}, account)
if err != nil {
return nil, err
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
Url: strings.ReplaceAll(downloadUrl.URL, "&amp;", "&"),
}, nil
}
func (driver Cloud189) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Cloud189) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file"
}
fullUrl += "/createFolder.action"
_, err = GetState(account).Request("POST", fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix()).SetQueryParams(map[string]string{
"folderName": name,
"relativePath": "",
})
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
"parentId": parentFile.Id,
})
} else {
r.SetQueryParams(map[string]string{
"parentFolderId": parentFile.Id,
})
}
}, account)
return err
}
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
_, err = GetState(account).Request("POST", API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"type": "MOVE",
"taskInfos": string(MustToBytes(utils.Json.Marshal(
[]*BatchTaskInfo{
{
FileId: srcFile.Id,
FileName: srcFile.Name,
IsFolder: BoolToNumber(srcFile.IsDir()),
},
}))),
"targetFolderId": dstDirFile.Id,
})
if isFamily(account) {
r.SetFormData(map[string]string{
"familyId": account.SiteId,
})
}
}, account)
return err
}
/*
func (driver Cloud189) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
var queryParam map[string]string
fullUrl := API_URL
method := "POST"
if isFamily(account) {
fullUrl += "/family/file"
method = "GET"
}
if srcFile.IsDir() {
fullUrl += "/moveFolder.action"
queryParam = map[string]string{
"folderId": srcFile.Id,
"destFolderName": srcFile.Name,
}
} else {
fullUrl += "/moveFile.action"
queryParam = map[string]string{
"fileId": srcFile.Id,
"destFileName": srcFile.Name,
}
}
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
if isFamily(account) {
r.SetQueryParams(map[string]string{
"familyId": account.SiteId,
"destParentId": dstDirFile.Id,
})
} else {
r.SetQueryParam("destParentFolderId", dstDirFile.Id)
}
}, account)
return err
}*/
func (driver Cloud189) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
var queryParam map[string]string
fullUrl := API_URL
method := "POST"
if isFamily(account) {
fullUrl += "/family/file"
method = "GET"
}
if srcFile.IsDir() {
fullUrl += "/renameFolder.action"
queryParam = map[string]string{
"folderId": srcFile.Id,
"destFolderName": filepath.Base(dst),
}
} else {
fullUrl += "/renameFile.action"
queryParam = map[string]string{
"fileId": srcFile.Id,
"destFileName": filepath.Base(dst),
}
}
_, err = GetState(account).Request(method, fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(queryParam).SetQueryParams(clientSuffix())
if isFamily(account) {
r.SetQueryParam("familyId", account.SiteId)
}
}, account)
return err
}
func (driver Cloud189) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
_, err = GetState(account).Request("POST", API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"type": "COPY",
"taskInfos": string(MustToBytes(utils.Json.Marshal(
[]*BatchTaskInfo{
{
FileId: srcFile.Id,
FileName: srcFile.Name,
IsFolder: BoolToNumber(srcFile.IsDir()),
},
}))),
"targetFolderId": dstDirFile.Id,
"targetFileName": filepath.Base(dst),
})
if isFamily(account) {
r.SetFormData(map[string]string{
"familyId": account.SiteId,
})
}
}, account)
return err
}
func (driver Cloud189) Delete(path string, account *model.Account) error {
path = utils.ParsePath(path)
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
_, err = GetState(account).Request("POST", API_URL+"/batch/createBatchTask.action", nil, func(r *resty.Request) {
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"type": "DELETE",
"taskInfos": string(MustToBytes(utils.Json.Marshal(
[]*BatchTaskInfo{
{
FileId: srcFile.Id,
FileName: srcFile.Name,
IsFolder: BoolToNumber(srcFile.IsDir()),
},
}))),
})
if isFamily(account) {
r.SetFormData(map[string]string{
"familyId": account.SiteId,
})
}
}, account)
return err
}
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
if isFamily(account) {
return driver.uploadFamily(file, parentFile, account)
}
return driver.uploadPerson(file, parentFile, account)
}
func (driver Cloud189) uploadFamily(file *model.FileStream, parentFile *model.File, account *model.Account) error {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
fileMd5 := md5.New()
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
return err
}
client := GetState(account)
var createUpload CreateUploadFileResult
_, err = client.Request("GET", API_URL+"/family/file/createFamilyFile.action", nil, func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"fileMd5": hex.EncodeToString(fileMd5.Sum(nil)),
"fileName": file.Name,
"familyId": account.SiteId,
"parentId": parentFile.Id,
"resumePolicy": "1",
"fileSize": fmt.Sprint(file.Size),
})
r.SetQueryParams(clientSuffix())
r.SetResult(&createUpload)
}, account)
if err != nil {
return err
}
if createUpload.FileDataExists != 1 {
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
return err
}
}
_, err = client.Request("GET", createUpload.FileCommitUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetHeaders(map[string]string{
"FamilyId": account.SiteId,
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
"ResumePolicy": "1",
})
}, account)
return err
}
func (driver Cloud189) uploadPerson(file *model.FileStream, parentFile *model.File, account *model.Account) error {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
fileMd5 := md5.New()
if _, err = io.Copy(io.MultiWriter(fileMd5, tempFile), file); err != nil {
return err
}
client := GetState(account)
var createUpload CreateUploadFileResult
_, err = client.Request("POST", API_URL+"/createUploadFile.action", nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetFormData(clientSuffix()).SetFormData(map[string]string{
"parentFolderId": parentFile.Id,
"baseFileId": "",
"fileName": file.Name,
"size": fmt.Sprint(file.Size),
"md5": hex.EncodeToString(fileMd5.Sum(nil)),
// "lastWrite": param.LastWrite,
// "localPath": strings.ReplaceAll(file.ParentPath, "\\", "/"),
"opertype": "1",
"flag": "1",
"resumePolicy": "1",
"isLog": "0",
"fileExt": "",
})
r.SetResult(&createUpload)
}, account)
if err != nil {
return err
}
if createUpload.FileDataExists != 1 {
if createUpload.UploadFileId, err = driver.uploadFileData(file, tempFile, createUpload, account); err != nil {
return err
}
}
_, err = client.Request("POST", createUpload.FileCommitUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetFormData(map[string]string{
"uploadFileId": fmt.Sprint(createUpload.UploadFileId),
"opertype": "5", //5 覆盖 1 重命名
"ResumePolicy": "1",
"isLog": "0",
})
}, account)
return err
}
func (driver Cloud189) uploadFileData(file *model.FileStream, tempFile *os.File, createUpload CreateUploadFileResult, account *model.Account) (int64, error) {
uploadFileState, err := driver.getUploadFileState(createUpload.UploadFileId, account)
if err != nil {
return 0, err
}
if uploadFileState.FileDataExists == 1 || uploadFileState.DataSize == int64(file.Size) {
return uploadFileState.UploadFileId, nil
}
if _, err = tempFile.Seek(uploadFileState.DataSize, io.SeekStart); err != nil {
return 0, err
}
_, err = GetState(account).Request("PUT", uploadFileState.FileUploadUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetHeaders(map[string]string{
"Content-Type": "application/octet-stream",
"ResumePolicy": "1",
"Edrive-UploadFileRange": fmt.Sprintf("bytes=%d-%d", uploadFileState.DataSize, file.Size),
"Expect": "100-continue",
})
if isFamily(account) {
r.SetHeaders(map[string]string{
"familyId": account.SiteId,
"UploadFileId": fmt.Sprint(uploadFileState.UploadFileId),
})
} else {
r.SetHeader("Edrive-UploadFileId", fmt.Sprint(uploadFileState.UploadFileId))
}
r.SetBody(tempFile)
}, account)
return uploadFileState.UploadFileId, err
}
func (driver Cloud189) getUploadFileState(uploadFileId int64, account *model.Account) (*UploadFileStatusResult, error) {
fullUrl := API_URL
if isFamily(account) {
fullUrl += "/family/file/getFamilyFileStatus.action"
} else {
fullUrl += "/getUploadFileStatus.action"
}
var uploadFileState UploadFileStatusResult
_, err := GetState(account).Request("GET", fullUrl, nil, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetQueryParams(map[string]string{
"uploadFileId": fmt.Sprint(uploadFileId),
"resumePolicy": "1",
})
if isFamily(account) {
r.SetQueryParam("familyId", account.SiteId)
}
r.SetResult(&uploadFileState)
}, account)
if err != nil {
return nil, err
}
return &uploadFileState, nil
}
/*
暂时未解决
func (driver Cloud189) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
fullUrl := UPLOAD_URL
if isFamily(account) {
fullUrl += "/family"
} else {
fullUrl += "/person"
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
// 初始化上传
const DEFAULT int64 = 10485760
count := int64(math.Ceil(float64(file.Size) / float64(DEFAULT)))
fileMd5 := md5.New()
silceMd5 := md5.New()
silceMd5Hexs := make([]string, 0, count)
silceMd5Base64s := make([]string, 0, count)
for i := int64(1); i <= count; i++ {
if _, err := io.CopyN(io.MultiWriter(fileMd5, silceMd5, tempFile), file, DEFAULT); err != io.EOF {
return err
}
md5Byte := silceMd5.Sum(nil)
silceMd5Hexs = append(silceMd5Hexs, strings.ToUpper(hex.EncodeToString(md5Byte)))
silceMd5Base64s = append(silceMd5Base64s, fmt.Sprint(i, "-", base64.StdEncoding.EncodeToString(md5Byte)))
}
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
sliceMd5Hex := fileMd5Hex
if int64(file.Size) > DEFAULT {
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
}
qID := uuid.NewString()
client := GetState(account)
param := MapToUrlValues(map[string]interface{}{
"parentFolderId": parentFile.Id,
"fileName": url.QueryEscape(file.Name),
"fileMd5": fileMd5Hex,
"fileSize": fmt.Sprint(file.Size),
"sliceMd5": sliceMd5Hex,
"sliceSize": fmt.Sprint(DEFAULT),
})
if isFamily(account) {
param.Set("familyId", account.SiteId)
}
var uploadInfo InitMultiUploadResp
_, err = client.Request("GET", fullUrl+"/initMultiUpload", param, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetHeader("X-Request-ID", qID)
r.SetResult(&uploadInfo)
}, account)
if err != nil {
return err
}
if uploadInfo.Data.FileDataExists != 1 {
param = MapToUrlValues(map[string]interface{}{
"uploadFileId": uploadInfo.Data.UploadFileID,
"partInfo": strings.Join(silceMd5Base64s, ","),
})
if isFamily(account) {
param.Set("familyId", account.SiteId)
}
var uploadUrls UploadUrlsResp
_, err := client.Request("GET", fullUrl+"/getMultiUploadUrls", param, func(r *resty.Request) {
r.SetQueryParams(clientSuffix())
r.SetHeader("X-Request-ID", qID).SetHeader("content-type", "application/x-www-form-urlencoded")
r.SetResult(&uploadUrls)
}, account)
if err != nil {
return err
}
var i int64
for _, uploadurl := range uploadUrls.UploadUrls {
req := resty.New().SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).SetProxy("http://192.168.0.30:8888").R()
for _, header := range strings.Split(decodeURIComponent(uploadurl.RequestHeader), "&") {
i := strings.Index(header, "=")
req.SetHeader(header[0:i], header[i+1:])
}
_, err := req.SetBody(io.NewSectionReader(tempFile, i*DEFAULT, DEFAULT)).Put(uploadurl.RequestURL)
if err != nil {
return err
}
}
}
param = MapToUrlValues(map[string]interface{}{
"uploadFileId": uploadInfo.Data.UploadFileID,
"isLog": "0",
"opertype": "1",
})
if isFamily(account) {
param.Set("familyId", account.SiteId)
}
_, err = client.Request("GET", fullUrl+"/commitMultiUploadFile", param, func(r *resty.Request) {
r.SetHeader("X-Request-ID", qID)
r.SetQueryParams(clientSuffix())
}, account)
return err
}
*/
var _ base.Driver = (*Cloud189)(nil)

View File

@ -1,180 +0,0 @@
package _189
import "encoding/xml"
type LoginParam struct {
CaptchaToken string
Lt string
ParamId string
ReqId string
jRsaKey string
vCodeID string
vCodeRS string
}
// 居然有四种返回方式
type Erron struct {
ResCode string `json:"res_code"`
ResMessage string `json:"res_message"`
XMLName xml.Name `xml:"error"`
Code string `json:"code" xml:"code"`
Message string `json:"message" xml:"message"`
// Code string `json:"code"`
Msg string `json:"msg"`
ErrorCode string `json:"errorCode"`
ErrorMsg string `json:"errorMsg"`
}
// 刷新session返回
type UserSessionResp struct {
ResCode int `json:"res_code"`
ResMessage string `json:"res_message"`
LoginName string `json:"loginName"`
KeepAlive int `json:"keepAlive"`
GetFileDiffSpan int `json:"getFileDiffSpan"`
GetUserInfoSpan int `json:"getUserInfoSpan"`
// 个人云
SessionKey string `json:"sessionKey"`
SessionSecret string `json:"sessionSecret"`
// 家庭云
FamilySessionKey string `json:"familySessionKey"`
FamilySessionSecret string `json:"familySessionSecret"`
}
//登录返回
type appSessionResp struct {
UserSessionResp
IsSaveName string `json:"isSaveName"`
// 会话刷新Token
AccessToken string `json:"accessToken"`
//Token刷新
RefreshToken string `json:"refreshToken"`
}
type FamilyInfoListResp struct {
FamilyInfoResp []FamilyInfoResp `json:"familyInfoResp"`
}
type FamilyInfoResp struct {
Count int `json:"count"`
CreateTime string `json:"createTime"`
FamilyID int `json:"familyId"`
RemarkName string `json:"remarkName"`
Type int `json:"type"`
UseFlag int `json:"useFlag"`
UserRole int `json:"userRole"`
}
/*文件部分*/
// 文件
type Cloud189File struct {
CreateDate string `json:"createDate"`
FileCata int64 `json:"fileCata"`
Icon struct {
//iconOption 5
SmallUrl string `json:"smallUrl"`
LargeUrl string `json:"largeUrl"`
// iconOption 10
Max600 string `json:"max600"`
MediumURL string `json:"mediumUrl"`
} `json:"icon"`
ID int64 `json:"id"`
LastOpTime string `json:"lastOpTime"`
Md5 string `json:"md5"`
MediaType int `json:"mediaType"`
Name string `json:"name"`
Orientation int64 `json:"orientation"`
Rev string `json:"rev"`
Size int64 `json:"size"`
StarLabel int64 `json:"starLabel"`
}
// 文件夹
type Cloud189Folder struct {
ID int64 `json:"id"`
ParentID int64 `json:"parentId"`
Name string `json:"name"`
FileCata int64 `json:"fileCata"`
FileCount int64 `json:"fileCount"`
LastOpTime string `json:"lastOpTime"`
CreateDate string `json:"createDate"`
FileListSize int64 `json:"fileListSize"`
Rev string `json:"rev"`
StarLabel int64 `json:"starLabel"`
}
type Cloud189FilesResp struct {
//ResCode int `json:"res_code"`
//ResMessage string `json:"res_message"`
FileListAO struct {
Count int `json:"count"`
FileList []Cloud189File `json:"fileList"`
FolderList []Cloud189Folder `json:"folderList"`
} `json:"fileListAO"`
}
// TaskInfo 任务信息
type BatchTaskInfo struct {
// FileId 文件ID
FileId string `json:"fileId"`
// FileName 文件名
FileName string `json:"fileName"`
// IsFolder 是否是文件夹0-否1-是
IsFolder int `json:"isFolder"`
// SrcParentId 文件所在父目录ID
//SrcParentId string `json:"srcParentId"`
}
type CreateUploadFileResult struct {
// UploadFileId 上传文件请求ID
UploadFileId int64 `json:"uploadFileId"`
// FileUploadUrl 上传文件数据的URL路径
FileUploadUrl string `json:"fileUploadUrl"`
// FileCommitUrl 上传文件完成后确认路径
FileCommitUrl string `json:"fileCommitUrl"`
// FileDataExists 文件是否已存在云盘中0-未存在1-已存在
FileDataExists int `json:"fileDataExists"`
}
type UploadFileStatusResult struct {
// 上传文件的ID
UploadFileId int64 `json:"uploadFileId"`
// 已上传的大小
DataSize int64 `json:"dataSize"`
FileUploadUrl string `json:"fileUploadUrl"`
FileCommitUrl string `json:"fileCommitUrl"`
FileDataExists int `json:"fileDataExists"`
}
/*
type InitMultiUploadResp struct {
//Code string `json:"code"`
Data struct {
UploadType int `json:"uploadType"`
UploadHost string `json:"uploadHost"`
UploadFileID string `json:"uploadFileId"`
FileDataExists int `json:"fileDataExists"`
} `json:"data"`
}
type UploadUrlsResp struct {
Code string `json:"code"`
UploadUrls map[string]Part `json:"uploadUrls"`
}
type Part struct {
RequestURL string `json:"requestURL"`
RequestHeader string `json:"requestHeader"`
}
*/

View File

@ -1,145 +0,0 @@
package _189
import (
"bytes"
"crypto/aes"
"crypto/hmac"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
rand2 "math/rand"
"net/http"
"net/url"
"strings"
"time"
"github.com/Xhofe/alist/model"
)
const (
APP_ID = "8025431004"
CLIENT_TYPE = "10020"
VERSION = "6.2"
WEB_URL = "https://cloud.189.cn"
AUTH_URL = "https://open.e.189.cn"
API_URL = "https://api.cloud.189.cn"
UPLOAD_URL = "https://upload.cloud.189.cn"
RETURN_URL = "https://m.cloud.189.cn/zhuanti/2020/loginErrorPc/index.html"
PC = "TELEPC"
MAC = "TELEMAC"
CHANNEL_ID = "web_cloud.189.cn"
)
func clientSuffix() map[string]string {
return map[string]string{
"clientType": PC,
"version": VERSION,
"channelId": CHANNEL_ID,
"rand": fmt.Sprintf("%d_%d", rand2.Int63n(1e5), rand2.Int63n(1e10)),
}
}
// 带params的SignatureOfHmac HMAC签名
func signatureOfHmac(sessionSecret, sessionKey, operate, fullUrl, dateOfGmt, param string) string {
u, _ := url.Parse(fullUrl)
mac := hmac.New(sha1.New, []byte(sessionSecret))
data := fmt.Sprintf("SessionKey=%s&Operate=%s&RequestURI=%s&Date=%s", sessionKey, operate, u.Path, dateOfGmt)
if param != "" {
data += fmt.Sprintf("&params=%s", param)
}
mac.Write([]byte(data))
return strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))
}
// 获取http规范的时间
func getHttpDateStr() string {
return time.Now().UTC().Format(http.TimeFormat)
}
// RAS 加密用户名密码
func rsaEncrypt(publicKey, origData string) string {
block, _ := pem.Decode([]byte(publicKey))
pubInterface, _ := x509.ParsePKIXPublicKey(block.Bytes)
data, _ := rsa.EncryptPKCS1v15(rand.Reader, pubInterface.(*rsa.PublicKey), []byte(origData))
return base64ToHex(base64.StdEncoding.EncodeToString(data))
}
// aes 加密params
func AesECBEncrypt(data, key string) string {
block, _ := aes.NewCipher([]byte(key))
paddingData := PKCS7Padding([]byte(data), block.BlockSize())
decrypted := make([]byte, len(paddingData))
size := block.BlockSize()
for src, dst := paddingData, decrypted; len(src) > 0; src, dst = src[size:], dst[size:] {
block.Encrypt(dst[:size], src[:size])
}
return strings.ToUpper(hex.EncodeToString(decrypted))
}
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// 时间戳
func timestamp() int64 {
return time.Now().UTC().UnixNano() / 1e6
}
func base64ToHex(a string) string {
v, _ := base64.StdEncoding.DecodeString(a)
return strings.ToUpper(hex.EncodeToString(v))
}
func isFamily(account *model.Account) bool {
return account.InternalType == "Family"
}
func toFamilyOrderBy(o string) string {
switch o {
case "filename":
return "1"
case "filesize":
return "2"
case "lastOpTime":
return "3"
default:
return "1"
}
}
func MapToUrlValues(m map[string]interface{}) url.Values {
url := make(url.Values, len(m))
for k, v := range m {
url.Add(k, fmt.Sprint(v))
}
return url
}
func decodeURIComponent(str string) string {
r, _ := url.QueryUnescape(str)
//r, _ := url.PathUnescape(str)
//r = strings.ReplaceAll(r, " ", "+")
return r
}
func MustToBytes(b []byte, err error) []byte {
return b
}
func BoolToNumber(b bool) int {
if b {
return 1
}
return 0
}

View File

@ -1,246 +0,0 @@
package alidrive
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
"time"
)
var aliClient = resty.New()
type AliRespError struct {
Code string `json:"code"`
Message string `json:"message"`
}
type AliFiles struct {
Items []AliFile `json:"items"`
NextMarker string `json:"next_marker"`
}
type AliFile struct {
DriveId string `json:"drive_id"`
CreatedAt *time.Time `json:"created_at"`
FileExtension string `json:"file_extension"`
FileId string `json:"file_id"`
Type string `json:"type"`
Name string `json:"name"`
Category string `json:"category"`
ParentFileId string `json:"parent_file_id"`
UpdatedAt *time.Time `json:"updated_at"`
Size int64 `json:"size"`
Thumbnail string `json:"thumbnail"`
Url string `json:"url"`
}
func (driver AliDrive) FormatFile(file *AliFile) *model.File {
f := &model.File{
Id: file.FileId,
Name: file.Name,
Size: file.Size,
UpdatedAt: file.UpdatedAt,
Thumbnail: file.Thumbnail,
Driver: driver.Config().Name,
Url: file.Url,
}
if file.Type == "folder" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(file.FileExtension)
}
if file.Category == "video" {
f.Type = conf.VIDEO
}
if file.Category == "image" {
f.Type = conf.IMAGE
}
return f
}
func (driver AliDrive) GetFiles(fileId string, account *model.Account) ([]AliFile, error) {
marker := "first"
res := make([]AliFile, 0)
for marker != "" {
if marker == "first" {
marker = ""
}
var resp AliFiles
var e AliRespError
_, err := aliClient.R().
SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"fields": "*",
"image_thumbnail_process": "image/resize,w_400/format,jpeg",
"image_url_process": "image/resize,w_1920/format,jpeg",
"limit": account.Limit,
"marker": marker,
"order_by": account.OrderBy,
"order_direction": account.OrderDirection,
"parent_file_id": fileId,
"video_thumbnail_process": "video/snapshot,t_0,f_jpg,ar_auto,w_300",
"url_expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/list")
if err != nil {
return nil, err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
} else {
_ = model.SaveAccount(account)
return driver.GetFiles(fileId, account)
}
}
return nil, fmt.Errorf("%s", e.Message)
}
marker = resp.NextMarker
res = append(res, resp.Items...)
}
return res, nil
}
func (driver AliDrive) GetFile(path string, account *model.Account) (*AliFile, error) {
dir, name := filepath.Split(path)
dir = utils.ParsePath(dir)
_, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
parentFiles_, _ := base.GetCache(dir, account)
parentFiles, _ := parentFiles_.([]AliFile)
for _, file := range parentFiles {
if file.Name == name {
if file.Type == "file" {
return &file, err
} else {
return nil, fmt.Errorf("not file")
}
}
}
return nil, base.ErrPathNotFound
}
func (driver AliDrive) RefreshToken(account *model.Account) error {
url := "https://auth.aliyundrive.com/v2/account/token"
var resp base.TokenResp
var e AliRespError
_, err := aliClient.R().
//ForceContentType("application/json").
SetBody(base.Json{"refresh_token": account.RefreshToken, "grant_type": "refresh_token"}).
SetResult(&resp).
SetError(&e).
Post(url)
if err != nil {
account.Status = err.Error()
return err
}
log.Debugf("%+v,%+v", resp, e)
if e.Code != "" {
account.Status = e.Message
return fmt.Errorf("failed to refresh token: %s", e.Message)
} else {
account.Status = "work"
}
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
func (driver AliDrive) rename(fileId, name string, account *model.Account) error {
var resp base.Json
var e AliRespError
_, err := aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"check_name_mode": "refuse",
"drive_id": account.DriveId,
"file_id": fileId,
"name": name,
}).Post("https://api.aliyundrive.com/v3/file/update")
if err != nil {
return err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.rename(fileId, name, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if resp["name"] == name {
return nil
}
return fmt.Errorf("%+v", resp)
}
func (driver AliDrive) batch(srcId, dstId string, url string, account *model.Account) error {
var e AliRespError
res, err := aliClient.R().SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"requests": []base.Json{
{
"headers": base.Json{
"Content-Type": "application/json",
},
"method": "POST",
"id": srcId,
"body": base.Json{
"drive_id": account.DriveId,
"file_id": srcId,
"to_drive_id": account.DriveId,
"to_parent_file_id": dstId,
},
"url": url,
},
},
"resource": "file",
}).Post("https://api.aliyundrive.com/v3/batch")
if err != nil {
return err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.batch(srcId, dstId, url, account)
}
}
return fmt.Errorf("%s", e.Message)
}
status := jsoniter.Get(res.Body(), "responses", 0, "status").ToInt()
if status < 400 && status >= 100 {
return nil
}
return errors.New(res.String())
}
func init() {
base.RegisterDriver(&AliDrive{})
aliClient.
SetTimeout(base.DefaultTimeout).
SetRetryCount(3).
SetHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36").
SetHeader("content-type", "application/json").
SetHeader("origin", "https://www.aliyundrive.com")
}

View File

@ -1,544 +0,0 @@
package alidrive
import (
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"math"
"math/big"
"net/http"
"os"
"path/filepath"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/robfig/cron/v3"
log "github.com/sirupsen/logrus"
)
type AliDrive struct{}
func (driver AliDrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "AliDrive",
}
}
func (driver AliDrive) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "name,size,updated_at,created_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "ASC,DESC",
Required: false,
},
{
Name: "limit",
Label: "limit",
Type: base.TypeNumber,
Required: false,
Description: ">0 and <=200",
},
}
}
func (driver AliDrive) Save(account *model.Account, old *model.Account) error {
if old != nil {
conf.Cron.Remove(cron.EntryID(old.CronId))
}
if account == nil {
return nil
}
if account.RootFolder == "" {
account.RootFolder = "root"
}
if account.Limit == 0 {
account.Limit = 200
}
err := driver.RefreshToken(account)
if err != nil {
return err
}
var resp base.Json
_, _ = aliClient.R().SetResult(&resp).
SetBody("{}").
SetHeader("authorization", "Bearer\t"+account.AccessToken).
Post("https://api.aliyundrive.com/v2/user/get")
log.Debugf("user info: %+v", resp)
account.DriveId = resp["default_drive_id"].(string)
cronId, err := conf.Cron.AddFunc("@every 2h", func() {
id := account.ID
log.Debugf("ali account id: %d", id)
newAccount, err := model.GetAccountById(id)
log.Debugf("ali account: %+v", newAccount)
if err != nil {
return
}
err = driver.RefreshToken(newAccount)
_ = model.SaveAccount(newAccount)
})
if err != nil {
return err
}
account.CronId = int(cronId)
err = model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver AliDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver AliDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []AliFile
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]AliFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver AliDrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
var resp base.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).
SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": file.Id,
"expire_sec": 14400,
}).Post("https://api.aliyundrive.com/v2/file/get_download_url")
if err != nil {
return nil, err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
} else {
_ = model.SaveAccount(account)
return driver.Link(args, account)
}
}
return nil, fmt.Errorf("%s", e.Message)
}
return &base.Link{
Headers: []base.Header{
{
Name: "Referer",
Value: "https://www.aliyundrive.com/",
},
},
Url: resp["url"].(string),
}, nil
}
func (driver AliDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("ali path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver AliDrive) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
// r.Header.Set("Referer", "https://www.aliyundrive.com/")
//}
func (driver AliDrive) Preview(path string, account *model.Account) (interface{}, error) {
file, err := driver.GetFile(path, account)
if err != nil {
return nil, err
}
// office
var resp base.Json
var e AliRespError
var url string
req := base.Json{
"drive_id": account.DriveId,
"file_id": file.FileId,
}
switch file.Category {
case "doc":
{
url = "https://api.aliyundrive.com/v2/file/get_office_preview_url"
req["access_token"] = account.AccessToken
}
case "video":
{
url = "https://api.aliyundrive.com/v2/file/get_video_preview_play_info"
req["category"] = "live_transcoding"
}
default:
return nil, base.ErrNotSupport
}
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(req).Post(url)
if err != nil {
return nil, err
}
if e.Code != "" {
return nil, fmt.Errorf("%s", e.Message)
}
return resp, nil
}
func (driver AliDrive) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
var resp base.Json
var e AliRespError
_, err = aliClient.R().SetResult(&resp).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"check_name_mode": "refuse",
"drive_id": account.DriveId,
"name": name,
"parent_file_id": parentFile.Id,
"type": "folder",
}).Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.MakeDir(path, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if resp["file_name"] == name {
return nil
}
return fmt.Errorf("%+v", resp)
}
func (driver AliDrive) Move(src string, dst string, account *model.Account) error {
dstDir, _ := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/move", account)
return err
}
func (driver AliDrive) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
err = driver.rename(srcFile.Id, dstName, account)
return err
}
func (driver AliDrive) Copy(src string, dst string, account *model.Account) error {
dstDir, _ := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
err = driver.batch(srcFile.Id, dstDirFile.Id, "/file/copy", account)
return err
}
func (driver AliDrive) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
if err != nil {
return err
}
var e AliRespError
res, err := aliClient.R().SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": file.Id,
}).Post("https://api.aliyundrive.com/v2/recyclebin/trash")
if err != nil {
return err
}
if e.Code != "" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.Delete(path, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if res.StatusCode() == 204 {
return nil
}
return errors.New(res.String())
}
type UploadResp struct {
FileId string `json:"file_id"`
UploadId string `json:"upload_id"`
PartInfoList []struct {
UploadUrl string `json:"upload_url"`
} `json:"part_info_list"`
RapidUpload bool `json:"rapid_upload"`
}
func (driver AliDrive) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
const DEFAULT int64 = 10485760
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
partInfoList := make([]base.Json, 0, count)
var i int64
for i = 0; i < count; i++ {
partInfoList = append(partInfoList, base.Json{
"part_number": i + 1,
})
}
buf := make([]byte, 1024)
n, _ := file.Read(buf[:])
reqBody := base.Json{
"check_name_mode": "overwrite",
"drive_id": account.DriveId,
"name": file.GetFileName(),
"parent_file_id": parentFile.Id,
"part_info_list": partInfoList,
"size": file.GetSize(),
"type": "file",
"pre_hash": utils.GetSHA1Encode(string(buf[:n])),
}
fileReader := io.MultiReader(bytes.NewReader(buf[:n]), file.File)
var resp UploadResp
var e AliRespError
client := aliClient.R().SetResult(&resp).SetError(&e).SetHeader("authorization", "Bearer\t"+account.AccessToken).SetBody(reqBody)
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if err != nil {
return err
}
if e.Code != "" && e.Code != "PreHashMatched" {
if e.Code == "AccessTokenInvalid" {
err = driver.RefreshToken(account)
if err != nil {
return err
} else {
_ = model.SaveAccount(account)
return driver.Upload(file, account)
}
}
return fmt.Errorf("%s", e.Message)
}
if e.Code == "PreHashMatched" {
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer tempFile.Close()
defer os.Remove(tempFile.Name())
delete(reqBody, "pre_hash")
h := sha1.New()
if _, err = io.Copy(tempFile, io.TeeReader(fileReader, h)); err != nil {
return err
}
reqBody["content_hash"] = hex.EncodeToString(h.Sum(nil))
reqBody["content_hash_name"] = "sha1"
reqBody["proof_version"] = "v1"
/*
js 隐性转换太坑不知道有没有bug
var n = e.access_token
r = new BigNumber('0x'.concat(md5(n).slice(0, 16)))
i = new BigNumber(t.file.size)
o = i ? r.mod(i) : new gt.BigNumber(0);
(t.file.slice(o.toNumber(), Math.min(o.plus(8).toNumber(), t.file.size)))
*/
r, _ := new(big.Int).SetString(utils.GetMD5Encode(account.AccessToken)[:16], 16)
i := new(big.Int).SetUint64(file.Size)
o := r.Mod(r, i)
n, _ = io.NewSectionReader(tempFile, o.Int64(), 8).Read(buf[:8])
reqBody["proof_code"] = base64.StdEncoding.EncodeToString(buf[:n])
_, err = client.Post("https://api.aliyundrive.com/adrive/v2/file/createWithFolders")
if err != nil {
return err
}
if e.Code != "" && e.Code != "PreHashMatched" {
return fmt.Errorf("%s", e.Message)
}
if resp.RapidUpload {
return nil
}
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
return err
}
fileReader = tempFile
}
for i = 0; i < count; i++ {
req, err := http.NewRequest("PUT", resp.PartInfoList[i].UploadUrl, io.LimitReader(fileReader, DEFAULT))
if err != nil {
return err
}
res, err := base.HttpClient.Do(req)
if err != nil {
return err
}
log.Debugf("%+v", res)
//res, err := base.BaseClient.R().
// SetHeader("Content-Type","").
// SetBody(byteData).Put(resp.PartInfoList[i].UploadUrl)
//if err != nil {
// return err
//}
//log.Debugf("put to %s : %d,%s", resp.PartInfoList[i].UploadUrl, res.StatusCode(),res.String())
}
var resp2 base.Json
_, err = aliClient.R().SetResult(&resp2).SetError(&e).
SetHeader("authorization", "Bearer\t"+account.AccessToken).
SetBody(base.Json{
"drive_id": account.DriveId,
"file_id": resp.FileId,
"upload_id": resp.UploadId,
}).Post("https://api.aliyundrive.com/v2/file/complete")
if err != nil {
return err
}
if e.Code != "" {
//if e.Code == "AccessTokenInvalid" {
// err = driver.RefreshToken(account)
// if err != nil {
// return err
// } else {
// _ = model.SaveAccount(account)
// return driver.Upload(file, account)
// }
//}
return fmt.Errorf("%s", e.Message)
}
if resp2["file_id"] == resp.FileId {
return nil
}
return fmt.Errorf("%+v", resp2)
}
var _ base.Driver = (*AliDrive)(nil)

View File

@ -1,44 +0,0 @@
package alist
import (
"errors"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
)
type BaseResp struct {
Code int `json:"code"`
Message string `json:"message"`
}
type PathResp struct {
BaseResp
Data struct {
Type string `json:"type"`
//Meta Meta `json:"meta"`
Files []model.File `json:"files"`
} `json:"data"`
}
type PreviewResp struct {
BaseResp
Data interface{} `json:"data"`
}
func (driver *Alist) Login(account *model.Account) error {
var resp BaseResp
_, err := base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
Get(account.SiteUrl + "/api/admin/login")
if err != nil {
return err
}
if resp.Code != 200 {
return errors.New(resp.Message)
}
return nil
}
func init() {
base.RegisterDriver(&Alist{})
}

View File

@ -1,192 +0,0 @@
package alist
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"path/filepath"
"strings"
"time"
)
type Alist struct{}
func (driver Alist) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Alist",
NoNeedSetLink: true,
NoCors: true,
}
}
func (driver Alist) Items() []base.Item {
return []base.Item{
{
Name: "site_url",
Label: "alist site url",
Type: base.TypeString,
Required: true,
},
{
Name: "access_token",
Label: "token",
Type: base.TypeString,
Description: "admin token",
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
}
}
func (driver Alist) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
account.SiteUrl = strings.TrimRight(account.SiteUrl, "/")
if account.RootFolder == "" {
account.RootFolder = "/"
}
err := driver.Login(account)
if err == nil {
account.Status = "work"
} else {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Alist) File(path string, account *model.Account) (*model.File, error) {
now := time.Now()
if path == "/" {
return &model.File{
Id: "root",
Name: "root",
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: &now,
}, nil
}
_, files, err := driver.Path(utils.Dir(path), account)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrPathNotFound
}
name := utils.Base(path)
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Alist) Files(path string, account *model.Account) ([]model.File, error) {
//return nil, base.ErrNotImplement
_, files, err := driver.Path(path, account)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrNotFolder
}
return files, nil
}
func (driver Alist) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
path = utils.ParsePath(path)
name := utils.Base(path)
flag := "d"
if utils.GetFileType(filepath.Ext(path)) == conf.TEXT {
flag = "p"
}
link := base.Link{}
link.Url = fmt.Sprintf("%s/%s%s?sign=%s", account.SiteUrl, flag, path, utils.SignWithToken(name, conf.Token))
return &link, nil
}
func (driver Alist) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
path = filepath.Join(account.RootFolder, path)
path = strings.ReplaceAll(path, "\\", "/")
cache, err := base.GetCache(path, account)
if err == nil {
files := cache.([]model.File)
return nil, files, nil
}
var resp PathResp
_, err = base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
SetBody(base.Json{
"path": path,
}).Post(account.SiteUrl + "/api/public/path")
if err != nil {
return nil, nil, err
}
if resp.Code != 200 {
return nil, nil, errors.New(resp.Message)
}
if resp.Data.Type == "file" {
return &resp.Data.Files[0], nil, nil
}
if len(resp.Data.Files) > 0 {
_ = base.SetCache(path, resp.Data.Files, account)
}
return nil, resp.Data.Files, nil
}
//func (driver Alist) Proxy(r *http.Request, account *model.Account) {}
func (driver Alist) Preview(path string, account *model.Account) (interface{}, error) {
var resp PathResp
_, err := base.RestyClient.R().SetResult(&resp).
SetHeader("Authorization", account.AccessToken).
SetBody(base.Json{
"path": path,
}).Post(account.SiteUrl + "/api/public/preview")
if err != nil {
return nil, err
}
if resp.Code != 200 {
return nil, errors.New(resp.Message)
}
return resp.Data, nil
}
func (driver Alist) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Rename(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver Alist) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Alist)(nil)

View File

@ -1,50 +1,6 @@
package drivers
import (
_ "github.com/Xhofe/alist/drivers/123"
_ "github.com/Xhofe/alist/drivers/139"
_ "github.com/Xhofe/alist/drivers/189"
_ "github.com/Xhofe/alist/drivers/189pc"
_ "github.com/Xhofe/alist/drivers/alidrive"
_ "github.com/Xhofe/alist/drivers/alist"
_ "github.com/Xhofe/alist/drivers/baidu"
"github.com/Xhofe/alist/drivers/base"
_ "github.com/Xhofe/alist/drivers/ftp"
_ "github.com/Xhofe/alist/drivers/google"
_ "github.com/Xhofe/alist/drivers/lanzou"
_ "github.com/Xhofe/alist/drivers/mediatrack"
_ "github.com/Xhofe/alist/drivers/native"
_ "github.com/Xhofe/alist/drivers/onedrive"
_ "github.com/Xhofe/alist/drivers/pikpak"
_ "github.com/Xhofe/alist/drivers/quark"
_ "github.com/Xhofe/alist/drivers/s3"
_ "github.com/Xhofe/alist/drivers/shandian"
_ "github.com/Xhofe/alist/drivers/teambition"
_ "github.com/Xhofe/alist/drivers/uss"
_ "github.com/Xhofe/alist/drivers/webdav"
_ "github.com/Xhofe/alist/drivers/xunlei"
_ "github.com/Xhofe/alist/drivers/yandex"
log "github.com/sirupsen/logrus"
"strings"
_ "github.com/alist-org/alist/v3/drivers/local"
_ "github.com/alist-org/alist/v3/drivers/virtual"
)
var NoCors string
var NoUpload string
func GetConfig() {
for k, v := range base.GetDriversMap() {
if v.Config().NoCors {
NoCors += k + ","
}
if v.Upload(nil, nil) != base.ErrEmptyFile {
NoUpload += k + ","
}
}
NoCors = strings.Trim(NoCors, ",")
NoUpload += "root"
}
func init() {
log.Debug("all init")
GetConfig()
}

View File

@ -1,186 +0,0 @@
package baidu
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
"path"
"strconv"
)
func (driver Baidu) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
err = driver.refreshToken(account)
}
if err != nil {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Baidu) refreshToken(account *model.Account) error {
u := "https://openapi.baidu.com/oauth/2.0/token"
var resp base.TokenResp
var e TokenErrResp
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetQueryParams(map[string]string{
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
}).Get(u)
if err != nil {
return err
}
if e.Error != "" {
return fmt.Errorf("%s : %s", e.Error, e.ErrorDescription)
}
if resp.RefreshToken == "" {
return base.ErrEmptyToken
}
account.Status = "work"
account.AccessToken, account.RefreshToken = resp.AccessToken, resp.RefreshToken
return nil
}
func (driver Baidu) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://pan.baidu.com/rest/2.0" + pathname
req := base.RestyClient.R()
req.SetQueryParam("access_token", account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Patch:
res, err = req.Patch(u)
case base.Delete:
res, err = req.Delete(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
errno := jsoniter.Get(res.Body(), "errno").ToInt()
if errno != 0 {
if errno == -6 {
err = driver.RefreshToken(account)
if err != nil {
return nil, err
}
return driver.Request(pathname, method, headers, query, form, data, resp, account)
}
return nil, fmt.Errorf("errno: %d, refer to https://pan.baidu.com/union/doc/", errno)
}
return res.Body(), nil
}
func (driver Baidu) Get(pathname string, params map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Get, nil, params, nil, nil, resp, account)
}
func (driver Baidu) Post(pathname string, params map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, params, nil, data, resp, account)
}
func (driver Baidu) manage(opera string, filelist interface{}, account *model.Account) ([]byte, error) {
params := map[string]string{
"method": "filemanager",
"opera": opera,
}
marshal, err := utils.Json.Marshal(filelist)
if err != nil {
return nil, err
}
data := fmt.Sprintf("async=0&filelist=%s&ondup=newcopy", string(marshal))
return driver.Post("/xpan/file", params, data, nil, account)
}
func (driver Baidu) GetFiles(dir string, account *model.Account) ([]model.File, error) {
dir = utils.Join(account.RootFolder, dir)
start := 0
limit := 200
params := map[string]string{
"method": "list",
"dir": dir,
"web": "web",
}
if account.OrderBy != "" {
params["order"] = account.OrderBy
if account.OrderDirection == "desc" {
params["desc"] = "1"
}
}
res := make([]model.File, 0)
for {
params["start"] = strconv.Itoa(start)
params["limit"] = strconv.Itoa(limit)
start += limit
var resp ListResp
_, err := driver.Get("/xpan/file", params, &resp, account)
if err != nil {
return nil, err
}
if len(resp.List) == 0 {
break
}
for _, f := range resp.List {
file := model.File{
Id: strconv.FormatInt(f.FsId, 10),
Name: f.ServerFilename,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: getTime(f.ServerMtime),
Thumbnail: f.Thumbs.Url3,
}
if f.Isdir == 1 {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(path.Ext(f.ServerFilename))
}
res = append(res, file)
}
}
return res, nil
}
func (driver Baidu) create(path string, size uint64, isdir int, uploadid, block_list string, account *model.Account) ([]byte, error) {
params := map[string]string{
"method": "create",
}
data := fmt.Sprintf("path=%s&size=%d&isdir=%d", path, size, isdir)
if uploadid != "" {
data += fmt.Sprintf("&uploadid=%s&block_list=%s", uploadid, block_list)
}
return driver.Post("/xpan/file", params, data, nil, account)
}
func init() {
base.RegisterDriver(&Baidu{})
}

View File

@ -1,358 +0,0 @@
package baidu
import (
"bytes"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"math"
"os"
"path/filepath"
"strconv"
"strings"
)
type Baidu struct{}
func (driver Baidu) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Baidu.Disk",
}
}
func (driver Baidu) Items() []base.Item {
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Default: "/",
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Default: "name",
Values: "name,time,size",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Default: "asc",
Required: false,
},
{
Name: "client_id",
Label: "client id",
Default: "iYCeC9g08h5vuP9UqvPHKKSVrKFXGa1v",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Default: "jXiFMOPVPCWlO2M5CwWQzffpNPaGTRBG",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Baidu) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
return driver.RefreshToken(account)
}
func (driver Baidu) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Baidu) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
files, err := driver.GetFiles(path, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Baidu) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
if file.IsDir() {
return nil, base.ErrNotFile
}
var resp DownloadResp
params := map[string]string{
"method": "filemetas",
"fsids": fmt.Sprintf("[%s]", file.Id),
"dlink": "1",
}
_, err = driver.Get("/xpan/multimedia", params, &resp, account)
if err != nil {
return nil, err
}
u := fmt.Sprintf("%s&access_token=%s", resp.List[0].Dlink, account.AccessToken)
res, err := base.NoRedirectClient.R().SetHeader("User-Agent", "pan.baidu.com").Head(u)
if err != nil {
return nil, err
}
//if res.StatusCode() == 302 {
u = res.Header().Get("location")
//}
return &base.Link{
Url: u,
Headers: []base.Header{
{Name: "User-Agent", Value: "pan.baidu.com"},
}}, nil
}
func (driver Baidu) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Baidu) Proxy(r *http.Request, account *model.Account) {
// r.Header.Set("User-Agent", "pan.baidu.com")
//}
func (driver Baidu) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Baidu) MakeDir(path string, account *model.Account) error {
_, err := driver.create(utils.Join(account.RootFolder, path), 0, 1, "", "", account)
return err
}
func (driver Baidu) Move(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("move", data, account)
return err
}
func (driver Baidu) Rename(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
newname := utils.Base(dst)
data := []base.Json{
{
"path": path,
"newname": newname,
},
}
_, err := driver.manage("rename", data, account)
return err
}
func (driver Baidu) Copy(src string, dst string, account *model.Account) error {
path := utils.Join(account.RootFolder, src)
dest, newname := utils.Split(utils.Join(account.RootFolder, dst))
data := []base.Json{
{
"path": path,
"dest": dest,
"newname": newname,
},
}
_, err := driver.manage("copy", data, account)
return err
}
func (driver Baidu) Delete(path string, account *model.Account) error {
path = utils.Join(account.RootFolder, path)
data := []string{path}
_, err := driver.manage("delete", data, account)
return err
}
func (driver Baidu) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
var Default uint64 = 4 * 1024 * 1024
defaultByteData := make([]byte, Default)
count := int(math.Ceil(float64(file.GetSize()) / float64(Default)))
var SliceSize uint64 = 256 * 1024
// cal md5
h1 := md5.New()
h2 := md5.New()
block_list := make([]string, 0)
content_md5 := ""
slice_md5 := ""
left := file.GetSize()
for i := 0; i < count; i++ {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
h1.Write(byteData)
h2.Write(byteData)
block_list = append(block_list, fmt.Sprintf("\"%s\"", hex.EncodeToString(h2.Sum(nil))))
h2.Reset()
}
content_md5 = hex.EncodeToString(h1.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
if file.GetSize() <= SliceSize {
slice_md5 = content_md5
} else {
sliceData := make([]byte, SliceSize)
_, err = io.ReadFull(tempFile, sliceData)
if err != nil {
return err
}
h2.Write(sliceData)
slice_md5 = hex.EncodeToString(h2.Sum(nil))
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
}
path := encodeURIComponent(utils.Join(account.RootFolder, file.ParentPath, file.Name))
block_list_str := fmt.Sprintf("[%s]", strings.Join(block_list, ","))
data := fmt.Sprintf("path=%s&size=%d&isdir=0&autoinit=1&block_list=%s&content-md5=%s&slice-md5=%s",
path, file.GetSize(),
block_list_str,
content_md5, slice_md5)
params := map[string]string{
"method": "precreate",
}
var precreateResp PrecreateResp
_, err = driver.Post("/xpan/file", params, data, &precreateResp, account)
if err != nil {
return err
}
log.Debugf("%+v", precreateResp)
if precreateResp.ReturnType == 2 {
return nil
}
params = map[string]string{
"method": "upload",
"access_token": account.AccessToken,
"type": "tmpfile",
"path": path,
"uploadid": precreateResp.Uploadid,
}
left = file.GetSize()
for _, partseq := range precreateResp.BlockList {
byteSize := Default
var byteData []byte
if left < Default {
byteSize = left
byteData = make([]byte, byteSize)
} else {
byteData = defaultByteData
}
left -= byteSize
_, err = io.ReadFull(tempFile, byteData)
if err != nil {
return err
}
u := "https://d.pcs.baidu.com/rest/2.0/pcs/superfile2"
params["partseq"] = strconv.Itoa(partseq)
res, err := base.RestyClient.R().SetQueryParams(params).SetFileReader("file", file.Name, bytes.NewReader(byteData)).Post(u)
if err != nil {
return err
}
log.Debugln(res.String())
}
_, err = driver.create(path, file.GetSize(), 0, precreateResp.Uploadid, block_list_str, account)
return err
}
var _ base.Driver = (*Baidu)(nil)

View File

@ -1,84 +0,0 @@
package baidu
type TokenErrResp struct {
ErrorDescription string `json:"error_description"`
Error string `json:"error"`
}
type File struct {
//TkbindId int `json:"tkbind_id"`
//OwnerType int `json:"owner_type"`
//Category int `json:"category"`
//RealCategory string `json:"real_category"`
FsId int64 `json:"fs_id"`
ServerMtime int64 `json:"server_mtime"`
//OperId int `json:"oper_id"`
//ServerCtime int `json:"server_ctime"`
Thumbs struct {
//Icon string `json:"icon"`
Url3 string `json:"url3"`
//Url2 string `json:"url2"`
//Url1 string `json:"url1"`
} `json:"thumbs"`
//Wpfile int `json:"wpfile"`
//LocalMtime int `json:"local_mtime"`
Size int64 `json:"size"`
//ExtentTinyint7 int `json:"extent_tinyint7"`
Path string `json:"path"`
//Share int `json:"share"`
//ServerAtime int `json:"server_atime"`
//Pl int `json:"pl"`
//LocalCtime int `json:"local_ctime"`
ServerFilename string `json:"server_filename"`
//Md5 string `json:"md5"`
//OwnerId int `json:"owner_id"`
//Unlist int `json:"unlist"`
Isdir int `json:"isdir"`
}
type ListResp struct {
Errno int `json:"errno"`
GuidInfo string `json:"guid_info"`
List []File `json:"list"`
RequestId int64 `json:"request_id"`
Guid int `json:"guid"`
}
type DownloadResp struct {
Errmsg string `json:"errmsg"`
Errno int `json:"errno"`
List []struct {
//Category int `json:"category"`
//DateTaken int `json:"date_taken,omitempty"`
Dlink string `json:"dlink"`
//Filename string `json:"filename"`
//FsId int64 `json:"fs_id"`
//Height int `json:"height,omitempty"`
//Isdir int `json:"isdir"`
//Md5 string `json:"md5"`
//OperId int `json:"oper_id"`
//Path string `json:"path"`
//ServerCtime int `json:"server_ctime"`
//ServerMtime int `json:"server_mtime"`
//Size int `json:"size"`
//Thumbs struct {
// Icon string `json:"icon,omitempty"`
// Url1 string `json:"url1,omitempty"`
// Url2 string `json:"url2,omitempty"`
// Url3 string `json:"url3,omitempty"`
//} `json:"thumbs"`
//Width int `json:"width,omitempty"`
} `json:"list"`
//Names struct {
//} `json:"names"`
RequestId string `json:"request_id"`
}
type PrecreateResp struct {
Path string `json:"path"`
Uploadid string `json:"uploadid"`
ReturnType int `json:"return_type"`
BlockList []int `json:"block_list"`
Errno int `json:"errno"`
RequestId int64 `json:"request_id"`
}

View File

@ -1,18 +0,0 @@
package baidu
import (
"net/url"
"strings"
"time"
)
func getTime(t int64) *time.Time {
tm := time.Unix(t, 0)
return &tm
}
func encodeURIComponent(str string) string {
r := url.QueryEscape(str)
r = strings.ReplaceAll(r, "+", "%20")
return r
}

View File

@ -1,28 +0,0 @@
package base
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
)
func KeyCache(path string, account *model.Account) string {
path = utils.ParsePath(path)
return fmt.Sprintf("%s%s", account.Name, path)
}
func SetCache(path string, obj interface{}, account *model.Account) error {
return conf.Cache.Set(conf.Ctx, KeyCache(path, account), obj, nil)
}
func GetCache(path string, account *model.Account) (interface{}, error) {
return conf.Cache.Get(conf.Ctx, KeyCache(path, account))
}
func DeleteCache(path string, account *model.Account) error {
err := conf.Cache.Delete(conf.Ctx, KeyCache(path, account))
log.Debugf("delete cache %s: %+v", path, err)
return err
}

View File

@ -1,180 +0,0 @@
package base
import (
"github.com/Xhofe/alist/model"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"time"
)
type DriverConfig struct {
Name string
OnlyProxy bool // 必须使用代理(本机或者其他机器)
OnlyLocal bool // 必须本机返回的
ApiProxy bool // 使用API中转的
NoNeedSetLink bool // 不需要设置链接的
NoCors bool // 不可以跨域
LocalSort bool // 本地排序
}
type Args struct {
Path string
IP string
}
type Driver interface {
// Config 配置
Config() DriverConfig
// Items 账号所需参数
Items() []Item
// Save 保存时处理
Save(account *model.Account, old *model.Account) error
// File 取文件
File(path string, account *model.Account) (*model.File, error)
// Files 取文件夹
Files(path string, account *model.Account) ([]model.File, error)
// Link 取链接
Link(args Args, account *model.Account) (*Link, error)
// Path 取路径(文件或文件夹)
Path(path string, account *model.Account) (*model.File, []model.File, error)
// Deprecated Proxy 代理处理
//Proxy(r *http.Request, account *model.Account)
// Preview 预览
Preview(path string, account *model.Account) (interface{}, error)
// MakeDir 创建文件夹
MakeDir(path string, account *model.Account) error
// Move 移动/改名
Move(src string, dst string, account *model.Account) error
// Rename 改名
Rename(src string, dst string, account *model.Account) error
// Copy 拷贝
Copy(src string, dst string, account *model.Account) error
// Delete 删除
Delete(path string, account *model.Account) error
// Upload 上传
Upload(file *model.FileStream, account *model.Account) error
// TODO
//Search(path string, keyword string, account *model.Account) ([]*model.File, error)
}
type Item struct {
Name string `json:"name"`
Label string `json:"label"`
Type string `json:"type"`
Default string `json:"default"`
Values string `json:"values"`
Required bool `json:"required"`
Description string `json:"description"`
}
var driversMap = map[string]Driver{}
func RegisterDriver(driver Driver) {
log.Infof("register driver: [%s]", driver.Config().Name)
driversMap[driver.Config().Name] = driver
}
func GetDriver(name string) (driver Driver, ok bool) {
driver, ok = driversMap[name]
return
}
func GetDriversMap() map[string]Driver {
return driversMap
}
func GetDrivers() map[string][]Item {
res := make(map[string][]Item)
for k, v := range driversMap {
webdavDirect := Item{
Name: "webdav_direct",
Label: "webdav direct",
Type: TypeBool,
Required: true,
Description: "Transfer the WebDAV of this account through the native",
}
if v.Config().OnlyProxy {
res[k] = append([]Item{
webdavDirect,
}, v.Items()...)
} else {
res[k] = append([]Item{
{
Name: "proxy",
Label: "proxy",
Type: TypeBool,
Required: true,
Description: "web proxy",
},
{
Name: "webdav_proxy",
Label: "webdav proxy",
Type: TypeBool,
Required: true,
Description: "Transfer the WebDAV of this account through the server",
},
webdavDirect,
}, v.Items()...)
}
res[k] = append([]Item{
{
Name: "down_proxy_url",
Label: "down_proxy_url",
Type: TypeText,
},
{
Name: "extract_folder",
Label: "extract_folder",
Values: "front,back",
Type: TypeSelect,
},
}, res[k]...)
if v.Config().ApiProxy {
res[k] = append([]Item{
{
Name: "api_proxy_url",
Label: "api_proxy_url",
Type: TypeString,
},
}, res[k]...)
}
if v.Config().LocalSort {
res[k] = append(res[k], []Item{
{
Name: "order_by",
Label: "order_by",
Type: TypeSelect,
Values: "name,size,updated_at",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: TypeSelect,
Values: "ASC,DESC",
Required: false,
},
}...)
}
}
return res
}
var NoRedirectClient *resty.Client
var RestyClient = resty.New()
var HttpClient = &http.Client{}
var UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
var DefaultTimeout = time.Second * 20
func init() {
NoRedirectClient = resty.New().SetRedirectPolicy(
resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}),
)
NoRedirectClient.SetHeader("user-agent", UserAgent)
RestyClient.SetHeader("user-agent", UserAgent)
RestyClient.SetRetryCount(3)
RestyClient.SetTimeout(DefaultTimeout)
}

View File

@ -1,52 +0,0 @@
package base
import (
"errors"
"io"
)
var (
ErrPathNotFound = errors.New("path not found")
ErrNotFile = errors.New("not file")
ErrNotImplement = errors.New("not implement")
ErrNotSupport = errors.New("not support")
ErrNotFolder = errors.New("not a folder")
ErrEmptyFile = errors.New("empty file")
ErrRelativePath = errors.New("access using relative path is not allowed")
ErrEmptyToken = errors.New("empty token")
)
const (
TypeString = "string"
TypeSelect = "select"
TypeBool = "bool"
TypeNumber = "number"
TypeText = "text"
)
const (
Get = iota
Post
Put
Delete
Patch
)
type Json map[string]interface{}
type TokenResp struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
}
type Header struct {
Name string `json:"name"`
Value string `json:"value"`
}
type Link struct {
Url string `json:"url"`
Headers []Header `json:"headers"`
Data io.ReadCloser
FilePath string `json:"path"` // for native
}

View File

@ -1,254 +0,0 @@
package ftp
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/jlaffaye/ftp"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type FTP struct{}
func (driver FTP) Config() base.DriverConfig {
return base.DriverConfig{
Name: "FTP",
OnlyProxy: true,
OnlyLocal: true,
NoNeedSetLink: true,
LocalSort: true,
}
}
func (driver FTP) Items() []base.Item {
return []base.Item{
{
Name: "site_url",
Label: "ftp host url",
Type: base.TypeString,
Required: true,
},
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
}
}
func (driver FTP) Save(account *model.Account, old *model.Account) error {
if old != nil {
conn, ok := connMap[old.Name]
if ok {
err := conn.Quit()
log.Error("ftp:", err)
delete(connMap, old.Name)
}
}
if account == nil {
return nil
}
if account.RootFolder == "" {
account.RootFolder = "/"
}
_, err := driver.Login(account)
if err != nil {
account.Status = err.Error()
} else {
account.Status = "work"
}
_ = model.SaveAccount(account)
return err
}
func (driver FTP) File(path string, account *model.Account) (*model.File, error) {
log.Debugf("file: %s", path)
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver FTP) Files(path string, account *model.Account) ([]model.File, error) {
log.Debugf("files: %s", path)
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
realPath := utils.Join(account.RootFolder, path)
conn, err := driver.Login(account)
if err != nil {
return nil, err
}
//defer func() { _ = conn.Quit() }()
entries, err := conn.List(realPath)
if err != nil {
return nil, err
}
res := make([]model.File, 0)
for i, _ := range entries {
entry := entries[i]
if entry.Name == "." || entry.Name == ".." {
continue
}
f := model.File{
Name: entry.Name,
Size: int64(entry.Size),
UpdatedAt: &entry.Time,
Driver: driver.Config().Name,
}
if entry.Type == ftp.EntryTypeFolder {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(entry.Name))
}
res = append(res, f)
}
if len(res) > 0 {
_ = base.SetCache(path, res, account)
}
return res, nil
}
func (driver FTP) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
path = utils.ParsePath(path)
realPath := utils.Join(account.RootFolder, path)
conn, err := driver.Login(account)
if err != nil {
return nil, err
}
//defer func() { _ = conn.Quit() }()
resp, err := conn.Retr(realPath)
if err != nil {
return nil, err
}
//defer func() { _ = resp.Close() }()
//data, err := ioutil.ReadAll(resp)
//if err != nil {
// return nil, err
//}
return &base.Link{
Data: resp,
}, nil
}
func (driver FTP) Path(path string, account *model.Account) (*model.File, []model.File, error) {
log.Debugf("ftp path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver FTP) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver FTP) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver FTP) MakeDir(path string, account *model.Account) error {
path = utils.ParsePath(path)
realPath := utils.Join(account.RootFolder, path)
conn, err := driver.Login(account)
if err != nil {
return err
}
//defer func() { _ = conn.Quit() }()
err = conn.MakeDir(realPath)
return err
}
func (driver FTP) Move(src string, dst string, account *model.Account) error {
realSrc := utils.Join(account.RootFolder, src)
realDst := utils.Join(account.RootFolder, dst)
conn, err := driver.Login(account)
if err != nil {
return err
}
//defer func() { _ = conn.Quit() }()
err = conn.Rename(realSrc, realDst)
return err
}
func (driver FTP) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver FTP) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver FTP) Delete(path string, account *model.Account) error {
path = utils.ParsePath(path)
realPath := utils.Join(account.RootFolder, path)
conn, err := driver.Login(account)
if err != nil {
return err
}
//defer func() { _ = conn.Quit() }()
err = conn.Delete(realPath)
return err
}
func (driver FTP) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
realPath := utils.Join(account.RootFolder, file.ParentPath, file.Name)
conn, err := driver.Login(account)
if err != nil {
return err
}
//defer func() { _ = conn.Quit() }()
err = conn.Stor(realPath, file)
return err
}
var _ base.Driver = (*FTP)(nil)

View File

@ -1,36 +0,0 @@
package ftp
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/jlaffaye/ftp"
)
var connMap map[string]*ftp.ServerConn
func (driver FTP) Login(account *model.Account) (*ftp.ServerConn, error) {
conn, ok := connMap[account.Name]
if ok {
_, err := conn.CurrentDir()
if err == nil {
return conn, nil
} else {
delete(connMap, account.Name)
}
}
conn, err := ftp.Connect(account.SiteUrl)
if err != nil {
return nil, err
}
err = conn.Login(account.Username, account.Password)
if err != nil {
return nil, err
}
connMap[account.Name] = conn
return conn, nil
}
func init() {
base.RegisterDriver(&FTP{})
connMap = make(map[string]*ftp.ServerConn)
}

View File

@ -1,289 +0,0 @@
package google
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io/ioutil"
"path/filepath"
)
type GoogleDrive struct{}
func (driver GoogleDrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "GoogleDrive",
OnlyProxy: true,
ApiProxy: true,
NoNeedSetLink: true,
}
}
func (driver GoogleDrive) Items() []base.Item {
return []base.Item{
{
Name: "client_id",
Label: "client id",
Type: base.TypeString,
Required: true,
Default: "202264815644.apps.googleusercontent.com",
},
{
Name: "client_secret",
Label: "client secret",
Type: base.TypeString,
Required: true,
Default: "X4Z3ca8xfWDb1Voo-F9a7ZxJ",
},
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeString,
Required: false,
Description: "such as: folder,name,modifiedTime",
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Required: false,
},
}
}
func (driver GoogleDrive) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
account.Proxy = true
err := driver.RefreshToken(account)
if err != nil {
account.Status = err.Error()
_ = model.SaveAccount(account)
return err
}
if account.RootFolder == "" {
account.RootFolder = "root"
}
account.Status = "work"
_ = model.SaveAccount(account)
return nil
}
func (driver GoogleDrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver GoogleDrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []File
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file, account))
}
return files, nil
}
func (driver GoogleDrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
if file.Type == conf.FOLDER {
return nil, base.ErrNotFile
}
url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?includeItemsFromAllDrives=true&supportsAllDrives=true", file.Id)
_, err = driver.Request(url, base.Get, nil, nil, nil, nil, nil, account)
if err != nil {
return nil, err
}
link := base.Link{
Url: url + "&alt=media",
Headers: []base.Header{
{
Name: "Authorization",
Value: "Bearer " + account.AccessToken,
},
},
}
return &link, nil
}
func (driver GoogleDrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("google path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver GoogleDrive) Proxy(r *http.Request, account *model.Account) {
// r.Header.Add("Authorization", "Bearer "+account.AccessToken)
//}
func (driver GoogleDrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver GoogleDrive) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := base.Json{
"name": utils.Base(path),
"parents": []string{parentFile.Id},
"mimeType": "application/vnd.google-apps.folder",
}
_, err = driver.Request("https://www.googleapis.com/drive/v3/files", base.Post, nil, nil, nil, &data, nil, account)
return err
}
func (driver GoogleDrive) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
url := "https://www.googleapis.com/drive/v3/files/" + srcFile.Id
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
query := map[string]string{
"addParents": dstParentFile.Id,
"removeParents": "root",
}
_, err = driver.Request(url, base.Patch, nil, query, nil, nil, nil, account)
return err
}
func (driver GoogleDrive) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
url := "https://www.googleapis.com/drive/v3/files/" + srcFile.Id
if err != nil {
return err
}
data := base.Json{
"name": utils.Base(dst),
}
_, err = driver.Request(url, base.Patch, nil, nil, nil, &data, nil, account)
return err
}
func (driver GoogleDrive) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver GoogleDrive) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
url := "https://www.googleapis.com/drive/v3/files/" + file.Id
if err != nil {
return err
}
_, err = driver.Request(url, base.Delete, nil, nil, nil, nil, nil, account)
return err
}
func (driver GoogleDrive) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
data := base.Json{
"name": file.Name,
"parents": []string{parentFile.Id},
}
var e Error
url := "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true"
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
res, err := base.NoRedirectClient.R().SetHeader("Authorization", "Bearer "+account.AccessToken).
SetError(&e).SetBody(data).
Post(url)
if err != nil {
return err
}
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = driver.RefreshToken(account)
if err != nil {
_ = model.SaveAccount(account)
return err
}
return driver.Upload(file, account)
}
return fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
putUrl := res.Header().Get("location")
byteData, _ := ioutil.ReadAll(file)
_, err = driver.Request(putUrl, base.Put, nil, nil, nil, byteData, nil, account)
return err
}
var _ base.Driver = (*GoogleDrive)(nil)

View File

@ -1,198 +0,0 @@
package google
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
"time"
)
type TokenError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (driver GoogleDrive) RefreshToken(account *model.Account) error {
url := "https://www.googleapis.com/oauth2/v4/token"
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
var resp base.TokenResp
var e TokenError
res, err := base.RestyClient.R().SetResult(&resp).SetError(&e).
SetFormData(map[string]string{
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
"refresh_token": account.RefreshToken,
"grant_type": "refresh_token",
}).Post(url)
if err != nil {
return err
}
log.Debug(res.String())
if e.Error != "" {
return fmt.Errorf(e.Error)
}
account.AccessToken = resp.AccessToken
account.Status = "work"
return nil
}
type File struct {
Id string `json:"id"`
Name string `json:"name"`
MimeType string `json:"mimeType"`
ModifiedTime *time.Time `json:"modifiedTime"`
Size string `json:"size"`
ThumbnailLink string `json:"thumbnailLink"`
}
func (driver GoogleDrive) IsDir(mimeType string) bool {
return mimeType == "application/vnd.google-apps.folder" || mimeType == "application/vnd.google-apps.shortcut"
}
func (driver GoogleDrive) FormatFile(file *File, account *model.Account) *model.File {
f := &model.File{
Id: file.Id,
Name: file.Name,
Driver: driver.Config().Name,
UpdatedAt: file.ModifiedTime,
Url: "",
}
if driver.IsDir(file.MimeType) {
f.Type = conf.FOLDER
} else {
size, _ := strconv.ParseInt(file.Size, 10, 64)
f.Size = size
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
if file.ThumbnailLink != "" {
if account.APIProxyUrl != "" {
f.Thumbnail = fmt.Sprintf("%s/%s", account.APIProxyUrl, file.ThumbnailLink)
} else {
f.Thumbnail = file.ThumbnailLink
}
}
return f
}
type Files struct {
NextPageToken string `json:"nextPageToken"`
Files []File `json:"files"`
}
type Error struct {
Error struct {
Errors []struct {
Domain string `json:"domain"`
Reason string `json:"reason"`
Message string `json:"message"`
LocationType string `json:"location_type"`
Location string `json:"location"`
}
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (driver GoogleDrive) GetFiles(id string, account *model.Account) ([]File, error) {
pageToken := "first"
res := make([]File, 0)
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
var resp Files
orderBy := "folder,name,modifiedTime desc"
if account.OrderBy != "" {
orderBy = account.OrderBy + " " + account.OrderDirection
}
query := map[string]string{
"orderBy": orderBy,
"fields": "files(id,name,mimeType,size,modifiedTime,thumbnailLink),nextPageToken",
"pageSize": "1000",
"q": fmt.Sprintf("'%s' in parents and trashed = false", id),
//"includeItemsFromAllDrives": "true",
//"supportsAllDrives": "true",
"pageToken": pageToken,
}
_, err := driver.Request("https://www.googleapis.com/drive/v3/files",
base.Get, nil, query, nil, nil, &resp, account)
if err != nil {
return nil, err
}
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}
func (driver GoogleDrive) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
rawUrl := url
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
req.SetQueryParam("includeItemsFromAllDrives", "true")
req.SetQueryParam("supportsAllDrives", "true")
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
var e Error
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
case base.Delete:
res, err = req.Delete(url)
case base.Patch:
res, err = req.Patch(url)
case base.Put:
res, err = req.Put(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debug(res.String())
if e.Error.Code != 0 {
if e.Error.Code == 401 {
err = driver.RefreshToken(account)
if err != nil {
_ = model.SaveAccount(account)
return nil, err
}
return driver.Request(rawUrl, method, headers, query, form, data, resp, account)
}
return nil, fmt.Errorf("%s: %v", e.Error.Message, e.Error.Errors)
}
return res.Body(), nil
}
func init() {
base.RegisterDriver(&GoogleDrive{})
}

View File

@ -1,202 +0,0 @@
package lanzou
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Lanzou struct{}
func (driver Lanzou) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Lanzou",
NoCors: true,
}
}
func (driver Lanzou) Items() []base.Item {
return []base.Item{
{
Name: "internal_type",
Label: "lanzou type",
Type: base.TypeSelect,
Required: true,
Values: "cookie,url",
},
{
Name: "access_token",
Label: "cookie",
Type: base.TypeString,
Description: "about 15 days valid",
Required: true,
},
{
Name: "site_url",
Label: "share url",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
},
{
Name: "password",
Label: "share password",
Type: base.TypeString,
},
}
}
func (driver Lanzou) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if account.InternalType == "cookie" {
if account.RootFolder == "" {
account.RootFolder = "-1"
}
}
account.Status = "work"
_ = model.SaveAccount(account)
return nil
}
func (driver Lanzou) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Lanzou) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var rawFiles []LanZouFile
cache, err := base.GetCache(path, account)
if err == nil {
rawFiles, _ = cache.([]LanZouFile)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(rawFiles) > 0 {
_ = base.SetCache(path, rawFiles, account)
}
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
return files, nil
}
func (driver Lanzou) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
log.Debugf("down file: %+v", file)
downId := file.Id
pwd := ""
if account.InternalType == "cookie" {
downId, pwd, err = driver.GetDownPageId(file.Id, account)
if err != nil {
return nil, err
}
}
var url string
//if pwd != "" {
//url, err = driver.GetLinkWithPassword(downId, pwd, account)
//} else {
url, err = driver.GetLink(downId, pwd, account)
//}
if err != nil {
return nil, err
}
link := base.Link{
Url: url,
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
}
return &link, nil
}
func (driver Lanzou) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("lanzou path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Lanzou) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
//}
func (driver Lanzou) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver *Lanzou) MakeDir(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Move(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Rename(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Delete(path string, account *model.Account) error {
return base.ErrNotImplement
}
func (driver *Lanzou) Upload(file *model.FileStream, account *model.Account) error {
return base.ErrNotImplement
}
var _ base.Driver = (*Lanzou)(nil)

View File

@ -1,285 +0,0 @@
package lanzou
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"net/url"
"path/filepath"
"regexp"
"strconv"
"time"
)
type LanZouFile struct {
Name string `json:"name"`
NameAll string `json:"name_all"`
Id string `json:"id"`
FolId string `json:"fol_id"`
Size string `json:"size"`
Time string `json:"time"`
Folder bool
}
func (driver *Lanzou) FormatFile(file *LanZouFile) *model.File {
now := time.Now()
f := &model.File{
Id: file.Id,
Name: file.Name,
Driver: driver.Config().Name,
SizeStr: file.Size,
TimeStr: file.Time,
UpdatedAt: &now,
}
if file.Folder {
f.Type = conf.FOLDER
f.Id = file.FolId
} else {
f.Name = file.NameAll
f.Type = utils.GetFileType(filepath.Ext(file.NameAll))
}
return f
}
type LanZouFilesResp struct {
Zt int `json:"zt"`
Info interface{} `json:"info"`
Text []LanZouFile `json:"text"`
}
func (driver *Lanzou) GetFiles(folderId string, account *model.Account) ([]LanZouFile, error) {
if account.InternalType == "cookie" {
files := make([]LanZouFile, 0)
var resp LanZouFilesResp
// folders
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{
"task": "47",
"folder_id": folderId,
}).Post("https://pc.woozooo.com/doupload.php")
if err != nil {
return nil, err
}
log.Debug(res.String())
if resp.Zt != 1 && resp.Zt != 2 {
return nil, fmt.Errorf("%v", resp.Info)
}
for _, file := range resp.Text {
file.Folder = true
files = append(files, file)
}
// files
pg := 1
for {
_, err = base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{
"task": "5",
"folder_id": folderId,
"pg": strconv.Itoa(pg),
}).Post("https://pc.woozooo.com/doupload.php")
if err != nil {
return nil, err
}
if resp.Zt != 1 {
return nil, fmt.Errorf("%v", resp.Info)
}
if len(resp.Text) == 0 {
break
}
files = append(files, resp.Text...)
pg++
}
return files, nil
} else {
return driver.GetFilesByUrl(account)
}
}
func (driver *Lanzou) GetFilesByUrl(account *model.Account) ([]LanZouFile, error) {
files := make([]LanZouFile, 0)
shareUrl := account.SiteUrl
u, err := url.Parse(shareUrl)
if err != nil {
return nil, err
}
res, err := base.RestyClient.R().Get(shareUrl)
if err != nil {
return nil, err
}
lxArr := regexp.MustCompile(`'lx':(.+?),`).FindStringSubmatch(res.String())
if len(lxArr) == 0 {
return nil, fmt.Errorf("get empty page")
}
lx := lxArr[1]
fid := regexp.MustCompile(`'fid':(.+?),`).FindStringSubmatch(res.String())[1]
uid := regexp.MustCompile(`'uid':'(.+?)',`).FindStringSubmatch(res.String())[1]
rep := regexp.MustCompile(`'rep':'(.+?)',`).FindStringSubmatch(res.String())[1]
up := regexp.MustCompile(`'up':(.+?),`).FindStringSubmatch(res.String())[1]
ls := ""
if account.Password != "" {
ls = regexp.MustCompile(`'ls':(.+?),`).FindStringSubmatch(res.String())[1]
}
tName := regexp.MustCompile(`'t':(.+?),`).FindStringSubmatch(res.String())[1]
kName := regexp.MustCompile(`'k':(.+?),`).FindStringSubmatch(res.String())[1]
t := regexp.MustCompile(`var ` + tName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
k := regexp.MustCompile(`var ` + kName + ` = '(.+?)';`).FindStringSubmatch(res.String())[1]
pg := 1
for {
var resp LanZouFilesResp
res, err = base.RestyClient.R().SetResult(&resp).SetFormData(map[string]string{
"lx": lx,
"fid": fid,
"uid": uid,
"pg": strconv.Itoa(pg),
"rep": rep,
"t": t,
"k": k,
"up": up,
"ls": ls,
"pwd": account.Password,
}).Post(fmt.Sprintf("https://%s/filemoreajax.php", u.Host))
if err != nil {
log.Debug(err)
break
}
log.Debug(res.String())
if resp.Zt != 1 {
return nil, fmt.Errorf("%v", resp.Info)
}
if len(resp.Text) == 0 {
break
}
pg++
time.Sleep(time.Second)
files = append(files, resp.Text...)
}
return files, nil
}
//type LanzouDownInfo struct {
// FId string `json:"f_id"`
// IsNewd string `json:"is_newd"`
//}
// GetDownPageId 获取下载页面的ID
func (driver *Lanzou) GetDownPageId(fileId string, account *model.Account) (string, string, error) {
var resp DownPageResp
res, err := base.RestyClient.R().SetResult(&resp).SetHeader("Cookie", account.AccessToken).
SetFormData(map[string]string{
"task": "22",
"file_id": fileId,
}).Post("https://pc.woozooo.com/doupload.php")
if err != nil {
return "", "", err
}
log.Debug(res.String())
if resp.Zt != 1 {
return "", "", fmt.Errorf("%v", resp.Info)
}
return resp.Info.FId, resp.Info.Pwd, nil
}
type LanzouLinkResp struct {
Dom string `json:"dom"`
Url string `json:"url"`
Zt int `json:"zt"`
}
func (driver *Lanzou) GetLink(downId string, pwd string, account *model.Account) (string, error) {
shareUrl := account.SiteUrl
u, err := url.Parse(shareUrl)
if err != nil {
return "", err
}
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
if err != nil {
return "", err
}
iframe := regexp.MustCompile(`<iframe class="ifr2" name=".{2,20}" src="(.+?)"`).FindStringSubmatch(res.String())
if len(iframe) == 0 {
return driver.GetLinkWithPassword(downId, pwd, res.String(), account)
}
iframeUrl := fmt.Sprintf("https://%s%s", u.Host, iframe[1])
res, err = base.RestyClient.R().Get(iframeUrl)
if err != nil {
return "", err
}
log.Debugln(res.String())
ajaxdata := regexp.MustCompile(`var ajaxdata = '(.+?)'`).FindStringSubmatch(res.String())
if len(ajaxdata) == 0 {
return "", fmt.Errorf("get iframe empty page")
}
signs := ajaxdata[1]
//sign := regexp.MustCompile(`var ispostdowns = '(.+?)';`).FindStringSubmatch(res.String())[1]
sign := regexp.MustCompile(`'sign':'(.+?)',`).FindStringSubmatch(res.String())[1]
//websign := regexp.MustCompile(`'websign':'(.+?)'`).FindStringSubmatch(res.String())[1]
websign := ""
websignR := regexp.MustCompile(`var websign = '(.+?)'`).FindStringSubmatch(res.String())
if len(websignR) > 1 {
websign = websignR[1]
}
//websign := ""
//websignkey := regexp.MustCompile(`'websignkey':'(.+?)'`).FindStringSubmatch(res.String())[1]
websignkey := regexp.MustCompile(`var websignkey = '(.+?)';`).FindStringSubmatch(res.String())[1]
var resp LanzouLinkResp
form := map[string]string{
"action": "downprocess",
"signs": signs,
"sign": sign,
"ves": "1",
"websign": websign,
"websignkey": websignkey,
}
log.Debugf("form: %+v", form)
res, err = base.RestyClient.R().SetResult(&resp).
SetHeader("origin", "https://"+u.Host).
SetHeader("referer", iframeUrl).
SetFormData(form).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
log.Debug(res.String())
if err != nil {
return "", err
}
if resp.Zt == 1 {
return resp.Dom + "/file/" + resp.Url, nil
}
return "", fmt.Errorf("failed get link")
}
func (driver *Lanzou) GetLinkWithPassword(downId string, pwd string, html string, account *model.Account) (string, error) {
shareUrl := account.SiteUrl
u, err := url.Parse(shareUrl)
if err != nil {
return "", err
}
if html == "" {
log.Debugln(fmt.Sprintf("https://%s/%s", u.Host, downId))
res, err := base.RestyClient.R().Get(fmt.Sprintf("https://%s/%s", u.Host, downId))
if err != nil {
return "", err
}
html = res.String()
}
data := regexp.MustCompile(`data : '(.+?)'\+pwd,`).FindStringSubmatch(html)[1] + pwd
var resp LanzouLinkResp
_, err = base.RestyClient.R().SetResult(&resp).SetHeaders(map[string]string{
"Referer": fmt.Sprintf("https://%s/%s", u.Host, downId),
"Origin": "https://" + u.Host,
"content-type": "application/x-www-form-urlencoded",
}).SetBody(data).Post(fmt.Sprintf("https://%s/ajaxm.php", u.Host))
if err != nil {
return "", err
}
if resp.Zt == 1 {
return resp.Dom + "/file/" + resp.Url, nil
}
return "", fmt.Errorf("failed get link with password")
}
func init() {
base.RegisterDriver(&Lanzou{})
}

View File

@ -1,13 +0,0 @@
package lanzou
type DownPageResp struct {
Zt int `json:"zt"`
Info struct {
Pwd string `json:"pwd"`
Onof string `json:"onof"`
FId string `json:"f_id"`
Taoc string `json:"taoc"`
IsNewd string `json:"is_newd"`
} `json:"info"`
Text interface{} `json:"text"`
}

182
drivers/local/driver.go Normal file
View File

@ -0,0 +1,182 @@
package local
import (
"context"
"github.com/alist-org/alist/v3/internal/errs"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/operations"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/pkg/errors"
)
type Local struct {
model.Storage
Addition
}
func (d *Local) Config() driver.Config {
return config
}
func (d *Local) Init(ctx context.Context, storage model.Storage) error {
d.Storage = storage
err := utils.Json.UnmarshalFromString(d.Storage.Addition, &d.Addition)
if err != nil {
return errors.Wrap(err, "error while unmarshal addition")
}
if !utils.Exists(d.RootFolder) {
err = errors.Errorf("root folder %s not exists", d.RootFolder)
d.SetStatus(err.Error())
} else {
if !filepath.IsAbs(d.RootFolder) {
d.RootFolder, err = filepath.Abs(d.RootFolder)
if err != nil {
return errors.Wrap(err, "error while get abs path")
}
}
d.SetStatus("OK")
}
operations.MustSaveDriverStorage(d)
return err
}
func (d *Local) Drop(ctx context.Context) error {
return nil
}
func (d *Local) GetAddition() driver.Additional {
return d.Addition
}
func (d *Local) List(ctx context.Context, dir model.Obj) ([]model.Obj, error) {
fullPath := dir.GetID()
rawFiles, err := ioutil.ReadDir(fullPath)
if err != nil {
return nil, errors.Wrapf(err, "error while read dir %s", fullPath)
}
var files []model.Obj
for _, f := range rawFiles {
if strings.HasPrefix(f.Name(), ".") {
continue
}
file := model.Object{
Name: f.Name(),
Modified: f.ModTime(),
Size: f.Size(),
IsFolder: f.IsDir(),
}
files = append(files, &file)
}
return files, nil
}
func (d *Local) Get(ctx context.Context, path string) (model.Obj, error) {
f, err := os.Stat(path)
if err != nil {
return nil, errors.Wrapf(err, "error while stat %s", path)
}
file := model.Object{
ID: path,
Name: f.Name(),
Modified: f.ModTime(),
Size: f.Size(),
IsFolder: f.IsDir(),
}
return &file, nil
}
func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
fullPath := file.GetID()
link := model.Link{
FilePath: &fullPath,
}
return &link, nil
}
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
fullPath := filepath.Join(parentDir.GetID(), dirName)
err := os.MkdirAll(fullPath, 0700)
if err != nil {
return errors.Wrapf(err, "error while make dir %s", fullPath)
}
return nil
}
func (d *Local) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
srcPath := srcObj.GetID()
dstPath := filepath.Join(dstDir.GetID(), srcObj.GetName())
err := os.Rename(srcPath, dstPath)
if err != nil {
return errors.Wrapf(err, "error while move %s to %s", srcPath, dstPath)
}
return nil
}
func (d *Local) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
srcPath := srcObj.GetID()
dstPath := filepath.Join(filepath.Dir(srcPath), newName)
err := os.Rename(srcPath, dstPath)
if err != nil {
return errors.Wrapf(err, "error while rename %s to %s", srcPath, dstPath)
}
return nil
}
func (d *Local) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
srcPath := srcObj.GetID()
dstPath := filepath.Join(dstDir.GetID(), srcObj.GetName())
var err error
if srcObj.IsDir() {
err = copyDir(srcPath, dstPath)
} else {
err = copyFile(srcPath, dstPath)
}
if err != nil {
return errors.Wrapf(err, "error while copy %s to %s", srcPath, dstPath)
}
return nil
}
func (d *Local) Remove(ctx context.Context, obj model.Obj) error {
var err error
if obj.IsDir() {
err = os.RemoveAll(obj.GetID())
} else {
err = os.Remove(obj.GetID())
}
if err != nil {
return errors.Wrapf(err, "error while remove %s", obj.GetID())
}
return nil
}
func (d *Local) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
fullPath := filepath.Join(dstDir.GetID(), stream.GetName())
out, err := os.Create(fullPath)
if err != nil {
return errors.Wrapf(err, "error while create file %s", fullPath)
}
defer func() {
_ = out.Close()
if errors.Is(err, context.Canceled) {
_ = os.Remove(fullPath)
}
}()
err = utils.CopyWithCtx(ctx, out, stream)
if err != nil {
return errors.Wrapf(err, "error while copy file %s", fullPath)
}
return nil
}
func (d *Local) Other(ctx context.Context, data interface{}) (interface{}, error) {
return nil, errs.NotSupport
}
var _ driver.Driver = (*Local)(nil)

26
drivers/local/meta.go Normal file
View File

@ -0,0 +1,26 @@
package local
import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/operations"
)
type Addition struct {
driver.RootFolderPath
}
var config = driver.Config{
Name: "Local",
OnlyLocal: true,
LocalSort: true,
NoCache: true,
DefaultRoot: "/",
}
func New() driver.Driver {
return &Local{}
}
func init() {
operations.RegisterDriver(config, New)
}

View File

@ -1,16 +1,15 @@
package native
package local
import (
"fmt"
"github.com/Xhofe/alist/drivers/base"
"io"
"io/ioutil"
"os"
"path"
)
// File copies a single file from src to dst
func (driver *Native) CopyFile(src, dst string) error {
// copyFile File copies a single file from src to dst
func copyFile(src, dst string) error {
var err error
var srcfd *os.File
var dstfd *os.File
@ -35,8 +34,8 @@ func (driver *Native) CopyFile(src, dst string) error {
return os.Chmod(dst, srcinfo.Mode())
}
// Dir copies a whole directory recursively
func (driver *Native) CopyDir(src string, dst string) error {
// copyDir Dir copies a whole directory recursively
func copyDir(src string, dst string) error {
var err error
var fds []os.FileInfo
var srcinfo os.FileInfo
@ -44,11 +43,9 @@ func (driver *Native) CopyDir(src string, dst string) error {
if srcinfo, err = os.Stat(src); err != nil {
return err
}
if err = os.MkdirAll(dst, srcinfo.Mode()); err != nil {
return err
}
if fds, err = ioutil.ReadDir(src); err != nil {
return err
}
@ -57,18 +54,14 @@ func (driver *Native) CopyDir(src string, dst string) error {
dstfp := path.Join(dst, fd.Name())
if fd.IsDir() {
if err = driver.CopyDir(srcfp, dstfp); err != nil {
if err = copyDir(srcfp, dstfp); err != nil {
fmt.Println(err)
}
} else {
if err = driver.CopyFile(srcfp, dstfp); err != nil {
if err = copyFile(srcfp, dstfp); err != nil {
fmt.Println(err)
}
}
}
return nil
}
func init() {
base.RegisterDriver(&Native{})
}

View File

@ -1,317 +0,0 @@
package mediatrack
import (
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/google/uuid"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
)
type MediaTrack struct{}
func (driver MediaTrack) Config() base.DriverConfig {
return base.DriverConfig{
Name: "MediaTrack",
}
}
func (driver MediaTrack) Items() []base.Item {
return []base.Item{
{
Name: "access_token",
Label: "Token",
Type: base.TypeString,
Description: "Unknown expiration time",
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "updated_at,title,size",
Required: true,
Description: "title",
},
{
Name: "order_direction",
Label: "desc",
Type: base.TypeSelect,
Values: "true,false",
Required: true,
Default: "false",
},
}
}
func (driver MediaTrack) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
return nil
}
func (driver MediaTrack) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver MediaTrack) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
files, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver MediaTrack) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
pre := "https://jayce.api.mediatrack.cn/v3/assets/" + file.Id
body, err := driver.Request(pre+"/token", base.Get, nil, nil, nil, nil, nil, account)
if err != nil {
return nil, err
}
url := pre + "/raw"
res, err := base.NoRedirectClient.R().SetQueryParam("token", jsoniter.Get(body, "data").ToString()).Get(url)
return &base.Link{Url: res.Header().Get("location")}, nil
}
func (driver MediaTrack) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("MediaTrack path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver MediaTrack) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver MediaTrack) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotImplement
}
func (driver MediaTrack) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
_, err = driver.Request(url, base.Post, nil, nil, nil, base.Json{
"type": 1,
"title": utils.Base(path),
}, nil, account)
return err
}
func (driver MediaTrack) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
data := base.Json{
"parent_id": dstParentFile.Id,
"ids": []string{srcFile.Id},
}
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/move"
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
return err
}
func (driver MediaTrack) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
url := "https://jayce.api.mediatrack.cn/v3/assets/" + srcFile.Id
data := base.Json{
"title": utils.Base(dst),
}
_, err = driver.Request(url, base.Put, nil, nil, nil, data, nil, account)
return err
}
func (driver MediaTrack) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
data := base.Json{
"parent_id": dstParentFile.Id,
"ids": []string{srcFile.Id},
}
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/clone"
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
return err
}
func (driver MediaTrack) Delete(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
file, err := driver.File(path, account)
if err != nil {
return err
}
data := base.Json{
"origin_id": parentFile.Id,
"ids": []string{file.Id},
}
url := "https://jayce.api.mediatrack.cn/v4/assets/batch/delete"
_, err = driver.Request(url, base.Delete, nil, nil, nil, data, nil, account)
return err
}
func (driver MediaTrack) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
src := "assets/" + uuid.New().String()
var resp UploadResp
_, err = driver.Request("https://jayce.api.mediatrack.cn/v3/storage/tokens/asset", base.Get, nil, map[string]string{
"src": src,
}, nil, nil, &resp, account)
if err != nil {
return err
}
credential := resp.Data.Credentials
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(credential.TmpSecretID, credential.TmpSecretKey, credential.Token),
Region: &resp.Data.Region,
Endpoint: aws.String("cos.accelerate.myqcloud.com"),
}
s, err := session.NewSession(cfg)
if err != nil {
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{
Bucket: &resp.Data.Bucket,
Key: &resp.Data.Object,
Body: tempFile,
}
_, err = uploader.Upload(input)
if err != nil {
return err
}
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v3/assets/%s/children", parentFile.Id)
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
h := md5.New()
_, err = io.Copy(h, tempFile)
if err != nil {
return err
}
hash := hex.EncodeToString(h.Sum(nil))
data := base.Json{
"category": 0,
"description": file.GetFileName(),
"hash": hash,
"mime": file.MIMEType,
"size": file.GetSize(),
"src": src,
"title": file.GetFileName(),
"type": 0,
}
_, err = driver.Request(url, base.Post, nil, nil, nil, data, nil, account)
return err
}
var _ base.Driver = (*MediaTrack)(nil)

View File

@ -1,183 +0,0 @@
package mediatrack
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"path"
"strconv"
"time"
)
type BaseResp struct {
Status string `json:"status"`
Message string `json:"message"`
}
type T struct {
BaseResp
}
func (driver MediaTrack) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
var e BaseResp
req.SetResult(&e)
var err error
var res *resty.Response
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
case base.Delete:
res, err = req.Delete(url)
case base.Patch:
res, err = req.Patch(url)
case base.Put:
res, err = req.Put(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debugln(res.String())
if e.Status != "SUCCESS" {
return nil, errors.New(e.Message)
}
if resp != nil {
err = utils.Json.Unmarshal(res.Body(), resp)
}
return res.Body(), err
}
type File struct {
Category int `json:"category"`
ChildAssets []interface{} `json:"childAssets"`
CommentCount int `json:"comment_count"`
CoverAsset interface{} `json:"cover_asset"`
CoverAssetID string `json:"cover_asset_id"`
CreatedAt time.Time `json:"created_at"`
DeletedAt string `json:"deleted_at"`
Description string `json:"description"`
File *struct {
Cover string `json:"cover"`
Src string `json:"src"`
} `json:"file"`
//FileID string `json:"file_id"`
ID string `json:"id"`
Size string `json:"size"`
Thumbnails []interface{} `json:"thumbnails"`
Title string `json:"title"`
UpdatedAt time.Time `json:"updated_at"`
}
func (driver MediaTrack) formatFile(file *File) *model.File {
f := model.File{
Id: file.ID,
Name: file.Title,
Size: 0,
Driver: driver.Config().Name,
UpdatedAt: &file.UpdatedAt,
}
if file.File == nil {
// folder
f.Type = conf.FOLDER
} else {
size, _ := strconv.ParseInt(file.Size, 10, 64)
f.Size = size
f.Type = utils.GetFileType(path.Ext(file.Title))
if file.File.Cover != "" {
f.Thumbnail = "https://nano.mtres.cn/" + file.File.Cover
}
}
return &f
}
type ChildrenResp struct {
Status string `json:"status"`
Data struct {
Total int `json:"total"`
Assets []File `json:"assets"`
} `json:"data"`
Path string `json:"path"`
TraceID string `json:"trace_id"`
RequestID string `json:"requestId"`
}
func (driver MediaTrack) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0)
url := fmt.Sprintf("https://jayce.api.mediatrack.cn/v4/assets/%s/children", parentId)
sort := ""
if account.OrderBy != "" {
if account.OrderDirection == "true" {
sort = "-"
}
sort += account.OrderBy
}
page := 1
for {
var resp ChildrenResp
_, err := driver.Request(url, base.Get, nil, map[string]string{
"page": strconv.Itoa(page),
"size": "50",
"sort": sort,
}, nil, nil, &resp, account)
if err != nil {
return nil, err
}
if len(resp.Data.Assets) == 0 {
break
}
page++
for _, file := range resp.Data.Assets {
files = append(files, *driver.formatFile(&file))
}
}
return files, nil
}
type UploadResp struct {
Status string `json:"status"`
Data struct {
Credentials struct {
TmpSecretID string `json:"TmpSecretId"`
TmpSecretKey string `json:"TmpSecretKey"`
Token string `json:"Token"`
ExpiredTime int `json:"ExpiredTime"`
Expiration time.Time `json:"Expiration"`
StartTime int `json:"StartTime"`
} `json:"credentials"`
Object string `json:"object"`
Bucket string `json:"bucket"`
Region string `json:"region"`
URL string `json:"url"`
Size string `json:"size"`
} `json:"data"`
Path string `json:"path"`
TraceID string `json:"trace_id"`
RequestID string `json:"requestId"`
}
func init() {
base.RegisterDriver(&MediaTrack{})
}

View File

@ -1,265 +0,0 @@
package native
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
)
type Native struct{}
func (driver Native) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Native",
OnlyProxy: true,
OnlyLocal: true,
NoNeedSetLink: true,
LocalSort: true,
}
}
func (driver Native) Items() []base.Item {
return []base.Item{
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: true,
},
}
}
func (driver Native) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
log.Debugf("save a account: [%s]", account.Name)
if !utils.Exists(account.RootFolder) {
account.Status = fmt.Sprintf("[%s] not exist", account.RootFolder)
_ = model.SaveAccount(account)
return fmt.Errorf("[%s] not exist", account.RootFolder)
}
account.Status = "work"
account.Proxy = true
err := model.SaveAccount(account)
if err != nil {
return err
}
return nil
}
func (driver Native) File(path string, account *model.Account) (*model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound
}
f, err := os.Stat(fullPath)
if err != nil {
return nil, err
}
time := f.ModTime()
file := &model.File{
Name: f.Name(),
Size: f.Size(),
UpdatedAt: &time,
Driver: driver.Config().Name,
}
if f.IsDir() {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
}
return file, nil
}
func (driver Native) Files(path string, account *model.Account) ([]model.File, error) {
if utils.IsContain(strings.Split(path, "/"), "..") {
return nil, base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
if !utils.Exists(fullPath) {
return nil, base.ErrPathNotFound
}
files := make([]model.File, 0)
rawFiles, err := ioutil.ReadDir(fullPath)
if err != nil {
return nil, err
}
for _, f := range rawFiles {
if strings.HasPrefix(f.Name(), ".") {
continue
}
time := f.ModTime()
file := model.File{
Name: f.Name(),
Size: f.Size(),
Type: 0,
UpdatedAt: &time,
Driver: driver.Config().Name,
}
if f.IsDir() {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
}
files = append(files, file)
}
return files, nil
}
func (driver Native) Link(args base.Args, account *model.Account) (*base.Link, error) {
_, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
fullPath := filepath.Join(account.RootFolder, args.Path)
s, err := os.Stat(fullPath)
if err != nil {
return nil, err
}
if s.IsDir() {
return nil, base.ErrNotFile
}
link := base.Link{
FilePath: fullPath,
}
return &link, nil
}
func (driver Native) Path(path string, account *model.Account) (*model.File, []model.File, error) {
log.Debugf("native path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
//file.Url, _ = driver.Link(path, account)
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
//model.SortFiles(files, account)
return nil, files, nil
}
//func (driver Native) Proxy(r *http.Request, account *model.Account) {
// // unnecessary
//}
func (driver Native) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Native) MakeDir(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
err := os.MkdirAll(fullPath, 0700)
return err
}
func (driver Native) Move(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst)
return os.Rename(fullSrc, fullDst)
}
func (driver Native) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver Native) Copy(src string, dst string, account *model.Account) error {
if utils.IsContain(strings.Split(src+"/"+dst, "/"), "..") {
return base.ErrRelativePath
}
fullSrc := filepath.Join(account.RootFolder, src)
fullDst := filepath.Join(account.RootFolder, dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstFile, err := driver.File(dst, account)
if err == nil {
if !dstFile.IsDir() {
return base.ErrNotSupport
}
}
if srcFile.IsDir() {
return driver.CopyDir(fullSrc, fullDst)
}
return driver.CopyFile(fullSrc, fullDst)
}
func (driver Native) Delete(path string, account *model.Account) error {
if utils.IsContain(strings.Split(path, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, path)
file, err := driver.File(path, account)
if err != nil {
return err
}
if file.IsDir() {
return os.RemoveAll(fullPath)
}
return os.Remove(fullPath)
}
func (driver Native) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
if utils.IsContain(strings.Split(file.ParentPath, "/"), "..") {
return base.ErrRelativePath
}
fullPath := filepath.Join(account.RootFolder, file.ParentPath, file.Name)
_, err := driver.File(filepath.Join(file.ParentPath, file.Name), account)
if err == nil {
// TODO overwrite?
}
basePath := filepath.Dir(fullPath)
if !utils.Exists(basePath) {
err := os.MkdirAll(basePath, 0744)
if err != nil {
return err
}
}
out, err := os.Create(fullPath)
if err != nil {
return err
}
defer func() {
_ = out.Close()
}()
//var buf bytes.Buffer
//reader := io.TeeReader(file, &buf)
//h := md5.New()
//_, err = io.Copy(h, reader)
//if err != nil {
// return err
//}
//hash := hex.EncodeToString(h.Sum(nil))
//log.Debugln("md5:", hash)
//_, err = io.Copy(out, &buf)
_, err = io.Copy(out, file)
return err
}
var _ base.Driver = (*Native)(nil)

View File

@ -1,278 +0,0 @@
package onedrive
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Onedrive struct{}
func (driver Onedrive) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Onedrive",
NoNeedSetLink: true,
}
}
func (driver Onedrive) Items() []base.Item {
return []base.Item{
{
Name: "zone",
Label: "zone",
Type: base.TypeSelect,
Required: true,
Values: "global,cn,us,de",
Description: "",
},
{
Name: "internal_type",
Label: "onedrive type",
Type: base.TypeSelect,
Required: true,
Values: "onedrive,sharepoint",
},
{
Name: "client_id",
Label: "client id",
Type: base.TypeString,
Required: true,
},
{
Name: "client_secret",
Label: "client secret",
Type: base.TypeString,
Required: true,
},
{
Name: "redirect_uri",
Label: "redirect uri",
Type: base.TypeString,
Required: true,
},
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "site_id",
Label: "site id",
Type: base.TypeString,
Required: false,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "name,size,lastModifiedDateTime",
Required: false,
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Required: false,
},
}
}
func (driver Onedrive) Save(account *model.Account, old *model.Account) error {
//if old != nil {
// conf.Cron.Remove(cron.EntryID(old.CronId))
//}
if account == nil {
return nil
}
_, ok := onedriveHostMap[account.Zone]
if !ok {
return fmt.Errorf("no [%s] zone", account.Zone)
}
account.RootFolder = utils.ParsePath(account.RootFolder)
err := driver.RefreshToken(account)
_ = model.SaveAccount(account)
if err != nil {
return err
}
//cronId, err := conf.Cron.AddFunc("@every 1h", func() {
// name := account.Name
// log.Debugf("onedrive account name: %s", name)
// newAccount, ok := model.GetAccount(name)
// log.Debugf("onedrive account: %+v", newAccount)
// if !ok {
// return
// }
// err = driver.RefreshToken(&newAccount)
// _ = model.SaveAccount(&newAccount)
//})
//if err != nil {
// return err
//}
//account.CronId = int(cronId)
return nil
}
func (driver Onedrive) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Onedrive) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
rawFiles, err := driver.GetFiles(account, path)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Onedrive) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.GetFile(account, args.Path)
if err != nil {
return nil, err
}
if file.File == nil {
return nil, base.ErrNotFile
}
link := base.Link{
Url: file.Url,
}
return &link, nil
}
func (driver Onedrive) Path(path string, account *model.Account) (*model.File, []model.File, error) {
log.Debugf("onedrive path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Onedrive) Proxy(r *http.Request, account *model.Account) {
// r.Header.Del("Origin")
//}
func (driver Onedrive) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Onedrive) MakeDir(path string, account *model.Account) error {
url := driver.GetMetaUrl(account, false, utils.Dir(path)) + "/children"
data := base.Json{
"name": utils.Base(path),
"folder": base.Json{},
"@microsoft.graph.conflictBehavior": "rename",
}
_, err := driver.Request(url, base.Post, nil, nil, nil, &data, nil, account)
return err
}
func (driver Onedrive) Move(src string, dst string, account *model.Account) error {
dstParentFile, err := driver.GetFile(account, utils.Dir(dst))
if err != nil {
return err
}
data := base.Json{
"parentReference": base.Json{
"id": dstParentFile.Id,
},
"name": utils.Base(dst),
}
url := driver.GetMetaUrl(account, false, src)
_, err = driver.Request(url, base.Patch, nil, nil, nil, &data, nil, account)
return err
}
func (driver Onedrive) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver Onedrive) Copy(src string, dst string, account *model.Account) error {
dstParentFile, err := driver.GetFile(account, utils.Dir(dst))
if err != nil {
return err
}
data := base.Json{
"parentReference": base.Json{
"driveId": dstParentFile.ParentReference.DriveId,
"id": dstParentFile.Id,
},
"name": utils.Base(dst),
}
url := driver.GetMetaUrl(account, false, src) + "/copy"
_, err = driver.Request(url, base.Post, nil, nil, nil, &data, nil, account)
return err
}
func (driver Onedrive) Delete(path string, account *model.Account) error {
url := driver.GetMetaUrl(account, false, path)
_, err := driver.Request(url, base.Delete, nil, nil, nil, nil, nil, account)
return err
}
func (driver Onedrive) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
var err error
if file.GetSize() <= 4*1024*1024 {
err = driver.UploadSmall(file, account)
} else {
err = driver.UploadBig(file, account)
}
return err
}
var _ base.Driver = (*Onedrive)(nil)

View File

@ -1,318 +0,0 @@
package onedrive
import (
"bytes"
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"net/http"
"path/filepath"
"strconv"
"time"
)
type Host struct {
Oauth string
Api string
}
var onedriveHostMap = map[string]Host{
"global": {
Oauth: "https://login.microsoftonline.com",
Api: "https://graph.microsoft.com",
},
"cn": {
Oauth: "https://login.chinacloudapi.cn",
Api: "https://microsoftgraph.chinacloudapi.cn",
},
"us": {
Oauth: "https://login.microsoftonline.us",
Api: "https://graph.microsoft.us",
},
"de": {
Oauth: "https://login.microsoftonline.de",
Api: "https://graph.microsoft.de",
},
}
func (driver Onedrive) GetMetaUrl(account *model.Account, auth bool, path string) string {
path = filepath.Join(account.RootFolder, path)
//log.Debugf(path)
host, _ := onedriveHostMap[account.Zone]
if auth {
return host.Oauth
}
switch account.InternalType {
case "onedrive":
{
if path == "/" || path == "\\" {
return fmt.Sprintf("%s/v1.0/me/drive/root", host.Api)
} else {
return fmt.Sprintf("%s/v1.0/me/drive/root:%s:", host.Api, path)
}
}
case "sharepoint":
{
if path == "/" || path == "\\" {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root", host.Api, account.SiteId)
} else {
return fmt.Sprintf("%s/v1.0/sites/%s/drive/root:%s:", host.Api, account.SiteId, path)
}
}
default:
return ""
}
}
type OneTokenErr struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
func (driver Onedrive) RefreshToken(account *model.Account) error {
err := driver.refreshToken(account)
if err != nil && err == base.ErrEmptyToken {
return driver.refreshToken(account)
}
return err
}
func (driver Onedrive) refreshToken(account *model.Account) error {
url := driver.GetMetaUrl(account, true, "") + "/common/oauth2/v2.0/token"
var resp base.TokenResp
var e OneTokenErr
_, err := base.RestyClient.R().SetResult(&resp).SetError(&e).SetFormData(map[string]string{
"grant_type": "refresh_token",
"client_id": account.ClientId,
"client_secret": account.ClientSecret,
"redirect_uri": account.RedirectUri,
"refresh_token": account.RefreshToken,
}).Post(url)
if err != nil {
account.Status = err.Error()
return err
}
if e.Error != "" {
account.Status = e.ErrorDescription
return fmt.Errorf("%s", e.ErrorDescription)
} else {
account.Status = "work"
}
if resp.RefreshToken == "" {
account.Status = base.ErrEmptyToken.Error()
return base.ErrEmptyToken
}
account.RefreshToken, account.AccessToken = resp.RefreshToken, resp.AccessToken
return nil
}
type OneFile struct {
Id string `json:"id"`
Name string `json:"name"`
Size int64 `json:"size"`
LastModifiedDateTime *time.Time `json:"lastModifiedDateTime"`
Url string `json:"@microsoft.graph.downloadUrl"`
File *struct {
MimeType string `json:"mimeType"`
} `json:"file"`
Thumbnails []struct {
Medium struct {
Url string `json:"url"`
} `json:"medium"`
} `json:"thumbnails"`
ParentReference struct {
DriveId string `json:"driveId"`
} `json:"parentReference"`
}
type OneFiles struct {
Value []OneFile `json:"value"`
NextLink string `json:"@odata.nextLink"`
}
type OneRespErr struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (driver Onedrive) FormatFile(file *OneFile) *model.File {
f := &model.File{
Name: file.Name,
Size: file.Size,
UpdatedAt: file.LastModifiedDateTime,
Driver: driver.Config().Name,
Url: file.Url,
Id: file.Id,
}
if len(file.Thumbnails) > 0 {
f.Thumbnail = file.Thumbnails[0].Medium.Url
}
if file.File == nil {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
func (driver Onedrive) GetFiles(account *model.Account, path string) ([]OneFile, error) {
var res []OneFile
nextLink := driver.GetMetaUrl(account, false, path) + "/children?$expand=thumbnails"
if account.OrderBy != "" {
nextLink += fmt.Sprintf("&orderby=%s", account.OrderBy)
if account.OrderDirection != "" {
nextLink += fmt.Sprintf("%%20%s", account.OrderDirection)
}
}
for nextLink != "" {
var files OneFiles
_, err := driver.Request(nextLink, base.Get, nil, nil, nil, nil, &files, account)
//var e OneRespErr
//_, err := oneClient.R().SetResult(&files).SetError(&e).
// SetHeader("Authorization", "Bearer "+account.AccessToken).
// Get(nextLink)
if err != nil {
return nil, err
}
//if e.Error.Code != "" {
// return nil, fmt.Errorf("%s", e.Error.Message)
//}
res = append(res, files.Value...)
nextLink = files.NextLink
}
return res, nil
}
func (driver Onedrive) GetFile(account *model.Account, path string) (*OneFile, error) {
var file OneFile
//var e OneRespErr
u := driver.GetMetaUrl(account, false, path)
_, err := driver.Request(u, base.Get, nil, nil, nil, nil, &file, account)
//_, err := oneClient.R().SetResult(&file).SetError(&e).
// SetHeader("Authorization", "Bearer "+account.AccessToken).
// Get(driver.GetMetaUrl(account, false, path))
if err != nil {
return nil, err
}
//if e.Error.Code != "" {
// return nil, fmt.Errorf("%s", e.Error.Message)
//}
return &file, nil
}
func (driver Onedrive) Request(url string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
rawUrl := url
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var res *resty.Response
var err error
var e OneRespErr
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
case base.Patch:
res, err = req.Patch(url)
case base.Delete:
res, err = req.Delete(url)
case base.Put:
res, err = req.Put(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
//log.Debug(res.String())
if e.Error.Code != "" {
if e.Error.Code == "InvalidAuthenticationToken" {
err = driver.RefreshToken(account)
if err != nil {
_ = model.SaveAccount(account)
return nil, err
}
return driver.Request(rawUrl, method, headers, query, form, data, resp, account)
}
return nil, errors.New(e.Error.Message)
}
return res.Body(), nil
}
func (driver Onedrive) UploadSmall(file *model.FileStream, account *model.Account) error {
url := driver.GetMetaUrl(account, false, utils.Join(file.ParentPath, file.Name)) + "/content"
data, err := ioutil.ReadAll(file)
if err != nil {
return err
}
_, err = driver.Request(url, base.Put, nil, nil, nil, data, nil, account)
return err
}
func (driver Onedrive) UploadBig(file *model.FileStream, account *model.Account) error {
url := driver.GetMetaUrl(account, false, utils.Join(file.ParentPath, file.Name)) + "/createUploadSession"
res, err := driver.Request(url, base.Post, nil, nil, nil, nil, nil, account)
if err != nil {
return err
}
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
var finish uint64 = 0
const DEFAULT = 4 * 1024 * 1024
for finish < file.GetSize() {
log.Debugf("upload: %d", finish)
var byteSize uint64 = DEFAULT
left := file.GetSize() - finish
if left < DEFAULT {
byteSize = left
}
byteData := make([]byte, byteSize)
n, err := io.ReadFull(file, byteData)
log.Debug(err, n)
if err != nil {
return err
}
req, err := http.NewRequest("PUT", uploadUrl, bytes.NewBuffer(byteData))
req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, file.Size))
finish += byteSize
res, err := base.HttpClient.Do(req)
if res.StatusCode != 201 && res.StatusCode != 202 {
data, _ := ioutil.ReadAll(res.Body)
return errors.New(string(data))
}
}
return nil
}
func init() {
base.RegisterDriver(&Onedrive{})
}

View File

@ -1,111 +0,0 @@
package operate
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"runtime/debug"
)
func Save(driver base.Driver, account, old *model.Account) error {
return driver.Save(account, old)
}
func Path(driver base.Driver, account *model.Account, path string) (*model.File, []model.File, error) {
return driver.Path(path, account)
}
func Files(driver base.Driver, account *model.Account, path string) ([]model.File, error) {
_, files, err := Path(driver, account, path)
if err != nil {
return nil, err
}
if files == nil {
return nil, base.ErrNotFolder
}
return files, nil
}
func File(driver base.Driver, account *model.Account, path string) (*model.File, error) {
return driver.File(path, account)
}
func MakeDir(driver base.Driver, account *model.Account, path string, clearCache bool) error {
log.Debugf("mkdir: %s", path)
_, err := Files(driver, account, path)
if err != base.ErrPathNotFound {
return nil
}
err = driver.MakeDir(path, account)
if err == nil && clearCache {
_ = base.DeleteCache(utils.Dir(path), account)
}
if err != nil {
log.Errorf("mkdir error: %s", err.Error())
}
return err
}
func Move(driver base.Driver, account *model.Account, src, dst string, clearCache bool) error {
log.Debugf("move %s to %s", src, dst)
rename := false
if utils.Dir(src) == utils.Dir(dst) {
rename = true
}
var err error
if rename {
err = driver.Rename(src, dst, account)
} else {
err = driver.Move(src, dst, account)
}
if err == nil && clearCache {
_ = base.DeleteCache(utils.Dir(src), account)
if !rename {
_ = base.DeleteCache(utils.Dir(dst), account)
}
}
if err != nil {
log.Errorf("move error: %s", err.Error())
}
return err
}
func Copy(driver base.Driver, account *model.Account, src, dst string, clearCache bool) error {
log.Debugf("copy %s to %s", src, dst)
err := driver.Copy(src, dst, account)
if err == nil && clearCache {
_ = base.DeleteCache(utils.Dir(dst), account)
}
if err != nil {
log.Errorf("copy error: %s", err.Error())
}
return err
}
func Delete(driver base.Driver, account *model.Account, path string, clearCache bool) error {
log.Debugf("delete %s", path)
err := driver.Delete(path, account)
if err == nil && clearCache {
_ = base.DeleteCache(utils.Dir(path), account)
}
if err != nil {
log.Errorf("delete error: %s", err.Error())
}
return err
}
func Upload(driver base.Driver, account *model.Account, file *model.FileStream, clearCache bool) error {
defer func() {
_ = file.Close()
}()
err := driver.Upload(file, account)
if err == nil && clearCache {
_ = base.DeleteCache(file.ParentPath, account)
}
if err != nil {
log.Errorf("upload error: %s", err.Error())
}
debug.FreeOSMemory()
return err
}

View File

@ -1,330 +0,0 @@
package pikpak
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
"strings"
)
type PikPak struct{}
func (driver PikPak) Config() base.DriverConfig {
return base.DriverConfig{
Name: "PikPak",
ApiProxy: true,
LocalSort: true,
}
}
func (driver PikPak) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder id",
Type: base.TypeString,
Required: false,
},
}
}
func (driver PikPak) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
err := driver.Login(account)
return err
}
func (driver PikPak) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver PikPak) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err := driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
files = make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver PikPak) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
var resp File
_, err = driver.Request(fmt.Sprintf("https://api-drive.mypikpak.com/drive/v1/files/%s?_magic=2021&thumbnail_size=SIZE_LARGE", file.Id),
base.Get, nil, nil, &resp, account)
if err != nil {
return nil, err
}
link := base.Link{
Url: resp.WebContentLink,
}
if len(resp.Medias) > 0 && resp.Medias[0].Link.Url != "" {
log.Debugln("use media link")
link.Url = resp.Medias[0].Link.Url
}
return &link, nil
}
func (driver PikPak) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("pikpak path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver PikPak) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver PikPak) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver PikPak) MakeDir(path string, account *model.Account) error {
path = utils.ParsePath(path)
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &base.Json{
"kind": "drive#folder",
"parent_id": parentFile.Id,
"name": name,
}, nil, account)
return err
}
func (driver PikPak) Move(src string, dst string, account *model.Account) error {
dstDir, _ := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(dstDir, account)
if err != nil {
return err
}
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchMove", base.Post, nil, &base.Json{
"ids": []string{srcFile.Id},
"to": base.Json{
"parent_id": dstDirFile.Id,
},
}, nil, account)
return err
}
func (driver PikPak) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files/"+srcFile.Id, base.Patch, nil, &base.Json{
"name": dstName,
}, nil, account)
return err
}
func (driver PikPak) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchCopy", base.Post, nil, &base.Json{
"ids": []string{srcFile.Id},
"to": base.Json{
"parent_id": dstDirFile.Id,
},
}, nil, account)
return err
}
func (driver PikPak) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
if err != nil {
return err
}
_, err = driver.Request("https://api-drive.mypikpak.com/drive/v1/files:batchTrash", base.Post, nil, &base.Json{
"ids": []string{file.Id},
}, nil, account)
return err
}
func (driver PikPak) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
data := base.Json{
"kind": "drive#file",
"name": file.GetFileName(),
"size": file.GetSize(),
"hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
"upload_type": "UPLOAD_TYPE_RESUMABLE",
"objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
"parent_id": parentFile.Id,
}
res, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &data, nil, account)
if err != nil {
return err
}
params := jsoniter.Get(res, "resumable").Get("params")
endpoint := params.Get("endpoint").ToString()
endpointS := strings.Split(endpoint, ".")
endpoint = strings.Join(endpointS[1:], ".")
accessKeyId := params.Get("access_key_id").ToString()
accessKeySecret := params.Get("access_key_secret").ToString()
securityToken := params.Get("security_token").ToString()
key := params.Get("key").ToString()
bucket := params.Get("bucket").ToString()
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(accessKeyId, accessKeySecret, securityToken),
Region: aws.String("pikpak"),
Endpoint: &endpoint,
}
s, err := session.NewSession(cfg)
if err != nil {
return err
}
uploader := s3manager.NewUploader(s)
input := &s3manager.UploadInput{
Bucket: &bucket,
Key: &key,
Body: file,
}
_, err = uploader.Upload(input)
return err
}
// use aliyun-oss-sdk
//func (driver PikPak) Upload(file *model.FileStream, account *model.Account) error {
// if file == nil {
// return base.ErrEmptyFile
// }
// parentFile, err := driver.File(file.ParentPath, account)
// if err != nil {
// return err
// }
// data := base.Json{
// "kind": "drive#file",
// "name": file.GetFileName(),
// "size": file.GetSize(),
// "hash": "1CF254FBC456E1B012CD45C546636AA62CF8350E",
// "upload_type": "UPLOAD_TYPE_RESUMABLE",
// "objProvider": base.Json{"provider": "UPLOAD_TYPE_UNKNOWN"},
// "parent_id": parentFile.Id,
// }
// res, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Post, nil, &data, nil, account)
// if err != nil {
// return err
// }
// params := jsoniter.Get(res, "resumable").Get("params")
// endpoint := params.Get("endpoint").ToString()
// endpointS := strings.Split(endpoint, ".")
// endpoint = strings.Join(endpointS[1:], ".")
// accessKeyId := params.Get("access_key_id").ToString()
// accessKeySecret := params.Get("access_key_secret").ToString()
// securityToken := params.Get("security_token").ToString()
// client, err := oss.New("https://"+endpoint, accessKeyId,
// accessKeySecret, oss.SecurityToken(securityToken))
// if err != nil {
// return err
// }
// bucket, err := client.Bucket(params.Get("bucket").ToString())
// if err != nil {
// return err
// }
// signedURL, err := bucket.SignURL(params.Get("key").ToString(), oss.HTTPPut, 60)
// if err != nil {
// return err
// }
// err = bucket.PutObjectWithURL(signedURL, file)
// return err
//}
var _ base.Driver = (*PikPak)(nil)

View File

@ -1,232 +0,0 @@
package pikpak
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
jsoniter "github.com/json-iterator/go"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
"time"
)
type RespErr struct {
ErrorCode int `json:"error_code"`
Error string `json:"error"`
}
func (driver PikPak) Login(account *model.Account) error {
url := "https://user.mypikpak.com/v1/auth/signin"
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
var e RespErr
res, err := base.RestyClient.R().SetError(&e).SetBody(base.Json{
"captcha_token": "",
"client_id": "YNxT9w7GMdWvEOKa",
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
"username": account.Username,
"password": account.Password,
}).Post(url)
if err != nil {
account.Status = err.Error()
_ = model.SaveAccount(account)
return err
}
log.Debug(res.String())
if e.ErrorCode != 0 {
account.Status = e.Error
err = errors.New(e.Error)
} else {
data := res.Body()
account.Status = "work"
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
}
_ = model.SaveAccount(account)
return nil
}
func (driver PikPak) RefreshToken(account *model.Account) error {
url := "https://user.mypikpak.com/v1/auth/token"
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
var e RespErr
res, err := base.RestyClient.R().SetError(&e).
SetHeader("user-agent", "").SetBody(base.Json{
"client_id": "YNxT9w7GMdWvEOKa",
"client_secret": "dbw2OtmVEeuUvIptb1Coyg",
"grant_type": "refresh_token",
"refresh_token": account.RefreshToken,
}).Post(url)
if err != nil {
account.Status = err.Error()
return err
}
if e.ErrorCode != 0 {
if e.ErrorCode == 4126 {
// refresh_token 失效,重新登陆
return driver.Login(account)
}
account.Status = e.Error
_ = model.SaveAccount(account)
return errors.New(e.Error)
}
data := res.Body()
account.Status = "work"
account.RefreshToken = jsoniter.Get(data, "refresh_token").ToString()
account.AccessToken = jsoniter.Get(data, "access_token").ToString()
log.Debugf("%s\n %+v", res.String(), account)
_ = model.SaveAccount(account)
return nil
}
func (driver PikPak) Request(url string, method int, query map[string]string, data *base.Json, resp interface{}, account *model.Account) ([]byte, error) {
rawUrl := url
if account.APIProxyUrl != "" {
url = fmt.Sprintf("%s/%s", account.APIProxyUrl, url)
}
req := base.RestyClient.R()
req.SetHeader("Authorization", "Bearer "+account.AccessToken)
if query != nil {
req.SetQueryParams(query)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e RespErr
req.SetError(&e)
var res *resty.Response
var err error
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
case base.Patch:
res, err = req.Patch(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
log.Debug(res.String())
if e.ErrorCode != 0 {
if e.ErrorCode == 16 {
// login / refresh token
err = driver.RefreshToken(account)
if err != nil {
return nil, err
}
return driver.Request(rawUrl, method, query, data, resp, account)
} else {
return nil, errors.New(e.Error)
}
}
return res.Body(), nil
}
type File struct {
Id string `json:"id"`
Kind string `json:"kind"`
Name string `json:"name"`
ModifiedTime *time.Time `json:"modified_time"`
Size string `json:"size"`
ThumbnailLink string `json:"thumbnail_link"`
WebContentLink string `json:"web_content_link"`
Medias []Media `json:"medias"`
}
type Media struct {
MediaId string `json:"media_id"`
MediaName string `json:"media_name"`
Video struct {
Height int `json:"height"`
Width int `json:"width"`
Duration int `json:"duration"`
BitRate int `json:"bit_rate"`
FrameRate int `json:"frame_rate"`
VideoCodec string `json:"video_codec"`
AudioCodec string `json:"audio_codec"`
VideoType string `json:"video_type"`
} `json:"video"`
Link struct {
Url string `json:"url"`
Token string `json:"token"`
Expire time.Time `json:"expire"`
} `json:"link"`
NeedMoreQuota bool `json:"need_more_quota"`
VipTypes []interface{} `json:"vip_types"`
RedirectLink string `json:"redirect_link"`
IconLink string `json:"icon_link"`
IsDefault bool `json:"is_default"`
Priority int `json:"priority"`
IsOrigin bool `json:"is_origin"`
ResolutionName string `json:"resolution_name"`
IsVisible bool `json:"is_visible"`
Category string `json:"category"`
}
func (driver PikPak) FormatFile(file *File) *model.File {
size, _ := strconv.ParseInt(file.Size, 10, 64)
f := &model.File{
Id: file.Id,
Name: file.Name,
Size: size,
Driver: driver.Config().Name,
UpdatedAt: file.ModifiedTime,
Thumbnail: file.ThumbnailLink,
}
if file.Kind == "drive#folder" {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(filepath.Ext(file.Name))
}
return f
}
type Files struct {
Files []File `json:"files"`
NextPageToken string `json:"next_page_token"`
}
func (driver PikPak) GetFiles(id string, account *model.Account) ([]File, error) {
res := make([]File, 0)
pageToken := "first"
for pageToken != "" {
if pageToken == "first" {
pageToken = ""
}
query := map[string]string{
"parent_id": id,
"thumbnail_size": "SIZE_LARGE",
"with_audit": "true",
"limit": "100",
"filters": `{"phase":{"eq":"PHASE_TYPE_COMPLETE"},"trashed":{"eq":false}}`,
"page_token": pageToken,
}
var resp Files
_, err := driver.Request("https://api-drive.mypikpak.com/drive/v1/files", base.Get, query, nil, &resp, account)
if err != nil {
return nil, err
}
log.Debugf("%+v", resp)
pageToken = resp.NextPageToken
res = append(res, resp.Files...)
}
return res, nil
}
func init() {
base.RegisterDriver(&PikPak{})
}

View File

@ -1,326 +0,0 @@
package quark
import (
"crypto/md5"
"crypto/sha1"
"encoding/hex"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"io"
"io/ioutil"
"os"
"path/filepath"
)
type Quark struct{}
func (driver Quark) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Quark",
OnlyProxy: true,
}
}
func (driver Quark) Items() []base.Item {
return []base.Item{
{
Name: "access_token",
Label: "Cookie",
Type: base.TypeString,
Required: true,
Description: "Unknown expiration time",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
Default: "0",
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "file_type,file_name,updated_at",
Required: true,
Default: "file_name",
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "asc,desc",
Required: true,
Default: "asc",
},
}
}
func (driver Quark) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
_, err := driver.Get("/config", nil, nil, account)
if err == nil {
account.Status = "work"
} else {
account.Status = err.Error()
}
_ = model.SaveAccount(account)
return err
}
func (driver Quark) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Quark) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
files, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver Quark) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
data := base.Json{
"fids": []string{file.Id},
}
var resp DownResp
_, err = driver.Post("/file/download", data, &resp, account)
if err != nil {
return nil, err
}
return &base.Link{
Url: resp.Data[0].DownloadUrl,
Headers: []base.Header{
{Name: "Cookie", Value: account.AccessToken},
},
}, nil
}
func (driver Quark) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("quark path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Quark) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Quark) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := base.Json{
"dir_init_lock": false,
"dir_path": "",
"file_name": utils.Base(path),
"pdir_fid": parentFile.Id,
}
_, err = driver.Post("/file", data, nil, account)
return err
}
func (driver Quark) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
data := base.Json{
"action_type": 1,
"exclude_fids": []string{},
"filelist": []string{srcFile.Id},
"to_pdir_fid": dstParentFile.Id,
}
_, err = driver.Post("/file/move", data, nil, account)
return err
}
func (driver Quark) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
data := base.Json{
"fid": srcFile.Id,
"file_name": utils.Base(dst),
}
_, err = driver.Post("/file/rename", data, nil, account)
return err
}
func (driver Quark) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver Quark) Delete(path string, account *model.Account) error {
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
data := base.Json{
"action_type": 1,
"exclude_fids": []string{},
"filelist": []string{srcFile.Id},
}
_, err = driver.Post("/file/delete", data, nil, account)
return err
}
func (driver Quark) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer func() {
_ = tempFile.Close()
_ = os.Remove(tempFile.Name())
}()
_, err = io.Copy(tempFile, file)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
m := md5.New()
_, err = io.Copy(m, tempFile)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
md5Str := hex.EncodeToString(m.Sum(nil))
s := sha1.New()
_, err = io.Copy(s, tempFile)
if err != nil {
return err
}
_, err = tempFile.Seek(0, io.SeekStart)
if err != nil {
return err
}
sha1Str := hex.EncodeToString(s.Sum(nil))
// pre
pre, err := driver.UpPre(file, parentFile.Id, account)
if err != nil {
return err
}
log.Debugln("hash: ", md5Str, sha1Str)
// hash
finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
if err != nil {
return err
}
if finish {
return nil
}
// part up
partSize := pre.Metadata.PartSize
var bytes []byte
md5s := make([]string, 0)
defaultBytes := make([]byte, partSize)
left := int64(file.GetSize())
partNumber := 1
for left > 0 {
if left > int64(partSize) {
bytes = defaultBytes
} else {
bytes = make([]byte, left)
}
_, err := io.ReadFull(tempFile, bytes)
if err != nil {
return err
}
left -= int64(partSize)
log.Debugf("left: %d", left)
m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account)
//m, err := driver.UpPart(pre, file.GetMIMEType(), partNumber, bytes, account, md5Str, sha1Str)
if err != nil {
return err
}
if m == "finish" {
return nil
}
md5s = append(md5s, m)
partNumber++
}
err = driver.UpCommit(pre, md5s, account)
if err != nil {
return err
}
return driver.UpFinish(pre, account)
}
var _ base.Driver = (*Quark)(nil)

View File

@ -1,272 +0,0 @@
package quark
import (
"crypto/md5"
"encoding/base64"
"errors"
"fmt"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/Xhofe/alist/utils/cookie"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"net/http"
"strconv"
"strings"
"time"
)
func (driver Quark) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
u := "https://drive.quark.cn/1/clouddrive" + pathname
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"Cookie": account.AccessToken,
"Accept": "application/json, text/plain, */*",
"Referer": "https://pan.quark.cn/",
})
req.SetQueryParam("pr", "ucpro")
req.SetQueryParam("fr", "pc")
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e Resp
var err error
var res *resty.Response
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Delete:
res, err = req.Delete(u)
case base.Patch:
res, err = req.Patch(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
__puus := cookie.GetCookie(res.Cookies(), "__puus")
if __puus != nil {
account.AccessToken = cookie.SetStr(account.AccessToken, "__puus", __puus.Value)
_ = model.SaveAccount(account)
}
//log.Debugf("%s response: %s", pathname, res.String())
if e.Status >= 400 || e.Code != 0 {
return nil, errors.New(e.Message)
}
return res.Body(), nil
}
func (driver Quark) Get(pathname string, query map[string]string, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Get, nil, query, nil, nil, resp, account)
}
func (driver Quark) Post(pathname string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
return driver.Request(pathname, base.Post, nil, nil, nil, data, resp, account)
}
func (driver Quark) GetFiles(parent string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0)
page := 1
size := 100
query := map[string]string{
"pdir_fid": parent,
"_size": strconv.Itoa(size),
"_fetch_total": "1",
"_sort": "file_type:asc," + account.OrderBy + ":" + account.OrderDirection,
}
for {
query["_page"] = strconv.Itoa(page)
var resp SortResp
_, err := driver.Get("/file/sort", query, &resp, account)
if err != nil {
return nil, err
}
for _, f := range resp.Data.List {
files = append(files, *driver.formatFile(&f))
}
if page*size >= resp.Metadata.Count {
break
}
page++
}
return files, nil
}
func (driver Quark) UpPre(file *model.FileStream, parentId string, account *model.Account) (UpPreResp, error) {
now := time.Now()
data := base.Json{
"ccp_hash_update": true,
"dir_name": "",
"file_name": file.Name,
"format_type": file.MIMEType,
"l_created_at": now.UnixMilli(),
"l_updated_at": now.UnixMilli(),
"pdir_fid": parentId,
"size": file.Size,
}
log.Debugf("uppre data: %+v", data)
var resp UpPreResp
_, err := driver.Post("/file/upload/pre", data, &resp, account)
return resp, err
}
func (driver Quark) UpHash(md5, sha1, taskId string, account *model.Account) (bool, error) {
data := base.Json{
"md5": md5,
"sha1": sha1,
"task_id": taskId,
}
log.Debugf("hash: %+v", data)
var resp HashResp
_, err := driver.Post("/file/update/hash", data, &resp, account)
return resp.Data.Finish, err
}
func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account) (string, error) {
//func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
timeStr := time.Now().UTC().Format(http.TimeFormat)
data := base.Json{
"auth_info": pre.Data.AuthInfo,
"auth_meta": fmt.Sprintf(`PUT
%s
%s
x-oss-date:%s
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
/%s/%s?partNumber=%d&uploadId=%s`,
mineType, timeStr, timeStr, pre.Data.Bucket, pre.Data.ObjKey, partNumber, pre.Data.UploadId),
"task_id": pre.Data.TaskId,
}
var resp UpAuthResp
_, err := driver.Post("/file/upload/auth", data, &resp, account)
if err != nil {
return "", err
}
//if partNumber == 1 {
// finish, err := driver.UpHash(md5Str, sha1Str, pre.Data.TaskId, account)
// if err != nil {
// return "", err
// }
// if finish {
// return "finish", nil
// }
//}
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
res, err := base.RestyClient.R().
SetHeaders(map[string]string{
"Authorization": resp.Data.AuthKey,
"Content-Type": mineType,
"Referer": "https://pan.quark.cn/",
"x-oss-date": timeStr,
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
}).
SetQueryParams(map[string]string{
"partNumber": strconv.Itoa(partNumber),
"uploadId": pre.Data.UploadId,
}).SetBody(bytes).Put(u)
if res.StatusCode() != 200 {
return "", fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
}
return res.Header().Get("ETag"), nil
}
func (driver Quark) UpCommit(pre UpPreResp, md5s []string, account *model.Account) error {
timeStr := time.Now().UTC().Format(http.TimeFormat)
log.Debugf("md5s: %+v", md5s)
bodyBuilder := strings.Builder{}
bodyBuilder.WriteString(`<?xml version="1.0" encoding="UTF-8"?>
<CompleteMultipartUpload>
`)
for i, m := range md5s {
bodyBuilder.WriteString(fmt.Sprintf(`<Part>
<PartNumber>%d</PartNumber>
<ETag>%s</ETag>
</Part>
`, i+1, m))
}
bodyBuilder.WriteString("</CompleteMultipartUpload>")
body := bodyBuilder.String()
m := md5.New()
m.Write([]byte(body))
contentMd5 := base64.StdEncoding.EncodeToString(m.Sum(nil))
callbackBytes, err := utils.Json.Marshal(pre.Data.Callback)
if err != nil {
return err
}
callbackBase64 := base64.StdEncoding.EncodeToString(callbackBytes)
data := base.Json{
"auth_info": pre.Data.AuthInfo,
"auth_meta": fmt.Sprintf(`POST
%s
application/xml
%s
x-oss-callback:%s
x-oss-date:%s
x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
/%s/%s?uploadId=%s`,
contentMd5, timeStr, callbackBase64, timeStr,
pre.Data.Bucket, pre.Data.ObjKey, pre.Data.UploadId),
"task_id": pre.Data.TaskId,
}
log.Debugf("xml: %s", body)
log.Debugf("auth data: %+v", data)
var resp UpAuthResp
_, err = driver.Post("/file/upload/auth", data, &resp, account)
if err != nil {
return err
}
u := fmt.Sprintf("https://%s.%s/%s", pre.Data.Bucket, pre.Data.UploadUrl[7:], pre.Data.ObjKey)
res, err := base.RestyClient.R().
SetHeaders(map[string]string{
"Authorization": resp.Data.AuthKey,
"Content-MD5": contentMd5,
"Content-Type": "application/xml",
"Referer": "https://pan.quark.cn/",
"x-oss-callback": callbackBase64,
"x-oss-date": timeStr,
"x-oss-user-agent": "aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit",
}).
SetQueryParams(map[string]string{
"uploadId": pre.Data.UploadId,
}).SetBody(body).Post(u)
if res.StatusCode() != 200 {
return fmt.Errorf("up status: %d, error: %s", res.StatusCode(), res.String())
}
return nil
}
func (driver Quark) UpFinish(pre UpPreResp, account *model.Account) error {
data := base.Json{
"obj_key": pre.Data.ObjKey,
"task_id": pre.Data.TaskId,
}
_, err := driver.Post("/file/upload/finish", data, nil, account)
if err != nil {
return err
}
time.Sleep(time.Second)
return nil
}
func init() {
base.RegisterDriver(&Quark{})
}

View File

@ -1,134 +0,0 @@
package quark
type Resp struct {
Status int `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
//ReqId string `json:"req_id"`
//Timestamp int `json:"timestamp"`
}
type File struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
//PdirFid string `json:"pdir_fid"`
//Category int `json:"category"`
//FileType int `json:"file_type"`
Size int64 `json:"size"`
//FormatType string `json:"format_type"`
//Status int `json:"status"`
//Tags string `json:"tags,omitempty"`
//LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
//NameSpace int `json:"name_space"`
//IncludeItems int `json:"include_items,omitempty"`
//RiskType int `json:"risk_type"`
//BackupSign int `json:"backup_sign"`
//Duration int `json:"duration"`
//FileSource string `json:"file_source"`
File bool `json:"file"`
//CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
//PrivateExtra struct {} `json:"_private_extra"`
//ObjCategory string `json:"obj_category,omitempty"`
//Thumbnail string `json:"thumbnail,omitempty"`
}
type SortResp struct {
Resp
Data struct {
List []File `json:"list"`
} `json:"data"`
Metadata struct {
Size int `json:"_size"`
Page int `json:"_page"`
Count int `json:"_count"`
Total int `json:"_total"`
Way string `json:"way"`
} `json:"metadata"`
}
type DownResp struct {
Resp
Data []struct {
Fid string `json:"fid"`
FileName string `json:"file_name"`
PdirFid string `json:"pdir_fid"`
Category int `json:"category"`
FileType int `json:"file_type"`
Size int `json:"size"`
FormatType string `json:"format_type"`
Status int `json:"status"`
Tags string `json:"tags"`
LCreatedAt int64 `json:"l_created_at"`
LUpdatedAt int64 `json:"l_updated_at"`
NameSpace int `json:"name_space"`
Thumbnail string `json:"thumbnail"`
DownloadUrl string `json:"download_url"`
Md5 string `json:"md5"`
RiskType int `json:"risk_type"`
RangeSize int `json:"range_size"`
BackupSign int `json:"backup_sign"`
ObjCategory string `json:"obj_category"`
Duration int `json:"duration"`
FileSource string `json:"file_source"`
File bool `json:"file"`
CreatedAt int64 `json:"created_at"`
UpdatedAt int64 `json:"updated_at"`
PrivateExtra struct {
} `json:"_private_extra"`
} `json:"data"`
Metadata struct {
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
} `json:"metadata"`
}
type UpPreResp struct {
Resp
Data struct {
TaskId string `json:"task_id"`
Finish bool `json:"finish"`
UploadId string `json:"upload_id"`
ObjKey string `json:"obj_key"`
UploadUrl string `json:"upload_url"`
Fid string `json:"fid"`
Bucket string `json:"bucket"`
Callback struct {
CallbackUrl string `json:"callbackUrl"`
CallbackBody string `json:"callbackBody"`
} `json:"callback"`
FormatType string `json:"format_type"`
Size int `json:"size"`
AuthInfo string `json:"auth_info"`
} `json:"data"`
Metadata struct {
PartThread int `json:"part_thread"`
Acc2 string `json:"acc2"`
Acc1 string `json:"acc1"`
PartSize int `json:"part_size"` // 分片大小
} `json:"metadata"`
}
type HashResp struct {
Resp
Data struct {
Finish bool `json:"finish"`
Fid string `json:"fid"`
Thumbnail string `json:"thumbnail"`
FormatType string `json:"format_type"`
} `json:"data"`
Metadata struct {
} `json:"metadata"`
}
type UpAuthResp struct {
Resp
Data struct {
AuthKey string `json:"auth_key"`
Speed int `json:"speed"`
Headers []interface{} `json:"headers"`
} `json:"data"`
Metadata struct {
} `json:"metadata"`
}

View File

@ -1,31 +0,0 @@
package quark
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"path"
"time"
)
func getTime(t int64) *time.Time {
tm := time.UnixMilli(t)
//log.Debugln(tm)
return &tm
}
func (driver Quark) formatFile(f *File) *model.File {
file := model.File{
Id: f.Fid,
Name: f.FileName,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: getTime(f.UpdatedAt),
}
if f.File {
file.Type = utils.GetFileType(path.Ext(f.FileName))
} else {
file.Type = conf.FOLDER
}
return &file
}

View File

@ -1,304 +0,0 @@
package s3
import (
"bytes"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
log "github.com/sirupsen/logrus"
"io/ioutil"
"net/url"
"path/filepath"
"time"
)
type S3 struct {
}
func (driver S3) Config() base.DriverConfig {
return base.DriverConfig{
Name: "S3",
LocalSort: true,
}
}
func (driver S3) Items() []base.Item {
return []base.Item{
{
Name: "bucket",
Label: "Bucket",
Type: base.TypeString,
Required: true,
},
{
Name: "endpoint",
Label: "Endpoint",
Type: base.TypeString,
Required: true,
},
{
Name: "region",
Label: "Region",
Type: base.TypeString,
Required: true,
},
{
Name: "access_key",
Label: "Access Key",
Type: base.TypeString,
Required: true,
},
{
Name: "access_secret",
Label: "Access Secret",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
{
Name: "custom_host",
Label: "Custom Host",
Type: base.TypeString,
},
{
Name: "limit",
Label: "Sign url expire time(hours)",
Type: base.TypeNumber,
Description: "default 4 hours",
},
{
Name: "zone",
Label: "placeholder filename",
Type: base.TypeString,
Description: "default empty string",
Default: defaultPlaceholderName,
},
{
Name: "bool_1",
Label: "S3ForcePathStyle",
Type: base.TypeBool,
},
{
Name: "internal_type",
Label: "ListObject Version",
Type: base.TypeSelect,
Values: "v1,v2",
Default: "v1",
},
}
}
func (driver S3) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if account.Limit == 0 {
account.Limit = 4
}
client, err := driver.NewSession(account)
if err != nil {
account.Status = err.Error()
} else {
sessionsMap[account.Name] = client
account.Status = "work"
}
_ = model.SaveAccount(account)
return err
}
func (driver S3) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver S3) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
if account.InternalType == "v2" {
files, err = driver.ListV2(path, account)
} else {
files, err = driver.List(path, account)
}
if err == nil && len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, err
}
func (driver S3) Link(args base.Args, account *model.Account) (*base.Link, error) {
client, err := driver.GetClient(account, true)
if err != nil {
return nil, err
}
path := driver.GetKey(args.Path, account, false)
disposition := fmt.Sprintf(`attachment;filename="%s"`, url.QueryEscape(utils.Base(path)))
input := &s3.GetObjectInput{
Bucket: &account.Bucket,
Key: &path,
//ResponseContentDisposition: &disposition,
}
if account.CustomHost == "" {
input.ResponseContentDisposition = &disposition
}
req, _ := client.GetObjectRequest(input)
var link string
if account.CustomHost != "" {
err = req.Build()
link = req.HTTPRequest.URL.String()
} else {
link, err = req.Presign(time.Hour * time.Duration(account.Limit))
}
if err != nil {
return nil, err
}
return &base.Link{
Url: link,
}, nil
}
func (driver S3) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("s3 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver S3) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver S3) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver S3) MakeDir(path string, account *model.Account) error {
// not support, generate a placeholder file
_, err := driver.File(path, account)
// exist
if err != base.ErrPathNotFound {
return nil
}
return driver.Upload(&model.FileStream{
File: ioutil.NopCloser(bytes.NewReader([]byte{})),
Size: 0,
ParentPath: path,
Name: getPlaceholderName(account.Zone),
MIMEType: "application/octet-stream",
}, account)
}
func (driver S3) Move(src string, dst string, account *model.Account) error {
err := driver.Copy(src, dst, account)
if err != nil {
return err
}
return driver.Delete(src, account)
}
func (driver S3) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver S3) Copy(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account, false)
if err != nil {
return err
}
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
srcKey := driver.GetKey(src, account, srcFile.IsDir())
dstKey := driver.GetKey(dst, account, srcFile.IsDir())
input := &s3.CopyObjectInput{
Bucket: &account.Bucket,
CopySource: &srcKey,
Key: &dstKey,
}
_, err = client.CopyObject(input)
return err
}
func (driver S3) Delete(path string, account *model.Account) error {
client, err := driver.GetClient(account, false)
if err != nil {
return err
}
file, err := driver.File(path, account)
if err != nil {
return err
}
key := driver.GetKey(path, account, file.IsDir())
input := &s3.DeleteObjectInput{
Bucket: &account.Bucket,
Key: &key,
}
_, err = client.DeleteObject(input)
return err
}
func (driver S3) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
s, ok := sessionsMap[account.Name]
if !ok {
return fmt.Errorf("can't find [%s] session", account.Name)
}
uploader := s3manager.NewUploader(s)
key := driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false)
log.Debugln("key:", key)
input := &s3manager.UploadInput{
Bucket: &account.Bucket,
Key: &key,
Body: file,
}
_, err := uploader.Upload(input)
return err
}
var _ base.Driver = (*S3)(nil)

View File

@ -1,195 +0,0 @@
package s3
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
log "github.com/sirupsen/logrus"
"net/http"
"net/url"
"path"
"strings"
)
var sessionsMap map[string]*session.Session
func (driver S3) NewSession(account *model.Account) (*session.Session, error) {
cfg := &aws.Config{
Credentials: credentials.NewStaticCredentials(account.AccessKey, account.AccessSecret, ""),
Region: &account.Region,
Endpoint: &account.Endpoint,
S3ForcePathStyle: aws.Bool(account.Bool1),
}
return session.NewSession(cfg)
}
func (driver S3) GetClient(account *model.Account, link bool) (*s3.S3, error) {
s, ok := sessionsMap[account.Name]
if !ok {
return nil, fmt.Errorf("can't find [%s] session", account.Name)
}
client := s3.New(s)
if link && account.CustomHost != "" {
cURL, err := url.Parse(account.CustomHost)
if err != nil {
return nil, err
}
client.Handlers.Build.PushBack(func(r *request.Request) {
if r.HTTPRequest.Method != http.MethodGet {
return
}
r.HTTPRequest.URL.Scheme = cURL.Scheme
r.HTTPRequest.URL.Host = cURL.Host
})
}
return client, nil
}
func (driver S3) List(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account, false)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
marker := ""
for {
input := &s3.ListObjectsInput{
Bucket: &account.Bucket,
Marker: &marker,
Prefix: &prefix,
Delimiter: aws.String("/"),
}
listObjectsResult, err := client.ListObjects(input)
if err != nil {
return nil, err
}
for _, object := range listObjectsResult.CommonPrefixes {
name := utils.Base(strings.Trim(*object.Prefix, "/"))
file := model.File{
//Id: *object.Key,
Name: name,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
TimeStr: "-",
Type: conf.FOLDER,
}
files = append(files, file)
}
for _, object := range listObjectsResult.Contents {
name := utils.Base(*object.Key)
if name == getPlaceholderName(account.Zone) {
continue
}
file := model.File{
//Id: *object.Key,
Name: name,
Size: *object.Size,
Driver: driver.Config().Name,
UpdatedAt: object.LastModified,
Type: utils.GetFileType(path.Ext(*object.Key)),
}
files = append(files, file)
}
if listObjectsResult.IsTruncated == nil {
return nil, errors.New("IsTruncated nil")
}
if *listObjectsResult.IsTruncated {
marker = *listObjectsResult.NextMarker
} else {
break
}
}
return files, nil
}
func (driver S3) ListV2(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
//if prefix == "" {
// prefix = "/"
//}
log.Debugf("list: %s", prefix)
client, err := driver.GetClient(account, false)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
var continuationToken, startAfter *string
for {
input := &s3.ListObjectsV2Input{
Bucket: &account.Bucket,
ContinuationToken: continuationToken,
Prefix: &prefix,
Delimiter: aws.String("/"),
StartAfter: startAfter,
}
listObjectsResult, err := client.ListObjectsV2(input)
if err != nil {
return nil, err
}
log.Debugf("resp: %+v", listObjectsResult)
for _, object := range listObjectsResult.CommonPrefixes {
name := utils.Base(strings.Trim(*object.Prefix, "/"))
file := model.File{
//Id: *object.Key,
Name: name,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
TimeStr: "-",
Type: conf.FOLDER,
}
files = append(files, file)
}
for _, object := range listObjectsResult.Contents {
name := utils.Base(*object.Key)
if name == getPlaceholderName(account.Zone) {
continue
}
file := model.File{
//Id: *object.Key,
Name: name,
Size: *object.Size,
Driver: driver.Config().Name,
UpdatedAt: object.LastModified,
Type: utils.GetFileType(path.Ext(*object.Key)),
}
files = append(files, file)
}
if !aws.BoolValue(listObjectsResult.IsTruncated) {
break
}
if listObjectsResult.NextContinuationToken != nil {
continuationToken = listObjectsResult.NextContinuationToken
continue
}
if len(listObjectsResult.Contents) == 0 {
break
}
startAfter = listObjectsResult.Contents[len(listObjectsResult.Contents)-1].Key
}
return files, nil
}
func (driver S3) GetKey(path string, account *model.Account, dir bool) string {
path = utils.Join(account.RootFolder, path)
path = strings.TrimPrefix(path, "/")
if path != "" && dir {
path += "/"
}
return path
}
func init() {
sessionsMap = make(map[string]*session.Session)
base.RegisterDriver(&S3{})
}

View File

@ -1,10 +0,0 @@
package s3
var defaultPlaceholderName = ".placeholder"
func getPlaceholderName(placeholder string) string {
if placeholder == "" {
return defaultPlaceholderName
}
return placeholder
}

View File

@ -1,278 +0,0 @@
package shandian
import (
"errors"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"path/filepath"
"strconv"
)
type Shandian struct{}
func (driver Shandian) Config() base.DriverConfig {
return base.DriverConfig{
Name: "ShandianPan",
NoNeedSetLink: true,
LocalSort: true,
}
}
func (driver Shandian) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: false,
},
}
}
func (driver Shandian) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if account.RootFolder == "" {
account.RootFolder = "0"
}
return driver.Login(account)
}
func (driver Shandian) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Shandian) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
rawFiles, err := driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
files = make([]model.File, 0)
for _, file := range rawFiles {
files = append(files, *driver.FormatFile(&file))
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver Shandian) Link(args base.Args, account *model.Account) (*base.Link, error) {
log.Debugf("shandian link")
file, err := driver.File(args.Path, account)
if err != nil {
return nil, err
}
var e Resp
res, err := base.NoRedirectClient.R().SetError(&e).SetHeader("Accept", "application/json").SetQueryParams(map[string]string{
"id": file.Id,
"token": account.AccessToken,
}).Get("https://shandianpan.com/api/pan/file-download")
if err != nil {
return nil, err
}
if e.Code != 0 {
if e.Code == 10 {
err = driver.Login(account)
if err != nil {
return nil, err
}
return driver.Link(args, account)
}
return nil, errors.New(e.Msg)
}
return &base.Link{
Url: res.Header().Get("location"),
}, nil
}
func (driver Shandian) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("shandian path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Shandian) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Shandian) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Shandian) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := map[string]interface{}{
"id": parentFile.Id,
"name": utils.Base(path),
}
_, err = driver.Post("https://shandianpan.com/api/pan/mkdir", data, nil, account)
return err
}
func (driver Shandian) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
data := map[string]interface{}{
"id": srcFile.Id,
"to_id": dstParentFile.Id,
}
_, err = driver.Post("https://shandianpan.com/api/pan/move", data, nil, account)
return err
}
func (driver Shandian) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
data := map[string]interface{}{
"id": srcFile.Id,
"name": utils.Base(dst),
}
_, err = driver.Post("https://shandianpan.com/api/pan/change", data, nil, account)
return err
}
func (driver Shandian) Copy(src string, dst string, account *model.Account) error {
return base.ErrNotSupport
}
func (driver Shandian) Delete(path string, account *model.Account) error {
file, err := driver.File(path, account)
if err != nil {
return err
}
data := map[string]interface{}{
"id": file.Id,
}
_, err = driver.Post("https://shandianpan.com/api/pan/recycle-in", data, nil, account)
return err
}
func (driver Shandian) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
var resp UploadResp
parentId, err := strconv.Atoi(parentFile.Id)
if err != nil {
return err
}
data := map[string]interface{}{
"id": parentId,
"name": file.GetFileName(),
}
res, err := driver.Post("https://shandianpan.com/api/pan/upload", data, nil, account)
if err != nil {
return err
}
err = utils.Json.Unmarshal(res, &resp)
if err != nil {
return err
}
if resp.Code != 0 {
if resp.Code == 10 {
err = driver.Login(account)
if err != nil {
return err
}
return driver.Upload(file, account)
}
return errors.New(resp.Msg)
}
var r Resp
_, err = base.RestyClient.R().SetMultipartFormData(map[string]string{
"token": account.AccessToken,
"id": "0",
"key": resp.Data.Key,
"ossAccessKeyId": resp.Data.Accessid,
"policy": resp.Data.Policy,
"signature": resp.Data.Signature,
"callback": resp.Data.Callback,
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
SetResult(&r).SetError(&r).Post("https:" + resp.Data.Host + "/")
if err != nil {
return err
}
if r.Code == 0 {
return nil
}
return errors.New(r.Msg)
}
var _ base.Driver = (*Shandian)(nil)

View File

@ -1,150 +0,0 @@
package shandian
import (
"errors"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"strconv"
"time"
)
type Resp struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
type LoginResp struct {
Resp
Data struct {
Token string `json:"token"`
} `json:"data"`
}
func (driver Shandian) Login(account *model.Account) error {
var resp LoginResp
_, err := base.RestyClient.R().SetResult(&resp).SetHeader("Accept", "application/json").SetBody(base.Json{
"mobile": account.Username,
"password": account.Password,
"smscode": "",
}).Post("https://shandianpan.com/api/login")
if err != nil {
return err
}
if resp.Code != 0 {
account.Status = resp.Msg
err = errors.New(resp.Msg)
} else {
account.Status = "work"
account.AccessToken = resp.Data.Token
}
_ = model.SaveAccount(account)
return err
}
type File struct {
Id int64 `json:"id"`
Type int `json:"type"`
Name string `json:"name"`
UpdateTime int64 `json:"update_time"`
Size int64 `json:"size"`
Ext string `json:"ext"`
}
func (driver Shandian) FormatFile(file *File) *model.File {
t := time.Unix(file.UpdateTime, 0)
f := &model.File{
Id: strconv.FormatInt(file.Id, 10),
Name: file.Name,
Size: file.Size,
Driver: driver.Config().Name,
UpdatedAt: &t,
}
if file.Type == 1 {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(file.Ext)
if file.Ext != "" {
f.Name += "." + file.Ext
}
}
return f
}
func (driver Shandian) Post(url string, data map[string]interface{}, resp interface{}, account *model.Account) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeader("Accept", "application/json")
data["token"] = account.AccessToken
req.SetBody(data)
var e Resp
if resp != nil {
req.SetResult(resp)
} else {
req.SetResult(&e)
}
req.SetError(&e)
res, err := req.Post(url)
if err != nil {
return nil, err
}
log.Debug(res.String())
if e.Code != 0 {
if e.Code == 10 {
err = driver.Login(account)
if err != nil {
return nil, err
}
return driver.Post(url, data, resp, account)
}
return nil, errors.New(e.Msg)
}
return res.Body(), nil
}
type FilesResp struct {
Resp
Data []File `json:"data"`
}
func (driver Shandian) GetFiles(id string, account *model.Account) ([]File, error) {
// TODO page not wok
//res := make([]File, 0)
page := 1
//for {
data := map[string]interface{}{
"id": id,
"page": page,
"page_size": 100,
}
var resp FilesResp
_, err := driver.Post("https://shandianpan.com/api/pan", data, &resp, account)
if err != nil {
return nil, err
}
//res = append(res, resp.Data...)
// if len(resp.Data) == 0 {
// break
// }
//}
//return res, nil
return resp.Data, nil
}
type UploadResp struct {
Resp
Data struct {
Accessid string `json:"accessid"`
Policy string `json:"policy"`
Expire int `json:"expire"`
Callback string `json:"callback"`
Key string `json:"key"`
Host string `json:"host"`
Signature string `json:"signature"`
} `json:"data"`
}
func init() {
base.RegisterDriver(&Shandian{})
}

View File

@ -1,286 +0,0 @@
package teambition
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"path/filepath"
)
type Teambition struct{}
func (driver Teambition) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Teambition",
}
}
func (driver Teambition) Items() []base.Item {
return []base.Item{
{
Name: "internal_type",
Label: "Teambition type",
Type: base.TypeSelect,
Required: true,
Values: "China,International",
},
{
Name: "access_token",
Label: "Cookie",
Type: base.TypeString,
Required: true,
Description: "Unknown expiration time",
},
{
Name: "zone",
Label: "Project id",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
{
Name: "order_by",
Label: "order_by",
Type: base.TypeSelect,
Values: "fileName,fileSize,updated,created",
Required: true,
Default: "fileName",
},
{
Name: "order_direction",
Label: "order_direction",
Type: base.TypeSelect,
Values: "Asc,Desc",
Required: true,
Default: "Asc",
},
}
}
func (driver Teambition) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
_, err := driver.Request("/api/v2/roles", base.Get, nil, nil, nil, nil, nil, account)
return err
}
func (driver Teambition) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Teambition) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
files, err = driver.GetFiles(file.Id, account)
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, nil
}
func (driver Teambition) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
url := file.Url
res, err := base.NoRedirectClient.R().Get(url)
if res.StatusCode() == 302 {
url = res.Header().Get("location")
}
return &base.Link{Url: url}, nil
}
func (driver Teambition) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("teambition path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver Teambition) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver Teambition) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver Teambition) MakeDir(path string, account *model.Account) error {
parentFile, err := driver.File(utils.Dir(path), account)
if err != nil {
return err
}
data := base.Json{
"objectType": "collection",
"_projectId": account.Zone,
"_creatorId": "",
"created": "",
"updated": "",
"title": utils.Base(path),
"color": "blue",
"description": "",
"workCount": 0,
"collectionType": "",
"recentWorks": []interface{}{},
"_parentId": parentFile.Id,
"subCount": nil,
}
_, err = driver.Request("/api/collections", base.Post, nil, nil, nil, &data, nil, account)
return err
}
func (driver Teambition) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
pre := "/api/works/"
if srcFile.IsDir() {
pre = "/api/collections/"
}
_, err = driver.Request(pre+srcFile.Id+"/move", base.Put, nil, nil, nil, &base.Json{
"_parentId": dstParentFile.Id,
}, nil, account)
return err
}
func (driver Teambition) Rename(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
pre := "/api/works/"
data := base.Json{
"fileName": utils.Base(dst),
}
if srcFile.IsDir() {
pre = "/api/collections/"
data = base.Json{
"title": utils.Base(dst),
}
}
_, err = driver.Request(pre+srcFile.Id, base.Put, nil, nil, nil, &data, nil, account)
return err
}
func (driver Teambition) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstParentFile, err := driver.File(utils.Dir(dst), account)
if err != nil {
return err
}
pre := "/api/works/"
if srcFile.IsDir() {
pre = "/api/collections/"
}
_, err = driver.Request(pre+srcFile.Id+"/fork", base.Put, nil, nil, nil, &base.Json{
"_parentId": dstParentFile.Id,
}, nil, account)
return err
}
func (driver Teambition) Delete(path string, account *model.Account) error {
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
pre := "/api/works/"
if srcFile.IsDir() {
pre = "/api/collections/"
}
_, err = driver.Request(pre+srcFile.Id+"/archive", base.Post, nil, nil, nil, nil, nil, account)
return err
}
func (driver Teambition) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if !parentFile.IsDir() {
return base.ErrNotFolder
}
if err != nil {
return err
}
res, err := driver.Request("/projects", base.Get, nil, nil, nil, nil, nil, account)
if err != nil {
return err
}
token := GetBetweenStr(string(res), "strikerAuth&quot;:&quot;", "&quot;,&quot;phoneForLogin")
var newFile *FileUpload
if file.Size <= 20971520 {
// post upload
newFile, err = driver.upload(file, token, account)
} else {
// chunk upload
//err = base.ErrNotImplement
newFile, err = driver.chunkUpload(file, token, account)
}
if err != nil {
return err
}
return driver.finishUpload(newFile, parentFile.Id, account)
}
var _ base.Driver = (*Teambition)(nil)

View File

@ -1,237 +0,0 @@
package teambition
import (
"errors"
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
log "github.com/sirupsen/logrus"
"io"
"path"
"strconv"
"time"
)
type ErrResp struct {
Name string `json:"name"`
Message string `json:"message"`
}
func (driver Teambition) Request(pathname string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
url := "https://www.teambition.com" + pathname
if account.InternalType == "International" {
url = "https://us.teambition.com" + pathname
}
req := base.RestyClient.R()
req.SetHeader("Cookie", account.AccessToken)
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e ErrResp
var err error
var res *resty.Response
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(url)
case base.Post:
res, err = req.Post(url)
case base.Delete:
res, err = req.Delete(url)
case base.Patch:
res, err = req.Patch(url)
case base.Put:
res, err = req.Put(url)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
if e.Name != "" {
return nil, errors.New(e.Message)
}
return res.Body(), nil
}
func (driver Teambition) GetFiles(parentId string, account *model.Account) ([]model.File, error) {
files := make([]model.File, 0)
page := 1
for {
var collections []Collection
_, err := driver.Request("/api/collections", base.Get, nil, map[string]string{
"_parentId": parentId,
"_projectId": account.Zone,
"order": account.OrderBy + account.OrderDirection,
"count": "50",
"page": strconv.Itoa(page),
}, nil, nil, &collections, account)
if err != nil {
return nil, err
}
if len(collections) == 0 {
break
}
page++
for _, collection := range collections {
if collection.Title == "" {
continue
}
files = append(files, model.File{
Id: collection.ID,
Name: collection.Title,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: collection.Updated,
})
}
}
page = 1
for {
var works []Work
_, err := driver.Request("/api/works", base.Get, nil, map[string]string{
"_parentId": parentId,
"_projectId": account.Zone,
"order": account.OrderBy + account.OrderDirection,
"count": "50",
"page": strconv.Itoa(page),
}, nil, nil, &works, account)
if err != nil {
return nil, err
}
if len(works) == 0 {
break
}
page++
for _, work := range works {
files = append(files, model.File{
Id: work.ID,
Name: work.FileName,
Size: work.FileSize,
Type: utils.GetFileType(path.Ext(work.FileName)),
Driver: driver.Config().Name,
UpdatedAt: work.Updated,
Thumbnail: work.Thumbnail,
Url: work.DownloadURL,
})
}
}
return files, nil
}
func (driver Teambition) upload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
if account.InternalType == "International" {
prefix = "us-tcs"
}
var newFile FileUpload
_, err := base.RestyClient.R().SetResult(&newFile).SetHeader("Authorization", token).
SetMultipartFormData(map[string]string{
"name": file.GetFileName(),
"type": file.GetMIMEType(),
"size": strconv.FormatUint(file.GetSize(), 10),
//"lastModifiedDate": "",
}).SetMultipartField("file", file.GetFileName(), file.GetMIMEType(), file).
Post(fmt.Sprintf("https://%s.teambition.net/upload", prefix))
if err != nil {
return nil, err
}
return &newFile, nil
}
func (driver Teambition) chunkUpload(file *model.FileStream, token string, account *model.Account) (*FileUpload, error) {
prefix := "tcs"
referer := "https://www.teambition.com/"
if account.InternalType == "International" {
prefix = "us-tcs"
referer = "https://us.teambition.com/"
}
var newChunk ChunkUpload
_, err := base.RestyClient.R().SetResult(&newChunk).SetHeader("Authorization", token).
SetBody(base.Json{
"fileName": file.GetFileName(),
"fileSize": file.GetSize(),
"lastUpdated": time.Now(),
}).Post(fmt.Sprintf("https://%s.teambition.net/upload/chunk", prefix))
if err != nil {
return nil, err
}
for i := 0; i < newChunk.Chunks; i++ {
chunkSize := newChunk.ChunkSize
if i == newChunk.Chunks-1 {
chunkSize = int(file.GetSize()) - i*chunkSize
}
log.Debugf("%d : %d", i, chunkSize)
chunkData := make([]byte, chunkSize)
_, err = io.ReadFull(file, chunkData)
if err != nil {
return nil, err
}
u := fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s?chunk=%d&chunks=%d",
prefix, newChunk.FileKey, i+1, newChunk.Chunks)
log.Debugf("url: %s", u)
res, err := base.RestyClient.R().SetHeaders(map[string]string{
"Authorization": token,
"Content-Type": "application/octet-stream",
"Referer": referer,
}).SetBody(chunkData).Post(u)
if err != nil {
return nil, err
}
log.Debug(res.Status(), res.String())
//req, err := http.NewRequest("POST",
// u,
// bytes.NewBuffer(chunkData))
//if err != nil {
// return nil, err
//}
//req.Header.Set("Authorization", token)
//req.Header.Set("Content-Type", "application/octet-stream")
//req.Header.Set("Referer", "https://www.teambition.com/")
//resp, err := base.HttpClient.Do(req)
//res, _ := ioutil.ReadAll(resp.Body)
//log.Debugf("chunk upload status: %s, res: %s", resp.Status, string(res))
if err != nil {
return nil, err
}
}
res, err := base.RestyClient.R().SetHeader("Authorization", token).Post(
fmt.Sprintf("https://%s.teambition.net/upload/chunk/%s",
prefix, newChunk.FileKey))
log.Debug(res.Status(), res.String())
if err != nil {
return nil, err
}
return &newChunk.FileUpload, nil
}
func (driver Teambition) finishUpload(file *FileUpload, parentId string, account *model.Account) error {
file.InvolveMembers = []interface{}{}
file.Visible = "members"
file.ParentId = parentId
_, err := driver.Request("/api/works", base.Post, nil, nil, nil, base.Json{
"works": []FileUpload{*file},
"_parentId": parentId,
}, nil, account)
return err
}
func init() {
base.RegisterDriver(&Teambition{})
}

View File

@ -1,63 +0,0 @@
package teambition
import "time"
type Collection struct {
ID string `json:"_id"`
Title string `json:"title"`
Updated *time.Time `json:"updated"`
}
type Work struct {
ID string `json:"_id"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
FileKey string `json:"fileKey"`
FileCategory string `json:"fileCategory"`
DownloadURL string `json:"downloadUrl"`
ThumbnailURL string `json:"thumbnailUrl"`
Thumbnail string `json:"thumbnail"`
Updated *time.Time `json:"updated"`
PreviewURL string `json:"previewUrl"`
}
type FileUpload struct {
FileKey string `json:"fileKey"`
FileName string `json:"fileName"`
FileType string `json:"fileType"`
FileSize int `json:"fileSize"`
FileCategory string `json:"fileCategory"`
ImageWidth int `json:"imageWidth"`
ImageHeight int `json:"imageHeight"`
InvolveMembers []interface{} `json:"involveMembers"`
Source string `json:"source"`
Visible string `json:"visible"`
ParentId string `json:"_parentId"`
}
type ChunkUpload struct {
FileUpload
Storage string `json:"storage"`
MimeType string `json:"mimeType"`
Chunks int `json:"chunks"`
ChunkSize int `json:"chunkSize"`
Created time.Time `json:"created"`
FileMD5 string `json:"fileMD5"`
LastUpdated time.Time `json:"lastUpdated"`
UploadedChunks []interface{} `json:"uploadedChunks"`
Token struct {
AppID string `json:"AppID"`
OrganizationID string `json:"OrganizationID"`
UserID string `json:"UserID"`
Exp time.Time `json:"Exp"`
Storage string `json:"Storage"`
Resource string `json:"Resource"`
Speed int `json:"Speed"`
} `json:"token"`
DownloadUrl string `json:"downloadUrl"`
ThumbnailUrl string `json:"thumbnailUrl"`
PreviewUrl string `json:"previewUrl"`
ImmPreviewUrl string `json:"immPreviewUrl"`
PreviewExt string `json:"previewExt"`
LastUploadTime interface{} `json:"lastUploadTime"`
}

View File

@ -1,18 +0,0 @@
package teambition
import "strings"
func GetBetweenStr(str, start, end string) string {
n := strings.Index(str, start)
if n == -1 {
return ""
}
n = n + len(start)
str = string([]byte(str)[n:])
m := strings.Index(str, end)
if m == -1 {
return ""
}
str = string([]byte(str)[:m])
return str
}

View File

@ -1,149 +0,0 @@
package template
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"path/filepath"
)
type Template struct {
}
func (driver Template) Config() base.DriverConfig {
return base.DriverConfig{
Name: "Template",
OnlyProxy: false,
OnlyLocal: false,
ApiProxy: false,
NoNeedSetLink: false,
NoCors: false,
LocalSort: false,
}
}
func (driver Template) Items() []base.Item {
// TODO fill need info
return []base.Item{
{
Name: "refresh_token",
Label: "refresh token",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Default: "/",
Required: true,
},
}
}
func (driver Template) Save(account *model.Account, old *model.Account) error {
// TODO test available or init
return nil
}
func (driver Template) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver Template) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
var files []model.File
// TODO get files
if err != nil {
return nil, err
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver Template) Link(args base.Args, account *model.Account) (*base.Link, error) {
// TODO get file link
return nil, base.ErrNotImplement
}
func (driver Template) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver Template) Preview(path string, account *model.Account) (interface{}, error) {
//TODO preview interface if driver support
return nil, base.ErrNotImplement
}
func (driver Template) MakeDir(path string, account *model.Account) error {
//TODO make dir
return base.ErrNotImplement
}
func (driver Template) Move(src string, dst string, account *model.Account) error {
//TODO move file/dir
return base.ErrNotImplement
}
func (driver Template) Rename(src string, dst string, account *model.Account) error {
//TODO rename file/dir
return base.ErrNotImplement
}
func (driver Template) Copy(src string, dst string, account *model.Account) error {
//TODO copy file/dir
return base.ErrNotImplement
}
func (driver Template) Delete(path string, account *model.Account) error {
//TODO delete file/dir
return base.ErrNotImplement
}
func (driver Template) Upload(file *model.FileStream, account *model.Account) error {
//TODO upload file
return base.ErrNotImplement
}
var _ base.Driver = (*Template)(nil)

View File

@ -1,90 +0,0 @@
package template
import (
"errors"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/go-resty/resty/v2"
"path"
)
func LoginOrRefreshToken(account *model.Account) error {
return nil
}
func Request(u string, method int, headers, query, form map[string]string, data interface{}, resp interface{}, account *model.Account) ([]byte, error) {
req := base.RestyClient.R()
req.SetHeaders(map[string]string{
"Authorization": "Bearer" + account.AccessToken,
"Accept": "application/json, text/plain, */*",
})
if headers != nil {
req.SetHeaders(headers)
}
if query != nil {
req.SetQueryParams(query)
}
if form != nil {
req.SetFormData(form)
}
if data != nil {
req.SetBody(data)
}
if resp != nil {
req.SetResult(resp)
}
var e Resp
var err error
var res *resty.Response
req.SetError(&e)
switch method {
case base.Get:
res, err = req.Get(u)
case base.Post:
res, err = req.Post(u)
case base.Delete:
res, err = req.Delete(u)
case base.Patch:
res, err = req.Patch(u)
case base.Put:
res, err = req.Put(u)
default:
return nil, base.ErrNotSupport
}
if err != nil {
return nil, err
}
if e.Code >= 400 {
if e.Code == 401 {
err = LoginOrRefreshToken(account)
if err != nil {
return nil, err
}
return Request(u, method, headers, query, form, data, resp, account)
}
return nil, errors.New(e.Message)
}
return res.Body(), nil
}
func (driver Template) formatFile(f *File) *model.File {
file := model.File{
Id: f.Id,
Name: f.FileName,
Size: f.Size,
Driver: driver.Config().Name,
UpdatedAt: f.UpdatedAt,
}
if f.File {
file.Type = utils.GetFileType(path.Ext(f.FileName))
} else {
file.Type = conf.FOLDER
}
return &file
}
func init() {
base.RegisterDriver(&Template{})
}

View File

@ -1,18 +0,0 @@
package template
import "time"
// write all struct here
type Resp struct {
Code int `json:"code"`
Message string `json:"message"`
}
type File struct {
Id string `json:"id"`
FileName string `json:"file_name"`
Size int64 `json:"size"`
File bool `json:"file"`
UpdatedAt *time.Time `json:"updated_at"`
}

View File

@ -1,3 +0,0 @@
package template
// write util func here, such as cal sign

View File

@ -1,237 +0,0 @@
package uss
import (
"fmt"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
"github.com/upyun/go-sdk/v3/upyun"
"net/url"
"path/filepath"
"strings"
"time"
)
type USS struct {
}
func (driver USS) Config() base.DriverConfig {
return base.DriverConfig{
Name: "USS",
LocalSort: true,
}
}
func (driver USS) Items() []base.Item {
return []base.Item{
{
Name: "bucket",
Label: "Bucket",
Type: base.TypeString,
Required: true,
},
{
Name: "endpoint",
Label: "Endpoint",
Type: base.TypeString,
Required: true,
},
{
Name: "access_key",
Label: "Operator Name",
Type: base.TypeString,
Required: true,
},
{
Name: "access_secret",
Label: "Operator Password",
Type: base.TypeString,
Required: true,
},
{
Name: "root_folder",
Label: "root folder path",
Type: base.TypeString,
Required: false,
},
{
Name: "custom_host",
Label: "Custom Host",
Type: base.TypeString,
},
{
Name: "limit",
Label: "Sign url expire time(hours)",
Type: base.TypeNumber,
Default: "4",
Description: "default 4 hours",
},
//{
// Name: "zone",
// Label: "placeholder filename",
// Type: base.TypeString,
// Description: "default empty string",
//},
}
}
func (driver USS) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
if account.Limit == 0 {
account.Limit = 4
}
client, err := driver.NewUpYun(account)
if err != nil {
account.Status = err.Error()
} else {
clientsMap[account.Name] = client
account.Status = "work"
}
_ = model.SaveAccount(account)
return err
}
func (driver USS) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver USS) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
var files []model.File
cache, err := base.GetCache(path, account)
if err == nil {
files, _ = cache.([]model.File)
} else {
files, err = driver.List(path, account)
if err == nil && len(files) > 0 {
_ = base.SetCache(path, files, account)
}
}
return files, err
}
func (driver USS) Link(args base.Args, account *model.Account) (*base.Link, error) {
key := driver.GetKey(args.Path, account, false)
host := account.CustomHost
if host == "" {
host = account.Endpoint
}
if strings.Contains(host, "://") {
host = "https://" + host
}
u := fmt.Sprintf("%s/%s", host, key)
downExp := time.Hour * time.Duration(account.Limit)
expireAt := time.Now().Add(downExp).Unix()
upd := url.QueryEscape(utils.Base(args.Path))
signStr := strings.Join([]string{account.AccessSecret, fmt.Sprint(expireAt), fmt.Sprintf("/%s", key)}, "&")
upt := utils.GetMD5Encode(signStr)[12:20] + fmt.Sprint(expireAt)
link := fmt.Sprintf("%s?_upd=%s&_upt=%s", u, upd, upt)
return &base.Link{Url: link}, nil
}
func (driver USS) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("s3 path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver USS) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver USS) MakeDir(path string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Mkdir(driver.GetKey(path, account, true))
}
func (driver USS) Move(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Move(&upyun.MoveObjectConfig{
SrcPath: driver.GetKey(src, account, false),
DestPath: driver.GetKey(dst, account, false),
})
}
func (driver USS) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver USS) Copy(src string, dst string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Copy(&upyun.CopyObjectConfig{
SrcPath: driver.GetKey(src, account, false),
DestPath: driver.GetKey(dst, account, false),
})
}
func (driver USS) Delete(path string, account *model.Account) error {
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Delete(&upyun.DeleteObjectConfig{
Path: driver.GetKey(path, account, false),
Async: false,
})
}
func (driver USS) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
client, err := driver.GetClient(account)
if err != nil {
return err
}
return client.Put(&upyun.PutObjectConfig{
Path: driver.GetKey(utils.Join(file.ParentPath, file.GetFileName()), account, false),
Reader: file,
})
}
var _ base.Driver = (*USS)(nil)

View File

@ -1,82 +0,0 @@
package uss
import (
"errors"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/upyun/go-sdk/v3/upyun"
"path"
"strings"
)
var clientsMap map[string]*upyun.UpYun
func (driver USS) NewUpYun(account *model.Account) (*upyun.UpYun, error) {
return upyun.NewUpYun(&upyun.UpYunConfig{
Bucket: account.Bucket,
Operator: account.AccessKey,
Password: account.AccessToken,
}), nil
}
func (driver USS) GetClient(account *model.Account) (*upyun.UpYun, error) {
client, ok := clientsMap[account.Name]
if ok {
return client, nil
}
return nil, errors.New("can't get client")
}
func (driver USS) List(prefix string, account *model.Account) ([]model.File, error) {
prefix = driver.GetKey(prefix, account, true)
client, err := driver.GetClient(account)
if err != nil {
return nil, err
}
objsChan := make(chan *upyun.FileInfo, 10)
defer close(objsChan)
go func() {
err = client.List(&upyun.GetObjectsConfig{
Path: prefix,
ObjectsChan: objsChan,
MaxListObjects: 0,
MaxListLevel: 1,
})
}()
if err != nil {
return nil, err
}
res := make([]model.File, 0)
for obj := range objsChan {
t := obj.Time
f := model.File{
Name: obj.Name,
Size: obj.Size,
UpdatedAt: &t,
Driver: driver.Config().Name,
}
if obj.IsDir {
f.Type = conf.FOLDER
} else {
f.Type = utils.GetFileType(path.Ext(obj.Name))
}
res = append(res, f)
}
return res, err
}
func (driver USS) GetKey(path string, account *model.Account, dir bool) string {
path = utils.Join(account.RootFolder, path)
path = strings.TrimPrefix(path, "/")
if dir {
path += "/"
}
return path
}
func init() {
clientsMap = make(map[string]*upyun.UpYun)
base.RegisterDriver(&USS{})
}

100
drivers/virtual/driver.go Normal file
View File

@ -0,0 +1,100 @@
package virtual
import (
"context"
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/errs"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/utils/random"
"github.com/pkg/errors"
"io"
"time"
)
type Virtual struct {
model.Storage
Addition
}
func (d *Virtual) Config() driver.Config {
return config
}
func (d *Virtual) Init(ctx context.Context, storage model.Storage) error {
d.Storage = storage
err := utils.Json.UnmarshalFromString(storage.Addition, &d.Addition)
if err != nil {
return errors.Wrap(err, "error while unmarshal addition")
}
return nil
}
func (d *Virtual) Drop(ctx context.Context) error {
return nil
}
func (d *Virtual) GetStorage() model.Storage {
return d.Storage
}
func (d *Virtual) GetAddition() driver.Additional {
return d.Addition
}
func (d *Virtual) List(ctx context.Context, dir model.Obj) ([]model.Obj, error) {
var res []model.Obj
for i := 0; i < d.NumFile; i++ {
res = append(res, model.Object{
Name: random.String(10),
Size: random.RangeInt64(d.MinFileSize, d.MaxFileSize),
IsFolder: false,
Modified: time.Now(),
})
}
for i := 0; i < d.NumFolder; i++ {
res = append(res, model.Object{
Name: random.String(10),
Size: 0,
IsFolder: true,
Modified: time.Now(),
})
}
return res, nil
}
func (d *Virtual) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
return &model.Link{
Data: io.NopCloser(io.LimitReader(random.Rand, file.GetSize())),
}, nil
}
func (d *Virtual) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
return nil
}
func (d *Virtual) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
return nil
}
func (d *Virtual) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
return nil
}
func (d *Virtual) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
return nil
}
func (d *Virtual) Remove(ctx context.Context, obj model.Obj) error {
return nil
}
func (d *Virtual) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
return nil
}
func (d *Virtual) Other(ctx context.Context, data interface{}) (interface{}, error) {
return nil, errs.NotSupport
}
var _ driver.Driver = (*Virtual)(nil)

29
drivers/virtual/meta.go Normal file
View File

@ -0,0 +1,29 @@
package virtual
import (
"github.com/alist-org/alist/v3/internal/driver"
"github.com/alist-org/alist/v3/internal/operations"
)
type Addition struct {
driver.RootFolderPath
NumFile int `json:"num_file" type:"number" default:"30" required:"true"`
NumFolder int `json:"num_folder" type:"number" default:"30" required:"true"`
MaxFileSize int64 `json:"max_file_size" type:"number" default:"1073741824" required:"true"`
MinFileSize int64 `json:"min_file_size" type:"number" default:"1048576" required:"true"`
}
var config = driver.Config{
Name: "Virtual",
OnlyLocal: true,
LocalSort: true,
//NoCache: true,
}
func New() driver.Driver {
return &Virtual{}
}
func init() {
operations.RegisterDriver(config, New)
}

View File

@ -1,185 +0,0 @@
package webdav
import (
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"path/filepath"
)
type WebDav struct{}
func (driver WebDav) Config() base.DriverConfig {
return base.DriverConfig{
Name: "WebDav",
OnlyProxy: true,
OnlyLocal: true,
NoNeedSetLink: true,
LocalSort: true,
}
}
func (driver WebDav) Items() []base.Item {
return []base.Item{
{
Name: "site_url",
Label: "webdav root url",
Type: base.TypeString,
Required: true,
},
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
},
}
}
func (driver WebDav) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
account.Status = "work"
_ = model.SaveAccount(account)
return nil
}
func (driver WebDav) File(path string, account *model.Account) (*model.File, error) {
if path == "/" {
return &model.File{
Id: "/",
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver WebDav) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
c := driver.NewClient(account)
rawFiles, err := c.ReadDir(driver.WebDavPath(path))
if err != nil {
return nil, err
}
files := make([]model.File, 0)
if len(rawFiles) == 0 {
return files, nil
}
for _, f := range rawFiles {
t := f.ModTime()
file := model.File{
Name: f.Name(),
Size: f.Size(),
Driver: driver.Config().Name,
UpdatedAt: &t,
}
if f.IsDir() {
file.Type = conf.FOLDER
} else {
file.Type = utils.GetFileType(filepath.Ext(f.Name()))
}
files = append(files, file)
}
_ = base.SetCache(path, files, account)
return files, nil
}
func (driver WebDav) Link(args base.Args, account *model.Account) (*base.Link, error) {
path := args.Path
c := driver.NewClient(account)
reader, err := c.ReadStream(driver.WebDavPath(path))
if err != nil {
return nil, err
}
return &base.Link{Data: reader}, nil
}
func (driver WebDav) Path(path string, account *model.Account) (*model.File, []model.File, error) {
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
//func (driver WebDav) Proxy(r *http.Request, account *model.Account) {
//
//}
func (driver WebDav) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver WebDav) MakeDir(path string, account *model.Account) error {
c := driver.NewClient(account)
err := c.MkdirAll(driver.WebDavPath(path), 0644)
return err
}
func (driver WebDav) Move(src string, dst string, account *model.Account) error {
c := driver.NewClient(account)
err := c.Rename(driver.WebDavPath(src), driver.WebDavPath(dst), true)
return err
}
func (driver WebDav) Rename(src string, dst string, account *model.Account) error {
return driver.Move(src, dst, account)
}
func (driver WebDav) Copy(src string, dst string, account *model.Account) error {
c := driver.NewClient(account)
err := c.Copy(driver.WebDavPath(src), driver.WebDavPath(dst), true)
return err
}
func (driver WebDav) Delete(path string, account *model.Account) error {
c := driver.NewClient(account)
err := c.RemoveAll(driver.WebDavPath(path))
return err
}
func (driver WebDav) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
c := driver.NewClient(account)
path := utils.Join(file.ParentPath, file.Name)
err := c.WriteStream(driver.WebDavPath(path), file, 0644)
return err
}
var _ base.Driver = (*WebDav)(nil)

View File

@ -1,23 +0,0 @@
package webdav
import (
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
"github.com/studio-b12/gowebdav"
"strings"
)
func (driver WebDav) NewClient(account *model.Account) *gowebdav.Client {
return gowebdav.NewClient(account.SiteUrl, account.Username, account.Password)
}
func (driver WebDav) WebDavPath(path string) string {
path = utils.ParsePath(path)
path = strings.TrimPrefix(path, "/")
return path
}
func init() {
base.RegisterDriver(&WebDav{})
}

View File

@ -1,340 +0,0 @@
package xunlei
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/go-resty/resty/v2"
"github.com/Xhofe/alist/conf"
"github.com/Xhofe/alist/drivers/base"
"github.com/Xhofe/alist/model"
"github.com/Xhofe/alist/utils"
log "github.com/sirupsen/logrus"
)
type XunLeiCloud struct{}
func init() {
base.RegisterDriver(new(XunLeiCloud))
}
func (driver XunLeiCloud) Config() base.DriverConfig {
return base.DriverConfig{
Name: "XunLeiCloud",
LocalSort: true,
}
}
func (driver XunLeiCloud) Items() []base.Item {
return []base.Item{
{
Name: "username",
Label: "username",
Type: base.TypeString,
Required: true,
Description: "account username/phone number",
},
{
Name: "password",
Label: "password",
Type: base.TypeString,
Required: true,
Description: "account password",
},
{
Name: "root_folder",
Label: "root folder file_id",
Type: base.TypeString,
Required: true,
},
}
}
func (driver XunLeiCloud) Save(account *model.Account, old *model.Account) error {
if account == nil {
return nil
}
state := GetState(account)
if state.isTokensExpires() {
return state.Login(account)
}
account.Status = "work"
model.SaveAccount(account)
return nil
}
func (driver XunLeiCloud) File(path string, account *model.Account) (*model.File, error) {
path = utils.ParsePath(path)
if path == "/" {
return &model.File{
Id: account.RootFolder,
Name: account.Name,
Size: 0,
Type: conf.FOLDER,
Driver: driver.Config().Name,
UpdatedAt: account.UpdatedAt,
}, nil
}
dir, name := filepath.Split(path)
files, err := driver.Files(dir, account)
if err != nil {
return nil, err
}
for _, file := range files {
if file.Name == name {
return &file, nil
}
}
return nil, base.ErrPathNotFound
}
func (driver XunLeiCloud) Files(path string, account *model.Account) ([]model.File, error) {
path = utils.ParsePath(path)
cache, err := base.GetCache(path, account)
if err == nil {
files, _ := cache.([]model.File)
return files, nil
}
file, err := driver.File(path, account)
if err != nil {
return nil, err
}
files := make([]model.File, 0)
for {
var fileList FileList
_, err = GetState(account).Request("GET", FILE_API_URL, func(r *resty.Request) {
r.SetQueryParams(map[string]string{
"parent_id": file.Id,
"page_token": fileList.NextPageToken,
"with_audit": "true",
"filters": `{"phase": {"eq": "PHASE_TYPE_COMPLETE"}, "trashed":{"eq":false}}`,
})
r.SetResult(&fileList)
}, account)
if err != nil {
return nil, err
}
for _, file := range fileList.Files {
if file.Kind == FOLDER || (file.Kind == FILE && file.Audit.Status == "STATUS_OK") {
files = append(files, *driver.formatFile(&file))
}
}
if fileList.NextPageToken == "" {
break
}
}
if len(files) > 0 {
_ = base.SetCache(path, files, account)
}
return files, nil
}
func (driver XunLeiCloud) formatFile(file *Files) *model.File {
size, _ := strconv.ParseInt(file.Size, 10, 64)
tp := conf.FOLDER
if file.Kind == FILE {
tp = utils.GetFileType(file.FileExtension)
}
return &model.File{
Id: file.ID,
Name: file.Name,
Size: size,
Type: tp,
Driver: driver.Config().Name,
UpdatedAt: file.CreatedTime,
Thumbnail: file.ThumbnailLink,
}
}
func (driver XunLeiCloud) Link(args base.Args, account *model.Account) (*base.Link, error) {
file, err := driver.File(utils.ParsePath(args.Path), account)
if err != nil {
return nil, err
}
if file.Type == conf.FOLDER {
return nil, base.ErrNotFile
}
var lFile Files
_, err = GetState(account).Request("GET", FILE_API_URL+"/{id}", func(r *resty.Request) {
r.SetPathParam("id", file.Id)
r.SetQueryParam("with_audit", "true")
r.SetResult(&lFile)
}, account)
if err != nil {
return nil, err
}
return &base.Link{
Headers: []base.Header{
{Name: "User-Agent", Value: base.UserAgent},
},
Url: lFile.WebContentLink,
}, nil
}
func (driver XunLeiCloud) Path(path string, account *model.Account) (*model.File, []model.File, error) {
path = utils.ParsePath(path)
log.Debugf("xunlei path: %s", path)
file, err := driver.File(path, account)
if err != nil {
return nil, nil, err
}
if !file.IsDir() {
return file, nil, nil
}
files, err := driver.Files(path, account)
if err != nil {
return nil, nil, err
}
return nil, files, nil
}
func (driver XunLeiCloud) Preview(path string, account *model.Account) (interface{}, error) {
return nil, base.ErrNotSupport
}
func (driver XunLeiCloud) MakeDir(path string, account *model.Account) error {
dir, name := filepath.Split(path)
parentFile, err := driver.File(dir, account)
if err != nil {
return err
}
if !parentFile.IsDir() {
return base.ErrNotFolder
}
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
r.SetBody(&base.Json{
"kind": FOLDER,
"name": name,
"parent_id": parentFile.Id,
})
}, account)
return err
}
func (driver XunLeiCloud) Move(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
_, err = GetState(account).Request("POST", FILE_API_URL+":batchMove", func(r *resty.Request) {
r.SetBody(&base.Json{
"to": base.Json{"parent_id": dstDirFile.Id},
"ids": []string{srcFile.Id},
})
}, account)
return err
}
func (driver XunLeiCloud) Copy(src string, dst string, account *model.Account) error {
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
dstDirFile, err := driver.File(filepath.Dir(dst), account)
if err != nil {
return err
}
_, err = GetState(account).Request("POST", FILE_API_URL+":batchCopy", func(r *resty.Request) {
r.SetBody(&base.Json{
"to": base.Json{"parent_id": dstDirFile.Id},
"ids": []string{srcFile.Id},
})
}, account)
return err
}
func (driver XunLeiCloud) Delete(path string, account *model.Account) error {
srcFile, err := driver.File(path, account)
if err != nil {
return err
}
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}/trash", func(r *resty.Request) {
r.SetPathParam("id", srcFile.Id)
r.SetBody(&base.Json{})
}, account)
return err
}
func (driver XunLeiCloud) Upload(file *model.FileStream, account *model.Account) error {
if file == nil {
return base.ErrEmptyFile
}
parentFile, err := driver.File(file.ParentPath, account)
if err != nil {
return err
}
tempFile, err := ioutil.TempFile(conf.Conf.TempDir, "file-*")
if err != nil {
return err
}
defer os.Remove(tempFile.Name())
gcid, err := getGcid(io.TeeReader(file, tempFile), int64(file.Size))
if err != nil {
return err
}
tempFile.Close()
var resp UploadTaskResponse
_, err = GetState(account).Request("POST", FILE_API_URL, func(r *resty.Request) {
r.SetBody(&base.Json{
"kind": FILE,
"parent_id": parentFile.Id,
"name": file.Name,
"size": fmt.Sprint(file.Size),
"hash": gcid,
"upload_type": UPLOAD_TYPE_RESUMABLE,
})
r.SetResult(&resp)
}, account)
if err != nil {
return err
}
param := resp.Resumable.Params
if resp.UploadType == UPLOAD_TYPE_RESUMABLE {
client, err := oss.New(param.Endpoint, param.AccessKeyID, param.AccessKeySecret, oss.SecurityToken(param.SecurityToken), oss.EnableMD5(true))
if err != nil {
return err
}
bucket, err := client.Bucket(param.Bucket)
if err != nil {
return err
}
return bucket.UploadFile(param.Key, tempFile.Name(), 1<<22, oss.Routines(3), oss.Checkpoint(true, ""), oss.Expires(param.Expiration))
}
return nil
}
func (driver XunLeiCloud) Rename(src string, dst string, account *model.Account) error {
_, dstName := filepath.Split(dst)
srcFile, err := driver.File(src, account)
if err != nil {
return err
}
_, err = GetState(account).Request("PATCH", FILE_API_URL+"/{id}", func(r *resty.Request) {
r.SetPathParam("id", srcFile.Id)
r.SetBody(&base.Json{"name": dstName})
}, account)
return err
}
var _ base.Driver = (*XunLeiCloud)(nil)

View File

@ -1,154 +0,0 @@
package xunlei
import (
"time"
)
type Erron struct {
Error string `json:"error"`
ErrorCode int64 `json:"error_code"`
ErrorDescription string `json:"error_description"`
// ErrorDetails interface{} `json:"error_details"`
}
type CaptchaTokenRequest struct {
Action string `json:"action"`
CaptchaToken string `json:"captcha_token"`
ClientID string `json:"client_id"`
DeviceID string `json:"device_id"`
Meta map[string]string `json:"meta"`
//RedirectUri string `json:"redirect_uri"`
}
type CaptchaTokenResponse struct {
CaptchaToken string `json:"captcha_token"`
ExpiresIn int64 `json:"expires_in"`
Url string `json:"url"`
}
type TokenResponse struct {
TokenType string `json:"token_type"`
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"`
Sub string `json:"sub"`
UserID string `json:"user_id"`
}
type SignInRequest struct {
CaptchaToken string `json:"captcha_token"`
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Username string `json:"username"`
Password string `json:"password"`
}
type FileList struct {
Kind string `json:"kind"`
NextPageToken string `json:"next_page_token"`
Files []Files `json:"files"`
Version string `json:"version"`
VersionOutdated bool `json:"version_outdated"`
}
type Files struct {
Kind string `json:"kind"`
ID string `json:"id"`
ParentID string `json:"parent_id"`
Name string `json:"name"`
UserID string `json:"user_id"`
Size string `json:"size"`
Revision string `json:"revision"`
FileExtension string `json:"file_extension"`
MimeType string `json:"mime_type"`
Starred bool `json:"starred"`
WebContentLink string `json:"web_content_link"`
CreatedTime *time.Time `json:"created_time"`
ModifiedTime *time.Time `json:"modified_time"`
IconLink string `json:"icon_link"`
ThumbnailLink string `json:"thumbnail_link"`
Md5Checksum string `json:"md5_checksum"`
Hash string `json:"hash"`
//Links struct{} `json:"links"`
Phase string `json:"phase"`
Audit struct {
Status string `json:"status"`
Message string `json:"message"`
Title string `json:"title"`
} `json:"audit"`
/* Medias []struct {
Category string `json:"category"`
IconLink string `json:"icon_link"`
IsDefault bool `json:"is_default"`
IsOrigin bool `json:"is_origin"`
IsVisible bool `json:"is_visible"`
//Link interface{} `json:"link"`
MediaID string `json:"media_id"`
MediaName string `json:"media_name"`
NeedMoreQuota bool `json:"need_more_quota"`
Priority int `json:"priority"`
RedirectLink string `json:"redirect_link"`
ResolutionName string `json:"resolution_name"`
Video struct {
AudioCodec string `json:"audio_codec"`
BitRate int `json:"bit_rate"`
Duration int `json:"duration"`
FrameRate int `json:"frame_rate"`
Height int `json:"height"`
VideoCodec string `json:"video_codec"`
VideoType string `json:"video_type"`
Width int `json:"width"`
} `json:"video"`
VipTypes []string `json:"vip_types"`
} `json:"medias"` */
Trashed bool `json:"trashed"`
DeleteTime string `json:"delete_time"`
OriginalURL string `json:"original_url"`
//Params struct{} `json:"params"`
OriginalFileIndex int `json:"original_file_index"`
Space string `json:"space"`
//Apps []interface{} `json:"apps"`
Writable bool `json:"writable"`
FolderType string `json:"folder_type"`
//Collection interface{} `json:"collection"`
}
type UploadTaskResponse struct {
UploadType string `json:"upload_type"`
/*//UPLOAD_TYPE_FORM
Form struct {
//Headers struct{} `json:"headers"`
Kind string `json:"kind"`
Method string `json:"method"`
MultiParts struct {
OSSAccessKeyID string `json:"OSSAccessKeyId"`
Signature string `json:"Signature"`
Callback string `json:"callback"`
Key string `json:"key"`
Policy string `json:"policy"`
XUserData string `json:"x:user_data"`
} `json:"multi_parts"`
URL string `json:"url"`
} `json:"form"`*/
//UPLOAD_TYPE_RESUMABLE
Resumable struct {
Kind string `json:"kind"`
Params struct {
AccessKeyID string `json:"access_key_id"`
AccessKeySecret string `json:"access_key_secret"`
Bucket string `json:"bucket"`
Endpoint string `json:"endpoint"`
Expiration time.Time `json:"expiration"`
Key string `json:"key"`
SecurityToken string `json:"security_token"`
} `json:"params"`
Provider string `json:"provider"`
} `json:"resumable"`
File Files `json:"file"`
}

View File

@ -1,109 +0,0 @@
package xunlei
import (
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"net/url"
"github.com/Xhofe/alist/utils"
)
const (
// 小米浏览器
CLIENT_ID = "X7MtiU0Gb5YqWv-6"
CLIENT_SECRET = "84MYEih3Eeu2HF4RrGce3Q"
CLIENT_VERSION = "5.1.0.51045"
ALG_VERSION = "1"
PACKAGE_NAME = "com.xunlei.xcloud.lib"
)
var Algorithms = []string{
"",
"BXza40wm+P4zw8rEFpHA",
"UfZLfKfYRmKTA0",
"OMBGVt/9Wcaln1XaBz",
"Jn217F4rk5FPPWyhoeV",
"w5OwkGo0pGpb0Xe/XZ5T3",
"5guM3DNiY4F78x49zQ97q75",
"QXwn4D2j884wJgrYXjGClM/IVrJX",
"NXBRosYvbHIm6w8vEB",
"2kZ8Ie1yW2ib4O2iAkNpJobP",
"11CoVJJQEc",
"xf3QWysVwnVsNv5DCxU+cgNT1rK",
"9eEfKkrqkfw",
"T78dnANexYRbiZy",
}
const (
API_URL = "https://api-pan.xunlei.com/drive/v1"
FILE_API_URL = API_URL + "/files"
XLUSER_API_URL = "https://xluser-ssl.xunlei.com/v1"
)
const (
FOLDER = "drive#folder"
FILE = "drive#file"
RESUMABLE = "drive#resumable"
)
const (
UPLOAD_TYPE_UNKNOWN = "UPLOAD_TYPE_UNKNOWN"
//UPLOAD_TYPE_FORM = "UPLOAD_TYPE_FORM"
UPLOAD_TYPE_RESUMABLE = "UPLOAD_TYPE_RESUMABLE"
UPLOAD_TYPE_URL = "UPLOAD_TYPE_URL"
)
func captchaSign(driverID string, time int64) string {
str := fmt.Sprint(CLIENT_ID, CLIENT_VERSION, PACKAGE_NAME, driverID, time)
for _, algorithm := range Algorithms {
str = utils.GetMD5Encode(fmt.Sprint(str, algorithm))
}
return fmt.Sprint(ALG_VERSION, ".", str)
}
func getAction(method string, u string) string {
c, _ := url.Parse(u)
return fmt.Sprint(method, ":", c.Path)
}
func getGcid(r io.Reader, size int64) (string, error) {
calcBlockSize := func(j int64) int64 {
if j >= 0 && j <= 134217728 {
return 262144
}
if j <= 134217728 || j > 268435456 {
if j <= 268435456 || j > 536870912 {
return 2097152
}
return 1048576
}
return 524288
}
/*
calcBlockSize := func(j int64) int64 {
psize := int64(0x40000)
for j/psize > 0x200 {
psize <<= 1
}
return psize
}
*/
hash1 := sha1.New()
hash2 := sha1.New()
for {
hash2.Reset()
if n, err := io.CopyN(hash2, r, calcBlockSize(size)); err != nil && n == 0 {
if err != io.EOF {
return "", err
}
break
}
hash1.Write(hash2.Sum(nil))
}
return hex.EncodeToString(hash1.Sum(nil)), nil
}

Some files were not shown because too many files have changed in this diff Show More