├── .gitignore ├── .prettierrc ├── README.md ├── adrive sdk ├── ReadMe.md ├── account.md ├── aims.md ├── album.md ├── archive.md ├── book.md ├── contact.md ├── drive.md ├── file.md ├── filedir.md ├── fileupload.md ├── image.md ├── member.md ├── note.md ├── offline.md ├── recyclebin.md ├── reddot.md ├── sbox.md ├── search.md ├── sfiia.md ├── share.md ├── timeline.md ├── token.md ├── user.md └── video.md ├── app.ico ├── app.png ├── changelog.txt ├── crx ├── devtools.html ├── devtools.js └── manifest.json ├── electron-builder.json ├── electron ├── main │ ├── index.ts │ ├── mainfile.ts │ ├── utils │ │ └── index.ts │ └── window.ts └── preload │ ├── index.ts │ └── preload-env.d.ts ├── index.html ├── nano-staged.mjs ├── package.json ├── public ├── comlink.js ├── favicon.ico ├── font │ └── VideoJS.woff ├── iconfont.css ├── iconfont.woff2 ├── imgerror.png ├── lang │ ├── en.js │ └── zh-CN.js ├── loading.png ├── main.html ├── main2.html ├── notify.wav ├── pinyinlite_full.min.js ├── prism-vsc-dark-plus.css ├── prism.js ├── sha1filework.js ├── silvermine-videojs-quality-selector.css ├── silvermine-videojs-quality-selector.min.js ├── userface.png ├── video-js.min.css ├── video.min.js ├── wasm.wasm └── wasm_exec.js ├── src ├── App.vue ├── aliapi │ ├── alihttp.ts │ ├── alimodels.ts │ ├── archive.ts │ ├── batch.ts │ ├── dirfilelist.ts │ ├── dirlist.ts │ ├── file.ts │ ├── filecmd.ts │ ├── fileicon.ts │ ├── filewalk.ts │ ├── following.ts │ ├── models.ts │ ├── server.tsx │ ├── share.ts │ ├── sharelist.ts │ ├── trash.ts │ ├── upload.ts │ ├── uploaddisk.ts │ ├── uploadhash.ts │ ├── uploadhashpool.ts │ ├── uploadmem.ts │ ├── user.ts │ └── utils.ts ├── assets │ ├── antd.css │ ├── fileitem.css │ └── global.css ├── down │ ├── DownDAL.ts │ ├── DownDowned.vue │ ├── DownDowning.vue │ ├── DownM3U8.vue │ ├── DownSync.vue │ ├── DownUploaded.vue │ ├── DownUploading.vue │ ├── DownedStore.ts │ ├── DowningStore.ts │ ├── Index.vue │ ├── UploadedStore.ts │ ├── UploadingStore.ts │ └── downmenu.ts ├── env.d.ts ├── global.d.ts ├── layout │ ├── MyLoading.vue │ ├── MyModal.vue │ ├── MySplit.vue │ ├── MySwitch.vue │ ├── MySwitchTab.vue │ ├── MyTags.vue │ ├── PageCode.vue │ ├── PageHelp.vue │ ├── PageImage.vue │ ├── PageLoading.vue │ ├── PageMain.vue │ ├── PageOffice.vue │ ├── PageVideo.vue │ ├── PageVideoXBT.vue │ ├── PageWorker.vue │ └── pagemain.ts ├── main.ts ├── pan │ ├── PanLeft.vue │ ├── PanRight.vue │ ├── index.vue │ ├── menus │ │ ├── DirLeftMenu.vue │ │ ├── DirTopPath.vue │ │ ├── FileRightMenu.vue │ │ ├── FileTopbtn.vue │ │ ├── PanTopbtn.vue │ │ ├── TrashRightMenu.vue │ │ └── TrashTopbtn.vue │ ├── pandal.ts │ ├── panfilestore.ts │ ├── pantreestore.ts │ └── topbtns │ │ ├── AlphaModal.vue │ │ ├── ArchiveModal.vue │ │ ├── ArchivePasswordModal.vue │ │ ├── CopyFileTreeModal.vue │ │ ├── CreatNewDirModal.vue │ │ ├── CreatNewDirMultiModal.vue │ │ ├── CreatNewFileModal.vue │ │ ├── CreatNewShareLinkModal.vue │ │ ├── DLNAPlayerModal.vue │ │ ├── DaoRuShareLinkModal.vue │ │ ├── DaoRuShareLinkMultiModal.vue │ │ ├── DownloadModal.vue │ │ ├── M3U8DownloadModal.vue │ │ ├── RenameModal.vue │ │ ├── RenameMultiModal.vue │ │ ├── SearchPanModal.vue │ │ ├── SelectPanDirModal.vue │ │ ├── ShuXingModal.vue │ │ ├── ShuXingMultiModal.vue │ │ ├── UploadModal.vue │ │ ├── renamemulti.ts │ │ └── topbtn.ts ├── pic │ ├── PicLeft.vue │ ├── PicRight.vue │ └── index.vue ├── rss │ ├── ScanDAL.ts │ ├── appsame │ │ ├── AppSame.vue │ │ └── same.ts │ ├── index.vue │ ├── rssdrivecopy │ │ ├── RssDriveCopy.vue │ │ └── drivecopy.ts │ ├── rssjiami │ │ ├── RssJiaMi.vue │ │ └── jiami.ts │ ├── rssrename │ │ └── RssRename.vue │ ├── rssscanclean │ │ ├── RssScanClean.vue │ │ └── ScanClean.ts │ ├── rssscanenmpty │ │ ├── RssScanEnmpty.vue │ │ └── scanenmpty.ts │ ├── rssscanpunish │ │ ├── RssScanPunish.vue │ │ └── scanpunish.ts │ ├── rssscansame │ │ ├── RssScanSame.vue │ │ └── scansame.ts │ ├── rssusercopy │ │ ├── RssUserCopy.vue │ │ └── usercopy.ts │ └── rssxima │ │ ├── RssXiMa.vue │ │ └── xima.ts ├── setting │ ├── SettingAria.vue │ ├── SettingDebug.vue │ ├── SettingDown.vue │ ├── SettingLog.vue │ ├── SettingPan.vue │ ├── SettingPlay.vue │ ├── SettingProxy.vue │ ├── SettingUI.vue │ ├── SettingUpload.vue │ ├── ShutDown.vue │ ├── index.vue │ └── settingstore.ts ├── share │ ├── following │ │ ├── FollowingDAL.ts │ │ ├── MyFollowingRight.vue │ │ ├── MyFollowingStore.ts │ │ ├── OtherFollowingRight.vue │ │ └── OtherFollowingStore.ts │ ├── index.vue │ └── share │ │ ├── EditShareLinkModal.vue │ │ ├── MyShareRight.vue │ │ ├── MyShareStore.ts │ │ ├── OtherShareRight.vue │ │ ├── OtherShareStore.ts │ │ ├── ShareDAL.ts │ │ ├── ShareSiteRight.vue │ │ └── ShowShareLinkModal.vue ├── store │ ├── appstore.ts │ ├── footstore.ts │ ├── index.ts │ ├── keyboardstore.ts │ ├── logstore.ts │ ├── modalstore.ts │ ├── serverstore.ts │ ├── treestore.ts │ └── winstore.ts ├── transfer │ ├── uploaddal.ts │ ├── uploadingdal.ts │ └── uploadingdata.ts ├── user │ ├── UserInfo.vue │ ├── UserLogin.vue │ ├── UserSpaceModal.vue │ ├── userdal.ts │ └── userstore.ts ├── utils │ ├── antdtree.ts │ ├── appcache.ts │ ├── aria2c.ts │ ├── aria2c.ts.bak │ ├── config.ts │ ├── db.ts │ ├── dbcache.ts │ ├── dbdown.ts │ ├── dbupload.ts │ ├── debounce.ts │ ├── debuglog.ts │ ├── electronhelper.ts │ ├── filehelper.ts │ ├── filenameorder.ts │ ├── foot.ts │ ├── format.ts │ ├── idhelper.ts │ ├── keyboardhelper.ts │ ├── levemap.ts │ ├── message.ts │ ├── modal.ts │ ├── openfile.ts │ ├── selecthelper.ts │ ├── sha1workerpool.ts │ ├── shareurl.ts │ ├── utils.ts │ └── worker.ts └── workerpage │ ├── uidownload.ts │ ├── uiupload.ts │ ├── uploader.ts │ └── workercmd.ts ├── tsconfig.json ├── tsconfig.node.json ├── types.d.ts ├── vite.config.ts ├── yarn.lock └── 源码开发打包帮助.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist* 4 | *.local 5 | .debug.env 6 | 7 | tmp 8 | **/.tmp 9 | release 10 | 11 | .idea 12 | aria2.conf 13 | aria2c.exe 14 | 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 260, 4 | "semi": false, 5 | "quoteProps": "as-needed", 6 | "jsxSingleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": true, 10 | "arrowParens": "always", 11 | "requirePragma": false, 12 | "insertPragma": false, 13 | "wrapAttributes": false, 14 | "sortAttributes": true, 15 | "proseWrap": "preserve", 16 | "htmlWhitespaceSensitivity": "css", 17 | "endOfLine": "lf", 18 | "overrides": [ 19 | { 20 | "files": ".prettierrc", 21 | "options": { "parser": "json" } 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 阿里云盘小白羊版 2 | 3 | #### 项目说明 4 | 5 | 原阿里云盘小白羊版v3的延续版 6 | 7 | 基于阿里云盘网页版开发的PC客户端,支持win7-11,macOS,linux 8 | 9 | > **08.25:[v3开发人员测试版](https://github.com/PingKuNet/aliyunpan/releases/tag/v3.8.25.alpha),追加上传,优化打包** 10 | 11 | > **04.14:[v2.12.14版已发布](https://github.com/liupan1890/aliyunpan/issues/639),适配官网升级** 12 | 13 | > **2022-01-02:在憋大招,耐心等待v3版** 14 |
15 | 16 | v1.6.29:[https://wwe.lanzoui.com/b01npsg8h](https://wwe.lanzoui.com/b01npsg8h) 17 | 18 | v2.12.14:[https://wwe.lanzoui.com/b01nqc4gd](https://wwe.lanzoui.com/b01nqc4gd) 19 | 20 | MacOS:[https://www.macwk.com/soft/aliyun-drive-xiaobaiyang](https://www.macwk.com/soft/aliyun-drive-xiaobaiyang) 21 | 22 | Mac版由macwk.com使用自有签名打包dmg,可以简单点击安装了(不需要输入终端命令),推荐下载此版本,已测MacOS10.12-11.4,兼容M1 23 |
24 | 25 | 已经发布在小众软件发现频道,大爱小众[meta.appinn.net](https://meta.appinn.net) 26 | 27 |
28 | 29 | 已发布了使用帮助文档 [https://www.yuque.com/liupan1890/xiaobaiyang](https://www.yuque.com/liupan1890/xiaobaiyang) 30 | 31 | `````` 32 | 2021年11月28日 已完成功能: 33 | 多账号登录、常用文件操作(新建文件夹、收藏、重命名、复制、移动、删除、详情、视频雪碧图)、 34 | 在线播放原始视频、在线播放转码视频、在线预览图片、在线预览文本、在线预览 word/excel/ppt/pdf、 35 | 连接到远程 Aria2 下载、上传文件、上传文件夹、批量改名、在线解压、回收站、收藏夹、 36 | 分享文件、导入阿里云分享链接、缩略图列表、网盘内文件搜索、视频文件洗码 37 | 38 | 等待完成的功能: 39 | 相册功能、网盘和相册间文件互相复制、文件同步盘、重复文件扫描、帐号间文件复制 40 | `````` 41 | 42 |
43 | 44 | # 45 | 46 | ![Image](https://raw.githubusercontent.com/liupan1890/aliyunpan/main/doc/v2.10.19.png) 47 | 48 | # 49 | 50 | #### 为什么要用小白羊? 51 | 52 | #### 一:因为更快 53 | 54 | ##### 上传和下载4.4万个json格式小文件(共24GB): 55 | 56 | | 程序 | 总用时 | 用时基准 | 57 | | --- | ---: | ---: | 58 | | 上传&小白羊版 v2.10 | 24分钟 | :zap:58% | 59 | | 上传&PC客户端 v2.2.6 | 41分钟 | 100% | 60 | | ... | | | | | 61 | | 下载&小白羊版 v2.10 | 25分钟 | :zap:42% | 62 | | 下载&PC客户端 v2.2.6 | 59分钟 | 100% | 63 | 64 | 65 | ##### 上传和下载33个大文件(共90GB): 66 | 67 | | 程序 | 总用时 | 用时基准 | 68 | | --- | ---: | ---: | 69 | | 上传&小白羊版 v2.10 | 1分10秒 | :zap:44% | 70 | | 上传&PC客户端 v2.2.6 | 2分40秒 | 100% | 71 | | ... | | | | | 72 | | 下载&小白羊版 v2.10 | 38分钟 | :zap:52% | 73 | | 下载&PC客户端 v2.2.6 | 72分钟 | 100% | 74 | 75 |
76 | 77 | 详情参阅 :[v2.10.19性能测试](https://github.com/liupan1890/aliyunpan/blob/main/v2.10.19%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95.md) 的性能测试文档 78 | 79 | #### 二:因为更好 80 | 81 | 小白羊支持同时登录多个账号管理 82 | 83 | 小白羊特有文件夹树,可以快速方便的操作 84 | 85 | 小白羊支持直接在线播放网盘里的各种格式的视频并且是高清原画,支持外挂字幕/音轨/播放速度调整,比官方的格式更多更清晰 86 | 87 | 小白羊可以显示文件夹体积,可以文件夹和文件混合排序(文件名/体积/时间),并且文件名排序时更准确! 88 | 89 | 小白羊可以通过远程Aria2功能把文件直接下载到远程的VPS/NAS上 90 | 91 | 小白羊可以批量的对 大量文件/多层嵌套的文件夹 一键重命名 92 | 93 | 小白羊可以快速复制文件,可以直接预览视频的雪碧图,可以直接删除文件 94 | 95 | 小白羊支持数万文件夹和数万文件的管理,支持一次性列出文件夹里包含的全部文件 96 | 97 | 小白羊支持单次上传/下载 一百万 量级的文件/文件夹 98 | 99 | 小白羊仍在努力开发新功能,让大家使用起来更方便! 100 | 101 | # 102 | 103 | #### 常见问题请参阅帮助文档 104 | 105 | 106 | #### 特别感谢 @jkqxl @iD2073 @ybbluesky 等为小白羊提供了大量的优化建议 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /adrive sdk/ReadMe.md: -------------------------------------------------------------------------------- 1 | ### 阿里云盘接口 2 | 3 | > 2022-03整理的阿里云盘SDK接口数据 4 | 5 | 仅用来记录官方提供的接口参数,共整理了144个,比较全了,与编程语言无关,方便大家据此开发 -------------------------------------------------------------------------------- /adrive sdk/account.md: -------------------------------------------------------------------------------- 1 | #### 刷新 token 2 | 3 | POST: `https://auth.aliyundrive.com/v2/account/token` 4 | 5 | ```json 6 | { "grant_type": "refresh_token", "app_id": "pJZInNHN2dZWk8qg", "refresh_token": "c65bf6d104ac510885c0124d74c4a099" } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "default_sbox_drive_id": "9600002", 14 | "role": "user", 15 | "device_id": "2909000000004f01aa28264bfc30e4ed", 16 | "user_name": "151***111", 17 | "need_link": false, 18 | "expire_time": "2022-03-21T06:33:21Z", 19 | "pin_setup": true, 20 | "need_rp_verify": false, 21 | "avatar": "https://ccp-bj29-bj-1592982087.oss-cn-beijing.aliyuncs.com/2GhCur3G%2F...", 22 | "user_data": { 23 | "DingDingRobotUrl": "https://oapi.dingtalk.com/robot/send?access_token=0b4a936d0e...", 24 | "EncourageDesc": "内测期间有效反馈前10名用户将获得终身免费会员", 25 | "FeedBackSwitch": true, 26 | "FollowingDesc": "34848372", 27 | "back_up_config": { 28 | "手机备份": { "folder_id": "605c0c29b7acf78b6ee34bf095594f7654e57d68", "photo_folder_id": "605c0c299af37539f3d34879b2f0d1c5543f27d5", "sub_folder": {}, "video_folder_id": "605c0c29e520154c22644bed904b76b25ced317a" } 29 | }, 30 | "ding_ding_robot_url": "https://oapi.dingtalk.com/robot/send?access_token=0b4a936d0e...", 31 | "encourage_desc": "内测期间有效反馈前10名用户将获得终身免费会员", 32 | "feed_back_switch": true, 33 | "following_desc": "34848372" 34 | }, 35 | "token_type": "Bearer", 36 | "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..................", 37 | "default_drive_id": "9600002", 38 | "domain_id": "bj29", 39 | "refresh_token": "b2d9c244d8a24df38aa1a5dec59e2a92", 40 | "is_first_login": false, 41 | "user_id": "9400000000bc480bbcbbb1e074f55a7f", 42 | "nick_name": "myname", 43 | "exist_link": [], 44 | "state": "", 45 | "expires_in": 7200, 46 | "status": "enabled" 47 | } 48 | ``` 49 | 50 | #### 退出登录 51 | 52 | POST: `https://auth.aliyundrive.com/v2/account/revoke` 53 | 54 | ```json 55 | 56 | ``` 57 | 58 | Response: 59 | 60 | ```json 61 | 62 | ``` 63 | 64 | #### 检查账号是否存在 x 65 | 66 | POST: `https://auth.aliyundrive.com/v2/account/mobile/check_exist` 67 | 68 | ```json 69 | { "app_id": "pJZInNHN2dZWk8qg", "phone_number": "151***111", "phone_region": "86" } 70 | ``` 71 | 72 | Response: 73 | 74 | ```json 75 | { "is_exist": true } 76 | ``` 77 | -------------------------------------------------------------------------------- /adrive sdk/book.md: -------------------------------------------------------------------------------- 1 | #### 列出图书 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v2/book/list` 4 | 5 | ```json 6 | { "book_progress_type": "ALL", "limit": 1, "marker": "", "order_by": "name asc", "show_hidden": false } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "items": [ 14 | { 15 | "category": "doc", 16 | "content_hash": "4DBF0000000023E6E756C29AF6AC487217921D53", 17 | "content_hash_name": "sha1", 18 | "content_type": "application/oct-stream", 19 | "crc64_hash": "1548000000008183211", 20 | "created_at": "2021-11-22T03:36:19.680Z", 21 | "domain_id": "bj29", 22 | "download_url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 23 | "drive_id": "9600002", 24 | "encrypt_mode": "none", 25 | "file_extension": "epub", 26 | "file_id": "623b00000000d89ef21d4118838aed83de7575ba", 27 | "hidden": false, 28 | "name": "Republic.epub", 29 | "parent_file_id": "613800000000336ae9164455b135a9729a298c9c", 30 | "punish_flag": 0, 31 | "size": 128000, 32 | "starred": false, 33 | "status": "available", 34 | "type": "file", 35 | "updated_at": "2022-01-12T12:44:16.835Z", 36 | "upload_id": "ED12000000004724833D47B5D5D3C8B9", 37 | "url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 38 | "user_meta": "{\"client\":\"web\"}", 39 | "user_tags": { "book_show": "true" } 40 | } 41 | ], 42 | "next_marker": "" 43 | } 44 | ``` 45 | 46 | #### 列出最近阅读的图书 47 | 48 | POST: `https://api.aliyundrive.com/adrive/v2/book/recentList` 49 | 50 | ```json 51 | { "book_progress_type": "ALL", "limit": 1, "marker": "", "order_by": "name asc", "show_hidden": false } 52 | ``` 53 | 54 | Response: 55 | 56 | ```json 57 | { 58 | "items": [ 59 | { 60 | "category": "doc", 61 | "content_hash": "4DBF0000000023E6E756C29AF6AC487217921D53", 62 | "content_hash_name": "sha1", 63 | "content_type": "application/oct-stream", 64 | "crc64_hash": "1548000000008183211", 65 | "created_at": "2021-09-24T12:50:00.905Z", 66 | "domain_id": "bj29", 67 | "download_url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 68 | "drive_id": "9600002", 69 | "encrypt_mode": "none", 70 | "file_extension": "epub", 71 | "file_id": "623b00000000d89ef21d4118838aed83de7575ba", 72 | "hidden": false, 73 | "name": "无声告白.epub", 74 | "parent_file_id": "613800000000336ae9164455b135a9729a298c9c", 75 | "punish_flag": 0, 76 | "size": 1027423, 77 | "starred": false, 78 | "status": "available", 79 | "type": "file", 80 | "updated_at": "2022-03-22T14:59:12.333Z", 81 | "url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 82 | "book_progress": "{\"bookName\":\"无声告白\",\"chapterNumber\":1,\"chapterPercent\":0.0,\"currentPageNumber\":-1,\"progress\":29,\"totalPageNumber\":-1,\"uid\":\"9400000000bc480bbcbbb1e074f55a7f\",\"updateTime\":1647961151874}", 83 | "user_tags": { 84 | "book_progress": "{\"bookName\":\"无声告白\",\"chapterNumber\":1,\"chapterPercent\":0.0,\"currentPageNumber\":-1,\"progress\":29,\"totalPageNumber\":-1,\"uid\":\"9400000000bc480bbcbbb1e074f55a7f\",\"updateTime\":1647961151874}", 85 | "book_progress_percentage": "35", 86 | "book_show": "true", 87 | "epub_book_progress": "{\"uid\":\"9400000000bc480bbcbbb1e074f55a7f\",\"href\":\"\\/text\\/part0000.html\",\"type\":\"application\\/xhtml+xml\",\"locations\":{\"progression\":0,\"position\":2,\"totalProgression\":8.873114463176575E-4}}", 88 | "start_read": "true" 89 | }, 90 | "book_name": "无声告白", 91 | "book_progress_percentage": 29 92 | } 93 | ] 94 | } 95 | ``` 96 | 97 | #### 添加到阅读室 98 | 99 | POST: `https://api.aliyundrive.com/adrive/v2/book/update` 100 | 101 | ```json 102 | { "file_ids": ["623b00000000d89ef21d4118838aed83de7575ba"], "operation": 1 } 103 | ``` 104 | 105 | Response: 106 | 107 | ```text 108 | HTTP/1.1 200 OK 109 | ``` 110 | 111 | #### 从阅读室移除 112 | 113 | POST: `https://api.aliyundrive.com/adrive/v2/book/update` 114 | 115 | ```json 116 | { "file_ids": ["623b00000000d89ef21d4118838aed83de7575ba"], "operation": 2 } 117 | ``` 118 | 119 | Response: 120 | 121 | ```text 122 | HTTP/1.1 200 OK 123 | ``` 124 | -------------------------------------------------------------------------------- /adrive sdk/contact.md: -------------------------------------------------------------------------------- 1 | #### 通讯录列出 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v1/contact/list` 4 | 5 | ```json 6 | {} 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "items": [ 14 | { 15 | "id": 223963400, 16 | "content": { 17 | "format": "vcard", 18 | "hash": "1a48648e8b140ae80be048f1681cfbe24a7b9579c2ffbabe2686eb79338dfe14", 19 | "value": "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:ez-vcard 0.11.2\r\nKIND:individual\r\nFN:高青\r\nN:高;青;;;\r\nTEL;TYPE=cell:15000065001\r\nEND:VCARD\r\n", 20 | "version": "4.0" 21 | }, 22 | "gmt_create": 1647864044589 23 | }, 24 | { 25 | "id": 224054214, 26 | "content": { 27 | "format": "vcard", 28 | "hash": "978edd2d967b94b34d3a90c00cbd4819e239f21a6f72f6a2f87b522fe81a62f4", 29 | "value": "BEGIN:VCARD\r\nVERSION:4.0\r\nPRODID:ez-vcard 0.11.2\r\nKIND:individual\r\nFN:木门\r\nN:木;门;;;\r\nTEL;TYPE=cell:03100000981\r\nEND:VCARD\r\n", 30 | "version": "4.0" 31 | }, 32 | "gmt_create": 1647864055972 33 | } 34 | ], 35 | "total_count": 2 36 | } 37 | ``` 38 | 39 | #### 通讯录删除 40 | 41 | POST: `https://api.aliyundrive.com/adrive/v1/contact/delete` 42 | 43 | ```json 44 | { "ids": [224206708] } 45 | ``` 46 | 47 | Response: 48 | 49 | ```json 50 | {} 51 | ``` 52 | 53 | #### 通讯录备份添加 54 | 55 | POST: `https://api.aliyundrive.com/adrive/v1/contact/add` 56 | 57 | ```json 58 | { 59 | "items": [ 60 | { 61 | "hash": "978edd2d967b94b34d3a90c00cbd4819e239f21a6f72f6a2f87b522fe81a62f4", 62 | "version": "4.0", 63 | "value": "BEGIN:VCARD\r\nVERSION:4.0\r\nN:木;门\r\nTEL;TYPE=cell:03000080981\r\nKIND:individual\r\nEND:VCARD\r\n", 64 | "avatar": "", 65 | "format": "vcard" 66 | } 67 | ] 68 | } 69 | ``` 70 | 71 | Response: 72 | 73 | ```json 74 | { "items": [{ "id": 223963801, "content": { "format": "vcard", "hash": "978edd2d967b94b34d3a90c00cbd4819e239f21a6f72f6a2f87b522fe81a62f4", "version": "4.0" } }] } 75 | ``` 76 | -------------------------------------------------------------------------------- /adrive sdk/filedir.md: -------------------------------------------------------------------------------- 1 | #### 文件夹大小 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v1/file/get_folder_size_info` 4 | 5 | ```json 6 | { "drive_id": "9600002", "file_id": "623b00000000d89ef21d4118838aed83de7575ba" } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { "size": 67200, "folder_count": 0, "file_count": 600, "reach_limit": true } 13 | ``` 14 | 15 | #### 批量读取文件夹封面 16 | 17 | POST: `https://api.aliyundrive.com/adrive/v1/file/cover/batchGet` 18 | 19 | ```json 20 | { "drive_id": "9600002", "file_ids": ["623b00000000d89ef21d4118838aed83de7575ba", "6061000000001af7c3034e3590ea7d5a50f58015"] } 21 | ``` 22 | 23 | Response: 24 | 25 | ```json 26 | { 27 | "items": [ 28 | { 29 | "folder_file_id": "623b00000000d89ef21d4118838aed83de7575ba", 30 | "cover_file_id": "6061000000001af7c3034e3590ea7d5a50f58015", 31 | "cover_file_thumbnail": "https://bj29.cn-beijing.data.alicloudccp.com/RV5OBihM%2F...", 32 | "cover_file_name": "反贪5.mp4", 33 | "cover_file_category": "video" 34 | }, 35 | { "folder_file_id": "623b00000000d89ef21d4118838aed83de7575ba", "cover_file_id": "6061000000001af7c3034e3590ea7d5a50f58015", "cover_file_name": "bbbc", "cover_file_category": "others" } 36 | ] 37 | } 38 | ``` 39 | 40 | #### 读取文件夹封面 41 | 42 | POST: `https://api.aliyundrive.com/adrive/v1/file/cover/get` 43 | 44 | ```json 45 | { "drive_id": "9600002", "file_id": "623b00000000d89ef21d4118838aed83de7575ba" } 46 | ``` 47 | 48 | Response: 49 | 50 | ```json 51 | { 52 | "folder_file_id": "623b00000000d89ef21d4118838aed83de7575ba", 53 | "cover_file_id": "6061000000001af7c3034e3590ea7d5a50f58015", 54 | "cover_file_thumbnail": "https://bj29.cn-beijing.data.alicloudccp.com/RV5OBihM%2F...", 55 | "cover_file_name": "firmware.3911(1).dat", 56 | "cover_file_category": "video" 57 | } 58 | ``` 59 | 60 | #### 设置-读取开启文件夹封面 61 | 62 | POST: `https://api.aliyundrive.com/adrive/v1/file/cover/config/get` 63 | 64 | ```json 65 | {} 66 | ``` 67 | 68 | Response: 69 | 70 | ```json 71 | {"enable":true} 72 | ``` 73 | 74 | #### 设置-保存开启文件夹封面 75 | 76 | POST: `https://api.aliyundrive.com/adrive/v1/file/cover/config/set` 77 | 78 | ```json 79 | {"enable":true} 80 | ``` 81 | 82 | Response: 83 | 84 | ```text 85 | HTTP/1.1 200 OK 86 | ``` 87 | -------------------------------------------------------------------------------- /adrive sdk/offline.md: -------------------------------------------------------------------------------- 1 | #### 离线任务列表 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v1/offline/jobsList` 4 | 5 | ```json 6 | {} 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { "maxResults": 10, "nextToken": "", "result": [] } 13 | ``` 14 | -------------------------------------------------------------------------------- /adrive sdk/recyclebin.md: -------------------------------------------------------------------------------- 1 | #### 列出回收站 2 | 3 | POST: `https://api.aliyundrive.com/v2/recyclebin/list` 4 | 5 | ```json 6 | { "fields": "*", "all": false, "drive_id": "9600002", "limit": 50 } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "items": [ 14 | { 15 | "drive_id": "9600002", 16 | "domain_id": "bj29", 17 | "file_id": "623b00000000d89ef21d4118838aed83de7575ba", 18 | "name": "[1.3.1].mp4", 19 | "type": "file", 20 | "content_type": "application/oct-stream", 21 | "created_at": "2022-01-19T04:51:12.832Z", 22 | "updated_at": "2022-03-10T03:10:04.074Z", 23 | "trashed_at": "2022-03-10T03:10:04.074Z", 24 | "file_extension": "mp4", 25 | "mime_type": "application/octet-stream", 26 | "mime_extension": "unknown", 27 | "hidden": false, 28 | "size": 94814980, 29 | "starred": false, 30 | "status": "available", 31 | "labels": ["艺术品"], 32 | "parent_file_id": "613800000000336ae9164455b135a9729a298c9c", 33 | "crc64_hash": "1548000000008183211", 34 | "content_hash": "4DBF0000000023E6E756C29AF6AC487217921D53", 35 | "content_hash_name": "sha1", 36 | "url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 37 | "thumbnail": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 38 | "category": "video", 39 | "encrypt_mode": "none", 40 | "video_media_metadata": { 41 | "width": 1280, 42 | "height": 720, 43 | "video_media_video_stream": [{ "duration": "1530.680000", "clarity": "720", "fps": "25/1", "code_name": "h264" }], 44 | "video_media_audio_stream": [{ "duration": "1530.581333", "channels": 2, "channel_layout": "stereo", "bit_rate": "143625", "code_name": "aac", "sample_rate": "48000" }], 45 | "duration": "1530.701333" 46 | }, 47 | "punish_flag": 0, 48 | "creator_type": "User", 49 | "creator_id": "9400000000bc480bbcbbb1e074f55a7f", 50 | "creator_name": "myname", 51 | "last_modifier_type": "User", 52 | "last_modifier_id": "9400000000bc480bbcbbb1e074f55a7f", 53 | "last_modifier_name": "myname", 54 | "revision_id": "6138000000000b81a8164550b1e7cba1d7fbe111" 55 | } 56 | ], 57 | "next_marker": "" 58 | } 59 | ``` 60 | 61 | #### 恢复文件 62 | 63 | POST: `https://api.aliyundrive.com/v2/recyclebin/restore` 64 | 65 | ```json 66 | { "file_id": "623b00000000d89ef21d4118838aed83de7575ba", "drive_id": "9600002" } 67 | ``` 68 | 69 | Response: 70 | 71 | ```text 72 | HTTP/1.1 204 No Content 73 | ``` 74 | 75 | #### 删除文件(放入回收站) 76 | 77 | POST: `https://api.aliyundrive.com/v2/recyclebin/trash` 78 | 79 | ```json 80 | { "drive_id": "9600002", "file_id": "623b00000000d89ef21d4118838aed83de7575ba" } 81 | ``` 82 | 83 | Response: 84 | 85 | ```text 86 | HTTP/1.1 204 No Content 87 | ``` 88 | 89 | #### 删除文件(从回收站彻底删除) 90 | 91 | POST: `https://api.aliyundrive.com/v3/file/delete` 92 | 93 | ```json 94 | { "permanently": true, "file_id": "623b00000000d89ef21d4118838aed83de7575ba", "drive_id": "9600002" } 95 | ``` 96 | 97 | Response: 98 | 99 | ```text 100 | HTTP/1.1 204 No Content 101 | ``` 102 | 103 | #### 清空回收站 104 | 105 | POST: `https://api.aliyundrive.com/v2/recyclebin/clear` 106 | 107 | ```json 108 | { "drive_id": "9600002" } 109 | ``` 110 | 111 | Response: 112 | 113 | ```json 114 | { "domain_id": "bj29", "drive_id": "9600002", "task_id": "e026000000007f609bcd6aa71b8fde94" } 115 | ``` 116 | -------------------------------------------------------------------------------- /adrive sdk/reddot.md: -------------------------------------------------------------------------------- 1 | #### 订阅的账号有更新 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v1/reddot/get` 4 | 5 | ```json 6 | {} 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "items": [ 14 | { 15 | "code": "followed_user_has_new_activity", 16 | "context": { 17 | "creator": { 18 | "description": "中国国家地理景观官方账号,带你领略目酣神醉的壮美景观、发现中国各地独具特色的人文胜迹。", 19 | "avatar": "https://ccp-bj29-bj-1592982087.oss-cn-beijing.aliyuncs.com/2GhCur3G%2F...", 20 | "user_id": "9400000000bc480bbcbbb1e074f55a7f", 21 | "nick_name": "中国国家地理景观", 22 | "phone": "136***902" 23 | } 24 | } 25 | } 26 | ] 27 | } 28 | ``` 29 | 30 | #### 标记已读 31 | 32 | POST: `https://api.aliyundrive.com/adrive/v1/reddot/read` 33 | 34 | ```json 35 | {"code":"followed_user_has_new_activity"} 36 | ``` 37 | 38 | Response: 39 | 40 | ```json 41 | {} 42 | ``` 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /adrive sdk/sbox.md: -------------------------------------------------------------------------------- 1 | #### 保险箱 2 | 3 | POST: `https://api.aliyundrive.com/v2/sbox/get` 4 | 5 | ```json 6 | {} 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { "drive_id": "9600002", "sbox_used_size": 0, "sbox_real_used_size": 0, "sbox_total_size": 53687091200, "recommend_vip": "svip", "pin_setup": true, "locked": true, "insurance_enabled": false } 13 | ``` 14 | 15 | #### 解锁 16 | 17 | POST: `https://api.aliyundrive.com/v2/sbox/unlock` 18 | 19 | ```json 20 | { 21 | "drive_id": "9600002", 22 | "app_id": "25dzX3vbYqktVxyX", 23 | "encrypted_pin": "pteN00000000/gLZpQaFKA==", 24 | "encrypted_key": "nNaV......r13doYbpmJxag==" 25 | } 26 | ``` 27 | 28 | Response: 29 | 30 | ```json 31 | { "drive_id": "9600002" } 32 | ``` 33 | 34 | #### 重新锁定 35 | 36 | POST: `https://api.aliyundrive.com/v2/sbox/lock` 37 | 38 | ```json 39 | {"drive_id":"9600002"} 40 | ``` 41 | 42 | Response: 43 | 44 | ```json 45 | { "drive_id": "9600002" } 46 | ``` 47 | -------------------------------------------------------------------------------- /adrive sdk/search.md: -------------------------------------------------------------------------------- 1 | #### 首页 widgets 2 | 3 | POST: `https://api.aliyundrive.com/v2/file/search` 4 | 5 | ```json 6 | //截图 7 | { 8 | "return_total_count": true, 9 | "image_thumbnail_process": "image/resize,m_lfit,w_256,limit_0/format,jpg", 10 | "order_by": "last_access_at DESC,updated_at DESC,image_time DESC", 11 | "query": "(label = '手机截图' or label = '截图') and category in ['video','image'] and status = 'available' and hidden = false", 12 | "limit": 100, 13 | "drive_id": "9600002" 14 | } 15 | ``` 16 | 17 | ```json 18 | //证件 19 | { 20 | "return_total_count": true, 21 | "image_thumbnail_process": "image/resize,m_lfit,w_256,limit_0/format,jpg", 22 | "order_by": "last_access_at DESC,updated_at DESC,image_time DESC", 23 | "query": "(label = '身份证明' or label = '证件' or label = '身份证' or label = '银行卡' or label = '护照') and category in ['video','image'] and status = 'available' and hidden = false", 24 | "limit": 100, 25 | "drive_id": "9600002" 26 | } 27 | ``` 28 | 29 | ```json 30 | //最近图片 31 | { 32 | "return_total_count": true, 33 | "image_thumbnail_process": "image/resize,m_lfit,w_256,limit_0/format,jpg", 34 | "order_by": "last_access_at DESC,updated_at DESC,image_time DESC", 35 | "query": "category = 'image' and status = 'available' and hidden = false", 36 | "limit": 100, 37 | "drive_id": "9600002" 38 | } 39 | ``` 40 | 41 | ```json 42 | //最近视频 43 | { 44 | "return_total_count": true, 45 | "image_thumbnail_process": "image/resize,m_lfit,w_256,limit_0/format,jpg", 46 | "order_by": "last_access_at DESC,updated_at DESC,image_time DESC", 47 | "query": "category = 'video' and status = 'available' and hidden = false", 48 | "limit": 100, 49 | "drive_id": "9600002" 50 | } 51 | ``` 52 | 53 | Response: 54 | 55 | ```json 56 | filelist 57 | ``` 58 | 59 | #### 列出人物(face)的图片 60 | 61 | POST: `https://api.aliyundrive.com/v2/file/search` 62 | 63 | ```json 64 | { 65 | "drive_id": "9600002", 66 | "limit": 100, 67 | "order_by": "created_at DESC", 68 | "query": "type = 'file' and category in ['image', 'video'] and face_group_id = 'Group-00000000-1703-4fc0-bf56-369478ed14df' and status = 'available' and hidden = false", 69 | "return_total_count": true 70 | } 71 | ``` 72 | 73 | Response: 74 | 75 | ```json 76 | filelist 77 | ``` 78 | -------------------------------------------------------------------------------- /adrive sdk/sfiia.md: -------------------------------------------------------------------------------- 1 | #### 文件中的图片 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v1/sfiia/get_recommends` 4 | 5 | ```json 6 | { "drive_id": "9600002" } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "items": [ 14 | { 15 | "category": "image", 16 | "content_hash": "4DBF0000000023E6E756C29AF6AC487217921D53", 17 | "content_hash_name": "sha1", 18 | "content_type": "application/oct-stream", 19 | "crc64_hash": "1548000000008183211", 20 | "created_at": "2021-10-16T02:10:51.625Z", 21 | "domain_id": "bj29", 22 | "download_url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 23 | "drive_id": "9600002", 24 | "encrypt_mode": "none", 25 | "file_extension": "png", 26 | "file_id": "623b00000000d89ef21d4118838aed83de7575ba", 27 | "hidden": false, 28 | "labels": ["衣服", "外貌特征", "其他事物", "艺术品", "笑脸", "墨镜", "护目镜", "微笑", "颜色", "动画", "黄色"], 29 | "mime_type": "image/png", 30 | "name": "cool_11.png", 31 | "parent_file_id": "613800000000336ae9164455b135a9729a298c9c", 32 | "punish_flag": 0, 33 | "size": 80480, 34 | "starred": false, 35 | "status": "available", 36 | "thumbnail": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F...", 37 | "type": "file", 38 | "updated_at": "2021-10-16T02:10:51.625Z", 39 | "upload_id": "ED12000000004724833D47B5D5D3C8B9", 40 | "url": "https://bj29.cn-beijing.data.alicloudccp.com/2GhCur3G%2F..." 41 | } 42 | ], 43 | "total_image_count": 28943 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /adrive sdk/timeline.md: -------------------------------------------------------------------------------- 1 | #### 用户信息 2 | 3 | POST: `https://api.aliyundrive.com/adrive/v1/timeline/user/get` 4 | 5 | ```json 6 | { "user_id": "9400000000bc480bbcbbb1e074f55a7f" } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "description": "", 14 | "avatar": "https://ccp-bj29-bj-1592982087.oss-cn-beijing.aliyuncs.com/2GhCur3G%2F...", 15 | "user_id": "9400000000bc480bbcbbb1e074f55a7f", 16 | "nick_name": "myname", 17 | "phone": "151***111", 18 | "is_following": false, 19 | "follower_count": 0, 20 | "homepage_visibility": 1, 21 | "latest_messages": [], 22 | "homepage_visible_time_range_text": "三个月", 23 | "homepage_visible_time_range_in_millis": 7776000000 24 | } 25 | ``` 26 | 27 | #### 用户发布的动态 28 | 29 | POST: `https://api.aliyundrive.com/adrive/v1/timeline/homepage/list_message` 30 | 31 | ```json 32 | { "order_direction": "DESC", "user_id": "9400000000bc480bbcbbb1e074f55a7f", "limit": 10 } 33 | ``` 34 | 35 | Response: 36 | 37 | ```json 38 | { "items": [], "pin_items": [] } 39 | ``` 40 | 41 | #### 推荐订阅 42 | 43 | POST: `https://api.aliyundrive.com/adrive/v1/timeline/user/recommend` 44 | 45 | ```json 46 | { "user_id": "9400000000bc480bbcbbb1e074f55a7f", "limit": 20, "order_by": "updated_at", "order_direction": "DESC" } 47 | ``` 48 | 49 | Response: 50 | 51 | ```json 52 | { 53 | "items": [ 54 | { 55 | "description": "Hi~小可爱!感恩关注~盘盘酱会不定时发放福利哦!让盘酱陪伴你更久✧( •˓◞•̀ ) ", 56 | "avatar": "https://ccp-bj29-bj-1592982087.oss-cn-beijing.aliyuncs.com/2GhCur3G%2F...", 57 | "user_id": "9400000000bc480bbcbbb1e074f55a7f", 58 | "nick_name": "阿里盘盘酱", 59 | "phone": "131***325", 60 | "is_following": true 61 | }, 62 | { 63 | "description": "中国国家地理景观官方账号,带你领略目酣神醉的壮美景观、发现中国各地独具特色的人文胜迹。", 64 | "avatar": "https://ccp-bj29-bj-1592982087.oss-cn-beijing.aliyuncs.com/2GhCur3G%2F...", 65 | "user_id": "9400000000bc480bbcbbb1e074f55a7f", 66 | "nick_name": "中国国家地理景观", 67 | "phone": "136***902", 68 | "is_following": true 69 | } 70 | ], 71 | "next_marker": "MjA=" 72 | } 73 | ``` 74 | -------------------------------------------------------------------------------- /adrive sdk/token.md: -------------------------------------------------------------------------------- 1 | #### 网页版登录 2 | 3 | POST: `https://api.aliyundrive.com/token/get` 4 | 5 | ```json 6 | { "code": "f98788cef51641728f2aad9c64a96a63", "loginType": "normal", "deviceId": "CPH800000000AbfFPI5QSJjO" } 7 | ``` 8 | 9 | Response: 10 | 11 | ```json 12 | { 13 | "default_sbox_drive_id": "9600002", 14 | "role": "user", 15 | "user_name": "151***111", 16 | "need_link": false, 17 | "expire_time": "2022-03-21T09:48:46Z", 18 | "pin_setup": true, 19 | "need_rp_verify": false, 20 | "avatar": "https://ccp-bj29-bj-1592982087.oss-cn-beijing.aliyuncs.com/2GhCur3G%2F...", 21 | "user_data": { 22 | "DingDingRobotUrl": "https://oapi.dingtalk.com/robot/send?access_token=0b4a00000000c08608cd99f693393c18fa905aa0868215485a28497501916fec", 23 | "EncourageDesc": "内测期间有效反馈前10名用户将获得终身免费会员", 24 | "FeedBackSwitch": true, 25 | "FollowingDesc": "34848372", 26 | "ding_ding_robot_url": "https://oapi.dingtalk.com/robot/send?access_token=0b4a00000000c08608cd99f693393c18fa905aa0868215485a28497501916fec", 27 | "encourage_desc": "内测期间有效反馈前10名用户将获得终身免费会员", 28 | "feed_back_switch": true, 29 | "following_desc": "34848372" 30 | }, 31 | "token_type": "Bearer", 32 | "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9......aixJ4k", 33 | "default_drive_id": "9600002", 34 | "domain_id": "bj29", 35 | "refresh_token": "82ad000000004fbda61b01b5a5cf103b", 36 | "is_first_login": false, 37 | "user_id": "9400000000bc480bbcbbb1e074f55a7f", 38 | "nick_name": "mynane", 39 | "exist_link": [], 40 | "state": "", 41 | "expires_in": 7200, 42 | "status": "enabled" 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/app.ico -------------------------------------------------------------------------------- /app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/app.png -------------------------------------------------------------------------------- /crx/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /crx/devtools.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.network.onRequestFinished.addListener(function (detail) { 2 | let url = detail.request.url; 3 | 4 | let isbreak = false; 5 | if (url.indexOf("api.aliyundrive.com") > 0) isbreak = true; /** 跳过api */ 6 | if (url.indexOf("img.aliyundrive.com") > 0) isbreak = true; /** 跳过img */ 7 | if (url.indexOf("_tmd_") > 0) isbreak = true; /** 跳过滑动验证 */ 8 | if (url.indexOf(".aliyuncs.com") > 0) isbreak = true; /** 跳过滑动验证 */ 9 | if (url.indexOf(".aliyun.com") > 0) isbreak = true; /** 跳过滑动验证 */ 10 | if (url.indexOf(".taobao.com") > 0) isbreak = true; /** 跳过滑动验证 */ 11 | if (url.indexOf(".mmstat.com") > 0) isbreak = true; /** 跳过日志 */ 12 | 13 | if (url.indexOf(".aliyundrive.com") < 0) isbreak = true; /** 跳过无效域名 */ 14 | if (isbreak) return; 15 | 16 | detail.getContent(function (content, mimeType) { 17 | try { 18 | if (typeof content == "string" && content.indexOf('"bizExt"') > 0) { 19 | let bizExt = ""; 20 | try { 21 | /** https://passport.aliyundrive.com/newlogin/login.do?appName=aliyun_drive&fromSite=52&_bx-v=2.0.31 */ 22 | const data = JSON.parse(content); 23 | bizExt = data.content?.data?.bizExt || ""; 24 | } catch (e) { 25 | bizExt = ""; 26 | chrome.devtools.inspectedWindow.eval( 27 | "console.log('" + JSON.stringify({ url, e, content }) + "')" 28 | ); 29 | } 30 | 31 | if (!bizExt) { 32 | /** https://passport.aliyundrive.com/newlogin/safe/ivCheckLogin.htm?havana_iv_token=... 二次短信验证 */ 33 | try { 34 | let temp = content.substring( 35 | content.indexOf('"bizExt"') + '"bizExt"'.length 36 | ); 37 | temp = temp.substring(temp.indexOf('"') + 1); // :"eyJ...", 38 | temp = temp.substring(0, temp.indexOf('"')); //eyJ... 39 | 40 | if (temp.startsWith("eyJ")) bizExt = temp; 41 | } catch (e) { 42 | bizExt = ""; 43 | chrome.devtools.inspectedWindow.eval( 44 | "console.log('" + JSON.stringify({ url, e, content }) + "')" 45 | ); 46 | } 47 | } 48 | 49 | if (bizExt) { 50 | chrome.devtools.inspectedWindow.eval( 51 | "console.log('" + JSON.stringify({ bizExt: bizExt }) + "')" 52 | ); 53 | } 54 | } 55 | } catch {} 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /crx/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "demo", 4 | "version": "1.0.0", 5 | "description": "demo", 6 | "devtools_page": "devtools.html", 7 | "host_permissions": [ 8 | "http://*/*", 9 | "https://*/*" 10 | ] 11 | } -------------------------------------------------------------------------------- /electron-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "appId": "YouAppID", 3 | "asar": true, 4 | "directories": { 5 | "output": "release" 6 | }, 7 | "files": [ 8 | "dist" 9 | ], 10 | "mac": { 11 | "artifactName": "${productName}_${version}.${ext}", 12 | "target": [ 13 | "dmg" 14 | ] 15 | }, 16 | "win": { 17 | "target": [ 18 | { 19 | "target": "nsis", 20 | "arch": [ 21 | "x64" 22 | ] 23 | } 24 | ], 25 | "artifactName": "${productName}_${version}.${ext}" 26 | }, 27 | "nsis": { 28 | "oneClick": false, 29 | "perMachine": false, 30 | "allowToChangeInstallationDirectory": true, 31 | "deleteAppDataOnUninstall": false 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /electron/main/utils/index.ts: -------------------------------------------------------------------------------- 1 | import net from 'net' 2 | 3 | export function portIsOccupied(port: number) { 4 | 5 | const server = net.createServer().listen(port, '0.0.0.0') 6 | 7 | return new Promise((resolve, reject) => { 8 | 9 | server.on('listening', () => { 10 | console.log(`the server is runnint on port ${port}`) 11 | server.close() 12 | resolve(port) // 返回可用端口 13 | }) 14 | 15 | server.on('error', (err: any) => { 16 | if (err.code === 'EADDRINUSE') { 17 | resolve(portIsOccupied(port + 1)) // 如传入端口号被占用则 +1 18 | console.log(`this port ${port} is occupied.try another.`) 19 | } else { 20 | console.log(err) 21 | // reject(err) 22 | resolve(port) 23 | } 24 | }) 25 | 26 | }) 27 | 28 | } 29 | -------------------------------------------------------------------------------- /electron/preload/preload-env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | declare namespace NodeJS { 3 | interface ProcessEnv { 4 | NODE_ENV: 'development' | 'production' 5 | readonly VITE_DEV_SERVER_HOST: string 6 | readonly VITE_DEV_SERVER_PORT: string 7 | } 8 | } 9 | declare interface Window { 10 | Electron: any 11 | platform: any 12 | WinMsg: any 13 | WebToElectron: any 14 | WebToElectronCB: any 15 | WebSpawnSync: any 16 | WebExecSync: any 17 | WebShowOpenDialogSync: any 18 | WebShowSaveDialogSync: any 19 | WebShowItemInFolder: any 20 | WebPlatformSync: any 21 | WebClearCookies: any 22 | WebClearCache: any 23 | WebUserToken: any 24 | WebSaveTheme: any 25 | WebReload: any 26 | WebRelaunch: any 27 | WebRelaunchAria: () => Promise 28 | WebSetProgressBar: any 29 | WebSetCookies: any 30 | WebOpenWindow: any 31 | WebOpenUrl: any 32 | WebShutDown: any 33 | WebSetProxy: any 34 | } 35 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 阿里云盘小白羊版 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 |
35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /nano-staged.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | // eslint 3 | '*.{js,ts,tsx,jsx}': ['prettier --write', 'eslint --cache --fix'], 4 | '*.{vue}': ['stylelint --fix', 'prettier --write', 'eslint --cache --fix'], 5 | '*.{less,css}': ['stylelint --fix', 'prettier --write'], 6 | // typecheck 7 | 'packages/renderer/**/{*.ts,*.tsx,*.vue,tsconfig.json}': ({ filenames }) => 'npm run typecheck' 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alixby", 3 | "version": "3.09.13", 4 | "main": "dist/electron/main/index.js", 5 | "author": "", 6 | "license": "", 7 | "scripts": { 8 | "dev": "vite", 9 | "build": "vue-tsc --noEmit && vite build && electron-builder" 10 | }, 11 | "engines": { 12 | "node": ">=14.17.0" 13 | }, 14 | "dependencies": {}, 15 | "devDependencies": { 16 | "@arco-design/web-vue": "^2.36.0", 17 | "@electron/remote": "^2.0.8", 18 | "@types/lodash": "^4.14.184", 19 | "@types/node": "^17.0.45", 20 | "@vitejs/plugin-vue": "^3.1.0", 21 | "@vitejs/plugin-vue-jsx": "^2.0.1", 22 | "ant-design-vue": "^3.2.12", 23 | "aria2-lib": "1.0.1", 24 | "axios": "^0.27.2", 25 | "dayjs": "^1.11.5", 26 | "dexie": "^3.2.2", 27 | "dom-to-image": "^2.6.0", 28 | "electron": "^20.1.1", 29 | "electron-builder": "^23.3.3", 30 | "fuzzysort": "^2.0.1", 31 | "isomorphic-fetch": "^3.0.0", 32 | "jschardet": "^3.0.0", 33 | "lodash": "^4.17.21", 34 | "pinia": "^2.0.22", 35 | "socks-proxy-agent": "^7.0.0", 36 | "terser": "^5.15.0", 37 | "typescript": "^4.8.2", 38 | "unplugin-vue-components": "^0.22.4", 39 | "viewerjs": "^1.10.5", 40 | "vite": "^3.1.0", 41 | "vite-plugin-electron": "^0.9.2", 42 | "vite-plugin-resolve": "^2.1.2", 43 | "vue": "^3.2.38", 44 | "vue-tsc": "^0.40.10" 45 | }, 46 | "debug": { 47 | "env": { 48 | "VITE_DEV_SERVER_HOSTNAME": "127.0.0.1", 49 | "VITE_DEV_SERVER_PORT": 3344, 50 | "VITE_DEV_SERVER_URL": "http://127.0.0.1:3344" 51 | } 52 | }, 53 | "keywords": [ 54 | "electron", 55 | "rollup", 56 | "vite", 57 | "vue3", 58 | "vue" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/favicon.ico -------------------------------------------------------------------------------- /public/font/VideoJS.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/font/VideoJS.woff -------------------------------------------------------------------------------- /public/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/iconfont.woff2 -------------------------------------------------------------------------------- /public/imgerror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/imgerror.png -------------------------------------------------------------------------------- /public/lang/en.js: -------------------------------------------------------------------------------- 1 | videojs.addLanguage('en', { 2 | "Audio Player": "Audio Player", 3 | "Video Player": "Video Player", 4 | "Play": "Play", 5 | "Pause": "Pause", 6 | "Replay": "Replay", 7 | "Current Time": "Current Time", 8 | "Duration": "Duration", 9 | "Remaining Time": "Remaining Time", 10 | "Stream Type": "Stream Type", 11 | "LIVE": "LIVE", 12 | "Seek to live, currently behind live": "Seek to live, currently behind live", 13 | "Seek to live, currently playing live": "Seek to live, currently playing live", 14 | "Loaded": "Loaded", 15 | "Progress": "Progress", 16 | "Progress Bar": "Progress Bar", 17 | "progress bar timing: currentTime={1} duration={2}": "{1} of {2}", 18 | "Fullscreen": "Fullscreen", 19 | "Non-Fullscreen": "Exit Fullscreen", 20 | "Mute": "Mute", 21 | "Unmute": "Unmute", 22 | "Playback Rate": "Playback Rate", 23 | "Subtitles": "Subtitles", 24 | "subtitles off": "subtitles off", 25 | "Captions": "Captions", 26 | "captions off": "captions off", 27 | "Chapters": "Chapters", 28 | "Descriptions": "Descriptions", 29 | "descriptions off": "descriptions off", 30 | "Audio Track": "Audio Track", 31 | "Volume Level": "Volume Level", 32 | "You aborted the media playback": "You aborted the media playback", 33 | "A network error caused the media download to fail part-way.": "A network error caused the media download to fail part-way.", 34 | "The media could not be loaded, either because the server or network failed or because the format is not supported.": "The media could not be loaded, either because the server or network failed or because the format is not supported.", 35 | "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.", 36 | "No compatible source was found for this media.": "No compatible source was found for this media.", 37 | "The media is encrypted and we do not have the keys to decrypt it.": "The media is encrypted and we do not have the keys to decrypt it.", 38 | "Play Video": "Play Video", 39 | "Close": "Close", 40 | "Close Modal Dialog": "Close Modal Dialog", 41 | "Modal Window": "Modal Window", 42 | "This is a modal window": "This is a modal window", 43 | "This modal can be closed by pressing the Escape key or activating the close button.": "This modal can be closed by pressing the Escape key or activating the close button.", 44 | ", opens captions settings dialog": ", opens captions settings dialog", 45 | ", opens subtitles settings dialog": ", opens subtitles settings dialog", 46 | ", opens descriptions settings dialog": ", opens descriptions settings dialog", 47 | ", selected": ", selected", 48 | "captions settings": "captions settings", 49 | "subtitles settings": "subtitles settings", 50 | "descriptions settings": "descriptions settings", 51 | "Text": "Text", 52 | "White": "White", 53 | "Black": "Black", 54 | "Red": "Red", 55 | "Green": "Green", 56 | "Blue": "Blue", 57 | "Yellow": "Yellow", 58 | "Magenta": "Magenta", 59 | "Cyan": "Cyan", 60 | "Background": "Background", 61 | "Window": "Window", 62 | "Transparent": "Transparent", 63 | "Semi-Transparent": "Semi-Transparent", 64 | "Opaque": "Opaque", 65 | "Font Size": "Font Size", 66 | "Text Edge Style": "Text Edge Style", 67 | "None": "None", 68 | "Raised": "Raised", 69 | "Depressed": "Depressed", 70 | "Uniform": "Uniform", 71 | "Dropshadow": "Dropshadow", 72 | "Font Family": "Font Family", 73 | "Proportional Sans-Serif": "Proportional Sans-Serif", 74 | "Monospace Sans-Serif": "Monospace Sans-Serif", 75 | "Proportional Serif": "Proportional Serif", 76 | "Monospace Serif": "Monospace Serif", 77 | "Casual": "Casual", 78 | "Script": "Script", 79 | "Small Caps": "Small Caps", 80 | "Reset": "Reset", 81 | "restore all settings to the default values": "restore all settings to the default values", 82 | "Done": "Done", 83 | "Caption Settings Dialog": "Caption Settings Dialog", 84 | "Beginning of dialog window. Escape will cancel and close the window.": "Beginning of dialog window. Escape will cancel and close the window.", 85 | "End of dialog window.": "End of dialog window.", 86 | "{1} is loading.": "{1} is loading.", 87 | "Exit Picture-in-Picture": "Exit Picture-in-Picture", 88 | "Picture-in-Picture": "Picture-in-Picture" 89 | }); -------------------------------------------------------------------------------- /public/lang/zh-CN.js: -------------------------------------------------------------------------------- 1 | videojs.addLanguage('zh-CN', { 2 | "Play": "播放", 3 | "Pause": "暂停", 4 | "Current Time": "当前时间", 5 | "Duration": "时长", 6 | "Remaining Time": "剩余时间", 7 | "Stream Type": "媒体流类型", 8 | "LIVE": "直播", 9 | "Loaded": "加载完成", 10 | "Progress": "进度", 11 | "Fullscreen": "全屏", 12 | "Non-Fullscreen": "退出全屏", 13 | "Picture-in-Picture": "画中画", 14 | "Exit Picture-in-Picture": "退出画中画", 15 | "Mute": "静音", 16 | "Unmute": "取消静音", 17 | "Playback Rate": "播放速度", 18 | "Subtitles": "字幕", 19 | "subtitles off": "关闭字幕", 20 | "Captions": "内嵌字幕", 21 | "captions off": "关闭内嵌字幕", 22 | "Chapters": "节目段落", 23 | "Close Modal Dialog": "关闭弹窗", 24 | "Descriptions": "描述", 25 | "descriptions off": "关闭描述", 26 | "Audio Track": "音轨", 27 | "You aborted the media playback": "视频播放被终止", 28 | "A network error caused the media download to fail part-way.": "网络错误导致视频下载中途失败。", 29 | "The media could not be loaded, either because the server or network failed or because the format is not supported.": "视频因格式不支持或者服务器或网络的问题无法加载。", 30 | "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "由于视频文件损坏或是该视频使用了你的浏览器不支持的功能,播放终止。", 31 | "No compatible source was found for this media.": "无法找到此视频兼容的源。", 32 | "The media is encrypted and we do not have the keys to decrypt it.": "视频已加密,无法解密。", 33 | "Play Video": "播放视频", 34 | "Close": "关闭", 35 | "Modal Window": "弹窗", 36 | "This is a modal window": "这是一个弹窗", 37 | "This modal can be closed by pressing the Escape key or activating the close button.": "可以按ESC按键或启用关闭按钮来关闭此弹窗。", 38 | ", opens captions settings dialog": ", 开启标题设置弹窗", 39 | ", opens subtitles settings dialog": ", 开启字幕设置弹窗", 40 | ", opens descriptions settings dialog": ", 开启描述设置弹窗", 41 | ", selected": ", 选择", 42 | "captions settings": "字幕设定", 43 | "Audio Player": "音频播放器", 44 | "Video Player": "视频播放器", 45 | "Replay": "重新播放", 46 | "Progress Bar": "进度条", 47 | "Volume Level": "音量", 48 | "subtitles settings": "字幕设定", 49 | "descriptions settings": "描述设定", 50 | "Text": "文字", 51 | "White": "白", 52 | "Black": "黑", 53 | "Red": "红", 54 | "Green": "绿", 55 | "Blue": "蓝", 56 | "Yellow": "黄", 57 | "Magenta": "紫红", 58 | "Cyan": "青", 59 | "Background": "背景", 60 | "Window": "窗口", 61 | "Transparent": "透明", 62 | "Semi-Transparent": "半透明", 63 | "Opaque": "不透明", 64 | "Font Size": "字体尺寸", 65 | "Text Edge Style": "字体边缘样式", 66 | "None": "无", 67 | "Raised": "浮雕", 68 | "Depressed": "压低", 69 | "Uniform": "均匀", 70 | "Dropshadow": "下阴影", 71 | "Font Family": "字体库", 72 | "Proportional Sans-Serif": "比例无细体", 73 | "Monospace Sans-Serif": "单间隔无细体", 74 | "Proportional Serif": "比例细体", 75 | "Monospace Serif": "单间隔细体", 76 | "Casual": "舒适", 77 | "Script": "手写体", 78 | "Small Caps": "小型大写字体", 79 | "Reset": "重置", 80 | "restore all settings to the default values": "恢复全部设定至预设值", 81 | "Done": "完成", 82 | "Caption Settings Dialog": "字幕设定窗口", 83 | "Beginning of dialog window. Escape will cancel and close the window.": "打开对话窗口。Escape键将取消并关闭对话窗口", 84 | "End of dialog window.": "结束对话窗口", 85 | "Seek to live, currently behind live": "尝试直播,当前为延时播放", 86 | "Seek to live, currently playing live": "尝试直播,当前为实时播放", 87 | "progress bar timing: currentTime={1} duration={2}": "{1}/{2}", 88 | "{1} is loading.": "正在加载 {1}。", 89 | "Open quality selector menu":"选择清晰度" 90 | }); -------------------------------------------------------------------------------- /public/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/loading.png -------------------------------------------------------------------------------- /public/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 阿里云盘小白羊版 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /public/main2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 阿里云盘小白羊版 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/notify.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/notify.wav -------------------------------------------------------------------------------- /public/sha1filework.js: -------------------------------------------------------------------------------- 1 | const worker = self 2 | const fspromises = global.require('fs/promises') 3 | var crypto = global.require('crypto') 4 | let running = false 5 | process.noAsar = true 6 | worker.addEventListener('message', (event) => { 7 | if (event.data.hash && event.data.hash == 'sha1') { 8 | const localFilePath = event.data.localFilePath 9 | const access_token = event.data.access_token 10 | fileSha1(localFilePath, access_token).catch((e) => { 11 | worker.postMessage({ hash: 'sha1', sha1: 'error', proof_code: '', error: FileSystemErrorMessage(e.code, e.message) }) 12 | running = false 13 | }) 14 | } 15 | if (event.data.stop) { 16 | running = false 17 | } 18 | if (event.data.close) { 19 | close() 20 | } 21 | }) 22 | worker.addEventListener('error', (e) => { 23 | worker.postMessage({ hash: 'sha1', sha1: 'error', proof_code: '', error: FileSystemErrorMessage(e.code, e.message) }) 24 | running = false 25 | }) 26 | 27 | async function fileSha1(localFilePath, access_token) { 28 | running = true 29 | let hash = '' 30 | var sha1 = crypto.createHash('sha1') 31 | const fileHandle = await fspromises.open(localFilePath, 'r') 32 | try { 33 | if (fileHandle) { 34 | const stat = await fileHandle.stat() 35 | const size = stat.size 36 | const buff = Buffer.alloc(4 * 1024 * 1024) 37 | let readlen = 0 38 | 39 | let timer = setInterval(function () { 40 | const message = '计算sha1(' + Math.floor((readlen * 100) / size).toString() + '%)' 41 | worker.postMessage({ hash: 'sha1', readlen, size, message }) 42 | }, 300) 43 | 44 | while (true) { 45 | if (!running) break 46 | const len = await fileHandle.read(buff, 0, buff.length, null) 47 | if (len.bytesRead > 0 && len.bytesRead == buff.length) { 48 | sha1.update(buff) 49 | } else if (len.bytesRead > 0) { 50 | sha1.update(buff.slice(0, len.bytesRead)) 51 | } 52 | readlen += len.bytesRead 53 | 54 | if (len.bytesRead <= 0) break 55 | } 56 | clearInterval(timer) 57 | let proof_code = '' 58 | if (running) { 59 | hash = sha1.digest('hex') 60 | hash = hash.toUpperCase() 61 | const m = unescape(encodeURIComponent(access_token)) 62 | const buffa = Buffer.from(m) 63 | const md5a = crypto.createHash('md5').update(buffa).digest('hex') 64 | const start = Number(BigInt('0x' + md5a.substr(0, 16)) % BigInt(size)) 65 | const end = Math.min(start + 8, size) 66 | const buffb = Buffer.alloc(end - start) 67 | await fileHandle.read(buffb, 0, buffb.length, start) 68 | proof_code = buffb.toString('base64') 69 | } else { 70 | hash = 'error' 71 | proof_code = '' 72 | /** 这里无所谓了,外部会error='' */ 73 | } 74 | 75 | worker.postMessage({ hash: 'sha1', sha1: hash, proof_code, error: '' }) 76 | running = false 77 | } else { 78 | worker.postMessage({ hash: 'sha1', sha1: 'error', proof_code: '', error: '打开文件失败' }) 79 | running = false 80 | } 81 | } catch {} 82 | try { 83 | await fileHandle?.close() 84 | } catch {} 85 | } 86 | 87 | function FileSystemErrorMessage(code, message) { 88 | if (!code && !message) return '读取文件失败' 89 | 90 | if (code) { 91 | switch (code) { 92 | case 'EACCES': 93 | return '没有权限访问' 94 | case 'EEXIST': 95 | return '存在重名文件' 96 | case 'EISDIR': 97 | return '不能是文件夹' 98 | case 'EMFILE': 99 | return '同时打开文件过多' 100 | case 'ENFILE': 101 | return '同时打开文件过多' 102 | case 'ENOENT': 103 | return '该路径不存在' 104 | case 'ENOTDIR': 105 | return '不能是文件' 106 | case 'ENOTEMPTY': 107 | return '文件夹不为空' 108 | case 'EPERM': 109 | return '没有权限访问' 110 | case 'EBUSY': 111 | return '文件被其他程序占用' 112 | case 'ETIMEDOUT': 113 | return '操作超时' 114 | case 'EDQUOT': 115 | return '超出磁盘配额' 116 | case 'EFBIG': 117 | return '文件太大' 118 | case 'EIDRM': 119 | return '文件已被删除' 120 | case 'EIO': 121 | return 'IO错误' 122 | case 'ELOOP': 123 | return '路径级别过多' 124 | case 'ENAMETOOLONG': 125 | return '文件名太长' 126 | case 'ENODEV': 127 | return '找不到设备' 128 | case 'ENOMEM': 129 | return '没有足够的空间' 130 | case 'ENOSPC': 131 | return '没有可用空间' 132 | case 'EROFS': 133 | return '只读文件' 134 | } 135 | } 136 | if (message && typeof message == 'string' && message.indexOf('EACCES') >= 0) return '没有权限访问' 137 | let err = (code || '') + (message || '') 138 | if (err) return err 139 | return '读取文件失败' 140 | } 141 | -------------------------------------------------------------------------------- /public/silvermine-videojs-quality-selector.css: -------------------------------------------------------------------------------- 1 | .vjs-quality-selector .vjs-menu-button { 2 | margin: 0; 3 | padding: 0; 4 | height: 100%; 5 | width: 100%; 6 | } 7 | .vjs-quality-selector .vjs-icon-placeholder { 8 | font-family: 'VideoJS'; 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | .vjs-quality-selector .vjs-icon-placeholder::before { 13 | content: '\f110'; 14 | } 15 | .vjs-quality-changing .vjs-big-play-button { 16 | display: none; 17 | } 18 | .vjs-quality-changing .vjs-control-bar { 19 | display: -webkit-box; 20 | display: -webkit-flex; 21 | display: flex; 22 | visibility: visible; 23 | opacity: 1; 24 | } 25 | 26 | .vjs-quality-selector .vjs-menu{ 27 | width: 6em; 28 | left: -1em; 29 | } 30 | -------------------------------------------------------------------------------- /public/userface.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/userface.png -------------------------------------------------------------------------------- /public/wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/public/wasm.wasm -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/aliapi/batch.ts: -------------------------------------------------------------------------------- 1 | import message from '../utils/message' 2 | 3 | 4 | export async function RunBatch(title: string, list: any[], max: number, func: (t: any) => Promise) { 5 | const loadingKey = 'runbatch' + Date.now().toString() + '_' + title 6 | if (title) message.success('正在执行' + title + '( 0 / ' + list.length + ' )', 0, loadingKey) 7 | let parr: Promise[] = [] 8 | for (let i = 0, maxi = list.length; i < maxi; i++) { 9 | parr.push(func(list[i])) 10 | if (parr.length == max) { 11 | await Promise.all(parr) 12 | .catch(() => {}) 13 | .then(() => { 14 | parr = [] 15 | }) 16 | if (title) message.success('正在执行' + title + '( ' + i + ' / ' + maxi + ' )', 0, loadingKey) 17 | } 18 | } 19 | if (parr.length > 0) 20 | await Promise.all(parr) 21 | .catch(() => {}) 22 | .then(() => { 23 | parr = [] 24 | }) 25 | 26 | if (title) message.success('成功执行' + title, 1, loadingKey) 27 | } 28 | -------------------------------------------------------------------------------- /src/aliapi/fileicon.ts: -------------------------------------------------------------------------------- 1 | export default function getFileIcon(category: string | undefined, ext: string | undefined, mimext: string | undefined, mime: string | undefined, size: number): string[] { 2 | if (!ext) ext = '' 3 | if (!mime) mime = '' 4 | if (!mimext) mimext = '' 5 | if (!category) category = 'others' 6 | 7 | /** 8 | * 1、图片支持以下格式:JPEG、BMP、PNG、JPG 9 | * 2、视频文件支持以下格式:MP4、3GP、AVI、FLV、Webm、MOV、AMR、ASF、VCD(MPEG-1 video)、DVD(MPEG-2)、M4V、3G2、MJPEG、DATA、AVI(H261,H263,H264)、DV、GXF、CAVS video、DNxHD、FFM 10 | * 3、音频文件支持以下格式:MP3、FLAC、AC3、Ogg、ADX、WAV、AIFF、ALAW、AU、DTS、MP2、Dirac、HLS 11 | * 4、文档/文本文件支持以下格式:PDF、WORD、TXT、PPT、EXCEL 12 | */ 13 | 14 | ext = '.' + ext.toLowerCase().replace('.', '').trim() + '.' 15 | mimext = '.' + mimext.toLowerCase().replace('.', '').trim() + '.' 16 | 17 | switch (ext) { 18 | case '.txt.': 19 | return ['doc', 'iconfile-txt'] 20 | case '.rar.': 21 | return ['zip', 'iconfile-rar'] 22 | case '.rtf.': 23 | return ['doc', 'iconfile-doc'] 24 | case '.psd.': 25 | return ['others', 'iconfile-psd'] 26 | case '.torrent.': 27 | return ['others', 'iconfile-bt'] 28 | case '.iso.': 29 | return ['others', 'iconfile-iso'] 30 | case '.exe.': 31 | return ['others', 'iconfile-exe'] 32 | case '.apk.': 33 | return ['others', 'iconfile-apk'] 34 | case '.tar.': 35 | return ['others', 'iconfile-tar'] 36 | case '.7z.': 37 | return ['others', 'iconfile-7z'] 38 | case '.svg.': 39 | return ['image3', 'iconfile-image'] 40 | case '.azw.': 41 | return ['doc', 'iconwenjian'] 42 | case '.azw3.': 43 | return ['doc', 'iconwenjian'] 44 | case '.epub.': 45 | return ['doc', 'iconwenjian'] 46 | } 47 | 48 | if (category == 'zip' || mimext == '.zip.') { 49 | 50 | return ['zip', 'iconfile-zip'] 51 | } 52 | 53 | 54 | if (';.apng.avif.ico.webp.gif.'.indexOf(ext) > 0) { 55 | return ['image2', 'iconfile-img'] 56 | } 57 | 58 | if (category == 'image') { 59 | return ['image', 'iconfile-img'] 60 | } 61 | 62 | if (mime.startsWith('image/')) return ['image3', 'iconfile-image'] 63 | if (ext == '.pdf.' || mimext == '.pdf.') return ['doc', 'iconfile-pdf'] 64 | 65 | if (';.doc.docm.docx.dot.dotm.dotx.wps.wpt.'.indexOf(ext) > 0) return ['doc', 'iconfile-doc'] 66 | if (';.pot.ett.'.indexOf(ext) > 0) return ['doc2', 'iconfile-doc'] 67 | if ((mimext.startsWith('.txt') || mimext.startsWith('.doc') || mimext.startsWith('.ppt')) && ';.dps.dpt.potm.potx.pps.ppsm.ppsx.ppt.pptm.pptx.'.indexOf(ext) > 0) return ['doc', 'iconfile-ppt'] 68 | if ((mimext.startsWith('.txt') || mimext.startsWith('.xls')) && ';.xls.xlsx.et.xlsm.xlt.xltm.xltx.'.indexOf(ext) > 0) return ['doc', 'iconfile-xsl'] 69 | 70 | if (mime.startsWith('text/')) return ['others', 'iconfile_txt2'] 71 | if (ext == '.json.') return ['others', 'iconfile_txt2'] 72 | 73 | if (category == 'video') { 74 | 75 | return ['video', 'iconfile_video'] 76 | } 77 | if (mime.startsWith('video/')) return ['video2', 'iconfile_video'] 78 | if (ext == '.ts.' && size > 5 * 1024 * 1024) return ['video2', 'iconfile_video'] 79 | if (';.3iv.cpk.divx.hdv.fli.f4v.f4p.m2t.m2ts.mts.trp.mkv.mp4.mpg4.nsv.nut.nuv.rm.rmvb.vob.wmv.mk3d.hevc.yuv.y4m.mov.avi.flv.mpg.3gp.m4v.mpeg.asf.wmz.webm.pmp.mpga'.indexOf(ext) > 0) { 80 | return ['video2', 'iconfile_video'] 81 | } 82 | if (ext == '.mp3.' && category == 'audio') return ['audio', 'iconfile-mp3'] 83 | if (category == 'audio' && mimext != '.unknown.') { 84 | 85 | return ['audio', 'iconfile-audio'] 86 | } 87 | if (mime.startsWith('audio/')) return ['audio', 'iconfile-audio'] 88 | if (';.ape.aac.cda.dsf.dtshd.eac3.m1a.m2a.m4a.mka.mpa.mpc.opus.ra.tak.tta.wma.wv.'.indexOf(ext) > 0) { 89 | return ['audio2', 'iconfile-audio'] 90 | } 91 | 92 | return ['others', 'iconwenjian'] 93 | } 94 | -------------------------------------------------------------------------------- /src/aliapi/filewalk.ts: -------------------------------------------------------------------------------- 1 | import DebugLog from '../utils/debuglog' 2 | import AliHttp, { IUrlRespData } from './alihttp' 3 | import { IAliFileItem } from './alimodels' 4 | import AliDirFileList, { IAliFileResp } from './dirfilelist' 5 | 6 | export default class AliFileWalk { 7 | static async ApiWalkFileList(user_id: string, drive_id: string, dirID: string, dirName: string, order: string, type: string = '', max: number = 3000): Promise { 8 | const dir: IAliFileResp = { 9 | items: [], 10 | itemsKey: new Set(), 11 | punished_file_count: 0, 12 | next_marker: '', 13 | m_user_id: user_id, 14 | m_drive_id: drive_id, 15 | dirID: dirID, 16 | dirName: dirName 17 | } 18 | 19 | if (!order) order = 'updated_at desc' 20 | const orders = order.split(' ') 21 | do { 22 | const isGet = await AliFileWalk._ApiWalkFileListOnePage(orders[0], orders[1], dir, type) 23 | if (isGet != true) { 24 | break 25 | } 26 | if (dir.items.length >= max && max > 0) { 27 | dir.next_marker = '' 28 | break 29 | } 30 | } while (dir.next_marker != '') 31 | return dir 32 | } 33 | 34 | private static async _ApiWalkFileListOnePage(orderby: string, order: string, dir: IAliFileResp, type: string = '') { 35 | const url = 'v2/file/walk?jsonmask=next_marker%2Cpunished_file_count%2Ctotal_count%2Citems(category%2Ccreated_at%2Cdomain_id%2Cdrive_id%2Cfile_extension%2Cfile_id%2Chidden%2Cmime_extension%2Cmime_type%2Cname%2Cparent_file_id%2Cpunish_flag%2Csize%2Cstarred%2Ctype%2Cupdated_at%2Cdescription)' 36 | let postData = { 37 | drive_id: dir.m_drive_id, 38 | parent_file_id: dir.dirID, 39 | marker: dir.next_marker, 40 | limit: 1000, 41 | all: false, 42 | url_expire_sec: 14400, 43 | fields: 'thumbnail' 44 | // order_by: orderby, 45 | // order_direction: order.toUpperCase() 46 | } 47 | if (type) postData = Object.assign(postData, { type }) 48 | const resp = await AliHttp.Post(url, postData, dir.m_user_id, '') 49 | return AliFileWalk._FileListOnePage(dir, resp) 50 | } 51 | 52 | private static _FileListOnePage(dir: IAliFileResp, resp: IUrlRespData) { 53 | try { 54 | if (AliHttp.IsSuccess(resp.code)) { 55 | dir.next_marker = resp.body.next_marker 56 | const isRecover = dir.dirID == 'recover' 57 | const downUrl = isRecover ? '' : 'https://api.aliyundrive.com/v2/file/download?t=' + Date.now().toString() 58 | 59 | for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) { 60 | const item = resp.body.items[i] as IAliFileItem 61 | if (dir.itemsKey.has(item.file_id)) continue 62 | const add = AliDirFileList.getFileInfo(item, downUrl) 63 | if (isRecover) add.description = item.content_hash 64 | dir.items.push(add) 65 | dir.itemsKey.add(item.file_id) 66 | } 67 | dir.punished_file_count += resp.body.punished_file_count || 0 68 | 69 | return true 70 | } else if (resp.code == 404) { 71 | 72 | dir.items.length = 0 73 | dir.next_marker = '' 74 | return true 75 | } else if (resp.body && resp.body.code) { 76 | dir.items.length = 0 77 | dir.next_marker = resp.body.code 78 | // message.warning('列出文件出错 ' + resp.body.code, 2) 79 | return false 80 | } else { 81 | DebugLog.mSaveWarning('_FileListOnePage err=' + (resp.code || '')) 82 | } 83 | } catch (err: any) { 84 | DebugLog.mSaveDanger('_FileListOnePage ' + dir.dirID, err) 85 | } 86 | dir.next_marker = 'error ' + resp.code 87 | console.log(resp) 88 | return false 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/aliapi/models.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IDownloadUrl { 3 | drive_id: string 4 | file_id: string 5 | expire_sec: number 6 | url: string 7 | size: number 8 | } 9 | 10 | 11 | export interface IVideoPreviewUrl { 12 | drive_id: string 13 | file_id: string 14 | expire_sec: number 15 | url: string 16 | duration: number 17 | width: number 18 | height: number 19 | urlFHD: string 20 | urlHD: string 21 | urlSD: string 22 | urlLD: string 23 | subtitles: { 24 | language: string 25 | url: string 26 | }[] 27 | } 28 | 29 | 30 | export interface IOfficePreViewUrl { 31 | drive_id: string 32 | file_id: string 33 | access_token: string 34 | preview_url: string 35 | } 36 | 37 | 38 | export interface IVideoXBTUrl { 39 | time: string 40 | url: string 41 | } 42 | 43 | 44 | export interface IUploadCreat { 45 | user_id: string 46 | drive_id: string 47 | file_id: string 48 | israpid: boolean 49 | isexist: boolean 50 | upload_id: string 51 | part_info_list: { 52 | upload_url: string 53 | part_number: number 54 | part_size: number 55 | isupload: boolean 56 | }[] 57 | errormsg: string 58 | } 59 | 60 | export interface IUploadInfo { 61 | token_type: string 62 | access_token: string 63 | sha1: string 64 | israpid: boolean 65 | isexist: boolean 66 | part_info_list: { 67 | upload_url: string 68 | part_number: number 69 | part_size: number 70 | isupload: boolean 71 | }[] 72 | } 73 | 74 | export interface IAliBatchResult { 75 | count: number 76 | async_task: { 77 | drive_id: string 78 | file_id: string 79 | task_id: string 80 | newdrive_id: string 81 | newfile_id: string 82 | }[] 83 | reslut: { 84 | id: string 85 | file_id?: string 86 | 87 | name?: string 88 | type?: string 89 | parent_file_id?: string 90 | 91 | share_id?: string 92 | share_pwd?: string 93 | share_url?: string 94 | expiration?: string 95 | share_name?: string 96 | 97 | body?: any 98 | }[] 99 | error: { 100 | id: string 101 | code: string 102 | message: string 103 | }[] 104 | } 105 | 106 | export interface IBatchResult { 107 | count: number 108 | task: { 109 | file_id: string 110 | task_id: string 111 | newdrive_id: string 112 | newfile_id: string 113 | }[] 114 | reslut: { 115 | id: string 116 | file_id: string 117 | }[] 118 | error: { 119 | id: string 120 | code: string 121 | message: string 122 | }[] 123 | } 124 | 125 | 126 | export interface IAliGetAlbumModel { 127 | album_id: string 128 | created_at: number 129 | description: string 130 | file_count: number 131 | image_count: number 132 | name: string 133 | owner: string 134 | updated_at: number 135 | video_count: number 136 | } 137 | 138 | export interface IAliUserDriveDetails { 139 | drive_used_size: number 140 | drive_total_size: number 141 | default_drive_used_size: number 142 | album_drive_used_size: number 143 | note_drive_used_size: number 144 | sbox_drive_used_size: number 145 | share_album_drive_used_size: number 146 | } 147 | 148 | export interface IAliUserDriveCapacity { 149 | type: string 150 | size: number 151 | sizeStr: string 152 | expired: string 153 | expiredstr: string 154 | description: string 155 | latest_receive_time: string /* "2022-05-02T00:50:51.379Z" */ 156 | } 157 | 158 | 159 | export interface IStateUploadFile { 160 | UploadID: string 161 | Info: { 162 | user_id: string 163 | 164 | localFilePath: string 165 | 166 | parent_file_id: string 167 | drive_id: string 168 | 169 | path: string 170 | 171 | name: string 172 | 173 | size: number 174 | sizeStr: string 175 | icon: string 176 | isDir: boolean 177 | isMiaoChuan: boolean 178 | 179 | sha1: string 180 | 181 | crc64: string 182 | } 183 | 184 | Upload: { 185 | 186 | DownState: string 187 | DownTime: number 188 | DownSize: number 189 | DownSpeed: number 190 | DownSpeedStr: string 191 | DownProcess: number 192 | IsStop: boolean 193 | IsDowning: boolean 194 | IsCompleted: boolean 195 | IsFailed: boolean 196 | /** 失败的代码 */ 197 | failedCode: number 198 | /** 失败的消息 */ 199 | failedMessage: string 200 | 201 | AutoTry: number 202 | 203 | upload_id: string 204 | 205 | file_id: string 206 | /** 是否覆盖上传 */ 207 | IsBreakExist: boolean 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/aliapi/uploadmem.ts: -------------------------------------------------------------------------------- 1 | import UserDAL from '../user/userdal' 2 | import DebugLog from '../utils/debuglog' 3 | import axios from 'axios' 4 | import AliUpload from './upload' 5 | import AliUploadHashPool from './uploadhashpool' 6 | 7 | export default class AliUploadMem { 8 | 9 | static async UploadMem(user_id: string, drive_id: string, parent_file_id: string, CreatFileName: string, context: string) { 10 | const token = await UserDAL.GetUserTokenFromDB(user_id) 11 | if (!token || !token.access_token) return '账号失效,操作取消' 12 | let hash = 'DA39A3EE5E6B4B0D3255BFEF95601890AFD80709' 13 | let proof = '' 14 | let buff = Buffer.from([]) 15 | if (context.length > 0) { 16 | buff = Buffer.from(context, 'utf-8') 17 | const dd = await AliUploadHashPool.GetBuffHashProof(token!.access_token, buff) 18 | hash = dd.sha1 19 | proof = dd.proof_code 20 | } 21 | const size = buff.length 22 | 23 | const upinfo = await AliUpload.UploadCreatFileWithFolders(user_id, drive_id, parent_file_id, CreatFileName, size, hash, proof, 'refuse') 24 | if (upinfo.errormsg != '') { 25 | return upinfo.errormsg 26 | } 27 | if (upinfo.isexist) return '网盘中已存在同名文件' 28 | if (upinfo.israpid) return 'success' 29 | 30 | await axios 31 | .put(upinfo.part_info_list[0].upload_url, buff, { 32 | responseType: 'text', 33 | timeout: 30000, 34 | headers: { 35 | 36 | 'Content-Type': '', 37 | Authorization: token!.token_type + ' ' + token!.access_token 38 | } 39 | }) 40 | .catch(function (err: any) { 41 | DebugLog.mSaveDanger('UploadMemError', err) 42 | }) 43 | const result = await AliUpload.UploadFileComplete(user_id, drive_id, upinfo.file_id, upinfo.upload_id, size, hash) 44 | if (result) return 'success' 45 | else return '合并文件失败' 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/down/DownM3U8.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/down/DownSync.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/down/Index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/down/downmenu.ts: -------------------------------------------------------------------------------- 1 | import DownDAL from './downdal' 2 | import UploadDAL from '../transfer/uploaddal' 3 | import UploadingDAL from '../transfer/uploadingdal' 4 | 5 | export function topStartDown(isdown: boolean) { 6 | if (isdown) { 7 | // DownDAL.DowningState(false, true) 8 | } else { 9 | } 10 | } 11 | export function topStartDownAll(isdown: boolean) { 12 | if (isdown) { 13 | // DownDAL.DowningState(true, true) 14 | } else { 15 | } 16 | } 17 | 18 | export function topStopDown(isdown: boolean) { 19 | if (isdown) { 20 | // DownDAL.DowningState(false, false) 21 | } else { 22 | } 23 | } 24 | export function topStopDownAll(isdown: boolean) { 25 | if (isdown) { 26 | // DownDAL.DowningState(true, false) 27 | } else { 28 | } 29 | } 30 | 31 | export function topDeleteDown(isdown: boolean) { 32 | if (isdown) { 33 | // DownDAL.DowningDelete(false) 34 | } else { 35 | UploadingDAL.aUploadingDelete(false) 36 | } 37 | } 38 | 39 | export function topDeleteDownAll(isdown: boolean) { 40 | if (isdown) { 41 | // DownDAL.DowningDelete(true) 42 | } else { 43 | UploadingDAL.aUploadingDelete(true) 44 | } 45 | } 46 | 47 | export function topDeleteDowned(isdown: boolean) { 48 | if (isdown) { 49 | // DownDAL.DownedDelete(false) 50 | } else { 51 | UploadDAL.UploadedDelete(false) 52 | } 53 | } 54 | 55 | export function topDeleteDownedAll(isdown: boolean) { 56 | if (isdown) { 57 | // DownDAL.DownedDelete(true) 58 | } else { 59 | UploadDAL.UploadedDelete(true) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /// 3 | 4 | declare module '*.vue' { 5 | import { DefineComponent } from 'vue' 6 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 7 | const component: DefineComponent<{ tabindex: string; id: string }, {}, any> 8 | export default component 9 | } 10 | 11 | declare enum TaskState { 12 | Success, // 已成功 13 | Error, // 出错停止 14 | Running, // 上传中 15 | Stoped, // 已暂停 16 | Waiting, // 排队中 17 | Autotry // 稍后自动重试 18 | } 19 | 20 | declare type CheckNameMode = 21 | | 'overwrite' // overwrite (直接覆盖,以后多版本有用) 22 | | 'auto_rename' // auto_rename (自动换一个随机名称) 23 | | 'refuse' // refuse (不会创建,告诉你已经存在) 24 | | 'ignore' // ignore (会创建重名的) 25 | 26 | declare type FileType = 27 | | 'file' // 文件 28 | | 'folder' // 文件夹(目录) 29 | 30 | declare type UploadStates = 31 | | 'waiting' // 排队中, 等待上传 32 | | 'start' // 开始 33 | | 'computing_hash' // 计算hash,预秒传,秒传 34 | | 'created' // 创建成功 35 | | 'running' // 上传中 36 | | 'stopped' // 暂停 37 | | 'complete' // 上传完成 38 | | 'checking' // 校验中, 检查 crc64 是否一致 39 | | 'success' // 上传成功 40 | | 'rapid_success' // 秒传成功 41 | | 'error' // 上传失败 42 | | 'cancelled' // 已取消 43 | 44 | // DownloadState 没有 computing_hash & rapid_success 45 | declare type DownloadStates = 46 | | 'waiting' // 排队中, 等待下载 47 | | 'start' // 开始 48 | | 'created' // 创建成功 49 | | 'running' // 下载中 50 | | 'stopped' // 暂停 51 | | 'complete' // 下载完成 52 | | 'checking' // 校验中, 检查 crc64 是否一致 53 | | 'success' // 下载成功 54 | | 'error' // 下载失败 55 | | 'cancelled' // 已取消 56 | 57 | declare module 'Go' 58 | declare module 'dom-to-image' 59 | declare module 'jschardet' 60 | declare function pinyinlite(text: string, config: any): any 61 | declare function videojs(ref: any, options: any, cb: any): any 62 | -------------------------------------------------------------------------------- /src/global.d.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | 3 | declare global { 4 | // eslint-disable-next-line no-unused-vars 5 | interface Window { 6 | Go: any 7 | require: any 8 | Electron: any 9 | openDatabase: any 10 | WebRelaunchAria: () => Promise 11 | platform: string 12 | WinMsg: any 13 | postdataFunc: any 14 | Prism: any 15 | WebUserToken: any 16 | WebToElectron: any 17 | WebClearCache: any 18 | WebRelaunch: any 19 | WebClearCookies: any 20 | WebShutDown: any 21 | WebOpenWindow: any 22 | WebOpenUrl: any 23 | WebShowOpenDialogSync: any 24 | WebExecSync: any 25 | WebPlatformSync: any 26 | UploadPort: any 27 | DownloadPort: any 28 | MainPort: any 29 | WinMsgToUpload: any 30 | WinMsgToDownload: any 31 | WinMsgToMain: any 32 | IsMainPage: boolean 33 | WebSetProxy: any 34 | speedLimte: number 35 | WebSetProgressBar: any 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/layout/MyLoading.vue: -------------------------------------------------------------------------------- 1 | 2 | 11 | -------------------------------------------------------------------------------- /src/layout/MySplit.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 47 | 77 | -------------------------------------------------------------------------------- /src/layout/MySwitch.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 65 | -------------------------------------------------------------------------------- /src/layout/MyTags.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 48 | 78 | -------------------------------------------------------------------------------- /src/layout/PageCode.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 94 | 95 | 132 | -------------------------------------------------------------------------------- /src/layout/PageHelp.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/layout/PageLoading.vue: -------------------------------------------------------------------------------- 1 | 2 | 12 | 49 | -------------------------------------------------------------------------------- /src/layout/PageOffice.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/layout/PageWorker.vue: -------------------------------------------------------------------------------- 1 | 6 | 25 | 26 | -------------------------------------------------------------------------------- /src/pan/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/pan/menus/DirLeftMenu.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 91 | 92 | -------------------------------------------------------------------------------- /src/pan/menus/DirTopPath.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 42 | 112 | -------------------------------------------------------------------------------- /src/pan/menus/PanTopbtn.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 59 | 60 | -------------------------------------------------------------------------------- /src/pan/menus/TrashRightMenu.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 36 | 37 | -------------------------------------------------------------------------------- /src/pan/menus/TrashTopbtn.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 34 | 35 | -------------------------------------------------------------------------------- /src/pan/topbtns/AlphaModal.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 45 | 46 | 86 | -------------------------------------------------------------------------------- /src/pan/topbtns/ArchivePasswordModal.vue: -------------------------------------------------------------------------------- 1 | 107 | 108 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /src/pan/topbtns/CreatNewDirMultiModal.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/pan/topbtns/DownloadModal.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/pan/topbtns/ShuXingMultiModal.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/pan/topbtns/UploadModal.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 125 | 126 | 132 | -------------------------------------------------------------------------------- /src/pic/PicLeft.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /src/pic/PicRight.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/pic/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/rss/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 77 | 78 | 84 | -------------------------------------------------------------------------------- /src/rss/rssdrivecopy/drivecopy.ts: -------------------------------------------------------------------------------- 1 | import { IAliGetFileModel } from '../../aliapi/alimodels' 2 | import AliFile from '../../aliapi/file' 3 | import message from '../../utils/message' 4 | import { fileiconfn, foldericonfn } from '../ScanDAL' 5 | import AliTrash from '../../aliapi/trash' 6 | 7 | export interface ICopyTreeInfo { 8 | user_id: string 9 | drive_id: string 10 | driveType: string 11 | dirID: string 12 | dirName: string 13 | parentID: string 14 | loading: boolean 15 | onlyDir: boolean 16 | } 17 | export function NewCopyTreeInfo(onlyDir: boolean) { 18 | const info: ICopyTreeInfo = { 19 | user_id: '', 20 | driveType: '', 21 | drive_id: '', 22 | dirID: '', 23 | dirName: '', 24 | parentID: '', 25 | loading: false, 26 | onlyDir: onlyDir 27 | } 28 | return info 29 | } 30 | 31 | export interface ICopyTreeNode { 32 | key: string 33 | title: string 34 | icon: any 35 | download_url: string 36 | disabled: boolean 37 | children?: ICopyTreeNode[] 38 | } 39 | 40 | export async function LoadDir(dirID: string, DirData: ICopyTreeInfo, treeData: ICopyTreeNode[], disabledFile: boolean): Promise { 41 | DirData.loading = true 42 | if (!dirID) dirID = 'root' 43 | if (dirID.startsWith('dir_')) dirID = dirID.substring('dir_'.length) 44 | if (dirID == 'root') { 45 | DirData.dirID = 'root' 46 | DirData.dirName = '根目录' 47 | DirData.parentID = 'root' 48 | } else { 49 | const getDir = await AliFile.ApiFileInfo(DirData.user_id, DirData.drive_id, dirID) 50 | if (getDir) { 51 | DirData.dirID = getDir.file_id 52 | DirData.dirName = getDir.name 53 | DirData.parentID = getDir.parent_file_id 54 | } else { 55 | message.error('读取文件夹信息失败') 56 | } 57 | } 58 | 59 | const resp = await AliTrash.ApiDirFileListNoLock(DirData.user_id, DirData.drive_id, dirID, '', '', '') 60 | DirData.loading = false 61 | const list: ICopyTreeNode[] = [] 62 | const items = resp.items 63 | let item: IAliGetFileModel 64 | for (let i = 0, maxi = items.length; i < maxi; i++) { 65 | item = items[i] 66 | list.push({ 67 | key: (item.isDir ? 'dir_' : 'file_') + item.file_id, 68 | title: item.name, 69 | disabled: item.isDir ? false : disabledFile, 70 | icon: item.isDir ? foldericonfn : fileiconfn, 71 | download_url: '' 72 | } as ICopyTreeNode) 73 | } 74 | treeData.splice(0, treeData.length, ...list) 75 | } -------------------------------------------------------------------------------- /src/rss/rssjiami/jiami.ts: -------------------------------------------------------------------------------- 1 | import { FileSystemErrorMessage } from '../../utils/filehelper' 2 | import DebugLog from '../../utils/debuglog' 3 | import message from '../../utils/message' 4 | import fsPromises from 'fs/promises' 5 | import { Buffer } from 'buffer' 6 | import path from 'path' 7 | 8 | export async function DoXiMa(dirPath: string, breakSmall: boolean, matchExtList: string[]): Promise { 9 | const fileList: string[] = [] 10 | await GetAllFiles(dirPath, breakSmall, fileList) 11 | if (fileList.length == 0) { 12 | message.error('选择的文件夹下找不到任何文件') 13 | return 0 14 | } else { 15 | let rand = Date.now() 16 | const rand1 = rand % 256 17 | rand = rand / 128 18 | const rand2 = Math.floor(rand % 256) 19 | let rand3 = Math.floor(Math.random() * 255) 20 | 21 | let RunCount = 0 22 | for (let i = 0, maxi = fileList.length; i < maxi; i++) { 23 | const file = fileList[i].toLowerCase().trimEnd() 24 | if (matchExtList.length > 0) { 25 | 26 | let find = false 27 | for (let j = 0; j < matchExtList.length; j++) { 28 | if (file.endsWith(matchExtList[j])) { 29 | find = true 30 | break 31 | } 32 | } 33 | if (find == false) continue 34 | } 35 | try { 36 | const rand4 = (i % 255) + 1 37 | if (rand4 == 200) rand3 = Math.floor(Math.random() * 255) 38 | const buff = Buffer.from([0, rand1, rand2, rand3, rand4]) 39 | fsPromises.appendFile(fileList[i], buff).catch(() => {}) 40 | RunCount++ 41 | } catch (err: any) { 42 | DebugLog.mSaveDanger('XM appendFile' + (err.message || '') + fileList[i]) 43 | } 44 | } 45 | return RunCount 46 | } 47 | } 48 | 49 | async function GetAllFiles(dir: string, breakSmall: boolean, fileList: string[]) { 50 | if (dir.endsWith(path.sep) == false) dir = dir + path.sep 51 | try { 52 | const childFiles = await fsPromises.readdir(dir).catch((err: any) => { 53 | err = FileSystemErrorMessage(err.code, err.message) 54 | DebugLog.mSaveDanger('XM GetAllFiles文件失败:' + dir, err) 55 | message.error('跳过文件夹:' + err + ' ' + dir) 56 | return [] 57 | }) 58 | 59 | let allTask: Promise[] = [] 60 | const dirList: string[] = [] 61 | for (let i = 0, maxi = childFiles.length; i < maxi; i++) { 62 | const name = childFiles[i] as string 63 | if (name.startsWith('.')) continue 64 | if (name.startsWith('#')) continue 65 | const item = dir + name 66 | allTask.push( 67 | fsPromises 68 | .lstat(item) 69 | .then((stat: any) => { 70 | if (stat.isDirectory()) dirList.push(item) 71 | else if (stat.isSymbolicLink()) { 72 | // donothing 73 | } else if (stat.isFile()) { 74 | if (breakSmall == false || stat.size > 5 * 1024 * 1024) fileList.push(item) 75 | } 76 | }) 77 | .catch() 78 | ) 79 | if (allTask.length > 10) { 80 | await Promise.all(allTask).catch(() => {}) 81 | allTask = [] 82 | } 83 | } 84 | 85 | if (allTask.length > 0) { 86 | await Promise.all(allTask).catch(() => {}) 87 | allTask = [] 88 | } 89 | 90 | for (let i = 0, maxi = dirList.length; i < maxi; i++) { 91 | await GetAllFiles(dirList[i], breakSmall, fileList) 92 | } 93 | } catch (err: any) { 94 | DebugLog.mSaveDanger('GetAllFiles' + (err.message || '')) 95 | } 96 | 97 | return true 98 | } 99 | -------------------------------------------------------------------------------- /src/rss/rssrename/RssRename.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 79 | 80 | 89 | -------------------------------------------------------------------------------- /src/rss/rssxima/xima.ts: -------------------------------------------------------------------------------- 1 | import { FileSystemErrorMessage } from '../../utils/filehelper' 2 | import DebugLog from '../../utils/debuglog' 3 | import message from '../../utils/message' 4 | import fsPromises from 'fs/promises' 5 | import { Buffer } from 'buffer' 6 | import path from 'path' 7 | 8 | export async function DoXiMa(dirPath: string, breakSmall: boolean, matchExtList: string[]): Promise { 9 | const fileList: string[] = [] 10 | await GetAllFiles(dirPath, breakSmall, fileList) 11 | if (fileList.length == 0) { 12 | message.error('选择的文件夹下找不到任何文件') 13 | return 0 14 | } else { 15 | let rand = Date.now() 16 | const rand1 = rand % 256 17 | rand = rand / 128 18 | const rand2 = Math.floor(rand % 256) 19 | let rand3 = Math.floor(Math.random() * 255) 20 | 21 | let runCount = 0 22 | for (let i = 0, maxi = fileList.length; i < maxi; i++) { 23 | const file = fileList[i].toLowerCase().trimEnd() 24 | if (matchExtList.length > 0) { 25 | 26 | let find = false 27 | for (let j = 0; j < matchExtList.length; j++) { 28 | if (file.endsWith(matchExtList[j])) { 29 | find = true 30 | break 31 | } 32 | } 33 | if (find == false) continue 34 | } 35 | try { 36 | const rand4 = (i % 255) + 1 37 | if (rand4 == 200) rand3 = Math.floor(Math.random() * 255) 38 | const buff = Buffer.from([0, rand1, rand2, rand3, rand4]) 39 | fsPromises.appendFile(fileList[i], buff).catch(() => {}) 40 | runCount++ 41 | } catch (err: any) { 42 | DebugLog.mSaveDanger('XM appendFile' + (err.message || '') + fileList[i]) 43 | } 44 | } 45 | return runCount 46 | } 47 | } 48 | 49 | async function GetAllFiles(dir: string, breakSmall: boolean, fileList: string[]) { 50 | if (dir.endsWith(path.sep) == false) dir = dir + path.sep 51 | try { 52 | const childfiles = await fsPromises.readdir(dir).catch((err: any) => { 53 | err = FileSystemErrorMessage(err.code, err.message) 54 | DebugLog.mSaveDanger('XMGetAllFiles文件失败:' + dir, err) 55 | message.error('跳过文件夹:' + err + ' ' + dir) 56 | return [] 57 | }) 58 | 59 | let allTask: Promise[] = [] 60 | const dirList: string[] = [] 61 | for (let i = 0, maxi = childfiles.length; i < maxi; i++) { 62 | const name = childfiles[i] as string 63 | if (name.startsWith('.')) continue 64 | if (name.startsWith('#')) continue 65 | const item = dir + name 66 | allTask.push( 67 | fsPromises 68 | .lstat(item) 69 | .then((stat: any) => { 70 | if (stat.isDirectory()) dirList.push(item) 71 | else if (stat.isSymbolicLink()) { 72 | // donothing 73 | } else if (stat.isFile()) { 74 | if (breakSmall == false || stat.size > 5 * 1024 * 1024) fileList.push(item) 75 | } 76 | }) 77 | .catch() 78 | ) 79 | if (allTask.length > 10) { 80 | await Promise.all(allTask).catch(() => {}) 81 | allTask = [] 82 | } 83 | } 84 | 85 | if (allTask.length > 0) { 86 | await Promise.all(allTask).catch(() => {}) 87 | allTask = [] 88 | } 89 | 90 | for (let i = 0, maxi = dirList.length; i < maxi; i++) { 91 | await GetAllFiles(dirList[i], breakSmall, fileList) 92 | } 93 | } catch (err: any) { 94 | DebugLog.mSaveDanger('GetAllFiles' + (err.message || '')) 95 | } 96 | 97 | return true 98 | } 99 | -------------------------------------------------------------------------------- /src/setting/SettingLog.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 64 | 65 | 83 | -------------------------------------------------------------------------------- /src/setting/SettingUI.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 56 | 57 | 66 | -------------------------------------------------------------------------------- /src/setting/ShutDown.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 59 | 66 | -------------------------------------------------------------------------------- /src/share/following/OtherFollowingStore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { IAliOtherFollowingModel } from '../../aliapi/alimodels' 3 | 4 | export declare interface FollowingState { 5 | 6 | TuiJianLoading: boolean 7 | 8 | TuiJianLoaded: boolean 9 | 10 | TuiJianList: { key: string; color: string; list: IAliOtherFollowingModel[] }[] 11 | } 12 | 13 | const useFollowingStore = defineStore('following', { 14 | state: (): FollowingState => ({ 15 | TuiJianLoading: false, 16 | TuiJianLoaded: false, 17 | TuiJianList: [{ key: '官方推荐', color: 'arcoblue', list: [] }] 18 | }), 19 | getters: {}, 20 | actions: { 21 | 22 | aSaveOtherFollowingList(key: string, color: string, list: IAliOtherFollowingModel[]) { 23 | list.sort((a, b) => b.follower_count - a.follower_count) 24 | for (let i = 0, maxi = this.TuiJianList.length; i < maxi; i++) { 25 | if (this.TuiJianList[i].key == key) { 26 | this.TuiJianList[i].color = color 27 | this.TuiJianList[i].list = list 28 | return 29 | } 30 | } 31 | this.TuiJianList.push({ key, color, list }) 32 | } 33 | } 34 | }) 35 | 36 | export default useFollowingStore 37 | -------------------------------------------------------------------------------- /src/share/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/share/share/ShareSiteRight.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 27 | 28 | 58 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia' 2 | import useAppStore from './appstore' 3 | import useKeyboardStore from './keyboardstore' 4 | import type { KeyboardState } from './keyboardstore' 5 | import useLogStore from './logstore' 6 | import useModalStore from './modalstore' 7 | import type { ModalState } from './modalstore' 8 | import useWinStore from './winstore' 9 | import type { WinState } from './winstore' 10 | import useSettingStore from '../setting/settingstore' 11 | import useUserStore from '../user/userstore' 12 | import type { ITokenInfo } from '../user/userstore' 13 | import usePanTreeStore from '../pan/pantreestore' 14 | import usePanFileStore from '../pan/panfilestore' 15 | 16 | import useServerStore from './serverstore' 17 | import type { IOtherShareLinkModel } from '../share/share/OtherShareStore' 18 | import type { IShareSiteModel } from './serverstore' 19 | import useMyShareStore from '../share/share/MyShareStore' 20 | import useOtherShareStore from '../share/share/OtherShareStore' 21 | import useMyFollowingStore from '../share/following/MyFollowingStore' 22 | import useOtherFollowingStore from '../share/following/OtherFollowingStore' 23 | import type { FollowingState } from '../share/following/OtherFollowingStore' 24 | 25 | import useUploadingStore from '../down/UploadingStore' 26 | import useUploadedStore from '../down/UploadedStore' 27 | import useDownedStore from '../down/DownedStore' 28 | import useDowningStore from '../down/DowningStore' 29 | 30 | import useFootStore from './footstore' 31 | import type { AsyncModel } from './footstore' 32 | 33 | const pinia = createPinia() 34 | export { 35 | useAppStore, 36 | useSettingStore, 37 | useLogStore, 38 | useModalStore, 39 | ModalState, 40 | useWinStore, 41 | WinState, 42 | useKeyboardStore, 43 | KeyboardState, 44 | useUserStore, 45 | ITokenInfo, 46 | usePanTreeStore, 47 | usePanFileStore, 48 | useServerStore, 49 | IOtherShareLinkModel, 50 | IShareSiteModel, 51 | useMyShareStore, 52 | useOtherShareStore, 53 | useOtherFollowingStore, 54 | FollowingState, 55 | useMyFollowingStore, 56 | useFootStore, 57 | AsyncModel, 58 | useUploadingStore, 59 | useUploadedStore, 60 | useDowningStore, 61 | useDownedStore 62 | } 63 | export default pinia 64 | -------------------------------------------------------------------------------- /src/store/keyboardstore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export interface KeyboardMessage { 4 | 5 | Code: string 6 | 7 | Key: string 8 | Ctrl: boolean 9 | Shift: boolean 10 | 11 | Alt: boolean 12 | 13 | Repeat: boolean 14 | 15 | IsInput: boolean 16 | IsEnmpty: boolean 17 | } 18 | 19 | export interface KeyboardState { 20 | KeyDownEvent: KeyboardMessage 21 | KeyUpEvent: KeyboardMessage 22 | } 23 | 24 | const useKeyboardStore = defineStore('keyboard', { 25 | state: (): KeyboardState => ({ 26 | KeyDownEvent: { Ctrl: false, Shift: false, Alt: false, Repeat: false, IsInput: false, Code: '', Key: '', IsEnmpty: true } as KeyboardMessage, 27 | KeyUpEvent: { Ctrl: false, Shift: false, Alt: false, Repeat: false, IsInput: false, Code: '', Key: '', IsEnmpty: true } as KeyboardMessage 28 | }), 29 | 30 | getters: {}, 31 | 32 | actions: { 33 | updateStore(partial: Partial) { 34 | this.$patch(partial) 35 | }, 36 | KeyDown(event: KeyboardEvent) { 37 | console.log(event) 38 | this.KeyDownEvent = { Ctrl: event.ctrlKey, Shift: event.shiftKey, Alt: event.altKey, Repeat: event.repeat, IsInput: false, Code: event.code, Key: event.key.toLowerCase(), IsEnmpty: false } 39 | } 40 | } 41 | }) 42 | 43 | export default useKeyboardStore 44 | -------------------------------------------------------------------------------- /src/store/logstore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export interface LogState { 4 | logTime: number 5 | } 6 | 7 | const useLogStore = defineStore('log', { 8 | state: (): LogState => ({ 9 | logTime: Date.now() 10 | }), 11 | 12 | getters: {}, 13 | 14 | actions: { 15 | logRefresh(time: number) { 16 | this.logTime = time 17 | } 18 | } 19 | }) 20 | 21 | export default useLogStore 22 | -------------------------------------------------------------------------------- /src/store/modalstore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import { onHideRightMenuScroll } from '../utils/keyboardhelper' 3 | 4 | export interface ModalState { 5 | modalName: string 6 | modalData: any 7 | } 8 | 9 | const useModalStore = defineStore('modal', { 10 | state: (): ModalState => ({ 11 | modalName: '', 12 | modalData: {} 13 | }), 14 | 15 | actions: { 16 | showModal(modalName: string, modalData: any) { 17 | if (modalName) onHideRightMenuScroll() 18 | if (modalName && modalName == this.modalName) { 19 | 20 | this.$patch({ modalName: '', modalData: {} }) 21 | setTimeout(() => { 22 | this.$patch({ modalName, modalData }) 23 | }, 300) 24 | } else this.$patch({ modalName, modalData }) 25 | } 26 | } 27 | }) 28 | 29 | export default useModalStore 30 | -------------------------------------------------------------------------------- /src/store/serverstore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | 4 | export interface IShareSiteModel { 5 | title: string 6 | url: string 7 | tip: string 8 | } 9 | 10 | export interface ServerState { 11 | 12 | shareSiteList: IShareSiteModel[] 13 | helpUrl: string 14 | } 15 | 16 | const useServerStore = defineStore('serverstore', { 17 | state: (): ServerState => ({ 18 | shareSiteList: [], 19 | helpUrl: 'aHR0cHM6Ly9naXRodWIuY29tL1BpbmdLdU5ldC9hbGl5dW5wYW4=' 20 | }), 21 | actions: { 22 | 23 | mSaveShareSiteList(shareSiteList: IShareSiteModel[]) { 24 | this.shareSiteList = shareSiteList || [] 25 | }, 26 | 27 | mSaveHelpUrl(url: string) { 28 | this.helpUrl = url || 'aHR0cHM6Ly9naXRodWIuY29tL1BpbmdLdU5ldC9hbGl5dW5wYW4=' 29 | } 30 | } 31 | }) 32 | 33 | export default useServerStore 34 | -------------------------------------------------------------------------------- /src/store/winstore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export interface WinState { 4 | width: number 5 | height: number 6 | } 7 | 8 | const useWinStore = defineStore('win', { 9 | state: (): WinState => ({ 10 | width: 0, 11 | height: 0 12 | }), 13 | 14 | getters: { 15 | GetListHeight(state: WinState): string { 16 | return (state.height - 192).toString() + 'px' 17 | }, 18 | GetListHeightNumber(state: WinState): number { 19 | return state.height - 192 20 | } 21 | }, 22 | 23 | actions: { 24 | updateStore(partial: Partial) { 25 | this.$patch(partial) 26 | } 27 | } 28 | }) 29 | 30 | export default useWinStore 31 | -------------------------------------------------------------------------------- /src/transfer/uploaddal.ts: -------------------------------------------------------------------------------- 1 | import { useSettingStore } from '../store' 2 | import DBUpload from '../utils/dbupload' 3 | import { clickWait, clickWaitDelete } from '../utils/debounce' 4 | import useUploadedStore from '../down/uploadedstore' 5 | 6 | export default class UploadDAL { 7 | 8 | static async aReloadUploaded() { 9 | const uploadedStore = useUploadedStore() 10 | if (uploadedStore.ListLoading == true) return 11 | uploadedStore.ListLoading = true 12 | const showlist = await DBUpload.getUploadedByTop(5000) 13 | const count = await DBUpload.getUploadTaskCount() 14 | uploadedStore.aLoadListData(showlist, count) 15 | uploadedStore.ListLoading = false 16 | } 17 | 18 | 19 | static async aClearUploaded() { 20 | const max = useSettingStore().debugDownedListMax 21 | return await DBUpload.deleteUploadedOutCount(max) 22 | } 23 | 24 | 25 | static async UploadedDelete(all: boolean) { 26 | if (clickWait('UploadedDelete', -1)) return 27 | if (all) { 28 | await DBUpload.clearUploadedAll() 29 | } else { 30 | const uploadedStore = useUploadedStore() 31 | const keys = Array.from(uploadedStore.ListSelected) 32 | await DBUpload.deleteUploadedBatch(keys) 33 | } 34 | await this.aReloadUploaded() 35 | clickWaitDelete('UploadedDelete') 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/user/userstore.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | import UserDAL from './userdal' 3 | 4 | 5 | export interface ITokenInfo { 6 | tokenfrom: 'token' | 'account' 7 | 8 | 9 | access_token: string 10 | refresh_token: string 11 | expires_in: number 12 | token_type: string 13 | user_id: string 14 | user_name: string 15 | avatar: string 16 | nick_name: string 17 | default_drive_id: string 18 | default_sbox_drive_id: string 19 | role: string 20 | status: string 21 | expire_time: string 22 | state: string 23 | pin_setup: boolean 24 | is_first_login: boolean 25 | need_rp_verify: boolean 26 | 27 | 28 | name: string 29 | spu_id: string 30 | is_expires: boolean 31 | used_size: number 32 | total_size: number 33 | spaceinfo: string 34 | vipname: string 35 | vipexpire: string 36 | 37 | 38 | pic_drive_id: string 39 | } 40 | 41 | export interface UserState { 42 | user_id: string 43 | userLogined: boolean 44 | userShowLogin: boolean 45 | } 46 | 47 | const useUserStore = defineStore('user', { 48 | state: (): UserState => ({ 49 | user_id: '', 50 | userLogined: false, 51 | userShowLogin: false 52 | }), 53 | 54 | getters: { 55 | GetUserToken(state: UserState): ITokenInfo { 56 | return UserDAL.GetUserToken(state.user_id) 57 | } 58 | }, 59 | 60 | actions: { 61 | userLogin(user_id: string) { 62 | this.user_id = user_id 63 | this.userLogined = true 64 | }, 65 | userLogOff() { 66 | this.user_id = '' 67 | this.userLogined = false 68 | } 69 | } 70 | }) 71 | 72 | export default useUserStore 73 | -------------------------------------------------------------------------------- /src/utils/antdtree.ts: -------------------------------------------------------------------------------- 1 | import { EventDataNode } from 'ant-design-vue/es/tree' 2 | 3 | 4 | export function treeSelectToExpand(keys: any[], info: { event: string; selected: Boolean; nativeEvent: MouseEvent; node: EventDataNode }) { 5 | let parent = info.nativeEvent.target as HTMLElement 6 | if (parent) { 7 | for (let i = 0; i < 10; i++) { 8 | if (parent.nodeName == 'DIV' && (parent.className == 'ant-tree-treenode' || parent.className.indexOf('ant-tree-treenode ') >= 0)) break 9 | if (parent.parentElement) parent = parent.parentElement 10 | } 11 | const children = parent.children 12 | if (children) { 13 | for (let i = 0, maxi = children.length; i < maxi; i++) { 14 | if (info.node.isLeaf) { 15 | 16 | if (children[i].className.indexOf('ant-tree-checkbox') >= 0) (children[i] as HTMLElement).click() 17 | } else { 18 | 19 | if (children[i].className.indexOf('ant-tree-switcher') >= 0) (children[i] as HTMLElement).click() 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/appcache.ts: -------------------------------------------------------------------------------- 1 | import { useSettingStore } from '../store' 2 | import DebugLog from './debuglog' 3 | import { getUserData } from './electronhelper' 4 | import { FileSystemErrorMessage } from './filehelper' 5 | import { humanSize, Sleep } from './format' 6 | import message from './message' 7 | 8 | import path from 'path' 9 | import fsPromises from 'fs/promises' 10 | 11 | export default class AppCache { 12 | 13 | static async LoadDirSize(dir: string): Promise { 14 | try { 15 | const childFiles = await fsPromises.readdir(dir, { withFileTypes: true }).catch((err: any) => { 16 | err = FileSystemErrorMessage(err.code, err.message) 17 | DebugLog.mSaveDanger('LoadDirSize失败:' + dir, err) 18 | message.error(err + ' ' + dir) 19 | return [] 20 | }) 21 | let total = 0 22 | for (let i = 0, maxi = childFiles.length; i < maxi; i++) { 23 | if (childFiles[i].isFile()) { 24 | 25 | const stat = await fsPromises.lstat(path.join(dir, childFiles[i].name)).catch(() => { 26 | return { size: 0 } 27 | }) 28 | total += stat.size 29 | } else if (childFiles[i].isDirectory()) { 30 | 31 | total += await AppCache.LoadDirSize(path.join(dir, childFiles[i].name)) 32 | } 33 | } 34 | return total 35 | } catch { 36 | return 0 37 | } 38 | } 39 | 40 | static DeleteDir(dir: string): Promise { 41 | return fsPromises 42 | .rm(dir, { force: true, recursive: true }) 43 | .then(() => {}) 44 | .catch(() => {}) 45 | } 46 | 47 | 48 | static async aLoadCacheSize(): Promise { 49 | const userData = getUserData() 50 | if (!userData) return 51 | const dirSize = await AppCache.LoadDirSize(userData) 52 | if (dirSize > 800 * 1024 * 1024) message.warning('缓存文件夹体积较大,该去 设置 里清理了') 53 | 54 | useSettingStore().debugCacheSize = humanSize(dirSize) 55 | } 56 | 57 | 58 | static async aClearCache(delby: string): Promise { 59 | const dir = getUserData() 60 | // await AppCache.DeleteDir(path.join(dir, 'Cache')) 61 | if (delby == 'all') { 62 | // window.WebClearCache({ cache: true }) 63 | if (window.WebClearCache) 64 | window.WebClearCache({ 65 | storages: ['appcache', 'cookies', 'filesystem', 'shadercache', 'serviceworkers', 'cachestorage', 'indexdb', 'localstorage', 'websql'], 66 | quotas: ['temporary', 'persistent', 'syncable'] 67 | }) 68 | } else { 69 | // window.WebClearCache({ cache: true }) 70 | if (window.WebClearCache) 71 | window.WebClearCache({ 72 | storages: ['appcache', 'cookies', 'filesystem', 'shadercache', 'serviceworkers', 'cachestorage'], 73 | quotas: ['temporary', 'persistent', 'syncable'] 74 | }) 75 | } 76 | if (delby == 'all') { 77 | await AppCache.DeleteDir(path.join(dir, 'databases')).catch(() => {}) 78 | await AppCache.DeleteDir(path.join(dir, 'IndexedDB')).catch(() => {}) 79 | await AppCache.DeleteDir(path.join(dir, 'Local Storage')).catch(() => {}) 80 | await AppCache.DeleteDir(path.join(dir, 'Session Storage')).catch(() => {}) 81 | } else if (delby == 'db') { 82 | await AppCache.DeleteDir(path.join(dir, 'databases')).catch(() => {}) 83 | } 84 | await AppCache.DeleteDir(path.join(dir, 'Code Cache', 'js')).catch(() => {}) 85 | await AppCache.DeleteDir(path.join(dir, 'Code Cache', 'wasm')).catch(() => {}) 86 | 87 | await Sleep(4000) 88 | 89 | 90 | if (delby == 'all') { 91 | message.success('删除全部数据成功,自动重启小白羊') 92 | Sleep(3000).then(() => { 93 | window.WebRelaunch() 94 | }) 95 | } else if (delby == 'db') { 96 | message.success('删除数据库成功,自动重启小白羊') 97 | Sleep(3000).then(() => { 98 | window.WebRelaunch() 99 | }) 100 | } else { 101 | message.success('清理缓存成功,自动重启小白羊') 102 | Sleep(3000).then(() => { 103 | // window.WebReload() 104 | window.WebRelaunch() 105 | }) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | export default class Config { 2 | static appVersion = 'v3.09.13' 3 | static referer = 'https://www.aliyundrive.com/' 4 | static downAgent = 'okhttp/4.2.2' 5 | static userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33' 6 | static loginUrl = 'https://auth.aliyundrive.com/v2/oauth/authorize?login_type=custom&response_type=code&redirect_uri=https%3A%2F%2Fwww.aliyundrive.com%2Fsign%2Fcallback&client_id=25dzX3vbYqktVxyX&state=%7B%22origin%22%3A%22https%3A%2F%2Fwww.aliyundrive.com%2F%22%7D' 7 | 8 | static loginUrlAccount = 'https://passport.aliyundrive.com/mini_login.htm?lang=zh_cn&appName=aliyun_drive&appEntrance=web&styleType=auto&bizParams=¬LoadSsoView=false¬KeepLogin=false&isMobile=false&&rnd=0.1100330129139' 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/dbcache.ts: -------------------------------------------------------------------------------- 1 | import Dexie from 'dexie' 2 | import { IStateDebugLog } from './debuglog' 3 | 4 | export interface IStateFileHash { 5 | size: number 6 | mtime: number 7 | 8 | presha1: string 9 | sha1: string 10 | name: string 11 | } 12 | class XBYDB3Cache extends Dexie { 13 | ilog: Dexie.Table 14 | ifilehash: Dexie.Table 15 | iobject: Dexie.Table 16 | 17 | constructor() { 18 | super('XBYDB3Cache') 19 | 20 | this.version(1) 21 | .stores({ 22 | ilog: '&logid', 23 | ifilehash: '++id,[size+mtime]', 24 | iobject: '' 25 | }) 26 | .upgrade((tx: any) => { 27 | console.log('upgrade', tx) 28 | }) 29 | this.ilog = this.table('ilog') 30 | this.ifilehash = this.table('ifilehash') 31 | this.iobject = this.table('iobject') 32 | } 33 | 34 | async saveLog(value: IStateDebugLog) { 35 | if (!this.isOpen()) await this.open().catch(() => {}) 36 | return this.ilog.put(value).catch(() => {}) 37 | } 38 | 39 | async getLogAll(): Promise { 40 | if (!this.isOpen()) await this.open().catch(() => {}) 41 | return await this.transaction('r', this.ilog, () => { 42 | return this.ilog.reverse().limit(500).toArray() 43 | }) 44 | } 45 | 46 | async deleteLogAll(): Promise { 47 | if (!this.isOpen()) await this.open().catch(() => {}) 48 | return this.ilog.clear() 49 | } 50 | 51 | async deleteLogOutCount(max: number): Promise { 52 | if (!this.isOpen()) await this.open().catch(() => {}) 53 | const count = await this.ilog.count() 54 | if (count > max) { 55 | return this.ilog.limit(max - count).delete() 56 | } 57 | return 0 58 | } 59 | 60 | async getFileHashList(size: number, mtime: number): Promise { 61 | if (!this.isOpen()) await this.open().catch(() => {}) 62 | return this.ifilehash.where({ size, mtime }).toArray() 63 | } 64 | 65 | async getFileHash(size: number, mtime: number, prehash: string, name: string): Promise { 66 | if (!this.isOpen()) await this.open().catch(() => {}) 67 | const hashList = await this.ifilehash.where({ size, mtime }).toArray() 68 | for (let i = 0, maxi = hashList.length; i < maxi; i++) { 69 | if (hashList[i].presha1 == prehash && hashList[i].name == name) { 70 | return hashList[i].sha1 71 | } 72 | } 73 | return '' 74 | } 75 | 76 | async saveFileHash(value: IStateFileHash) { 77 | if (!this.isOpen()) await this.open().catch(() => {}) 78 | return this.ifilehash.put(value).catch(() => {}) 79 | } 80 | } 81 | 82 | const DBCache = new XBYDB3Cache() 83 | export default DBCache 84 | -------------------------------------------------------------------------------- /src/utils/dbdown.ts: -------------------------------------------------------------------------------- 1 | import Dexie from 'dexie' 2 | 3 | export interface IDowningInfo { 4 | 5 | key: string 6 | 7 | time: number 8 | 9 | fileCount: number 10 | 11 | fileSize: number 12 | 13 | dirCount: number 14 | } 15 | 16 | class XBYDB3Down extends Dexie { 17 | 18 | downingProgress: Dexie.Table 19 | 20 | downingInfo: Dexie.Table 21 | 22 | downingGzip: Dexie.Table 23 | 24 | downedGzip: Dexie.Table 25 | 26 | constructor() { 27 | super('XBYDB3Down') 28 | 29 | this.version(1) 30 | .stores({ 31 | downingProgress: '', 32 | downingInfo: 'key', 33 | downingGzip: '', 34 | downedGzip: '' 35 | }) 36 | .upgrade((tx: any) => { 37 | console.log('upgrade', tx) 38 | }) 39 | 40 | this.downingProgress = this.table('downingProgress') 41 | this.downingInfo = this.table('downingInfo') 42 | this.downingGzip = this.table('downingGzip') 43 | this.downedGzip = this.table('downedGzip') 44 | } 45 | 46 | async getDowningGzip(key: string): Promise { 47 | if (!this.isOpen()) await this.open().catch(() => {}) 48 | const val = await this.downingGzip.get(key) 49 | if (val) return val 50 | else return undefined 51 | } 52 | 53 | async deleteDowningGzip(key: string): Promise { 54 | if (!this.isOpen()) await this.open().catch(() => {}) 55 | return this.downingGzip.delete(key) 56 | } 57 | 58 | async saveDowningGzip(key: string, value: Buffer): Promise { 59 | if (!this.isOpen()) await this.open().catch(() => {}) 60 | return this.downingGzip.put(value, key).catch(() => {}) 61 | } 62 | 63 | async deleteDowningGzipAll(): Promise { 64 | if (!this.isOpen()) await this.open().catch(() => {}) 65 | return this.downingGzip.clear() 66 | } 67 | 68 | async getDownedGzip(key: string): Promise { 69 | if (!this.isOpen()) await this.open().catch(() => {}) 70 | const val = await this.downedGzip.get(key) 71 | if (val) return val 72 | else return undefined 73 | } 74 | 75 | async deleteDownedGzip(key: string): Promise { 76 | if (!this.isOpen()) await this.open().catch(() => {}) 77 | return this.downedGzip.delete(key) 78 | } 79 | 80 | async saveDownedGzip(key: string, value: Buffer): Promise { 81 | if (!this.isOpen()) await this.open().catch(() => {}) 82 | return this.downedGzip.put(value, key).catch(() => {}) 83 | } 84 | 85 | async deleteDownedGzipAll(): Promise { 86 | if (!this.isOpen()) await this.open().catch(() => {}) 87 | return this.downedGzip.clear() 88 | } 89 | 90 | async getDowningInfo(key: string): Promise { 91 | if (!this.isOpen()) await this.open().catch(() => {}) 92 | const val = await this.downingInfo.get(key) 93 | if (val) return val 94 | else return undefined 95 | } 96 | 97 | async deleteDowningInfo(key: string): Promise { 98 | if (!this.isOpen()) await this.open().catch(() => {}) 99 | return this.downingInfo.delete(key) 100 | } 101 | 102 | async saveDowningInfo(key: string, value: IDowningInfo): Promise { 103 | if (!this.isOpen()) await this.open().catch(() => {}) 104 | return this.downingInfo.put(value, key).catch(() => {}) 105 | } 106 | 107 | async deleteDowningInfoAll(): Promise { 108 | if (!this.isOpen()) await this.open().catch(() => {}) 109 | return this.downingInfo.clear() 110 | } 111 | 112 | async getDowningProgress(key: string): Promise { 113 | if (!this.isOpen()) await this.open().catch(() => {}) 114 | const val = await this.downingProgress.get(key) 115 | if (val) return val 116 | else return undefined 117 | } 118 | 119 | async deleteDowningProgress(key: string): Promise { 120 | if (!this.isOpen()) await this.open().catch(() => {}) 121 | return this.downingProgress.delete(key) 122 | } 123 | 124 | async saveDowningProgress(key: string, value: object): Promise { 125 | if (!this.isOpen()) await this.open().catch(() => {}) 126 | return this.downingProgress.put(value, key).catch(() => {}) 127 | } 128 | 129 | async deleteDowningProgressAll(): Promise { 130 | if (!this.isOpen()) await this.open().catch(() => {}) 131 | return this.downingProgress.clear() 132 | } 133 | } 134 | 135 | const DB = new XBYDB3Down() 136 | export default DB 137 | -------------------------------------------------------------------------------- /src/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | import message from './message' 2 | 3 | 4 | export function debounce(func: Function, wait: number, immediate: boolean = true, lastCall: boolean = true, leakCall: boolean = false) { 5 | if (lastCall !== false) lastCall = true 6 | if (immediate !== false) immediate = true 7 | let previous = 0 8 | let timer: any 9 | return function (...args: any) { 10 | // @ts-ignore 11 | const context = this 12 | const now = Date.now() 13 | 14 | const timeoutToCall = function timeoutToCall() { 15 | if (!leakCall && timer) { 16 | clearTimeout(timer) 17 | timer = undefined 18 | } 19 | 20 | if (!timer) { 21 | timer = setTimeout(function () { 22 | timer = undefined 23 | func.apply(context, args) 24 | }, wait) 25 | } 26 | } 27 | 28 | if (now - previous > wait) { 29 | previous = now 30 | 31 | if (immediate) { 32 | func.apply(context, args) 33 | } else if (lastCall) { 34 | timeoutToCall() 35 | } 36 | } else { 37 | previous = now 38 | if (lastCall) timeoutToCall() 39 | } 40 | } 41 | } 42 | 43 | 44 | export function throttle(func: Function, wait: number, immediate: boolean = true, lastCall: boolean = true) { 45 | return debounce(func, wait, immediate, lastCall, true) 46 | } 47 | 48 | const clkcimap = new Set() 49 | 50 | export function clickWait(cmdkey: string, wait: number = -1): boolean { 51 | if (clkcimap.has(cmdkey)) { 52 | message.info('上一个操作还在执行中,稍等1秒再点') 53 | return true 54 | } 55 | clkcimap.add(cmdkey) 56 | if (wait > 0) { 57 | setTimeout(() => { 58 | clkcimap.delete(cmdkey) 59 | }, wait) 60 | } 61 | return false 62 | } 63 | 64 | export function clickWaitDelete(cmdkey: string): void { 65 | clkcimap.delete(cmdkey) 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/debuglog.ts: -------------------------------------------------------------------------------- 1 | import { useLogStore } from '../store' 2 | import DBCache from './dbcache' 3 | 4 | export interface IStateDebugLog { 5 | logid: number 6 | logtime: string 7 | logtype: string 8 | logmessage: string 9 | } 10 | 11 | class DebugLogC { 12 | public logList: IStateDebugLog[] = [] 13 | public logTime: number = 0 14 | mSaveLogClear() { 15 | this.logList = [] 16 | this.logTime = Date.now() 17 | 18 | try { 19 | DBCache.deleteLogAll().catch(() => {}) 20 | useLogStore().logRefresh(this.logTime) 21 | } catch {} 22 | } 23 | 24 | mSaveDanger(logmessage: string, err: any = undefined) { 25 | this.mSaveLog('danger', logmessage, err) 26 | } 27 | 28 | mSaveWarning(logmessage: string, err: any = undefined) { 29 | this.mSaveLog('warning', logmessage, err) 30 | } 31 | 32 | mSaveSuccess(logmessage: string, err: any = undefined) { 33 | this.mSaveLog('success', logmessage, err) 34 | } 35 | 36 | mSaveLog(logtype: string, logmessage: string, err: any) { 37 | if (!logmessage && !err) return 38 | if (logmessage && typeof logmessage == 'string' && logmessage.length > 500) logmessage = logmessage.substring(0, 500) + '...' 39 | const time = new Date() 40 | if (this.logList.length > 500) { 41 | this.logList.splice(400) 42 | DBCache.deleteLogOutCount(400) 43 | } 44 | 45 | const log = { 46 | logid: time.getTime(), 47 | logtime: time.getDate().toString().padStart(2, '0') + ' ' + time.getHours().toString().padStart(2, '0') + ':' + time.getMinutes().toString().padStart(2, '0') + ':' + time.getSeconds().toString().padStart(2, '0'), 48 | logtype: logtype, 49 | logmessage: logmessage 50 | } 51 | 52 | if (err) { 53 | if (typeof err == 'string') { 54 | log.logmessage = logmessage + ' \n//== Error ==//\n ' + err 55 | } else if (err.message) { 56 | let m = err.message + (err.stack ? ' \n//== Stack ===//\n ' + err.stack : '') 57 | if (m.length > 500) m = m.substring(0, 500) + '...' 58 | log.logmessage = logmessage + ' \n//== Error ==//\n ' + m 59 | } else { 60 | try { 61 | log.logmessage = logmessage + ' \n//== Error ==//\n ' + JSON.stringify(err) 62 | } catch { 63 | log.logmessage = logmessage + ' \n//== Error ==//\n stringify failed' 64 | } 65 | } 66 | } 67 | this.logList = [log].concat(this.logList) 68 | this.logTime = time.getTime() 69 | try { 70 | DBCache.saveLog(log).catch(() => {}) 71 | useLogStore().logRefresh(this.logTime) 72 | } catch {} 73 | } 74 | 75 | async aLoadFromDB() { 76 | const logList2 = await DBCache.getLogAll() 77 | if (logList2) this.logList = logList2 as IStateDebugLog[] 78 | this.logTime = Date.now() 79 | } 80 | } 81 | 82 | const DebugLog = new DebugLogC() 83 | export default DebugLog 84 | -------------------------------------------------------------------------------- /src/utils/electronhelper.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { throttle } from './debounce' 3 | 4 | export function getFromClipboard(): string { 5 | return window.Electron.clipboard.readText() as string 6 | } 7 | 8 | export function copyToClipboard(text: string): void { 9 | window.Electron.clipboard.writeText(text, 'clipboard') 10 | } 11 | export function openExternal(url: string): void { 12 | window.Electron.shell.openExternal(url) 13 | } 14 | 15 | const ElectronPath = { 16 | 17 | AppUserData: '', 18 | 19 | AppResourcesPath: '', 20 | 21 | AppPlatform: '', 22 | 23 | AppArch: '', 24 | 25 | AppExecPath: '', 26 | 27 | AppUserName: '', 28 | env: '' 29 | } 30 | 31 | 32 | function LoadElectronPath(): void { 33 | if (!ElectronPath.AppUserData) { 34 | ElectronPath.AppPlatform = process.platform 35 | ElectronPath.AppArch = process.arch 36 | ElectronPath.AppExecPath = process.execPath 37 | ElectronPath.env = JSON.stringify(process.env) 38 | ElectronPath.AppUserName = process.env.USERNAME || process.env.USER || '' 39 | ElectronPath.AppResourcesPath = (process as any).resourcesPath 40 | if (window.WebPlatformSync) { 41 | window.WebPlatformSync((data: { appPath: string; execPath: string }) => { 42 | ElectronPath.AppUserData = data.appPath 43 | ElectronPath.AppExecPath = data.execPath 44 | window.Electron.WebPlatformSync = data 45 | }) 46 | } 47 | 48 | window.Electron.ElectronPath = ElectronPath 49 | } 50 | } 51 | 52 | export function getUserData(): string { 53 | LoadElectronPath() 54 | return ElectronPath.AppUserData 55 | } 56 | 57 | export function getResourcesPath(fileName: string): string { 58 | try { 59 | LoadElectronPath() 60 | return path.join(ElectronPath.AppResourcesPath, fileName) as string 61 | } catch { 62 | return '' 63 | } 64 | } 65 | 66 | export function getAppNewPath(): string { 67 | try { 68 | LoadElectronPath() 69 | return path.join(ElectronPath.AppResourcesPath, 'app.new') as string 70 | } catch { 71 | return '' 72 | } 73 | } 74 | 75 | let ProgressBarBy = '' 76 | let ProgressBarValue = -1 77 | let ProgressBarNew = -1 78 | const setProgressBar = throttle(() => { 79 | ProgressBarValue = ProgressBarNew 80 | const mode = ProgressBarValue < 0 ? 'none' : ProgressBarBy == 'download' ? 'normal' : 'paused' 81 | if (window.WebSetProgressBar) window.WebSetProgressBar({ pro: ProgressBarValue, mode }) 82 | }, 5000) 83 | 84 | 85 | export function SetProgressBar(value: number, by: string): void { 86 | if (value < 0) value = -1 87 | if (ProgressBarValue == value && ProgressBarBy == by) return 88 | 89 | ProgressBarNew = value 90 | ProgressBarBy = by 91 | if (value < 0 || (ProgressBarValue < 0 && value > 0)) { 92 | 93 | const mode = value < 0 ? 'none' : ProgressBarBy == 'download' ? 'normal' : 'paused' 94 | ProgressBarValue = value 95 | if (window.WebSetProgressBar) window.WebSetProgressBar({ pro: ProgressBarValue, mode: mode }) 96 | } else { 97 | 98 | setProgressBar() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/utils/foot.ts: -------------------------------------------------------------------------------- 1 | const FootMap = new Map() 2 | 3 | export function FootLoading(msg: string, key: string) { 4 | console.log('FootLoading', key, msg) 5 | 6 | if (msg != '') FootMap.set(key, msg) 7 | else FootMap.delete(key) 8 | 9 | let info = '' 10 | FootMap.forEach(function (value, key) { 11 | const item = '' + value + '' 12 | if (info.includes(item) == false) info += item 13 | }) 14 | 15 | const doc = document.getElementById('footLoading') 16 | if (doc) { 17 | if (!info) { 18 | doc.innerHTML = '' 19 | } else { 20 | doc.innerHTML = 21 | '
' + 22 | info 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/utils/idhelper.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PingKuNet/aliyunpan/b2b9a27dbfe439487c4b06c89b4e5c8e5f7a8912/src/utils/idhelper.ts -------------------------------------------------------------------------------- /src/utils/levemap.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { message } from 'ant-design-vue' 3 | import DB from './db' 4 | 5 | interface Dir { 6 | file_id: string 7 | } 8 | 9 | // 1122334455667788 10 | 11 | // datamap 12 | 13 | // 11 14 | 15 | // 62ad76cd1f7dfdc28ad64c1eb6f22492a672a510 16 | function GetDirSize(file_id: string) { 17 | let obj = DirSizeDataMap 18 | let i = 0 19 | const maxi = file_id.length - 4 20 | while (i < maxi) { 21 | const key = file_id.substring(i, i + 4) 22 | obj = obj.get(key) 23 | if (!obj) return 0 24 | i += 4 25 | } 26 | 27 | return obj.get(file_id.substring(i)) || 0 28 | } 29 | 30 | let DirSizeDataMap: Map = new Map() 31 | function SetDirSizeMap(file_id: string, size: number) { 32 | let obj = DirSizeDataMap 33 | 34 | let i = 0 35 | const maxi = file_id.length - 8 36 | while (i < maxi) { 37 | const key = file_id.substring(i, i + 8) 38 | let find = obj.get(key) 39 | if (!find) { 40 | find = new Map() 41 | obj.set(key, find) 42 | } 43 | obj = find 44 | i += 8 45 | } 46 | 47 | obj.set(file_id.substring(i), size) 48 | } 49 | 50 | let DirSizeDataObj: { [key: string]: any } = Object.create(null) 51 | function SetDirSizeObject(file_id: string, size: number) { 52 | let obj = DirSizeDataObj 53 | 54 | let i = 0 55 | const maxi = file_id.length - 8 56 | while (i < maxi) { 57 | const key = file_id.substring(i, i + 8) 58 | let find = obj[key] 59 | if (!find) { 60 | find = Object.create(null) 61 | obj[key] = find 62 | } 63 | obj = find 64 | i += 8 65 | } 66 | 67 | obj[file_id.substring(i)] = size 68 | } 69 | 70 | export async function LoadObject() { 71 | console.time('LoadObject') 72 | window.openDatabase = {} 73 | const drive_id = '8699982' 74 | const jsonsize = await DB.getValueObject('DirFileSize_' + drive_id) 75 | window.openDatabase.sizemap = jsonsize ? (jsonsize as { [key: string]: number }) : {} 76 | // const jsonsizetime = await DB.getValueObject('DirFileSizeTime_' + drive_id) 77 | // window.openDatabase.sizetimemap = jsonsizetime ? (jsonsizetime as { [key: string]: number }) : {} 78 | console.timeEnd('LoadObject') 79 | message.success('LoadObject') 80 | } 81 | 82 | export async function CreatMap() { 83 | console.time('CreatMap') 84 | DirSizeDataMap = new Map() 85 | const sizemap = window.openDatabase.sizemap as { [key: string]: number } 86 | const keys = Object.keys(sizemap) 87 | for (let i = 0, maxi = keys.length; i < maxi; i++) { 88 | // SetDirSizeMap(keys[i], sizemap[keys[i]]) 89 | DirSizeDataMap.set(keys[i], [sizemap[keys[i]], 'size', true]) 90 | } 91 | window.openDatabase.DirSizeDataMap = DirSizeDataMap 92 | DirSizeDataMap = new Map() 93 | console.timeEnd('CreatMap') 94 | message.success('CreatMap') 95 | } 96 | 97 | export async function CreatObject() { 98 | console.time('CreatObject') 99 | DirSizeDataObj = Object.create(null) 100 | const sizemap = window.openDatabase.sizemap as { [key: string]: number } 101 | const keys = Object.keys(sizemap) 102 | for (let i = 0, maxi = keys.length; i < maxi; i++) { 103 | // SetDirSizeObject(keys[i], sizemap[keys[i]]) 104 | DirSizeDataObj[keys[i]] = [sizemap[keys[i]], 'size', true] 105 | } 106 | window.openDatabase.DirSizeDataObj = DirSizeDataObj 107 | DirSizeDataObj = Object.create(null) 108 | console.timeEnd('CreatObject') 109 | message.success('CreatObject') 110 | } 111 | -------------------------------------------------------------------------------- /src/utils/message.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@arco-design/web-vue' 2 | import { h } from 'vue' 3 | 4 | const MessageMap = new Map() 5 | function getCount(msg: string) { 6 | let count = MessageMap.get(msg) || 0 7 | count++ 8 | MessageMap.set(msg, count) 9 | return count 10 | } 11 | export default class message { 12 | static info(msg: string, duration: number = 3, key: string = '') { 13 | const count = getCount(key || msg) 14 | return Message.info({ 15 | id: key || msg, 16 | content: count > 1 ? () => h('div', { innerHTML: msg + '' + count + '' }) : msg, 17 | position: 'bottom', 18 | duration: duration * 1000, 19 | onClose: (id) => MessageMap.delete(key || msg) 20 | }) 21 | } 22 | 23 | static error(msg: string, duration: number = 3, key: string = '') { 24 | const count = getCount(key || msg) 25 | return Message.error({ 26 | id: key || msg, 27 | content: count > 1 ? () => h('div', { innerHTML: msg + '' + count + '' }) : msg, 28 | position: 'bottom', 29 | duration: duration * 1000, 30 | onClose: (id) => MessageMap.delete(key || msg) 31 | }) 32 | } 33 | 34 | static success(msg: string, duration: number = 3, key: string = '') { 35 | const count = getCount(key || msg) 36 | return Message.success({ 37 | id: key || msg, 38 | content: count > 1 ? () => h('div', { innerHTML: msg + '' + count + '' }) : msg, 39 | position: 'bottom', 40 | duration: duration == 0 ? 1 : duration * 1000, 41 | onClose: (id) => MessageMap.delete(key || msg) 42 | }) 43 | } 44 | 45 | static warning(msg: string, duration: number = 3, key: string = '') { 46 | const count = getCount(key || msg) 47 | return Message.warning({ 48 | id: key || msg, 49 | content: count > 1 ? () => h('div', { innerHTML: msg + '' + count + '' }) : msg, 50 | position: 'bottom', 51 | duration: duration * 1000, 52 | onClose: (id) => MessageMap.delete(key || msg) 53 | }) 54 | } 55 | 56 | static loading(msg: string, duration: number = 3, key: string = '') { 57 | const count = 0 58 | return Message.loading({ 59 | id: key || msg, 60 | content: count > 1 ? () => h('div', { innerHTML: msg + '' + count + '' }) : msg, 61 | position: 'bottom', 62 | duration: duration * 1000 63 | }) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/modal.ts: -------------------------------------------------------------------------------- 1 | import { IAliGetFileModel, IAliShareItem } from '../aliapi/alimodels' 2 | import { useModalStore } from '../store' 3 | 4 | export function modalCloseAll() { 5 | useModalStore().showModal('', {}) 6 | } 7 | 8 | export function modalUserSpace() { 9 | useModalStore().showModal('userspace', {}) 10 | } 11 | export function modalCreatNewFile() { 12 | useModalStore().showModal('creatfile', {}) 13 | } 14 | export function modalCreatNewDir(dirtype: string, parentdirid: string = '', callback: any = undefined) { 15 | useModalStore().showModal('creatdir', { dirtype, parentdirid, callback }) 16 | } 17 | 18 | export function modalCreatNewShareLink(sharetype: string, filelist: IAliGetFileModel[]) { 19 | useModalStore().showModal('creatshare', { sharetype, filelist }) 20 | } 21 | 22 | export function modalDaoRuShareLink() { 23 | useModalStore().showModal('daorushare', {}) 24 | } 25 | export function modalDaoRuShareLinkMulti() { 26 | useModalStore().showModal('daorusharemulti', {}) 27 | } 28 | 29 | export function modalRename(istree: boolean, ismulti: boolean) { 30 | useModalStore().showModal(ismulti ? 'renamemulti' : 'rename', { istree }) 31 | } 32 | 33 | export function modalEditShareLink(sharelist: IAliShareItem[]) { 34 | useModalStore().showModal('editshare', { sharelist }) 35 | } 36 | 37 | export function modalShowShareLink(share_id: string, share_pwd: string, share_token: string, withsave: boolean, file_id_list: string[]) { 38 | useModalStore().showModal('showshare', { share_id, share_pwd, share_token, withsave, file_id_list }) 39 | } 40 | 41 | export function modalSelectPanDir(selecttype: string, selectid: string, callback: (user_id: string, drive_id: string, dirID: string, dirName: string) => void) { 42 | useModalStore().showModal('selectpandir', { selecttype, selectid, callback }) 43 | } 44 | 45 | export function modalShuXing(istree: boolean, ismulti: boolean) { 46 | ismulti = false 47 | useModalStore().showModal(ismulti ? 'shuxingmulti' : 'shuxing', { istree }) 48 | } 49 | 50 | export function modalSearchPan() { 51 | useModalStore().showModal('searchpan', {}) 52 | } 53 | 54 | export function modalDLNAPlayer() { 55 | useModalStore().showModal('dlna', {}) 56 | } 57 | export function modalM3U8Download() { 58 | useModalStore().showModal('m3u8download', {}) 59 | } 60 | 61 | export function modalCopyFileTree(filelist: IAliGetFileModel[]) { 62 | useModalStore().showModal('copyfiletree', { filelist }) 63 | } 64 | 65 | export function modalArchive(user_id: string, drive_id: string, file_id: string, file_name: string, parent_file_id: string, password: string) { 66 | useModalStore().showModal('archive', { user_id, drive_id, file_id, file_name, parent_file_id, password }) 67 | } 68 | 69 | export function modalArchivePassword(user_id: string, drive_id: string, file_id: string, file_name: string, parent_file_id: string, domain_id: string, ext: string) { 70 | useModalStore().showModal('archivepassword', { user_id, drive_id, file_id, file_name, parent_file_id, domain_id, ext }) 71 | } 72 | 73 | export function modalUpload(file_id: string, filelist: string[]) { 74 | useModalStore().showModal('upload', { file_id, filelist }) 75 | } 76 | 77 | export function modalDownload(istree: boolean) { 78 | useModalStore().showModal('download', { istree }) 79 | } 80 | -------------------------------------------------------------------------------- /src/utils/sha1workerpool.ts: -------------------------------------------------------------------------------- 1 | const MAXSIZE = Math.max(2, navigator.hardwareConcurrency - 1) 2 | 3 | export interface Sha1Model { 4 | hash: string 5 | localFilePath: string 6 | access_token: string 7 | } 8 | 9 | export default class Sha1WorkerPool { 10 | queueWithCallback: [args: any, callback: (result: any, worker: Worker) => void, error: (err: any, worker: Worker) => void][] = [] 11 | freeWorkers: Worker[] = [] 12 | workers: Set = new Set() 13 | 14 | public Init() { 15 | if (this.workers.size > 0) return 16 | 17 | for (let i = 0; i < MAXSIZE; i++) { 18 | const worker = new Worker('./sha1filework.js') 19 | this.freeWorkers.push(worker) 20 | this.workers.add(worker) 21 | } 22 | } 23 | 24 | 25 | public StartWithCallback(args: Sha1Model, callback: (result: any, worker: Worker) => void, error: (err: any, worker: Worker) => void) { 26 | this.Init() 27 | if (this.freeWorkers.length > 0) { 28 | const worker = this.freeWorkers.pop()! 29 | worker.onmessage = (e: any) => { 30 | callback(e.data, worker) 31 | } 32 | worker.onerror = (e: any) => { 33 | error(e, worker) 34 | } 35 | worker.postMessage(args) 36 | } else { 37 | this.queueWithCallback.push([args, callback, error]) 38 | } 39 | } 40 | 41 | public FinishWithCallback(worker: Worker) { 42 | worker.onmessage = null 43 | worker.onerror = null 44 | this.freeWorkers.push(worker) 45 | if (this.queueWithCallback.length > 0) { 46 | const item = this.queueWithCallback.shift()! 47 | this.StartWithCallback(item[0], item[1], item[2]) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/shareurl.ts: -------------------------------------------------------------------------------- 1 | import { useSettingStore } from '../store' 2 | 3 | export function GetShareUrlFormate(share_name: string, share_url: string, share_pwd: string): string { 4 | let Formate = useSettingStore().uiShareFormate.replaceAll('\\n', '\n') 5 | const s1 = Formate.indexOf('URL') 6 | if (!share_pwd) { 7 | 8 | const s2 = Formate.indexOf('PWD') 9 | if (s1 >= 0 && s2 > s1) Formate = Formate.substring(0, s1 + 3) + Formate.substring(s2 + 3) 10 | console.log(Formate) 11 | } 12 | const url = Formate.replace('URL', share_url).replace('PWD', share_pwd).replace('NAME', share_name) 13 | if (url && s1 >= 0) return url 14 | return share_name + ' ' + share_url + (share_pwd ? ' 提取码:' + share_pwd : '') 15 | } 16 | 17 | export interface IID { 18 | id: string 19 | pwd: string 20 | } 21 | 22 | 23 | export function ParseShareIDList(txt: string): IID[] { 24 | txt = txt.replaceAll('密码', '提取码').replaceAll('password', '提取码').replaceAll('pwd', '提取码').replaceAll('PWD', '提取码') 25 | txt = txt.replaceAll('\n提取码', '提取码') 26 | const list: IID[] = [] 27 | txt.split('\n').map((t) => { 28 | const p = GetShareID(t) 29 | if (p.id) list.push(p) 30 | return true 31 | }) 32 | return list 33 | } 34 | 35 | export function ParseShareIDOne(txt: string): IID { 36 | txt = txt.replaceAll('密码', '提取码').replaceAll('password', '提取码').replaceAll('pwd', '提取码').replaceAll('PWD', '提取码') 37 | txt = txt.replaceAll('\n提取码', '提取码') 38 | return GetShareID(txt) 39 | } 40 | 41 | 42 | function GetShareID(txt: string): IID { 43 | const ret = { id: '', pwd: '' } 44 | const id = txt.match(/(?<=\/s\/)[0-9a-zA-Z]{11,12}/) 45 | if (id && id.length > 0) ret.id = id[0] 46 | const pwd = txt.match(/(?<=提取码[^0-9a-zA-Z]{0,6})[0-9a-zA-Z]{4}/) 47 | if (pwd && pwd.length > 0) ret.pwd = pwd[0] 48 | return ret 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/utils.ts: -------------------------------------------------------------------------------- 1 | import { deflateRawSync, inflateRawSync } from 'zlib' 2 | import crypto from 'crypto' 3 | 4 | export function ArrayCopyReverse(arr: any[]): any[] { 5 | const copy: any[] = [] 6 | for (let i = arr.length - 1; i >= 0; i--) { 7 | copy.push(arr[i]) 8 | } 9 | return copy 10 | } 11 | export function ArrayCopy(arr: any[]): any[] { 12 | const copy: any[] = [] 13 | for (let i = 0, maxi = arr.length; i < maxi; i++) { 14 | copy.push(arr[i]) 15 | } 16 | return copy 17 | } 18 | 19 | export function MapKeyToArray(map: Map): T[] { 20 | const arr: T[] = [] 21 | const keys = map.keys() 22 | for (let i = 0, maxi = map.size; i < maxi; i++) { 23 | arr.push(keys.next().value) 24 | } 25 | return arr 26 | } 27 | 28 | export function MapValueToArray(map: Map): T[] { 29 | const arr: T[] = [] 30 | const keys = map.values() 31 | for (let i = 0, maxi = map.size; i < maxi; i++) { 32 | arr.push(keys.next().value) 33 | } 34 | return arr 35 | } 36 | 37 | export function ArrayToMap(keyname: string, arr: T[]) { 38 | const map = new Map() 39 | let item: any 40 | for (let i = 0, maxi = arr.length; i < maxi; i++) { 41 | item = arr[i] 42 | map.set(item[keyname], item) 43 | } 44 | return map 45 | } 46 | 47 | export function ArrayKeyList(keyname: string, arr: any[]): T[] { 48 | const selectkeys: T[] = [] 49 | for (let i = 0, maxi = arr.length; i < maxi; i++) { 50 | selectkeys.push(arr[i][keyname]) 51 | } 52 | return selectkeys 53 | } 54 | 55 | export function BlobToString(body: Blob, encoding: string): Promise { 56 | return new Promise((resolve) => { 57 | const reader = new FileReader() 58 | reader.readAsText(body, encoding) 59 | reader.onload = function () { 60 | resolve((reader.result as string) || '') 61 | } 62 | }) 63 | } 64 | 65 | export function BlobToBuff(body: Blob): Promise { 66 | return new Promise((resolve) => { 67 | const reader = new FileReader() 68 | reader.readAsArrayBuffer(body) 69 | reader.onload = function () { 70 | resolve(reader.result as ArrayBuffer) 71 | } 72 | }) 73 | } 74 | 75 | export function GzipObject(input: object): Buffer { 76 | return deflateRawSync(JSON.stringify(input)) 77 | } 78 | 79 | export function UnGzipObject(input: Buffer): object { 80 | return JSON.parse(inflateRawSync(input).toString()) 81 | } 82 | 83 | export function HanToPin(input: string): string { 84 | if (!input) return '' 85 | // eslint-disable-next-line no-undef 86 | const arr = pinyinlite(input, { keepUnrecognized: true }) 87 | const strarr = new Array(arr.length * 2 + 1) 88 | let l = false 89 | for (let p = 1, i = 0, maxi = arr.length; i < maxi; p += 2, i++) { 90 | strarr[p] = arr[i].join(' ') 91 | l = strarr[p].length > 1 92 | if (l) { 93 | strarr[p - 1] = ' ' 94 | strarr[p + 1] = ' ' 95 | } else { 96 | strarr[p + 1] = '' 97 | } 98 | } 99 | strarr[0] = '' 100 | return strarr.join('') 101 | } 102 | 103 | 104 | export function GetOssExpires(downUrl: string) { 105 | if (!downUrl || downUrl.includes('x-oss-expires=') == false) return 0 106 | try { 107 | 108 | let expires = downUrl.substring(downUrl.indexOf('x-oss-expires=') + 'x-oss-expires='.length) 109 | expires = expires.substring(0, expires.indexOf('&')) 110 | const lastTime = parseInt(expires) - Math.floor(Date.now() / 1000) 111 | return lastTime 112 | } catch { 113 | return 0 114 | } 115 | } 116 | 117 | export function hashCode(key: string) { 118 | let hash = 0 119 | for (let i = 0, maxi = key.length; i < maxi; i++) { 120 | hash = ((hash << 5) - hash + key.charCodeAt(i++)) << 0 121 | } 122 | return (hash >>> 0).toString(16).padStart(8, '0') 123 | } 124 | 125 | export function md5Code(key: string) { 126 | const buffa = Buffer.from(key) 127 | const md5a = crypto.createHash('md5').update(buffa).digest('hex') 128 | return md5a 129 | } 130 | -------------------------------------------------------------------------------- /src/utils/worker.ts: -------------------------------------------------------------------------------- 1 | export async function WorkerUploadFiles(ingoredList: string[], user_id: string, drive_id: string, parent_file_id: string, files: string[]) { 2 | let worker: any 3 | let result: any 4 | await new Promise((resolve) => { 5 | worker = new Worker('uploadfilesworker.js') 6 | worker.addEventListener('message', (event: any) => { 7 | if (event.data.state == 'success') { 8 | console.log(event) 9 | result = event.data.result 10 | resolve() 11 | } 12 | }) 13 | worker.addEventListener('error', function (event: any) { 14 | resolve() 15 | }) 16 | worker.postMessage({ ingoredList, user_id, drive_id, parent_file_id, files }) 17 | }) 18 | .catch(() => {}) 19 | .then(() => { 20 | if (worker) worker.terminate() 21 | }) 22 | return result 23 | } 24 | -------------------------------------------------------------------------------- /src/workerpage/uidownload.ts: -------------------------------------------------------------------------------- 1 | export function DownloadTrigger() { 2 | 3 | 4 | 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/workerpage/workercmd.ts: -------------------------------------------------------------------------------- 1 | import AliDirList from '../aliapi/dirlist' 2 | import { useSettingStore } from '../store' 3 | import TreeStore from '../store/treestore' 4 | import UserDAL from '../user/userdal' 5 | import DebugLog from '../utils/debuglog' 6 | import { DownloadTrigger } from './uidownload' 7 | import { UploadAdd, UploadCmd, UploadReport } from './uiupload' 8 | 9 | // eslint-disable-next-line no-unused-vars 10 | let workerTimer: any 11 | export function WorkerPage(type: string) { 12 | if (window.WinMsg) return 13 | 14 | if (type == 'upload') { 15 | window.WinMsg = WinMsgUpload 16 | const func = () => { 17 | try { 18 | UploadReport().catch() 19 | } catch {} 20 | workerTimer = setTimeout(func, 1000) 21 | } 22 | workerTimer = setTimeout(func, 6000) 23 | const element = document.createElement('div') 24 | element.innerHTML = '

上传进程

' 25 | document.body.append(element) 26 | } 27 | if (type == 'download') { 28 | window.WinMsg = WinMsgDownload 29 | const func = () => { 30 | try { 31 | DownloadTrigger() 32 | } catch {} 33 | workerTimer = setTimeout(func, 1000) 34 | } 35 | workerTimer = setTimeout(func, 6000) 36 | const element = document.createElement('div') 37 | element.innerHTML = '

下载进程

' 38 | document.body.append(element) 39 | } 40 | } 41 | const AllDirLock = new Map() 42 | export const WinMsgUpload = function (arg: any) { 43 | // console.log(arg) 44 | try { 45 | if (arg.cmd == 'SettingRefresh') { 46 | useSettingStore().$reset() 47 | } else if (arg.cmd == 'ClearUserToken') { 48 | UserDAL.ClearUserTokenMap() 49 | } else if (arg.cmd == 'UploadAdd') { 50 | UploadAdd(arg.UploadList) 51 | } else if (arg.cmd == 'UploadCmd') { 52 | UploadCmd(arg.Command, arg.IsAll, arg.UploadIDList, arg.TaskIDList) 53 | } else if (arg.cmd == 'AllDirList') { 54 | LoadAllDirList(arg.user_id, arg.drive_id) 55 | } 56 | } catch {} 57 | } 58 | 59 | function LoadAllDirList(user_id: string, drive_id: string): void { 60 | console.time('AllDirList') 61 | const lock = AllDirLock.get(drive_id) || 0 62 | const time = Date.now() / 1000 63 | console.log('AllDirList', 'lock=', lock, 'time=', time) 64 | if (lock) { 65 | if (time - lock < 300) { 66 | console.log('AllDirList Break') 67 | window.WinMsgToMain({ cmd: 'MainSaveAllDir', OneDriver: undefined, ErrorMessage: 'time' }) 68 | return 69 | } 70 | } 71 | AllDirLock.set(drive_id, time) 72 | AliDirList.ApiFastAllDirListByPID(user_id, drive_id) 73 | .then((data) => { 74 | console.timeEnd('AllDirList') 75 | AllDirLock.delete(drive_id) 76 | if (!data.next_marker) { 77 | TreeStore.ConvertToOneDriver(drive_id, data.items, true, false).then((one) => { 78 | window.WinMsgToMain({ cmd: 'MainSaveAllDir', OneDriver: one, ErrorMessage: '' }) 79 | }) 80 | } else { 81 | DebugLog.mSaveWarning('列出文件夹失败file_id=all' + ' next_marker=' + data.next_marker) 82 | window.WinMsgToMain({ cmd: 'MainSaveAllDir', OneDriver: undefined, ErrorMessage: data.next_marker }) 83 | } 84 | }) 85 | .catch((err: any) => { 86 | DebugLog.mSaveWarning('列出文件夹失败file_id=all', err) 87 | window.WinMsgToMain({ cmd: 'MainSaveAllDir', OneDriver: undefined, ErrorMessage: err.message || '未知错误' }) 88 | }) 89 | } 90 | 91 | export const WinMsgDownload = function (arg: any) { 92 | // console.log(arg) 93 | try { 94 | if (arg.cmd == 'SettingRefresh') { 95 | useSettingStore().$reset() 96 | } else if (arg.cmd == 'ClearUserToken') { 97 | UserDAL.ClearUserTokenMap() 98 | } 99 | } catch {} 100 | } 101 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "importHelpers": true, 7 | "jsx": "preserve", 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true, 10 | "sourceMap": false, 11 | "baseUrl": "./", 12 | "strict": true, 13 | "paths": {}, 14 | "allowSyntheticDefaultImports": true, 15 | "skipLibCheck": true 16 | }, 17 | "include": ["src"], 18 | "references": [ 19 | { "path": "./tsconfig.node.json" } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "composite": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "jsx": "preserve", 8 | "resolveJsonModule": true, 9 | "allowSyntheticDefaultImports": true, 10 | "types": [ 11 | "vite-plugin-electron/electron-env" 12 | ] 13 | }, 14 | "include": ["package.json", "electron"] 15 | } 16 | -------------------------------------------------------------------------------- /types.d.ts: -------------------------------------------------------------------------------- 1 | 2 | declare namespace NodeJS { 3 | interface ProcessEnv { 4 | NODE_ENV: 'development' | 'production' 5 | readonly VITE_DEV_SERVER_HOST: string 6 | readonly VITE_DEV_SERVER_PORT: string 7 | } 8 | } 9 | 10 | declare interface Window { 11 | Electron: any 12 | platform: any 13 | WebToElectron: any 14 | WebToElectronCB: any 15 | WebSpawnSync: any 16 | WebExecSync: any 17 | WebShowOpenDialogSync: any 18 | WebShowSaveDialogSync: any 19 | WebShowItemInFolder: any 20 | WebPlatformSync: any 21 | WebClearCookies: any 22 | WebClearCache: any 23 | WebSaveTheme: any 24 | WebUserToken: any 25 | WebReload: any 26 | WebRelaunch: any 27 | WebRelaunchAria: () => Promise 28 | WebSetProgressBar: any 29 | WebSetCookies: any 30 | WebOpenWindow: any 31 | WebShutDown: any 32 | openDatabase: any 33 | loginfn: any 34 | postdataFunc: any 35 | Prism: any 36 | winmain: number 37 | winworker: number 38 | WinMsg: any 39 | WinMsgToMain: any 40 | WinMsgToUI: any 41 | getDvaApp: any 42 | dark: boolean 43 | test: any 44 | } 45 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { rmSync } from 'fs' 2 | import path from 'path' 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import electron, { onstart } from 'vite-plugin-electron' 6 | import pkg from './package.json' 7 | 8 | rmSync('dist', { recursive: true, force: true }) // v14.14.0 9 | 10 | // https://vitejs.dev/config/ 11 | export default defineConfig({ 12 | build: { 13 | rollupOptions: { 14 | output: { 15 | chunkFileNames: '[name].js', 16 | entryFileNames: '[name].js', 17 | assetFileNames: '[name].[ext]', 18 | } 19 | } 20 | }, 21 | plugins: [ 22 | vue({ 23 | template:{ 24 | compilerOptions:{ 25 | isCustomElement: tag => tag == 'Webview' 26 | } 27 | } 28 | }), 29 | electron({ 30 | main: { 31 | entry: 'electron/main/index.ts', 32 | vite: { 33 | build: { 34 | // For Debug 35 | // sourcemap: true, 36 | outDir: 'dist/electron/main', 37 | }, 38 | // Will start Electron via VSCode Debug 39 | plugins: [process.env.VSCODE_DEBUG ? onstart() : null], 40 | }, 41 | }, 42 | preload: { 43 | input: { 44 | // You can configure multiple preload here 45 | index: path.join(__dirname, 'electron/preload/index.ts'), 46 | }, 47 | vite: { 48 | build: { 49 | // For Debug 50 | // sourcemap: 'inline', 51 | outDir: 'dist/electron/preload', 52 | }, 53 | }, 54 | }, 55 | // Enables use of Node.js API in the Renderer-process 56 | // https://github.com/electron-vite/vite-plugin-electron/tree/main/packages/electron-renderer#electron-renderervite-serve 57 | renderer: {}, 58 | }), 59 | ], 60 | server: process.env.VSCODE_DEBUG ? { 61 | host: pkg.debug.env.VITE_DEV_SERVER_HOSTNAME, 62 | port: pkg.debug.env.VITE_DEV_SERVER_PORT, 63 | } : undefined, 64 | }) 65 | -------------------------------------------------------------------------------- /源码开发打包帮助.md: -------------------------------------------------------------------------------- 1 | ### 小白羊v3版本源码帮助 2 | 3 | v3采用 ts + vue3 + vite + electron 模板开发 4 | 5 | #### 1.下载源代码 6 | 7 | ``` 8 | https://github.com/PingKuNet/aliyunpan.git 9 | ``` 10 | 11 | #### 2.打开代码目录,安装依赖 12 | 13 | ```cmd 14 | yarn install 15 | ``` 16 | 17 | #### 3.开发调试运行 18 | 19 | ```cmd 20 | yarn run dev 21 | ``` 22 | 23 | 执行命令后会调起electron窗口,配合vscode正常开发调试即可 24 | 25 | #### 4.打包发布 26 | 27 | ```cmd 28 | yarn run build 29 | ``` 30 | 31 | 执行命令后会生成`release\win-unpacked\resources\app.asar`文件 32 | 把该文件复制到任意electron版本的`resources`目录下即可打包出安装包 33 | --------------------------------------------------------------------------------