├── .github ├── release.info ├── release.json └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── UPDATE.md ├── downloaders_template ├── AM3u8Downloader.class ├── AM3u8Downloader.java ├── BM3u8Downloader.class └── BM3u8Downloader.java ├── libs ├── Java-WebSocket-1.4.0-with-dependencies.jar └── core-3.3.3.jar ├── package.bat ├── package.sh ├── release ├── ILikeAcFun.jar ├── LICENSE │ ├── LICENSE.GPLv3 │ └── third-party │ │ ├── COPYING.GPLv2 │ │ ├── COPYING.GPLv3 │ │ ├── COPYING.LGPLv2.1 │ │ └── COPYING.LGPLv3 ├── config │ ├── favicon.ico │ └── repo.config ├── ffmpeg.exe ├── install.vbs ├── run-UI-debug.bat ├── run-UI.bat ├── uninstall.bat └── update.bat └── src ├── nicelee ├── acfun │ ├── INeedAV.java │ ├── INeedLogin.java │ ├── PackageScanLoader.java │ ├── annotations │ │ └── Acfun.java │ ├── downloaders │ │ ├── Downloader.java │ │ ├── IDownloader.java │ │ └── impl │ │ │ ├── FLVDownloader.java │ │ │ ├── M3u8Downloader.java │ │ │ ├── M4SDownloader.java │ │ │ ├── MP4Downloader.java │ │ │ └── TestDownloader.java │ ├── enums │ │ ├── ChannelEnum.java │ │ ├── StatusEnum.java │ │ └── VideoQualityEnum.java │ ├── model │ │ ├── ClipInfo.java │ │ ├── FavList.java │ │ ├── UserInfo.java │ │ └── VideoInfo.java │ ├── parsers │ │ ├── IInputParser.java │ │ ├── IParamSetter.java │ │ ├── InputParser.java │ │ └── impl │ │ │ ├── AABangumiParser.java │ │ │ ├── AACollectionParser.java │ │ │ ├── ACParser.java │ │ │ ├── AbstractBaseParser.java │ │ │ ├── AbstractPageQueryParser.java │ │ │ ├── URL4FavParser.java │ │ │ └── URL4UPAllParser.java │ ├── plugin │ │ ├── CustomClassLoader.java │ │ └── Plugin.java │ └── util │ │ ├── CmdUtil.java │ │ ├── ConfigUtil.java │ │ ├── HttpCookies.java │ │ ├── HttpHeaders.java │ │ ├── HttpRequestUtil.java │ │ ├── Logger.java │ │ ├── MD5.java │ │ ├── QrCodeUtil.java │ │ ├── RepoUtil.java │ │ ├── VersionManagerUtil.java │ │ └── net │ │ ├── TrustAllCertSSLUtil.java │ │ └── stream │ │ ├── ChunkedInputStream.java │ │ ├── InflateWithHeaderInputStream.java │ │ └── TestStream.java ├── test │ └── junit │ │ ├── INeedAVTest.java │ │ ├── INeedLoginTest.java │ │ ├── RepoTest.java │ │ ├── UtilTest.java │ │ └── VersionManagerTest.java └── ui │ ├── FrameAbout.java │ ├── FrameMain.java │ ├── FrameQRCode.java │ ├── Global.java │ ├── TabDownload.java │ ├── TabIndex.java │ ├── TabVideo.java │ ├── item │ ├── ClipInfoPanel.java │ ├── DownloadInfoPanel.java │ ├── JOptionPaneManager.java │ ├── MJMenuBar.java │ ├── MJTextField.java │ ├── MJTitleBar.java │ ├── OperationPanel.java │ └── impl │ │ ├── TextTransferHandler.java │ │ └── TextTransferable.java │ └── thread │ ├── DownloadRunnable.java │ ├── GetVideoDetailThread.java │ ├── LoginThread.java │ ├── MonitoringThread.java │ └── StreamManager.java ├── org └── json │ ├── .gitignore │ ├── CDL.java │ ├── Cookie.java │ ├── CookieList.java │ ├── HTTP.java │ ├── HTTPTokener.java │ ├── JSONArray.java │ ├── JSONException.java │ ├── JSONML.java │ ├── JSONObject.java │ ├── JSONPointer.java │ ├── JSONPointerException.java │ ├── JSONPropertyIgnore.java │ ├── JSONPropertyName.java │ ├── JSONString.java │ ├── JSONStringer.java │ ├── JSONTokener.java │ ├── JSONWriter.java │ ├── LICENSE │ ├── Property.java │ ├── README.md │ ├── XML.java │ └── XMLTokener.java └── resources ├── _.jpg ├── _h.jpg ├── about.html ├── app.config ├── background.png ├── favicon.png ├── header.png ├── title.png ├── x.jpg └── xh.jpg /.github/release.info: -------------------------------------------------------------------------------- 1 | * fix: 修复两种`aa[0-9]+`类型链接的解析冲突 2 | * feat: 添加`1080P60`、`720P60`清晰度支持 3 | * feat: 提供选项`acfun.quality.noQualityRequest = true/false`,可以不尝试获取视频实际清晰度,直接提供所有选择。 #17 4 | * feat: 添加打印信息到`download.txt`的模板`downloader` #17 5 | * feat: 添加调用`N_m3u8DL-CLI`下载m3u8的模板`downloader` #17 6 | * feat: 提供选项`acfun.debug.ffmpeg= true/false `,可以控制是否打印ffmpeg的输出 7 | 8 | 9 | * 如果没有ffmpeg环境,请将```ffmpeg```[下载](https://github.com/nICEnnnnnnnLee/AcFunDown/raw/master/release/ffmpeg.exe)后置于```ILikeAcFun.jar```同级目录下(全编译70M左右) 10 | * 结构目录参考[此处](https://github.com/nICEnnnnnnnLee/AcFunDown/tree/master/release) 11 | * 附: [ffmpeg官网](http://www.ffmpeg.org/download.html) -------------------------------------------------------------------------------- /.github/release.json: -------------------------------------------------------------------------------- 1 | { 2 | "tag_latest": "1.3" 3 | } -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a package using Maven and then publish it to GitHub packages when a release is created 2 | # For more information see: https://github.com/actions/setup-java#apache-maven-with-a-settings-path 3 | 4 | name: CI 5 | 6 | on: 7 | push: 8 | paths: 9 | # Trigger only when src/** changes 10 | - ".github/release.json" 11 | 12 | pull_request: 13 | paths: 14 | # Trigger only when src/** changes 15 | - ".github/release.json" 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up JDK 1.8 25 | uses: actions/setup-java@v4 26 | with: 27 | distribution: 'temurin' 28 | java-version: '8' 29 | 30 | - name: Read tag_latest 31 | id: tag_latest 32 | uses: ashley-taylor/read-json-property-action@v1.2 33 | with: 34 | path: ./.github/release.json 35 | property: tag_latest 36 | 37 | 38 | - name: Package Jar 39 | run: | 40 | # 复制整个文件夹 41 | mkdir target 42 | cp -r src/. target 43 | 44 | # 删除不需要的java文件 45 | rm -rf ./target/nicelee/test 46 | 47 | # 获取java文件列表 48 | cd target 49 | find `pwd` -name "*.java" > ../sources.txt 50 | cd .. 51 | 52 | # 获取环境变量,解压lib包 53 | cd libs 54 | find `pwd` -name "*.jar" > ../libs.txt 55 | cat ../libs.txt 56 | cd ../target 57 | for jar in `cat ../libs.txt` 58 | do 59 | jclasspath=$jar:$jclasspath 60 | echo $jar 61 | jar xvf $jar 62 | done 63 | cd .. 64 | echo $jclasspath 65 | 66 | # 编译java 67 | javac -cp $jclasspath -encoding UTF-8 @sources.txt 68 | 69 | # 删除所有.java文件 70 | cd target 71 | find . -name "*.java" |xargs rm -rf {} 72 | cd .. 73 | 74 | # 打包 75 | jar cvfe ILikeAcFun.jar nicelee.ui.FrameMain -C ./target . 76 | 77 | - name: ZIP files 78 | run: | 79 | rm -rf ./config 80 | rm -rf ./LICENSE 81 | mkdir ./config/ 82 | mkdir ./LICENSE/ 83 | mv -f ./release/install.vbs . 84 | mv -f ./release/run-UI.bat . 85 | mv -f ./release/run-UI-debug.bat . 86 | mv -f ./release/uninstall.bat . 87 | mv -f ./release/update.bat . 88 | mv -f ./release/config/* ./config/ 89 | mv -f ./release/LICENSE/* ./LICENSE/ 90 | 91 | zip AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ILikeAcFun.jar 92 | zip -m AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./install.vbs 93 | zip -m AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./run-UI.bat 94 | zip -m AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./run-UI-debug.bat 95 | zip -m AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./uninstall.bat 96 | zip -m AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./update.bat 97 | zip -rm AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./config/ 98 | zip -rm AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip ./LICENSE/ 99 | 100 | - name: Create Release 101 | uses: softprops/action-gh-release@v2 102 | with: 103 | tag_name: V${{steps.tag_latest.outputs.value}} 104 | name: AcfunDown - v${{steps.tag_latest.outputs.value}} 105 | body_path: ./.github/release.info 106 | draft: false 107 | prerelease: false 108 | files: | 109 | AcfunDown.v${{steps.tag_latest.outputs.value}}.release.zip 110 | 111 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.settings 3 | /.classpath 4 | */download 5 | *.zip 6 | *.log 7 | *.config 8 | *.bat 9 | *.flv 10 | *.mp4 11 | *.exe 12 | *.jar 13 | *.part 14 | *.m4s 15 | *.txt 16 | *.ts 17 | /bin 18 | /target 19 | /release/prelook/png 20 | /release/prelook/gif 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ILikeAcFun - AcFunDown 2 | ![语言java](https://img.shields.io/badge/Require-java-green.svg) 3 | ![支持系统 Win/Linux/Mac](https://img.shields.io/badge/Platform-%20win%20|%20linux%20|%20mac-lightgrey.svg) 4 | ![测试版本64位Win10系统, jre 1.8.0_101](https://img.shields.io/badge/TestPass-Win10%20x64__java__1.8.0__101-green.svg) 5 | ![开源协议GPL3.0](https://img.shields.io/badge/license-gpl--3.0-green.svg) 6 | ![当前版本](https://img.shields.io/github/release/nICEnnnnnnnLee/AcFunDown.svg?style=flat-square) 7 | ![Release 下载总量](https://img.shields.io/github/downloads/nICEnnnnnnnLee/AcFunDown/total.svg?style=flat-square) 8 | 9 | AcFun 视频下载器,用于下载A站视频。 10 | =============================== 11 | 由[B站视频下载器](https://github.com/nICEnnnnnnnLee/BilibiliDown)改编而来 12 | **以下多图警告** 13 | 14 | ## :smile:特性 15 | + 支持UI界面(自认为是傻瓜式操作) 16 | + 支持扫码登录(能看=能下,反过来也一样) 17 | + 支持各种链接解析(直接输入各种网页链接或 ac号等) 18 | + 支持多p下载! 19 | + 支持收藏夹下载!! 20 | + 支持UP主视频下载!!! 21 | + 支持断点续传下载!!!!!(因异常原因退出后, 只要下载目录不变, 直接在上次基础上继续下载) 22 | 23 | ## :smile:免责声明 24 | + 本项目为基于浏览器行为的个性化定制工具,其功能是**为A站用户提供其可接触权限内的内容的离线保存**,涉及到的多媒体内容版权归其所有者所有。 25 | + 用户对多媒体资源的剪辑、再发布等任何行为,均应确保获得所有者授权。 26 | + 作者对使用此工具或基于此工具的二次开发所产生的任何行为概不负责。 27 | 28 | ## :smile:第三方库使用声明 29 | * 使用[JSON.org](https://github.com/stleary/JSON-java)库做简单的Json解析[![](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/stleary/JSON-java/blob/master/LICENSE) 30 | * 使用[zxing](https://github.com/zxing/zxing)库生成链接二维码图片[![](https://img.shields.io/badge/license-Apache%202-green.svg)](https://raw.githubusercontent.com/zxing/zxing/master/LICENSE) 31 | * 使用[ffmpeg](http://www.ffmpeg.org)进行转码(ts片段转换为mp4)[![](https://img.shields.io/badge/license-LGPL%20(%3E%3D%202.1)%2FGPL%20(%3E%3D%202)-green.svg)](http://www.ffmpeg.org/legal.html) 32 | 33 | ## :smile:Win32/Linux/Mac用户请看过来 34 | + 自带的```ffmpeg.exe```为WIN 64位,32位系统或其它平台请自行[官网](http://www.ffmpeg.org/download.html)下载,替换源程序; 35 | + 对于非WIN用户,请直接使用命令行调用该程序 36 | ```javaw -Dfile.encoding=utf-8 -jar ILikeAcFun.jar``` 37 | + 对于非WIN用户,使用程序的一键更新功能后,请人工`update/ILikeAcFun.update.jar`替换掉`ILikeAcFun.jar` 38 | 39 | ## :smile:其它 40 | * **下载地址**: [https://github.com/nICEnnnnnnnLee/AcFunDown/releases](https://github.com/nICEnnnnnnnLee/AcFunDown/releases) 41 | * **GitHub**: [https://github.com/nICEnnnnnnnLee/AcFunDown](https://github.com/nICEnnnnnnnLee/AcFunDown) 42 | * [**更新日志**](https://github.com/nICEnnnnnnnLee/AcFunDown/blob/master/UPDATE.md) 43 | 44 |
45 | LICENSE 46 | 47 | 48 | [第三方LICENSE](https://github.com/nICEnnnnnnnLee/AcFunDown/tree/master/release/LICENSE/third-party) 49 | GPL 3.0 50 |
51 | -------------------------------------------------------------------------------- /UPDATE.md: -------------------------------------------------------------------------------- 1 | ## UPDATE 2 | * v1.3 3 | * fix: 修复两种`aa[0-9]+`类型链接的解析冲突 4 | * feat: 添加`1080P60`、`720P60`清晰度支持 5 | * feat: 提供选项`acfun.quality.noQualityRequest = true/false`,可以不尝试获取视频实际清晰度,直接提供所有选择。 #17 6 | * feat: 添加打印信息到`download.txt`的模板`downloader` #17 7 | * feat: 添加调用`N_m3u8DL-CLI`下载m3u8的模板`downloader` #17 8 | * feat: 提供选项`acfun.debug.ffmpeg= true/false `,可以控制是否打印ffmpeg的输出 9 | * v1.2 10 | * fix #14, 修复up主所有列表链接解析 11 | * fix: 支持下载某些登录才能播放的视频(e.g. ac15112793) 12 | * feat: 支持视频合集链接 13 | * feat: 支持收藏夹功能 14 | * feat: 支持自定义解析下载 15 | 16 | * v1.1 17 | * 修复 [issue #12](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/12)修复解析问题 18 | * 优化 [issue #11](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/11)添加自定义背景图片功能,将想要的背景图放入config文件夹下即可`config/background.png|jpg` 19 | * v1.0 20 | * 修复遗留问题:文件名清晰度不匹配 21 | * fix [issue #9](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/9)修复UP主频道解析失败的问题 22 | * 添加自动化部署workflow 23 | * 添加打包脚本 24 | * v0.9 25 | * fix [issue #8](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/8)重写清晰度获取逻辑 26 | * v0.8 27 | * fix [issue #7](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/7)重写获取信息的正则表达式 28 | * v0.7 29 | * fix [issue #6](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/6)修复AcFun番剧清晰度错误的问题 30 | * v0.6 31 | * fix [issue #5](https://github.com/nICEnnnnnnnLee/AcFunDown/issues/5)修复AcFun番剧接口失效导致的番剧无法下载的问题 32 | * v0.5 33 | * 修复AcFun更新导致的番剧无法下载的问题 34 | * v0.4 35 | * 优化下载后TS合成失败提示 36 | * 修复番剧最新链接的错误解析 37 | * 纠正番剧下载内部识别序号,正确判断是否下载 38 | * v0.3 39 | * 新增`https://www.acfun.cn/bangumi/ab5024869`最新番剧解析 40 | * 修复半自动更新的若干bug 41 | * v0.2 42 | * 修复AcFun更新导致的番剧无法下载的问题 43 | * v0.1 44 | * 初始版本 -------------------------------------------------------------------------------- /downloaders_template/AM3u8Downloader.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/downloaders_template/AM3u8Downloader.class -------------------------------------------------------------------------------- /downloaders_template/AM3u8Downloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import java.io.BufferedWriter; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStreamWriter; 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.LinkedList; 11 | import java.util.Map.Entry; 12 | import java.util.function.Consumer; 13 | import java.util.regex.Matcher; 14 | import java.util.regex.Pattern; 15 | 16 | import nicelee.acfun.annotations.Acfun; 17 | import nicelee.acfun.enums.StatusEnum; 18 | import nicelee.acfun.util.CmdUtil; 19 | import nicelee.acfun.util.HttpHeaders; 20 | import nicelee.acfun.util.Logger; 21 | import nicelee.ui.Global; 22 | import nicelee.ui.item.DownloadInfoPanel; 23 | 24 | @Acfun(name = "m3u8-downloader:打印输出到文件", type = "downloader", note = "m3u8") 25 | public class AM3u8Downloader extends FLVDownloader { 26 | 27 | static BufferedWriter output; 28 | 29 | static { 30 | try { 31 | System.out.println("------------打开文件------------"); 32 | FileOutputStream f = new FileOutputStream("download.txt", true); 33 | output = new BufferedWriter(new OutputStreamWriter(f, "utf-8")); 34 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 35 | try { 36 | System.out.println("------------关闭文件占用------------"); 37 | output.close(); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | } 41 | })); 42 | } catch (FileNotFoundException | UnsupportedEncodingException e) { 43 | e.printStackTrace(); 44 | } 45 | } 46 | 47 | synchronized static String append(String data) { 48 | try { 49 | output.append(data); 50 | output.newLine(); 51 | output.flush(); 52 | return null; 53 | } catch (IOException e) { 54 | e.printStackTrace(); 55 | return e.getMessage(); 56 | } 57 | 58 | } 59 | 60 | @Override 61 | public boolean matches(String url) { 62 | if (url.contains(".m3u8")) { 63 | return true; 64 | } 65 | return false; 66 | } 67 | 68 | /** 69 | * 下载视频 70 | * 71 | * @param url 72 | * @param avId 73 | * @param qn 74 | * @param page 75 | * @return 76 | */ 77 | @Override 78 | public boolean download(String url, String avId, int qn, int page) { 79 | // 前期准备 80 | convertingStatus = StatusEnum.NONE; 81 | currentTask = totalTaskCnt = 1; 82 | sumSuccessDownloaded = 0L; 83 | String fName = avId + "-" + qn + "-p" + page; 84 | if (file == null) { 85 | file = new File(Global.savePath + fName, fName + ".mp4"); 86 | } 87 | DownloadInfoPanel d = null; 88 | for (DownloadInfoPanel dip : Global.downloadTaskList.keySet()) { 89 | if (dip.getAvid().equals(avId) && dip.getPage() == page) { 90 | d = dip; 91 | break; 92 | } 93 | } 94 | String info = String.format("%s - %s - %s", fName, d.formattedTitle, url); 95 | errorInfo = append(info); 96 | if (errorInfo == null) { 97 | convertingStatus = StatusEnum.SUCCESS; 98 | return true; 99 | } else { 100 | convertingStatus = StatusEnum.FAIL; 101 | return false; 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /downloaders_template/BM3u8Downloader.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/downloaders_template/BM3u8Downloader.class -------------------------------------------------------------------------------- /downloaders_template/BM3u8Downloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import java.io.File; 4 | 5 | import nicelee.acfun.annotations.Acfun; 6 | import nicelee.acfun.enums.StatusEnum; 7 | import nicelee.acfun.util.CmdUtil; 8 | import nicelee.acfun.util.Logger; 9 | import nicelee.ui.Global; 10 | 11 | @Acfun(name = "m3u8-downloader:调用N_m3u8DL-CLI", type = "downloader", note = "m3u8") 12 | public class BM3u8Downloader extends FLVDownloader { 13 | 14 | final static String M3U8_PATH; 15 | static { 16 | M3U8_PATH = System.getenv().getOrDefault("m3u8_path", "C:\\Users\\user\\Downloads\\N_m3u8DL-CLI_v2.6.3_with_ffmpeg_and_SimpleG\\N_m3u8DL-CLI_v3.0.2.exe"); 17 | Logger.println("------加载m3u8-downloader:调用N_m3u8DL-CLI-------"); 18 | Logger.println(M3U8_PATH); 19 | } 20 | 21 | @Override 22 | public boolean matches(String url) { 23 | if (url.contains(".m3u8")) { 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | /** 30 | * 下载视频 31 | * 32 | * @param url 33 | * @param avId 34 | * @param qn 35 | * @param page 36 | * @return 37 | */ 38 | @Override 39 | public boolean download(String url, String avId, int qn, int page) { 40 | // 前期准备 41 | convertingStatus = StatusEnum.DOWNLOADING; 42 | currentTask = totalTaskCnt = 1; 43 | sumSuccessDownloaded = 0L; 44 | String fName = avId + "-" + qn + "-p" + page; 45 | if (file == null) { 46 | file = new File(Global.savePath + fName, fName + ".mp4"); 47 | } 48 | 49 | String cmd[] = new String[] { 50 | M3U8_PATH, 51 | url, "--workDir", Global.savePath, "--saveName", fName, "--headers", 52 | "User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0", 53 | "--maxThreads", "4", "--minThreads", "2", "--retryCount", "5", "--timeOut", "120", 54 | "--enableDelAfterDone", "--disableDateInfo" }; 55 | boolean result = CmdUtil.run(cmd); 56 | 57 | if (result) { 58 | convertingStatus = StatusEnum.SUCCESS; 59 | return true; 60 | } else { 61 | errorInfo = "N_m3u8DL-CLI下载失败"; 62 | convertingStatus = StatusEnum.FAIL; 63 | return false; 64 | } 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /libs/Java-WebSocket-1.4.0-with-dependencies.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/libs/Java-WebSocket-1.4.0-with-dependencies.jar -------------------------------------------------------------------------------- /libs/core-3.3.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/libs/core-3.3.3.jar -------------------------------------------------------------------------------- /package.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/package.bat -------------------------------------------------------------------------------- /package.sh: -------------------------------------------------------------------------------- 1 | # cd 到脚本所在目录 2 | cd $(dirname $0) 3 | 4 | # 复制整个文件夹 5 | mkdir target 6 | cp -r src/. target 7 | 8 | # 删除不需要的java文件 9 | rm -rf ./target/nicelee/test 10 | 11 | # 获取java文件列表 12 | cd target 13 | find `pwd` -name "*.java" > ../sources.txt 14 | cd .. 15 | 16 | # 获取环境变量,解压lib包 17 | cd libs 18 | find `pwd` -name "*.jar" > ../libs.txt 19 | cat ../libs.txt 20 | cd ../target 21 | for jar in `cat ../libs.txt` 22 | do 23 | jclasspath=$jar:$jclasspath 24 | jar xvf $jar 25 | done 26 | cd .. 27 | 28 | # 编译java 29 | javac -cp $jclasspath -encoding UTF-8 @sources.txt 30 | 31 | # 删除所有.java文件 32 | cd target 33 | find . -name "*.java" |xargs rm -rf {} 34 | cd .. 35 | 36 | # 打包 37 | jar cvfe ILikeAcFun.jar nicelee.ui.FrameMain -C ./target . 38 | 39 | rm -rf target 40 | rm -f sources.txt 41 | rm -f libs.txt -------------------------------------------------------------------------------- /release/ILikeAcFun.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/release/ILikeAcFun.jar -------------------------------------------------------------------------------- /release/config/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/release/config/favicon.ico -------------------------------------------------------------------------------- /release/config/repo.config: -------------------------------------------------------------------------------- 1 | ac10257980-3-p0 2 | -------------------------------------------------------------------------------- /release/ffmpeg.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/release/ffmpeg.exe -------------------------------------------------------------------------------- /release/install.vbs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/release/install.vbs -------------------------------------------------------------------------------- /release/run-UI-debug.bat: -------------------------------------------------------------------------------- 1 | cd /d %~dp0 2 | java -Dfile.encoding=utf-8 -jar ILikeAcFun.jar -------------------------------------------------------------------------------- /release/run-UI.bat: -------------------------------------------------------------------------------- 1 | cd /d %~dp0 2 | start javaw -Dfile.encoding=utf-8 -jar ILikeAcFun.jar -------------------------------------------------------------------------------- /release/uninstall.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/release/uninstall.bat -------------------------------------------------------------------------------- /release/update.bat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/release/update.bat -------------------------------------------------------------------------------- /src/nicelee/acfun/INeedAV.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun; 2 | 3 | import java.net.HttpCookie; 4 | import java.util.List; 5 | 6 | import nicelee.acfun.downloaders.Downloader; 7 | import nicelee.acfun.model.ClipInfo; 8 | import nicelee.acfun.model.VideoInfo; 9 | import nicelee.acfun.parsers.InputParser; 10 | import nicelee.acfun.util.ConfigUtil; 11 | import nicelee.acfun.util.HttpCookies; 12 | import nicelee.acfun.util.HttpRequestUtil; 13 | import nicelee.acfun.util.Logger; 14 | import nicelee.ui.Global; 15 | 16 | public class INeedAV { 17 | 18 | private HttpRequestUtil util; 19 | private InputParser inputParser = null; 20 | private Downloader downloader = null; 21 | 22 | public INeedAV() { 23 | util = new HttpRequestUtil(); 24 | downloader = new Downloader(); 25 | downloader.init(util); 26 | inputParser = new InputParser(util, Global.pageSize, Global.pageDisplay); 27 | //inputParser.init(util); 28 | } 29 | 30 | public static void main(String[] args) { 31 | //args = new String[]{"https://space.bilibili.com/8741628/favlist?fid=70263328"}; 32 | System.out.println("-------------------------------"); 33 | System.out.println("输入av号, 下载当前cookie所能下载的最清晰链接"); 34 | System.out.println("-------------------------------"); 35 | // 初始化配置 36 | ConfigUtil.initConfigs(); 37 | 38 | INeedAV ina = new INeedAV(); 39 | INeedLogin inl = new INeedLogin(); 40 | // 0. 尝试读取cookie 41 | List cookies = null; 42 | String cookiesStr = inl.readCookies(); 43 | // 检查有没有本地cookie配置 44 | if (cookiesStr != null) { 45 | cookies = HttpCookies.convertCookies(cookiesStr); 46 | } 47 | HttpCookies.setGlobalCookies(cookies); 48 | 49 | // 1. 获取av 信息, 并下载当前Cookies最清晰的链接 50 | try { 51 | VideoInfo avInfo = ina.getVideoDetail(args[0], Global.downloadFormat, false); 52 | 53 | // 下载最清晰的链接 54 | for (ClipInfo clip : avInfo.getClips().values()) { 55 | Logger.println(clip.getAvTitle()); 56 | ina.downloadClip(clip); 57 | } 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | 63 | /** 64 | * 下载视频 65 | * 66 | * @external input downFormat 67 | * @param avClipInfo 68 | * @return 69 | */ 70 | public boolean downloadClip(ClipInfo avClip) { 71 | return downloadClip( 72 | inputParser.getVideoLink(avClip.getAvId(), "" + avClip.getcId(), 120, 1), 73 | avClip.getAvId(), inputParser.getRealQN(), avClip.getPage()); 74 | } 75 | 76 | /** 77 | * 下载视频 78 | * 79 | * @external input downFormat 80 | * @param url 81 | * @param avId 82 | * @param page 83 | * @return 84 | */ 85 | public boolean downloadClip(String url, String avId, int qn, int page) { 86 | return downloader.download(url, avId, qn, page); 87 | } 88 | 89 | /** 90 | * 获取AVid 视频的所有信息(全部) 91 | * 92 | * @input HttpRequestUtil util 93 | * @param avId 94 | * @param isGetLink 95 | * @return 96 | */ 97 | public VideoInfo getVideoDetail(String avId, int downFormat, boolean isGetLink) { 98 | return inputParser.result(avId, downFormat, isGetLink); 99 | } 100 | 101 | /** 102 | * 获取类似于 avID/ssID这类合法id 103 | * 104 | * @param 未经加工的 avId/ssId字符串 105 | * @return 106 | */ 107 | public String getValidID(String avId) { 108 | return inputParser.validStr(avId); 109 | } 110 | 111 | public InputParser getInputParser(String avId) { 112 | return inputParser; 113 | } 114 | 115 | public Downloader getDownloader() { 116 | return downloader; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/nicelee/acfun/annotations/Acfun.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.annotations; 2 | 3 | import static java.lang.annotation.ElementType.PACKAGE; 4 | import static java.lang.annotation.ElementType.TYPE; 5 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 6 | 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RUNTIME) 11 | @Target({ TYPE, PACKAGE }) 12 | public @interface Acfun { 13 | 14 | 15 | String name(); 16 | 17 | String type() default "parser"; 18 | 19 | String ifLoad() default ""; 20 | 21 | String note() default ""; 22 | } 23 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/Downloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders; 2 | 3 | import java.io.File; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import nicelee.acfun.PackageScanLoader; 8 | import nicelee.acfun.enums.StatusEnum; 9 | import nicelee.acfun.util.HttpRequestUtil; 10 | import nicelee.acfun.util.Logger; 11 | 12 | public class Downloader implements IDownloader { 13 | 14 | private List downloaders = null; 15 | private IDownloader downloader = null; 16 | private HttpRequestUtil util; 17 | private StatusEnum status; 18 | 19 | @Override 20 | public boolean matches(String url) { 21 | return true; 22 | } 23 | 24 | @Override 25 | public void init(HttpRequestUtil util) { 26 | downloaders = new ArrayList<>(); 27 | status = StatusEnum.NONE; 28 | this.util = util; 29 | 30 | try { 31 | for (Class clazz : PackageScanLoader.validDownloaderClasses) { 32 | IDownloader downloader = (IDownloader) clazz.newInstance(); 33 | downloader.init(util); 34 | downloaders.add(downloader); 35 | } 36 | } catch (Exception e) { 37 | e.printStackTrace(); 38 | } 39 | } 40 | 41 | @Override 42 | public boolean download(String url, String avId, int qn, int page) { 43 | for (IDownloader downloader : downloaders) { 44 | if (downloader.matches(url)) { 45 | this.downloader = downloader; 46 | status = StatusEnum.DOWNLOADING; 47 | downloader.init(util); 48 | return downloader.download(url, avId, qn, page); 49 | } 50 | } 51 | System.out.println("未找到匹配当前url的下载器"); 52 | status = StatusEnum.FAIL; 53 | return false; 54 | } 55 | 56 | @Override 57 | public void startTask() { 58 | if (downloader != null) { 59 | status = StatusEnum.DOWNLOADING; 60 | downloader.startTask(); 61 | } else { 62 | Logger.println(StatusEnum.NONE.toString()); 63 | status = StatusEnum.NONE; 64 | } 65 | } 66 | 67 | @Override 68 | public void stopTask() { 69 | if (downloader != null) { 70 | downloader.stopTask(); 71 | } 72 | status = StatusEnum.STOP; 73 | } 74 | 75 | @Override 76 | public File file() { 77 | if (downloader != null) { 78 | return downloader.file(); 79 | } 80 | return null; 81 | } 82 | 83 | @Override 84 | public StatusEnum currentStatus() { 85 | // 如果有downloader, 以downloader为准 86 | if (downloader != null) { 87 | return downloader.currentStatus(); 88 | } else { 89 | return status; 90 | } 91 | } 92 | 93 | @Override 94 | public int totalTaskCount() { 95 | if (downloader != null) { 96 | return downloader.totalTaskCount(); 97 | } 98 | return 0; 99 | } 100 | 101 | @Override 102 | public int currentTask() { 103 | if (downloader != null) { 104 | return downloader.currentTask(); 105 | } 106 | return 0; 107 | } 108 | 109 | @Override 110 | public long sumTotalFileSize() { 111 | if (downloader != null) { 112 | return downloader.sumTotalFileSize(); 113 | } 114 | return 0; 115 | } 116 | 117 | @Override 118 | public long sumDownloadedFileSize() { 119 | if (downloader != null) { 120 | return downloader.sumDownloadedFileSize(); 121 | } 122 | return 0; 123 | } 124 | 125 | @Override 126 | public long currentFileDownloadedSize() { 127 | if (downloader != null) { 128 | return downloader.currentFileDownloadedSize(); 129 | } 130 | return 0; 131 | } 132 | 133 | @Override 134 | public long currentFileTotalSize() { 135 | if (downloader != null) { 136 | return downloader.currentFileTotalSize(); 137 | } 138 | return 0; 139 | } 140 | 141 | @Override 142 | public String errorInfo() { 143 | if (downloader != null) { 144 | return downloader.errorInfo(); 145 | } 146 | return null; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/IDownloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders; 2 | 3 | import java.io.File; 4 | 5 | import nicelee.acfun.enums.StatusEnum; 6 | import nicelee.acfun.util.HttpRequestUtil; 7 | 8 | public interface IDownloader { 9 | 10 | /** 11 | * 根据下载链接匹配下载器 12 | * 13 | * @param url 14 | * @return 15 | */ 16 | public boolean matches(String url); 17 | 18 | /** 19 | * 初始化 20 | * 21 | * @param util 22 | */ 23 | public void init(HttpRequestUtil util); 24 | 25 | /** 26 | * 下载视频 27 | * 28 | * @param url 29 | * @param avId 30 | * @param qn 31 | * @param page 32 | * @return 33 | */ 34 | public boolean download(String url, String avId, int qn, int page); 35 | 36 | /** 37 | * 开始下载 38 | * 39 | * @return 40 | */ 41 | public void startTask(); 42 | 43 | /** 44 | * 停止下载 45 | * 46 | * @return 47 | */ 48 | public void stopTask(); 49 | 50 | /** 51 | * 返回完成后目标文件 52 | * 53 | * @return 54 | */ 55 | public File file(); 56 | 57 | /** 58 | * 返回当前状态 59 | * 60 | * @return 61 | */ 62 | public StatusEnum currentStatus(); 63 | 64 | /** 65 | * 返回总任务数 66 | * 67 | * @return 68 | */ 69 | public int totalTaskCount(); 70 | 71 | /** 72 | * 返回当前第几个任务 73 | * 74 | * @return 75 | */ 76 | public int currentTask(); 77 | 78 | /** 79 | * 返回该任务所有下载的总文件大小(没有下载完成时,该统计没有意义) 80 | * 81 | * @return 82 | */ 83 | public long sumTotalFileSize(); 84 | 85 | /** 86 | * 返回该任务已经下载的总文件大小 87 | * 88 | * @return 89 | */ 90 | public long sumDownloadedFileSize(); 91 | 92 | /** 93 | * 返回当前正在下载的文件进度 94 | * 95 | * @return 96 | */ 97 | public long currentFileDownloadedSize(); 98 | 99 | /** 100 | * 返回当前正在下载的文件总大小 101 | * 102 | * @return 103 | */ 104 | public long currentFileTotalSize(); 105 | 106 | final static long KB = 1024L; 107 | final static long MB = KB * 1024L; 108 | final static long GB = MB * 1024L; 109 | 110 | /** 111 | * 文件大小转换为字符串 112 | * 113 | * @param size 114 | * @return 115 | */ 116 | public static String transToSizeStr(long size) { 117 | if (size == 0) { 118 | return "未知"; 119 | } 120 | double dSize; 121 | if (size >= GB) { 122 | dSize = size * 1.0 / GB; 123 | return String.format("%.2f GB", dSize); 124 | } else if (size >= MB) { 125 | dSize = size * 1.0 / MB; 126 | return String.format("%.2f MB", dSize); 127 | } else if (size >= KB) { 128 | dSize = size * 1.0 / KB; 129 | return String.format("%.2f KB", dSize); 130 | } 131 | return size + " Byte"; 132 | } 133 | 134 | /** 135 | * 获取异常信息,如果没有则为null 136 | * @return 137 | */ 138 | public String errorInfo(); 139 | } 140 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/impl/FLVDownloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import java.io.File; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import nicelee.acfun.annotations.Acfun; 8 | import nicelee.acfun.downloaders.IDownloader; 9 | import nicelee.acfun.enums.StatusEnum; 10 | import nicelee.acfun.util.CmdUtil; 11 | import nicelee.acfun.util.HttpHeaders; 12 | import nicelee.acfun.util.HttpRequestUtil; 13 | import nicelee.ui.Global; 14 | 15 | 16 | @Acfun(name = "flv-downloader", 17 | type = "downloader", 18 | note = "FLV下载") 19 | public class FLVDownloader implements IDownloader { 20 | 21 | protected HttpRequestUtil util; 22 | protected File file = null; 23 | protected int currentTask = 1; 24 | protected int totalTaskCnt = 1; 25 | protected StatusEnum convertingStatus = StatusEnum.NONE; 26 | protected long sumSuccessDownloaded = 0; 27 | protected String errorInfo; 28 | @Override 29 | public boolean matches(String url) { 30 | if(url.contains(".flv")) { 31 | return true; 32 | } 33 | return false; 34 | } 35 | 36 | @Override 37 | public void init(HttpRequestUtil util) { 38 | this.util = util; 39 | } 40 | 41 | /** 42 | * 下载视频 43 | * @param url 44 | * @param avId 45 | * @param qn 46 | * @param page 47 | * @return 48 | */ 49 | @Override 50 | public boolean download(String url, String avId, int qn, int page) { 51 | return download(url, avId, qn, page, ".flv"); 52 | } 53 | 54 | protected boolean download(String url, String avId, int qn, int page, String suffix) { 55 | convertingStatus = StatusEnum.NONE; 56 | errorInfo = null; 57 | currentTask = 1; 58 | String fName = avId + "-" + qn + "-p" + page; 59 | HttpHeaders header = new HttpHeaders(); 60 | if(file == null) { 61 | file = new File(Global.savePath, fName + suffix); 62 | } 63 | if (url.contains("#")) { 64 | String links[] = url.split("#"); 65 | totalTaskCnt = links.length; 66 | Pattern numUrl = Pattern.compile("^([0-9]+)(http.*)$"); 67 | if(util.getStatus() == StatusEnum.STOP) 68 | return false; 69 | // 从 currentTask 继续开始任务 70 | util.init(); 71 | for (int i = currentTask - 1; i < links.length; i++) { 72 | currentTask = (i + 1); 73 | Matcher matcher = numUrl.matcher(links[i]); 74 | matcher.find(); 75 | String order = matcher.group(1); 76 | String tUrl = matcher.group(2); 77 | String fileName = fName + "-part" + order + suffix; 78 | if (!util.download(tUrl, fileName, header.getBiliWwwFLVHeaders(avId))) { 79 | return false; 80 | } 81 | sumSuccessDownloaded += util.getTotalFileSize(); 82 | util.reset(); 83 | } 84 | // 下载完毕后,进行合并 85 | convertingStatus = StatusEnum.PROCESSING; 86 | boolean result = CmdUtil.convert(fName + suffix, links.length); 87 | if (result) { 88 | convertingStatus = StatusEnum.SUCCESS; 89 | } else { 90 | errorInfo = "FLV文件合并失败"; 91 | convertingStatus = StatusEnum.FAIL; 92 | } 93 | return result; 94 | } else { 95 | String fileName = fName + suffix; 96 | boolean succ = util.download(url, fileName, header.getBiliWwwFLVHeaders(avId)); 97 | if (succ) { 98 | sumSuccessDownloaded += util.getTotalFileSize(); 99 | util.reset(); 100 | } 101 | return succ; 102 | } 103 | } 104 | 105 | /** 106 | * 返回当前状态 107 | * 108 | * @return 109 | */ 110 | @Override 111 | public StatusEnum currentStatus() { 112 | // totalTask 113 | // currentTask 114 | // util status; // 0 正在下载; 1 下载完毕; -1 出现错误; -2 人工停止;-3 队列中 115 | if(file != null && file.exists() && (convertingStatus == StatusEnum.SUCCESS || convertingStatus == StatusEnum.NONE)) { 116 | return StatusEnum.SUCCESS; 117 | } 118 | //System.out.println("转码状态: " + convertingStatus.getDescription()); 119 | //System.out.println("下载工具状态: " + util.getStatus().getDescription()); 120 | // 如果当前是最后一个任务 121 | if (currentTask == totalTaskCnt) { 122 | // 当前任务转码状态判断 123 | if (convertingStatus == StatusEnum.SUCCESS) { 124 | return StatusEnum.SUCCESS; 125 | } else if (convertingStatus == StatusEnum.FAIL) { 126 | return StatusEnum.FAIL; 127 | } else if(convertingStatus == StatusEnum.PROCESSING){ 128 | return StatusEnum.PROCESSING; 129 | } else { //与转码无关 130 | return util.getStatus(); 131 | } 132 | } 133 | 134 | switch (util.getStatus()) { 135 | case DOWNLOADING: 136 | return StatusEnum.DOWNLOADING; 137 | case STOP: 138 | return StatusEnum.STOP; 139 | case FAIL: 140 | return StatusEnum.FAIL; 141 | case SUCCESS: { 142 | return StatusEnum.DOWNLOADING; 143 | } 144 | default: 145 | return StatusEnum.NONE;// 还未处理,在队列中 146 | } 147 | } 148 | 149 | /** 150 | * 返回总任务数 151 | * 152 | * @return 153 | */ 154 | @Override 155 | public int totalTaskCount() { 156 | return totalTaskCnt; 157 | } 158 | 159 | /** 160 | * 返回当前第几个任务 161 | * 162 | * @return 163 | */ 164 | @Override 165 | public int currentTask() { 166 | return currentTask; 167 | } 168 | 169 | @Override 170 | public void startTask() { 171 | util.init(); 172 | } 173 | @Override 174 | public void stopTask() { 175 | util.stopDownload(); 176 | } 177 | 178 | @Override 179 | public long sumTotalFileSize() { 180 | return sumSuccessDownloaded + util.getTotalFileSize(); 181 | } 182 | 183 | @Override 184 | public long sumDownloadedFileSize() { 185 | return sumSuccessDownloaded + util.getDownloadedFileSize(); 186 | } 187 | 188 | @Override 189 | public long currentFileDownloadedSize() { 190 | return util.getDownloadedFileSize(); 191 | } 192 | 193 | @Override 194 | public long currentFileTotalSize() { 195 | return util.getTotalFileSize(); 196 | } 197 | @Override 198 | public File file() { 199 | return file; 200 | } 201 | 202 | @Override 203 | public String errorInfo() { 204 | return errorInfo; 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/impl/M3u8Downloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import java.io.File; 4 | import java.util.LinkedList; 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | 8 | import nicelee.acfun.annotations.Acfun; 9 | import nicelee.acfun.enums.StatusEnum; 10 | import nicelee.acfun.util.CmdUtil; 11 | import nicelee.acfun.util.HttpHeaders; 12 | import nicelee.acfun.util.Logger; 13 | import nicelee.ui.Global; 14 | 15 | @Acfun(name = "m3u8-downloader", type = "downloader", note = "m3u8") 16 | public class M3u8Downloader extends FLVDownloader { 17 | 18 | @Override 19 | public boolean matches(String url) { 20 | if (url.contains(".m3u8")) { 21 | return true; 22 | } 23 | return false; 24 | } 25 | 26 | /** 27 | * 下载视频 28 | * 29 | * @param url 30 | * @param avId 31 | * @param qn 32 | * @param page 33 | * @return 34 | */ 35 | @Override 36 | public boolean download(String url, String avId, int qn, int page) { 37 | // 前期准备 38 | String suffix = ".ts"; 39 | convertingStatus = StatusEnum.NONE; 40 | errorInfo = null; 41 | currentTask = 1; 42 | String fName = avId + "-" + qn + "-p" + page; 43 | HttpHeaders header = new HttpHeaders(); 44 | if (file == null) { 45 | file = new File(Global.savePath + fName, fName + ".mp4"); 46 | file.getParentFile().mkdirs(); 47 | } 48 | 49 | // 由m3u8获取ts列表 50 | LinkedList links = new LinkedList(); 51 | String m3u8Content = util.getContent(url, header.getCommonHeaders()); 52 | Logger.println(url); 53 | //Logger.println(m3u8Content); 54 | String[] lines = m3u8Content.split("\r?\n"); 55 | for(String line: lines) { 56 | if(!line.startsWith("#") && !line.isEmpty()) { 57 | // 如果是相对路径,补全 58 | if(!line.startsWith("http")) { 59 | line = genABUrl(line, url); 60 | links.add(line); 61 | //Logger.println(line); 62 | } 63 | } 64 | } 65 | 66 | totalTaskCnt = links.size(); 67 | // Pattern numUrl = Pattern.compile("^([0-9]+)(http.*)$"); 68 | if (util.getStatus() == StatusEnum.STOP) 69 | return false; 70 | // 从 currentTask 继续开始任务 71 | util.init(); 72 | for (int i = currentTask - 1; i < links.size(); i++) { 73 | currentTask = (i + 1); 74 | String order = "" + currentTask; 75 | String tUrl = links.get(i); 76 | String fileName = fName + File.separatorChar + fName + "-part" + order + suffix; 77 | if (!util.download(tUrl, fileName, header.getEmptyHeaders())) { 78 | return false; 79 | } 80 | sumSuccessDownloaded += util.getTotalFileSize(); 81 | util.reset(); 82 | } 83 | // 下载完毕后,进行合并 84 | convertingStatus = StatusEnum.PROCESSING; 85 | boolean result = CmdUtil.convertM3u8(fName, links.size()); 86 | if (result) { 87 | convertingStatus = StatusEnum.SUCCESS; 88 | } else { 89 | errorInfo = "ts文件合并失败"; 90 | System.out.println(errorInfo); 91 | convertingStatus = StatusEnum.FAIL; 92 | } 93 | return result; 94 | } 95 | 96 | /** 97 | * 生成绝对路径 98 | */ 99 | final static Pattern hostPattern = Pattern.compile("^https?\\://[^/]+"); 100 | final static Pattern rootPattern = Pattern.compile("^https?\\://.*/"); 101 | public static String genABUrl(String url, String parentUrl) { 102 | if(url.startsWith("http")) { 103 | // 如果是绝对路径,直接返回 104 | return url; 105 | }else if(url.startsWith("//")) { 106 | // 如果缺scheme,补上https? 107 | if(parentUrl.startsWith("https")) { 108 | return "https"+ url; 109 | }else { 110 | return "http"+ url; 111 | } 112 | }else if(url.startsWith("/")) { 113 | // 补上host 114 | Matcher m1 = hostPattern.matcher(parentUrl); 115 | m1.find(); 116 | return m1.group() + url; 117 | }else { 118 | // 纯相对路径 119 | Matcher m2 = rootPattern.matcher(parentUrl); 120 | m2.find(); 121 | return m2.group() + url; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/impl/M4SDownloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import java.io.File; 4 | 5 | import nicelee.acfun.annotations.Acfun; 6 | import nicelee.acfun.enums.StatusEnum; 7 | import nicelee.acfun.util.CmdUtil; 8 | import nicelee.acfun.util.HttpHeaders; 9 | import nicelee.ui.Global; 10 | 11 | 12 | @Acfun(name = "m4s-downloader", 13 | type = "downloader", 14 | note = "音视频分流下载, 完成后合成MP4") 15 | public class M4SDownloader extends FLVDownloader{ 16 | 17 | 18 | 19 | @Override 20 | public boolean matches(String url) { 21 | if(url.contains(".m4s")) { 22 | return true; 23 | } 24 | return false; 25 | } 26 | 27 | /** 28 | * 下载视频 29 | * @param url 30 | * @param avId 31 | * @param qn 32 | * @param page 33 | * @return 34 | */ 35 | @Override 36 | public boolean download(String url, String avId, int qn, int page) { 37 | convertingStatus = StatusEnum.NONE; 38 | errorInfo = null; 39 | HttpHeaders header = new HttpHeaders(); 40 | String links[] = url.split("#"); 41 | String fName = avId + "-" + qn + "-p" + page; 42 | String suffix = ".mp4"; 43 | String videoName = fName + "_video.m4s"; 44 | String audioName = fName + "_audio.m4s"; 45 | String dstName = fName + suffix; 46 | if(file == null) { 47 | file = new File(Global.savePath, dstName); 48 | } 49 | totalTaskCnt = 2; 50 | if (util.download(links[0], videoName, header.getBiliWwwM4sHeaders(avId))) { 51 | // 如下载成功,统计数据后重置 52 | sumSuccessDownloaded += util.getTotalFileSize(); 53 | util.reset(); 54 | currentTask = 2; 55 | if(util.getStatus() == StatusEnum.STOP) 56 | return false; 57 | util.init(); 58 | if (util.download(links[1], audioName, header.getBiliWwwM4sHeaders(avId))) { 59 | // 如下载成功,统计数据后重置 60 | sumSuccessDownloaded += util.getTotalFileSize(); 61 | util.reset(); 62 | // 下载完毕后,进行合并 63 | convertingStatus = StatusEnum.PROCESSING; 64 | boolean result = CmdUtil.convert(videoName, audioName, dstName); 65 | if (result) { 66 | convertingStatus = StatusEnum.SUCCESS; 67 | } else { 68 | errorInfo = "M4S文件转码失败"; 69 | convertingStatus = StatusEnum.FAIL; 70 | } 71 | return result; 72 | } 73 | return false; 74 | } 75 | return false; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/impl/MP4Downloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import nicelee.acfun.annotations.Acfun; 4 | 5 | 6 | @Acfun(name = "mp4-downloader", 7 | type = "downloader", 8 | note = "MP4下载") 9 | public class MP4Downloader extends FLVDownloader{ 10 | 11 | @Override 12 | public boolean matches(String url) { 13 | if(url.contains(".mp4")) { 14 | return true; 15 | } 16 | return false; 17 | } 18 | 19 | /** 20 | * 下载视频 21 | * @param url 22 | * @param avId 23 | * @param qn 24 | * @param page 25 | * @return 26 | */ 27 | @Override 28 | public boolean download(String url, String avId, int qn, int page) { 29 | return download(url, avId, qn, page, ".mp4"); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/nicelee/acfun/downloaders/impl/TestDownloader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.downloaders.impl; 2 | 3 | import nicelee.acfun.enums.StatusEnum; 4 | 5 | //@Bilibili(name = "test-downloader", type = "downloader", note = "用于测试") 6 | public class TestDownloader extends FLVDownloader{ 7 | 8 | 9 | 10 | @Override 11 | public boolean matches(String url) { 12 | if(url.contains(".test")) { 13 | return true; 14 | } 15 | return false; 16 | } 17 | 18 | /** 19 | * 下载视频 20 | * @param url 21 | * @param avId 22 | * @param qn 23 | * @param page 24 | * @return 25 | */ 26 | @Override 27 | public boolean download(String url, String avId, int qn, int page) { 28 | convertingStatus = StatusEnum.NONE; 29 | util.init(); 30 | totalTaskCnt = 2; 31 | currentTask = 1; 32 | convertingStatus = StatusEnum.PROCESSING; 33 | return true; 34 | } 35 | 36 | @Override 37 | public StatusEnum currentStatus() { 38 | return StatusEnum.PROCESSING; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/nicelee/acfun/enums/ChannelEnum.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.enums; 2 | 3 | public enum ChannelEnum { 4 | 默认("默认", 0), 5 | AC正义("AC正义", 177), 6 | 番剧("番剧", 155), 7 | 动画("动画", 1), 8 | 音乐("音乐", 58), 9 | 舞蹈偶像("舞蹈·偶像", 123), 10 | 游戏("游戏", 59), 11 | 娱乐("娱乐", 60), 12 | 科技("科技", 70), 13 | 影视("影视", 68), 14 | 体育("体育", 69), 15 | 鱼塘("鱼塘", 125); 16 | 17 | private String description; 18 | private int id; 19 | 20 | ChannelEnum(String description, int id){ 21 | this.description = description; 22 | this.id = id; 23 | } 24 | 25 | public static int getChannelId(String description) { 26 | ChannelEnum[] enums = ChannelEnum.values(); 27 | for(ChannelEnum item : enums) { 28 | if(item.getDescription().equals(description)) { 29 | return item.getId(); 30 | } 31 | } 32 | return 0; 33 | } 34 | 35 | public static String getDescription(int id) { 36 | ChannelEnum[] enums = ChannelEnum.values(); 37 | for(ChannelEnum item : enums) { 38 | if(id == item.getId()) { 39 | return item.getDescription(); 40 | } 41 | } 42 | return ""; 43 | } 44 | 45 | public String getDescription() { 46 | return description; 47 | } 48 | 49 | public void setDescription(String description) { 50 | this.description = description; 51 | } 52 | 53 | public int getId() { 54 | return id; 55 | } 56 | 57 | public void setId(int id) { 58 | this.id = id; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/nicelee/acfun/enums/StatusEnum.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.enums; 2 | 3 | public enum StatusEnum { 4 | NONE(9, "尚未开始"), 5 | DOWNLOADING(0, "正在下载"), 6 | PROCESSING(0, "转码中"), 7 | STOP(-2, "人工停止"), 8 | FAIL(-1, "因异常造成的停止"), 9 | SUCCESS(1, "成功"); 10 | 11 | private int code; 12 | private String description; 13 | StatusEnum(int code, String description){ 14 | this.code = code; 15 | this.description = description; 16 | } 17 | 18 | public int getCode() { 19 | return code; 20 | } 21 | 22 | public String getDescription() { 23 | return description; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/nicelee/acfun/enums/VideoQualityEnum.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.enums; 2 | 3 | import nicelee.acfun.util.Logger; 4 | 5 | public enum VideoQualityEnum { 6 | Q50("2160P", 50, "2160P"), // ac30217383 7 | Q40("1080P+", 40, "1080P+"), // ac30217383 8 | Q31("1080P60", 31, "1080P60"), // ac46303302 9 | Q30("1080P", 30, "1080P"), // ac30217383 10 | Q21("720P60", 21, "720P60"), // ac46303302 11 | Q20("720P", 20, "720P"), // ac30217383 12 | Q10("540P", 10, "540P"), // ac30217383 13 | Q00("360P", 0, "360P"); // ac30217383 14 | // Q1080P60("1080P60", 116, "高清1080P60"), 15 | // Q1080PPlus("1080P+", 112, "高清1080P+"), 16 | // Q1080P("1080P", 80, "高清1080P"), 17 | // Q720P60("720P60", 74, "高清720P60"), 18 | // Q720P("720P", 64, "高清720P"), 19 | // Q480P("480P", 32, "清晰480P"), 20 | // Q320P("320P", 16, "流畅320P"); 21 | 22 | private String quality; 23 | private int qn; 24 | private String description; 25 | 26 | private static final int[] qns; 27 | 28 | static { 29 | VideoQualityEnum[] enums = VideoQualityEnum.values(); 30 | qns = new int[enums.length]; 31 | for (int i = 0; i < qns.length; i++) 32 | qns[i] = enums[i].qn; 33 | } 34 | 35 | VideoQualityEnum(String quality, int qn, String description) { 36 | this.quality = quality; 37 | this.qn = qn; 38 | this.description = description; 39 | } 40 | 41 | public static int[] availableQNs() { 42 | return qns; 43 | } 44 | 45 | public static String getQualityDescript(int qn) { 46 | VideoQualityEnum[] enums = VideoQualityEnum.values(); 47 | for (VideoQualityEnum item : enums) { 48 | if (item.getQn() == qn) { 49 | return item.getDescription(); 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | public static int getQN(String quality) { 56 | VideoQualityEnum[] enums = VideoQualityEnum.values(); 57 | for (VideoQualityEnum item : enums) { 58 | if (item.getQuality().equals(quality)) { 59 | return item.getQn(); 60 | } 61 | } 62 | Logger.println("未找到相应选项:" + quality); 63 | return 0; 64 | } 65 | 66 | public static boolean contains(int quality) { 67 | VideoQualityEnum[] enums = VideoQualityEnum.values(); 68 | for (VideoQualityEnum item : enums) { 69 | if (item.getQn() == quality) { 70 | return true; 71 | } 72 | } 73 | return false; 74 | } 75 | 76 | public String getQuality() { 77 | return quality; 78 | } 79 | 80 | public int getQn() { 81 | return qn; 82 | } 83 | 84 | String getDescription() { 85 | return description; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/nicelee/acfun/model/ClipInfo.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.model; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map.Entry; 5 | 6 | public class ClipInfo { 7 | String avTitle; 8 | long cId; 9 | String avId; 10 | int page; 11 | String title; 12 | String picPreview; 13 | String listName; // 收藏夹名称 或其它集合名称(不一定存在) 14 | String listOwnerName; // 收藏夹主人 或其它集合的拥有者(不一定存在) 15 | HashMap links; 16 | 17 | int remark = -1; // 用于ss番剧查询时显示顺序 18 | 19 | @Override 20 | public String toString() { 21 | StringBuilder sb = new StringBuilder(); 22 | sb.append("------------------------------\r\n"); 23 | sb.append("--avTitle为 :").append(avTitle).append(" \r\n"); 24 | sb.append("--page为 :").append(page).append(" \r\n"); 25 | sb.append("--cId为 :").append(cId).append(" \r\n"); 26 | sb.append("--title为 :").append(title).append(" \r\n"); 27 | if (avId != null) 28 | sb.append("--avId为 :").append(avId).append(" \r\n"); 29 | 30 | if (links != null) { 31 | for (Entry entry: links.entrySet()) { 32 | sb.append("----清晰度 :").append(entry.getKey()).append(" "); 33 | sb.append("----下载链接 :").append(entry.getValue()).append(" \r\n"); 34 | } 35 | } 36 | 37 | return sb.toString(); 38 | } 39 | 40 | @Override 41 | public boolean equals(Object obj) { 42 | if (this != null && obj != null) { 43 | if (obj instanceof ClipInfo) { 44 | ClipInfo clip = (ClipInfo) obj; 45 | return (this.cId == clip.cId); 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | return (int) cId; 54 | } 55 | 56 | public long getcId() { 57 | return cId; 58 | } 59 | 60 | public void setcId(long cId) { 61 | this.cId = cId; 62 | } 63 | 64 | public String getAvId() { 65 | return avId; 66 | } 67 | 68 | public void setAvId(String avId) { 69 | this.avId = avId; 70 | } 71 | 72 | public int getPage() { 73 | return page; 74 | } 75 | 76 | public void setPage(int page) { 77 | this.page = page; 78 | } 79 | 80 | public String getTitle() { 81 | return title; 82 | } 83 | 84 | public void setTitle(String title) { 85 | this.title = title; 86 | } 87 | 88 | public HashMap getLinks() { 89 | return links; 90 | } 91 | 92 | public void setLinks(HashMap links) { 93 | this.links = links; 94 | } 95 | 96 | public int getRemark() { 97 | int p = remark == -1 ? page : remark; 98 | return p; 99 | } 100 | 101 | public void setRemark(int remark) { 102 | this.remark = remark; 103 | } 104 | 105 | public String getAvTitle() { 106 | return avTitle; 107 | } 108 | 109 | public void setAvTitle(String avTitle) { 110 | this.avTitle = avTitle; 111 | } 112 | 113 | public String getPicPreview() { 114 | return picPreview; 115 | } 116 | 117 | public void setPicPreview(String picPreview) { 118 | this.picPreview = picPreview; 119 | } 120 | 121 | public String getListName() { 122 | return listName; 123 | } 124 | 125 | public void setListName(String listName) { 126 | this.listName = listName; 127 | } 128 | 129 | public String getListOwnerName() { 130 | return listOwnerName; 131 | } 132 | 133 | public void setListOwnerName(String listOwnerName) { 134 | this.listOwnerName = listOwnerName; 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /src/nicelee/acfun/model/FavList.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.model; 2 | 3 | public class FavList { 4 | 5 | long fId; // 收藏夹id 6 | int size; // 收藏夹内视频个数 7 | String title = "111"; // 收藏夹名称 8 | 9 | public FavList(long fId, int size, String title) { 10 | this.fId = fId; 11 | this.size = size; 12 | this.title = title; 13 | } 14 | 15 | @Override 16 | public String toString() { 17 | return title + "(" + size +")"; 18 | } 19 | 20 | @Override 21 | public boolean equals(Object obj) { 22 | if (this != null && obj != null) { 23 | if (obj instanceof FavList) { 24 | FavList fav = (FavList) obj; 25 | return (this.fId == fav.fId); 26 | } 27 | } 28 | return false; 29 | } 30 | 31 | @Override 32 | public int hashCode() { 33 | return (int) fId; 34 | } 35 | 36 | public long getfId() { 37 | return fId; 38 | } 39 | 40 | public void setfId(long fId) { 41 | this.fId = fId; 42 | } 43 | 44 | public int getSize() { 45 | return size; 46 | } 47 | 48 | public void setSize(int size) { 49 | this.size = size; 50 | } 51 | 52 | public String getTitle() { 53 | return title; 54 | } 55 | 56 | public void setTitle(String title) { 57 | this.title = title; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/nicelee/acfun/model/UserInfo.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.model; 2 | 3 | public class UserInfo { 4 | String name; 5 | String poster; 6 | public String getName() { 7 | return name; 8 | } 9 | public void setName(String name) { 10 | this.name = name; 11 | } 12 | public String getPoster() { 13 | return poster; 14 | } 15 | public void setPoster(String poster) { 16 | this.poster = poster; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/nicelee/acfun/model/VideoInfo.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.model; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | public class VideoInfo { 6 | String videoName; 7 | String videoId; 8 | String author; 9 | String authorId; 10 | String videoPreview; 11 | String brief; 12 | LinkedHashMap clips;// 未使用 Integer 为 Page 13 | String videoLink; 14 | 15 | public void print() { 16 | System.out.print(toString()); 17 | } 18 | 19 | public String toString() { 20 | StringBuilder sb = new StringBuilder(); 21 | sb.append("------------------------------\r\n"); 22 | sb.append("视频名称为 :").append(videoName).append(" \r\n"); 23 | sb.append("视频ID为 :").append(videoId).append(" \r\n"); 24 | sb.append("作者为 :").append(author).append(" \r\n"); 25 | sb.append("作者ID为 :").append(authorId).append(" \r\n"); 26 | sb.append("作者的话为 :").append(brief).append(" \r\n"); 27 | sb.append("预览图路径为 :").append(videoPreview).append(" \r\n"); 28 | sb.append("视频下载链接为 :").append(videoLink).append(" \r\n"); 29 | if (clips != null) { 30 | sb.append("当前有小视频个数 :").append(clips.size()).append(" \r\n"); 31 | for (ClipInfo clip : clips.values()) { 32 | sb.append(clip.toString()); 33 | } 34 | } 35 | sb.append("------------------------------\r\n"); 36 | return sb.toString(); 37 | } 38 | 39 | public String getAuthor() { 40 | return author; 41 | } 42 | 43 | public void setAuthor(String author) { 44 | this.author = author; 45 | } 46 | 47 | public String getAuthorId() { 48 | return authorId; 49 | } 50 | 51 | public void setAuthorId(String authorId) { 52 | this.authorId = authorId; 53 | } 54 | 55 | public String getVideoId() { 56 | return videoId; 57 | } 58 | 59 | public void setVideoId(String videoId) { 60 | this.videoId = videoId; 61 | } 62 | 63 | public String getVideoName() { 64 | return videoName; 65 | } 66 | 67 | public void setVideoName(String videoName) { 68 | this.videoName = videoName; 69 | } 70 | 71 | public String getBrief() { 72 | return brief; 73 | } 74 | 75 | public void setBrief(String brief) { 76 | this.brief = brief; 77 | } 78 | 79 | public String getVideoPreview() { 80 | return videoPreview; 81 | } 82 | 83 | public void setVideoPreview(String videoPreview) { 84 | this.videoPreview = videoPreview; 85 | } 86 | 87 | public String getVideoLink() { 88 | return videoLink; 89 | } 90 | 91 | public void setVideoLink(String videoLink) { 92 | this.videoLink = videoLink; 93 | } 94 | 95 | public LinkedHashMap getClips() { 96 | return clips; 97 | } 98 | 99 | public void setClips(LinkedHashMap clips) { 100 | this.clips = clips; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/IInputParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers; 2 | 3 | import nicelee.acfun.model.VideoInfo; 4 | 5 | public interface IInputParser { 6 | 7 | 8 | /** 9 | * 该Parser类型是否可以解析 10 | * @return 11 | */ 12 | public boolean matches(String input); 13 | 14 | /** 15 | * 获取处理过后的字符串 16 | */ 17 | public String validStr(String input); 18 | 19 | /** 20 | * 获取视频信息 21 | */ 22 | public VideoInfo result(String avId, int videoFormat, boolean getVideoLink); 23 | 24 | /** 25 | * 获取视频链接 26 | */ 27 | public String getVideoLink(String avId, String cid, int qn, int downFormat); 28 | 29 | /** 30 | * 获取上一次查询的视频链接的实际清晰度 31 | */ 32 | public int getVideoLinkQN(); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/IParamSetter.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers; 2 | 3 | public interface IParamSetter { 4 | 5 | 6 | public void setPage(int page); 7 | 8 | public int getPage(); 9 | 10 | public void setChannelId(int channelId); 11 | 12 | public int getChannelId(); 13 | 14 | public void setRealQN(int qn); 15 | 16 | public int getRealQN(); 17 | } 18 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/InputParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers; 2 | 3 | import java.lang.reflect.Constructor; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | import java.util.regex.Matcher; 7 | import java.util.regex.Pattern; 8 | 9 | import nicelee.acfun.PackageScanLoader; 10 | import nicelee.acfun.annotations.Acfun; 11 | import nicelee.acfun.model.VideoInfo; 12 | import nicelee.acfun.util.HttpRequestUtil; 13 | import nicelee.acfun.util.Logger; 14 | 15 | public class InputParser implements IInputParser, IParamSetter { 16 | 17 | protected final static Pattern pagePattern = Pattern.compile(" ?&?p=([0-9]+)");// 自定义参数, 目前只匹配个人主页视频的页码 18 | protected final static Pattern channelIdPattern = Pattern.compile(" ?&?channelId=([0-9]+)"); 19 | private List parsers = null; 20 | private IInputParser parser = null; 21 | private int page = 1; 22 | private int channelId = 0; 23 | private int realQN = 1; 24 | 25 | public InputParser(HttpRequestUtil util, int pageSize, String loadContition) { 26 | parsers = new ArrayList<>(); 27 | try { 28 | for (Class clazz : PackageScanLoader.validParserClasses) { 29 | // 判断是否需要载入 30 | Acfun acfun = clazz.getAnnotation(Acfun.class); 31 | if (acfun.ifLoad().isEmpty() || acfun.ifLoad().equals(loadContition)) { 32 | // 实例化并加入parser列表 33 | // IInputParser inputParser = (IInputParser) clazz.newInstance(); 34 | // 获取构造函数 35 | // Constructor con = (Constructor) 36 | // clazz.getConstructor(Object[].class); 37 | Constructor con = (Constructor) clazz.getConstructors()[0]; 38 | IInputParser inputParser = con.newInstance(new Object[] { new Object[] { util, this, pageSize } }); 39 | parsers.add(inputParser); 40 | } 41 | 42 | } 43 | } catch (Exception e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | 48 | public void selectParser(String input) { 49 | for (IInputParser parser : parsers) { 50 | if (parser.matches(input)) { 51 | // Logger.println(input); 52 | this.parser = parser; 53 | break; 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public boolean matches(String input) { 60 | return true; 61 | } 62 | 63 | @Override 64 | public String validStr(String input) { 65 | // 获取参数 66 | Matcher paramMatcher = pagePattern.matcher(input); 67 | if (paramMatcher.find()) { 68 | this.page = Integer.parseInt(paramMatcher.group(1)); 69 | input = input.replaceFirst(" ?&?p=([0-9]+)", ""); 70 | } 71 | paramMatcher = channelIdPattern.matcher(input); 72 | if (paramMatcher.find()) { 73 | this.channelId = Integer.parseInt(paramMatcher.group(1)); 74 | input = input.replaceFirst(" ?&?channelId=([0-9]+)", ""); 75 | } 76 | selectParser(input); 77 | if (parser != null) { 78 | return parser.validStr(input); 79 | } 80 | Logger.println("当前没有parser匹配"); 81 | return ""; 82 | } 83 | 84 | @Override 85 | public VideoInfo result(String input, int videoFormat, boolean getVideoLink) { 86 | // 获取参数 87 | Matcher paramMatcher = pagePattern.matcher(input); 88 | if (paramMatcher.find()) { 89 | this.page = Integer.parseInt(paramMatcher.group(1)); 90 | input = input.replaceFirst(" ?&?p=([0-9]+)", ""); 91 | } 92 | paramMatcher = channelIdPattern.matcher(input); 93 | if (paramMatcher.find()) { 94 | this.channelId = Integer.parseInt(paramMatcher.group(1)); 95 | input = input.replaceFirst(" ?&?channelId=([0-9]+)", ""); 96 | } 97 | 98 | selectParser(input); 99 | if (parser != null) { 100 | return parser.result(input, videoFormat, getVideoLink); 101 | } 102 | return null; 103 | } 104 | 105 | @Override 106 | public String getVideoLink(String avId, String cid, int qn, int downFormat) { 107 | selectParser(avId); 108 | if (parser != null) { 109 | return parser.getVideoLink(avId, cid, qn, downFormat); 110 | } 111 | return null; 112 | } 113 | 114 | @Override 115 | public int getVideoLinkQN() { 116 | if (parser != null) { 117 | return parser.getVideoLinkQN(); 118 | } 119 | return 0; 120 | } 121 | 122 | @Override 123 | public void setPage(int page) { 124 | this.page = page; 125 | } 126 | 127 | @Override 128 | public int getPage() { 129 | return page; 130 | } 131 | 132 | @Override 133 | public void setRealQN(int qn) { 134 | realQN = qn; 135 | 136 | } 137 | 138 | @Override 139 | public int getRealQN() { 140 | return realQN; 141 | } 142 | 143 | @Override 144 | public void setChannelId(int channelId) { 145 | this.channelId = channelId; 146 | } 147 | 148 | @Override 149 | public int getChannelId() { 150 | return channelId; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/impl/AABangumiParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers.impl; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | 10 | import nicelee.acfun.annotations.Acfun; 11 | import nicelee.acfun.enums.VideoQualityEnum; 12 | import nicelee.acfun.model.ClipInfo; 13 | import nicelee.acfun.model.VideoInfo; 14 | import nicelee.acfun.util.HttpCookies; 15 | import nicelee.acfun.util.HttpHeaders; 16 | import nicelee.acfun.util.Logger; 17 | 18 | @Acfun(name = "aaParser", note = "番剧") 19 | public class AABangumiParser extends AbstractBaseParser { 20 | 21 | // private final static Pattern pattern = Pattern.compile("https://www.acfun.cn/bangumi/(aa[0-9]+)"); 22 | private final static Pattern pattern = Pattern.compile("(? clipMap = new LinkedHashMap(); 77 | for (int i = 0; i < items.length(); i++) { 78 | JSONObject clipObj = items.getJSONObject(i); 79 | ClipInfo clip = new ClipInfo(); 80 | clip.setAvTitle(viInfo.getVideoName()); 81 | clip.setAvId(bangumiId); 82 | clip.setcId(clipObj.getLong("itemId")); 83 | clip.setPage(i); 84 | clip.setRemark(i); 85 | clip.setTitle(clipObj.getString("episodeName") + " " + clipObj.getString("title")); 86 | clip.setPicPreview(clipObj.getJSONObject("imgInfo").getString("thumbnailImageCdnUrl")); 87 | 88 | LinkedHashMap links = new LinkedHashMap(); 89 | try { 90 | int qnList[] = VideoQualityEnum.availableQNs(); 91 | for (int qn : qnList) { 92 | if (getVideoLink) { 93 | String link = getVideoLink(bangumiId, "" + clip.getcId(), qn, 0); 94 | links.put(qn, link); 95 | } else { 96 | links.put(qn, ""); 97 | } 98 | } 99 | clip.setLinks(links); 100 | } catch (Exception e) { 101 | e.printStackTrace(); 102 | clip.setLinks(links); 103 | } 104 | 105 | clipMap.put(clip.getcId(), clip); 106 | } 107 | viInfo.setClips(clipMap); 108 | viInfo.print(); 109 | return viInfo; 110 | } 111 | 112 | private final static Pattern pBangumiData = Pattern.compile("window.bangumiData ?= ?(.*?});"); 113 | 114 | @Override 115 | public String getVideoLink(String bangumiId, String cid, int qn, int downFormat) { 116 | HttpHeaders headers = new HttpHeaders(); 117 | String basicInfoUrl = String.format("https://www.acfun.cn/bangumi/%s_36188_%s", bangumiId, cid); 118 | String html = util.getContent(basicInfoUrl, headers.getCommonHeaders("www.acfun.cn"), 119 | HttpCookies.getGlobalCookies()); 120 | Matcher matcher = pBangumiData.matcher(html); 121 | matcher.find(); 122 | String json = matcher.group(1); 123 | Logger.println(json); 124 | 125 | JSONObject jObj = new JSONObject( 126 | new JSONObject(json).getJSONObject("currentVideoInfo").getString("ksPlayJson")); 127 | JSONArray jArr = jObj.getJSONArray("adaptationSet").getJSONObject(0).getJSONArray("representation"); 128 | Integer realQn = null; 129 | for (int i = 0; i < jArr.length(); i++) { 130 | if (VideoQualityEnum.getQualityDescript(qn).equals(jArr.getJSONObject(i).getString("qualityLabel"))) { 131 | Logger.println("找到相应清晰度:" + VideoQualityEnum.getQualityDescript(qn)); 132 | realQn = i; 133 | break; 134 | } 135 | } 136 | 137 | if (realQn == null) { 138 | Logger.println("没有找到相应清晰度"); 139 | realQn = 0; 140 | if (qn <= realQn) { 141 | realQn = qn; 142 | } 143 | } 144 | JSONObject qnobj = jArr.getJSONObject(realQn); 145 | Logger.println(qnobj.getString("url")); 146 | paramSetter.setRealQN(VideoQualityEnum.getQN(qnobj.getString("qualityLabel"))); 147 | return qnobj.getString("url"); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/impl/AACollectionParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers.impl; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | 10 | import nicelee.acfun.annotations.Acfun; 11 | import nicelee.acfun.model.ClipInfo; 12 | import nicelee.acfun.model.VideoInfo; 13 | import nicelee.acfun.util.HttpCookies; 14 | import nicelee.acfun.util.HttpHeaders; 15 | import nicelee.acfun.util.Logger; 16 | 17 | /** 18 | * https://www.acfun.cn/a/aa6125087 19 | * 20 | */ 21 | @Acfun(name = "AACollectionParser", note = "视频合集") 22 | public class AACollectionParser extends AbstractPageQueryParser { 23 | 24 | private final static Pattern pattern = Pattern.compile("https://www.acfun.cn/a/aa([0-9]+)"); 25 | private String spaceID; 26 | 27 | public AACollectionParser(Object... obj) { 28 | super(obj); 29 | } 30 | 31 | @Override 32 | public boolean matches(String input) { 33 | matcher = pattern.matcher(input); 34 | if (matcher.find()) { 35 | System.out.println("匹配视频合集"); 36 | spaceID = matcher.group(1); 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | 42 | } 43 | 44 | @Override 45 | public String validStr(String input) { 46 | return matcher.group().trim() + "p=" + paramSetter.getPage(); 47 | } 48 | 49 | @Override 50 | public VideoInfo result(String input, int videoFormat, boolean getVideoLink) { 51 | Logger.println(paramSetter.getPage()); 52 | return result(pageSize, paramSetter.getPage(), videoFormat, getVideoLink); 53 | } 54 | 55 | @Override 56 | public void initPageQueryParam() { 57 | API_PMAX = 20; 58 | pageQueryResult = new VideoInfo(); 59 | pageQueryResult.setClips(new LinkedHashMap<>()); 60 | } 61 | 62 | private final static Pattern pInfo = Pattern.compile("window.__INITIAL_STATE__ ?= ?(.*?});"); 63 | @Override 64 | protected boolean query(int page, int min, int max, Object... obj) { 65 | int videoFormat = (int) obj[0]; 66 | boolean getVideoLink = (boolean) obj[1]; 67 | try { 68 | HttpHeaders headers = new HttpHeaders(); 69 | if (pageQueryResult.getVideoName() == null) { 70 | String basicInfoUrl = String.format("https://www.acfun.cn/a/aa%s", spaceID); 71 | String html = util.getContent(basicInfoUrl, headers.getCommonHeaders("www.acfun.cn"), 72 | HttpCookies.getGlobalCookies()); 73 | Matcher matcher = pInfo.matcher(html); 74 | matcher.find(); 75 | String json = matcher.group(1); 76 | Logger.println(json); 77 | 78 | JSONObject data = new JSONObject(json).getJSONObject("album"); 79 | JSONObject albumInfo = data.getJSONObject("albumInfo"); 80 | pageQueryResult.setVideoId(spaceID); 81 | pageQueryResult.setAuthor(albumInfo.getString("authorName")); 82 | pageQueryResult.setVideoName(albumInfo.getString("title") + paramSetter.getPage()); 83 | pageQueryResult.setVideoPreview(albumInfo.getString("coverImage")); 84 | pageQueryResult.setAuthorId(albumInfo.optString("authorId")); 85 | pageQueryResult.setBrief(albumInfo.getString("title")); 86 | } 87 | 88 | // 视频列表 89 | String url = "https://www.acfun.cn/rest/pc-direct/arubamu/content/list?page=%d&size=%d&arubamuId=%s"; 90 | url = String.format(url, page, API_PMAX, spaceID); 91 | String json = util.getContent(url, new HttpHeaders().getCommonHeaders("www.acfun.cn"), HttpCookies.getGlobalCookies()); 92 | Logger.println(json); 93 | JSONArray jArray = new JSONObject(json).getJSONArray("contents"); 94 | 95 | LinkedHashMap map = pageQueryResult.getClips(); 96 | for (int i = min - 1; i < jArray.length() && i < max; i++) { 97 | try { 98 | map.putAll(convertVideoToClipMap( 99 | "ac" + jArray.getJSONObject(i).optString("resourceId"), 100 | (page - 1) * API_PMAX + i + 1, 101 | videoFormat, 102 | getVideoLink)); 103 | }catch (Exception e) { 104 | 105 | } 106 | } 107 | return true; 108 | } catch (Exception e) { 109 | // e.printStackTrace(); 110 | return false; 111 | } 112 | } 113 | 114 | /** 115 | * 使用此方法会产生许多请求,慎用 116 | * 117 | * @param acId 118 | * @param remark 119 | * @param videoFormat 120 | * @param getVideoLink 121 | * @return 将所有avId的视频封装成Map 122 | */ 123 | private LinkedHashMap convertVideoToClipMap(String acId, int remark, int videoFormat, 124 | boolean getVideoLink) { 125 | LinkedHashMap map = new LinkedHashMap<>(); 126 | VideoInfo video = getAVDetail(acId, videoFormat, getVideoLink); // 耗时 127 | for (ClipInfo clip : video.getClips().values()) { 128 | try { 129 | //clip.setTitle(clip.getAvTitle() + "-" + clip.getTitle()); 130 | //clip.setAvTitle(pageQueryResult.getVideoName()); 131 | // >= V3.6, ClipInfo 增加可选ListXXX字段,将收藏夹信息移入其中 132 | clip.setListName(pageQueryResult.getBrief()); 133 | clip.setListOwnerName(pageQueryResult.getAuthor()); 134 | 135 | clip.setRemark(remark); 136 | map.put(clip.getcId(), clip); 137 | }catch (Exception e) { 138 | e.printStackTrace(); 139 | } 140 | } 141 | return map; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/impl/ACParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers.impl; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | import nicelee.acfun.annotations.Acfun; 6 | import nicelee.acfun.model.VideoInfo; 7 | 8 | @Acfun(name = "ac", note="普通视频") 9 | public class ACParser extends AbstractBaseParser { 10 | 11 | private final static Pattern pattern = Pattern.compile("ac[0-9]+"); 12 | private String avId; 13 | 14 | // public AVParser(HttpRequestUtil util,IParamSetter paramSetter, int pageSize) { 15 | public ACParser(Object... obj) { 16 | super(obj); 17 | } 18 | 19 | @Override 20 | public boolean matches(String input) { 21 | matcher = pattern.matcher(input); 22 | boolean matches = matcher.find(); 23 | if (matches) { 24 | avId = matcher.group(); 25 | System.out.println("匹配ACParser: " + avId); 26 | } 27 | return matches; 28 | } 29 | 30 | @Override 31 | public String validStr(String input) { 32 | return avId; 33 | } 34 | 35 | @Override 36 | public VideoInfo result(String input, int videoFormat, boolean getVideoLink) { 37 | System.out.println("ACParser正在获取结果" + avId); 38 | return getAVDetail(avId, videoFormat, getVideoLink); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/impl/AbstractPageQueryParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers.impl; 2 | 3 | import nicelee.acfun.util.Logger; 4 | 5 | public abstract class AbstractPageQueryParser extends AbstractBaseParser{ 6 | 7 | protected int API_PMAX = 20; // API 实际每次分页查询个数 8 | protected T pageQueryResult; 9 | 10 | public AbstractPageQueryParser(Object... obj) { 11 | super(obj); 12 | } 13 | 14 | /** 15 | * 初始化 result 设置每次分页实际查询的个数 16 | */ 17 | public abstract void initPageQueryParam(); 18 | 19 | /** 20 | * 21 | *

22 | * 查询第p 页的结果,将第min 到 第max 的数据加入 result 23 | *

24 | * (此处每页大小为固定设置,与配置文件不一定相符) 25 | * 26 | * @param begin 27 | * @param end 28 | * @param obj 29 | * @return 查询成功/ 失败 30 | */ 31 | protected abstract boolean query(int p, int min, int max, Object... obj); 32 | 33 | /** 34 | * 分页查询 35 | * 36 | * @param pageSize 37 | * @param page 38 | * @param obj 39 | * @return 以pageSize 进行分页查询,获取第page页的结果 40 | */ 41 | public T result(int pageSize, int page, Object... obj) { 42 | initPageQueryParam(); 43 | // 获取第 begin 个到第 end 个视频 44 | int begin = (page - 1) * pageSize + 1; 45 | int end = page * pageSize; 46 | Logger.printf("当前 pageSize: %d, 查询第 %d页。 即获取第 %d 到第%d个视频", 47 | pageSize, page, begin, end); 48 | query(begin, end, obj); 49 | return pageQueryResult; 50 | } 51 | 52 | /** 53 | * 查询第begin 到 第end 的结果, 加入 result 54 | * 55 | * @param begin 56 | * @param end 57 | * @param obj 58 | * @return 查询成功/ 失败 59 | */ 60 | private boolean query(int begin, int end, Object... obj) { 61 | // begin 属于第 (begin-1)/FAV_PMAX + 1 页 62 | // end 属于第(end-1)/FAV_PMAX + 1 页 63 | int pageBegin = (begin - 1) / API_PMAX + 1; 64 | int minPointerinBegin = (begin - 1) % API_PMAX + 1; 65 | int pageEnd = (end - 1) / API_PMAX + 1; 66 | int maxPointerinEnd = (end - 1) % API_PMAX + 1; 67 | // 如果一次请求可以搞定 68 | if (pageBegin == pageEnd) { 69 | return query(pageBegin, minPointerinBegin, maxPointerinEnd, obj); 70 | } 71 | // 如果需要两次以上, 先请求一次, 72 | boolean listNotEmpty = query(pageBegin, minPointerinBegin, API_PMAX, obj); 73 | if (!listNotEmpty) { 74 | return false; 75 | } 76 | for (int i = pageBegin + 1; i <= pageEnd; i++) { 77 | if (i < pageEnd) { 78 | listNotEmpty = query(i, 1, API_PMAX, obj); 79 | } else { 80 | listNotEmpty = query(i, 1, maxPointerinEnd, obj); 81 | } 82 | if (!listNotEmpty) { 83 | break; 84 | } 85 | } 86 | return true; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/impl/URL4FavParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers.impl; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.regex.Pattern; 5 | 6 | import org.json.JSONArray; 7 | import org.json.JSONObject; 8 | 9 | import nicelee.acfun.annotations.Acfun; 10 | import nicelee.acfun.enums.ChannelEnum; 11 | import nicelee.acfun.model.ClipInfo; 12 | import nicelee.acfun.model.VideoInfo; 13 | import nicelee.acfun.util.HttpCookies; 14 | import nicelee.acfun.util.HttpHeaders; 15 | import nicelee.acfun.util.Logger; 16 | 17 | /** 18 | * https://www.acfun.cn/member/favourite/folder/[0-9]+ 19 | * 20 | */ 21 | @Acfun(name = "URL4FavParser", ifLoad = "listAll", note = "个人收藏的视频列表") 22 | public class URL4FavParser extends AbstractPageQueryParser { 23 | 24 | private final static Pattern pattern = Pattern.compile("https://www.acfun.cn/member/favourite/folder/([0-9]+)"); 25 | private String spaceID; 26 | 27 | public URL4FavParser(Object... obj) { 28 | super(obj); 29 | } 30 | 31 | @Override 32 | public boolean matches(String input) { 33 | matcher = pattern.matcher(input); 34 | if (matcher.find()) { 35 | System.out.println("匹配收藏夹,返回 ac1 ac2 ac3 ..."); 36 | spaceID = matcher.group(1); 37 | return true; 38 | } else { 39 | return false; 40 | } 41 | 42 | } 43 | 44 | @Override 45 | public String validStr(String input) { 46 | return matcher.group().trim() + "p=" + paramSetter.getPage(); 47 | } 48 | 49 | @Override 50 | public VideoInfo result(String input, int videoFormat, boolean getVideoLink) { 51 | Logger.println(paramSetter.getPage()); 52 | return result(pageSize, paramSetter.getPage(), videoFormat, getVideoLink); 53 | } 54 | 55 | @Override 56 | public void initPageQueryParam() { 57 | API_PMAX = 20; 58 | pageQueryResult = new VideoInfo(); 59 | pageQueryResult.setClips(new LinkedHashMap<>()); 60 | } 61 | 62 | @Override 63 | protected boolean query(int page, int min, int max, Object... obj) { 64 | int videoFormat = (int) obj[0]; 65 | boolean getVideoLink = (boolean) obj[1]; 66 | try { 67 | HttpHeaders headers = new HttpHeaders(); 68 | if (pageQueryResult.getVideoName() == null) { 69 | String url = "https://www.acfun.cn/rest/pc-direct/favorite/folder/info"; 70 | String param = "folderId=" + spaceID; 71 | String json = util.postContent(url, headers.getCommonHeaders("www.acfun.cn"), param, HttpCookies.getGlobalCookies()); 72 | Logger.println(json); 73 | JSONObject data = new JSONObject(json).getJSONObject("data"); 74 | pageQueryResult.setVideoId(spaceID); 75 | pageQueryResult.setAuthor("收藏夹"); 76 | pageQueryResult.setVideoName(data.getString("name") + paramSetter.getPage()); 77 | pageQueryResult.setVideoPreview(data.getString("cover")); 78 | pageQueryResult.setAuthorId(spaceID); 79 | pageQueryResult.setBrief(data.getString("name")); 80 | } 81 | 82 | // 视频列表 83 | String url = "https://www.acfun.cn/rest/pc-direct/favorite/resource/dougaList"; 84 | String param = String.format("folderId=%s&page=%d&perpage=%d", spaceID, page, API_PMAX); 85 | String json = util.postContent(url, new HttpHeaders().getCommonHeaders("www.acfun.cn"), param, HttpCookies.getGlobalCookies()); 86 | Logger.println(json); 87 | JSONArray jArray = new JSONObject(json).getJSONArray("favoriteList"); 88 | 89 | LinkedHashMap map = pageQueryResult.getClips(); 90 | for (int i = min - 1; i < jArray.length() && i < max; i++) { 91 | try { 92 | map.putAll(convertVideoToClipMap( 93 | "ac" + jArray.getJSONObject(i).optString("contentId"), 94 | (page - 1) * API_PMAX + i + 1, 95 | videoFormat, 96 | getVideoLink)); 97 | }catch (Exception e) { 98 | 99 | } 100 | } 101 | return true; 102 | } catch (Exception e) { 103 | // e.printStackTrace(); 104 | return false; 105 | } 106 | } 107 | 108 | /** 109 | * 使用此方法会产生许多请求,慎用 110 | * 111 | * @param acId 112 | * @param remark 113 | * @param videoFormat 114 | * @param getVideoLink 115 | * @return 将所有avId的视频封装成Map 116 | */ 117 | private LinkedHashMap convertVideoToClipMap(String acId, int remark, int videoFormat, 118 | boolean getVideoLink) { 119 | LinkedHashMap map = new LinkedHashMap<>(); 120 | VideoInfo video = getAVDetail(acId, videoFormat, getVideoLink); // 耗时 121 | for (ClipInfo clip : video.getClips().values()) { 122 | try { 123 | //clip.setTitle(clip.getAvTitle() + "-" + clip.getTitle()); 124 | //clip.setAvTitle(pageQueryResult.getVideoName()); 125 | // >= V3.6, ClipInfo 增加可选ListXXX字段,将收藏夹信息移入其中 126 | clip.setListName(pageQueryResult.getBrief()); 127 | clip.setListOwnerName(pageQueryResult.getAuthor()); 128 | 129 | clip.setRemark(remark); 130 | map.put(clip.getcId(), clip); 131 | }catch (Exception e) { 132 | e.printStackTrace(); 133 | } 134 | } 135 | return map; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/nicelee/acfun/parsers/impl/URL4UPAllParser.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.parsers.impl; 2 | 3 | import java.util.LinkedHashMap; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import org.json.JSONObject; 8 | 9 | import nicelee.acfun.annotations.Acfun; 10 | import nicelee.acfun.model.ClipInfo; 11 | import nicelee.acfun.model.VideoInfo; 12 | import nicelee.acfun.util.HttpHeaders; 13 | import nicelee.acfun.util.Logger; 14 | 15 | @Acfun(name = "URL4UPAllParser", ifLoad = "listAll", note = "个人上传的视频列表") 16 | public class URL4UPAllParser extends AbstractPageQueryParser { 17 | 18 | private final static Pattern pattern = Pattern.compile("acfun\\.cn/u/([0-9]+)"); 19 | private String spaceID; 20 | 21 | public URL4UPAllParser(Object... obj) { 22 | super(obj); 23 | } 24 | 25 | @Override 26 | public boolean matches(String input) { 27 | matcher = pattern.matcher(input); 28 | if (matcher.find()) { 29 | System.out.println("匹配UP主主页全部视频,返回 ac1 ac2 ac3 ..."); 30 | spaceID = matcher.group(1); 31 | return true; 32 | } else { 33 | return false; 34 | } 35 | 36 | } 37 | 38 | @Override 39 | public String validStr(String input) { 40 | return matcher.group().trim() + "p=" + paramSetter.getPage(); 41 | } 42 | 43 | @Override 44 | public VideoInfo result(String input, int videoFormat, boolean getVideoLink) { 45 | Logger.println(paramSetter.getPage()); 46 | return result(pageSize, paramSetter.getPage(), videoFormat, getVideoLink); 47 | } 48 | 49 | @Override 50 | public void initPageQueryParam() { 51 | API_PMAX = 20; 52 | pageQueryResult = new VideoInfo(); 53 | pageQueryResult.setClips(new LinkedHashMap<>()); 54 | } 55 | 56 | private final static Pattern acPattern = Pattern.compile(""); 59 | @Override 60 | protected boolean query(int page, int min, int max, Object... obj) { 61 | int videoFormat = (int) obj[0]; 62 | boolean getVideoLink = (boolean) obj[1]; 63 | try { 64 | if (pageQueryResult.getVideoName() == null) { 65 | // UP主信息 66 | String indexUrl = String.format("https://www.acfun.cn/u/%s", spaceID); 67 | String indexHtml = util.getContent(indexUrl, new HttpHeaders().getCommonHeaders("www.acfun.cn")); 68 | Matcher m = userImgPattern.matcher(indexHtml); 69 | m.find(); 70 | String userImg = m.group(1); 71 | m = userNamePattern.matcher(indexHtml); 72 | m.find(); 73 | String userName = m.group(1); 74 | pageQueryResult.setVideoId(spaceID); 75 | pageQueryResult.setAuthor(userName); 76 | pageQueryResult.setVideoName(pageQueryResult.getAuthor() + "的视频列表"); 77 | pageQueryResult.setVideoPreview(userImg); 78 | pageQueryResult.setAuthorId(spaceID); 79 | pageQueryResult.setBrief("视频列表 - " + paramSetter.getPage()); 80 | } 81 | 82 | // UP主视频列表 83 | // String urlFormat = "https://www.acfun.cn/space/next?uid=%s&type=video&orderBy=2&pageNo=%d"; 84 | String urlFormat = "https://www.acfun.cn/u/%s?quickViewId=ac-space-video-list&reqID=1&ajaxpipe=1&type=video&order=newest&page=%d&pageSize=%d&t=%d"; 85 | String url = String.format(urlFormat, spaceID, page, API_PMAX, System.currentTimeMillis()); 86 | String json = util.getContent(url, new HttpHeaders().getCommonHeaders("www.acfun.cn")); 87 | Logger.println(url); 88 | Logger.println(json); 89 | if(json.endsWith("*/")) { 90 | int index = json.lastIndexOf("/*"); 91 | json = json.substring(0, index); 92 | } 93 | JSONObject jobj = new JSONObject(json); 94 | 95 | String results[] = jobj.getString("html").split(""); 96 | LinkedHashMap map = pageQueryResult.getClips(); 97 | for (int i = min - 1; i < results.length && i < max; i++) { 98 | Matcher matcher = acPattern.matcher(results[i]); 99 | matcher.find(); 100 | map.putAll(convertVideoToClipMap( 101 | matcher.group(1), 102 | (page - 1) * API_PMAX + i + 1, 103 | videoFormat, 104 | getVideoLink)); 105 | } 106 | return true; 107 | } catch (Exception e) { 108 | // e.printStackTrace(); 109 | return false; 110 | } 111 | } 112 | 113 | /** 114 | * 使用此方法会产生许多请求,慎用 115 | * 116 | * @param acId 117 | * @param remark 118 | * @param videoFormat 119 | * @param getVideoLink 120 | * @return 将所有avId的视频封装成Map 121 | */ 122 | private LinkedHashMap convertVideoToClipMap(String acId, int remark, int videoFormat, 123 | boolean getVideoLink) { 124 | LinkedHashMap map = new LinkedHashMap<>(); 125 | VideoInfo video = getAVDetail(acId, videoFormat, getVideoLink); // 耗时 126 | for (ClipInfo clip : video.getClips().values()) { 127 | //clip.setTitle(clip.getAvTitle() + "-" + clip.getTitle()); 128 | //clip.setAvTitle(pageQueryResult.getVideoName()); 129 | // >= V3.6, ClipInfo 增加可选ListXXX字段,将收藏夹信息移入其中 130 | clip.setListName(pageQueryResult.getVideoName()); 131 | clip.setListOwnerName(pageQueryResult.getAuthor()); 132 | 133 | clip.setRemark(remark); 134 | map.put(clip.getcId(), clip); 135 | } 136 | return map; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/nicelee/acfun/plugin/CustomClassLoader.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.plugin; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.util.HashMap; 7 | 8 | public class CustomClassLoader extends ClassLoader { 9 | 10 | private HashMap> classList = new HashMap<>(); 11 | 12 | protected Class findClass(String classPath, String className) { 13 | if (classList.containsKey(className)) 14 | return classList.get(className); 15 | try { 16 | FileInputStream in = new FileInputStream(new File(classPath)); 17 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 18 | byte[] buffer = new byte[1024]; 19 | for (int len = 0; (len = in.read(buffer)) != -1;) { 20 | out.write(buffer, 0, len); 21 | } 22 | in.close(); 23 | byte[] bytes = out.toByteArray(); 24 | Class clazz = this.defineClass(className, bytes, 0, bytes.length); 25 | classList.put(className, clazz); 26 | return clazz; 27 | } catch (Exception e) { 28 | e.printStackTrace(); 29 | return null; 30 | } 31 | } 32 | 33 | protected Class findClass(String className) { 34 | return classList.get(className); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/nicelee/acfun/plugin/Plugin.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.plugin; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import javax.tools.JavaCompiler; 7 | import javax.tools.ToolProvider; 8 | 9 | public class Plugin { 10 | 11 | File workingDir; 12 | String packageName; 13 | 14 | public Plugin() { 15 | workingDir = new File("./parsers"); 16 | packageName = "nicelee.bilibili.parsers.impl"; 17 | } 18 | 19 | public Plugin(String workingDir, String packageName) { 20 | this.setEnv(workingDir); 21 | this.packageName = packageName; 22 | } 23 | 24 | private void setEnv(String workingDirectory) { 25 | workingDir = new File(workingDirectory); 26 | } 27 | 28 | /** 29 | * 是否需要编译相应类 30 | * @param clazzName 不带.java, 不带全路径的类名 31 | * @return 是否需要编译 32 | */ 33 | public boolean isToCompile(String clazzName) { 34 | File classF = new File(workingDir, clazzName + ".class"); 35 | if(classF.exists()) { 36 | long timeLastModifiedClass = classF.lastModified(); 37 | File javaF = new File(workingDir, clazzName + ".java"); 38 | long timeLastModifiedJava = javaF.lastModified(); 39 | if(timeLastModifiedClass > timeLastModifiedJava) 40 | return false; 41 | else 42 | return true; 43 | }else { 44 | return true; 45 | } 46 | } 47 | 48 | /** 49 | * 编译相应类 50 | * @param clazzName 不带.java, 不带全路径的类名 51 | * @return 编译是否成功 52 | */ 53 | public boolean compile(String clazzName) { 54 | try { 55 | File file = new File(workingDir, clazzName + ".java"); 56 | JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 57 | int result = compiler.run(null, null, null, file.getCanonicalPath()); 58 | return result == 0; 59 | } catch (Exception e) { 60 | return false; 61 | } 62 | 63 | } 64 | 65 | /** 66 | * 加载类 67 | * @param ccloader 自定义的类加载器 68 | * @param clazzName clazzName 不带.java, 不带全路径的类名 69 | * @return 加载类 70 | * @throws IOException 71 | * @throws ClassNotFoundException 72 | */ 73 | public Class loadClass(CustomClassLoader ccloader, String clazzName) throws IOException, ClassNotFoundException { 74 | try { 75 | File f = new File(workingDir, clazzName + ".class"); 76 | return ccloader.findClass(f.getCanonicalPath(), packageName + "." + clazzName); 77 | } catch (NoClassDefFoundError e) { 78 | String classNotFound = e.getMessage(); 79 | classNotFound = classNotFound.substring(classNotFound.lastIndexOf("/") + 1); 80 | System.out.printf("尝试加载未找到的类: %s\r\n", classNotFound); 81 | loadClass(ccloader, classNotFound); 82 | return loadClass(ccloader, clazzName); 83 | } catch (LinkageError e) { 84 | System.err.printf("加载时出现状况: %s \r\n" ,e.getMessage()); 85 | return ccloader.findClass(packageName + "." + clazzName); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/ConfigUtil.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | import java.util.concurrent.Executors; 10 | //import java.util.HashMap; 11 | import java.util.regex.Matcher; 12 | import java.util.regex.Pattern; 13 | 14 | import javax.swing.ImageIcon; 15 | 16 | import nicelee.ui.Global; 17 | 18 | public class ConfigUtil { 19 | final static Pattern patternConfig = Pattern.compile("^[ ]*([0-9|a-z|A-Z|.|_]+)[ ]*=[ ]*([^ ]+.*$)"); 20 | 21 | public static void initConfigs() { 22 | // 先初始化默认值 23 | BufferedReader buReader = null; 24 | try { 25 | InputStream in = ConfigUtil.class.getResourceAsStream("/resources/app.config"); 26 | buReader = new BufferedReader(new InputStreamReader(in)); 27 | String config; 28 | while ((config = buReader.readLine()) != null) { 29 | Matcher matcher = patternConfig.matcher(config); 30 | if (matcher.find()) { 31 | System.setProperty(matcher.group(1), matcher.group(2).trim()); 32 | // System.out.printf(" key-->value: %s --> %s\r\n", matcher.group(1), 33 | // matcher.group(2)); 34 | } 35 | } 36 | buReader.close(); 37 | } catch (IOException e) { 38 | e.printStackTrace(); 39 | } 40 | 41 | // 从配置文件读取 42 | buReader = null; 43 | System.out.println("----Config init begin...----"); 44 | try { 45 | buReader = new BufferedReader(new FileReader("./config/app.config")); 46 | String config; 47 | while ((config = buReader.readLine()) != null) { 48 | Matcher matcher = patternConfig.matcher(config); 49 | if (matcher.find()) { 50 | System.setProperty(matcher.group(1), matcher.group(2).trim()); 51 | System.out.printf(" key-->value: %s --> %s\r\n", matcher.group(1), matcher.group(2)); 52 | } 53 | } 54 | } catch (IOException e) { 55 | // e.printStackTrace(); 56 | } finally { 57 | try { 58 | buReader.close(); 59 | } catch (Exception e) { 60 | } 61 | } 62 | System.out.println("----Config ini end...----"); 63 | Global.noQualityRequest = "true".equals(System.getProperty("acfun.quality.noQualityRequest")); 64 | Global.debugFFmpeg = "true".equals(System.getProperty("acfun.debug.ffmpeg")); 65 | //下载设置相关 66 | int fixPool = Integer.parseInt(System.getProperty("acfun.download.poolSize")); 67 | Global.downLoadThreadPool = Executors.newFixedThreadPool(fixPool); 68 | Global.downloadFormat = Integer.parseInt(System.getProperty("acfun.format")); 69 | Global.savePath = System.getProperty("acfun.savePath"); 70 | Global.maxFailRetry = Integer.parseInt(System.getProperty("acfun.download.maxFailRetry")); 71 | //查询或显示相关 72 | Global.pageSize = Integer.parseInt(System.getProperty("acfun.pageSize")); 73 | Global.pageDisplay = System.getProperty("acfun.pageDisplay"); 74 | Global.themeDefault = "default".equals(System.getProperty("acfun.theme")); 75 | //临时文件 76 | Global.restrictTempMode = "on".equals(System.getProperty("acfun.restrictTempMode")); 77 | //仓库功能 78 | Global.useRepo = "on".equals(System.getProperty("acfun.repo")); 79 | boolean saveToRepo = "on".equals(System.getProperty("acfun.repo.save")); 80 | Global.saveToRepo = Global.useRepo || saveToRepo; 81 | Global.repoInDefinitionStrictMode = "on".equals(System.getProperty("acfun.repo.definitionStrictMode")); 82 | //重命名配置 83 | Global.formatStr = System.getProperty("acfun.name.format"); 84 | Global.doRenameAfterComplete = "true".equals(System.getProperty("acfun.name.doAfterComplete")); 85 | //弹出框设置 86 | Global.isAlertIfDownloded = "true".equals(System.getProperty("acfun.alert.isAlertIfDownloded")); 87 | Global.maxAlertPrompt = Integer.parseInt(System.getProperty("acfun.alert.maxAlertPrompt")); 88 | String version = System.getProperty("acfun.version"); 89 | if(version != null) { 90 | Global.version = version; 91 | } 92 | 93 | File backImgPNG = new File("config/background.png"); 94 | if(backImgPNG.exists()) { 95 | Global.backgroundImg = new ImageIcon(backImgPNG.getPath()); 96 | }else { 97 | File backImgJPG = new File("config/background.jpg"); 98 | if(backImgJPG.exists()) { 99 | Global.backgroundImg = new ImageIcon(backImgJPG.getPath()); 100 | }else { 101 | Global.backgroundImg = new ImageIcon(Global.class.getResource("/resources/background.png")); 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/HttpCookies.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | import java.net.HttpCookie; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | public class HttpCookies { 8 | static List globalCookies; 9 | 10 | public static List convertCookies(String cookie) { 11 | List iCookies = new ArrayList(); 12 | String[] cookieStrs = cookie.replaceAll("\\||\r|\n| |\\[|\\]|\"", "").split(",|;|&"); 13 | for (String cookieStr : cookieStrs) { 14 | String entry[] = cookieStr.split("="); 15 | HttpCookie cCookie = new HttpCookie(entry[0], entry[1]); 16 | iCookies.add(cCookie); 17 | } 18 | return iCookies; 19 | } 20 | 21 | public static List getGlobalCookies() { 22 | return globalCookies; 23 | } 24 | 25 | public static void setGlobalCookies(List globalCookies) { 26 | HttpCookies.globalCookies = globalCookies; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/HttpHeaders.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | import java.util.HashMap; 4 | 5 | public class HttpHeaders { 6 | HashMap headerMap = new HashMap(); 7 | private static HashMap userInfoHeaderMap = null; 8 | private static HashMap loginAuthHeaderMap = null; 9 | private static HashMap loginAuthVaHeaderMap = null; 10 | 11 | public void setHeader(String key, String value) { 12 | headerMap.put(key, value); 13 | } 14 | 15 | public String getHeader(String key) { 16 | return headerMap.get(key); 17 | } 18 | 19 | public HashMap getHeaders() { 20 | return headerMap; 21 | } 22 | 23 | /** 24 | * 该Header配置用于登录AuthKey验证 25 | */ 26 | public HashMap getAcFunLoginAuthVaHeaders() { 27 | if (loginAuthVaHeaderMap == null) { 28 | loginAuthVaHeaderMap = new HashMap(); 29 | loginAuthVaHeaderMap.put("Accept", "*/*"); 30 | loginAuthVaHeaderMap.put("Accept-Encoding", "gzip, deflate, br"); 31 | loginAuthVaHeaderMap.put("Accept-Language", "zh-CN,zh;q=0.8"); 32 | loginAuthVaHeaderMap.put("Connection", "keep-alive"); 33 | loginAuthVaHeaderMap.put("Host", "scan.acfun.cn"); 34 | loginAuthVaHeaderMap.put("Origin", "https://www.acfun.cn"); 35 | loginAuthVaHeaderMap.put("Referer", "https://www.acfun.cn/"); 36 | loginAuthVaHeaderMap.put("User-Agent", 37 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0"); 38 | } 39 | return loginAuthVaHeaderMap; 40 | } 41 | 42 | /** 43 | * 该Header配置用于FLV视频下载 44 | */ 45 | public HashMap getBiliWwwFLVHeaders(String avId) { 46 | headerMap.put("X-Requested-With", "ShockwaveFlash/28.0.0.137"); 47 | //headerMap.put("Referer", "https://www.bilibili.com/video/" + avId);// need addavId 48 | headerMap.put("User-Agent", 49 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"); 50 | return headerMap; 51 | } 52 | 53 | /** 54 | * 该Header配置用于M4s视频下载 55 | */ 56 | public HashMap getBiliWwwM4sHeaders(String avId) { 57 | headerMap.remove("X-Requested-With"); 58 | //headerMap.put("Referer", "https://www.bilibili.com/video/" + avId);// need addavId 59 | headerMap.put("User-Agent", 60 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"); 61 | return headerMap; 62 | } 63 | 64 | /** 65 | * 该Header配置用于通用PC端页面访问 66 | */ 67 | public HashMap getAcFunM3u8() { 68 | headerMap = new HashMap(); 69 | headerMap.put("Accept", "text/html,application/xhtml+xml;q=0.9,image/webp,*/*;q=0.8"); 70 | headerMap.put("Accept-Encoding", "gzip, deflate, sdch, br"); 71 | headerMap.put("Accept-Language", "zh-CN,zh;q=0.8"); 72 | headerMap.put("Connection", "keep-alive"); 73 | headerMap.put("Host", "video.acfun.cn"); 74 | headerMap.put("User-Agent", 75 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"); 76 | return headerMap; 77 | } 78 | 79 | /** 80 | * 该Header配置用于通用PC端页面访问 81 | */ 82 | public HashMap getAcFunTsDownload() { 83 | headerMap = new HashMap(); 84 | headerMap.put("Accept", "text/html,application/xhtml+xml;q=0.9,image/webp,*/*;q=0.8"); 85 | headerMap.put("Accept-Encoding", "gzip, deflate, sdch, br"); 86 | headerMap.put("Accept-Language", "zh-CN,zh;q=0.8"); 87 | headerMap.put("Connection", "keep-alive"); 88 | headerMap.put("Host", "video.acfun.cn"); 89 | headerMap.put("Origin", "https://www.acfun.cn"); 90 | headerMap.put("Referer", "https://www.acfun.cn/"); 91 | headerMap.put("User-Agent", 92 | "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"); 93 | return headerMap; 94 | } 95 | 96 | /** 97 | * 该Header配置用于通用PC端页面访问 98 | */ 99 | public HashMap getCommonHeaders(String host) { 100 | headerMap = new HashMap(); 101 | headerMap.put("Accept", "text/html,application/xhtml+xm…ml;q=0.9,image/webp,*/*;q=0.8"); 102 | headerMap.put("Accept-Encoding", "gzip, deflate, sdch, br"); 103 | headerMap.put("Accept-Language", "zh-CN,zh;q=0.8"); 104 | headerMap.put("Cache-Control", "max-age=0"); 105 | headerMap.put("Connection", "keep-alive"); 106 | headerMap.put("Host", host); 107 | headerMap.put("User-Agent", 108 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0"); 109 | return headerMap; 110 | } 111 | 112 | /** 113 | * 该Header配置用于通用PC端页面访问 114 | */ 115 | public HashMap getCommonHeaders() { 116 | headerMap = new HashMap(); 117 | headerMap.put("Accept", "text/html,application/xhtml+xm…ml;q=0.9,image/webp,*/*;q=0.8"); 118 | headerMap.put("Accept-Encoding", "gzip, deflate, sdch, br"); 119 | headerMap.put("Accept-Language", "zh-CN,zh;q=0.8"); 120 | headerMap.put("Cache-Control", "max-age=0"); 121 | headerMap.put("Connection", "keep-alive"); 122 | headerMap.put("User-Agent", 123 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0"); 124 | return headerMap; 125 | } 126 | 127 | /** 128 | * 空Header配置 129 | */ 130 | public HashMap getEmptyHeaders() { 131 | headerMap = new HashMap(); 132 | return headerMap; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/Logger.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | public class Logger { 4 | 5 | 6 | /** 7 | * 测试用 8 | * @param str 9 | */ 10 | public static void printf(String str, Object... obj) { 11 | StackTraceElement ele = Thread.currentThread().getStackTrace()[2]; 12 | String file = ele.getFileName(); 13 | file = file.substring(0, file.length() - 5); 14 | String method = ele.getMethodName(); 15 | int line = ele.getLineNumber(); 16 | String preStr = String.format(str, obj); 17 | String result = String.format("%s-%s/%d : %s", file, method, line, preStr); 18 | System.out.println(result); 19 | } 20 | 21 | /** 22 | * 测试用 23 | * @param str 24 | */ 25 | public static void println(String str) { 26 | StackTraceElement ele = Thread.currentThread().getStackTrace()[2]; 27 | String file = ele.getFileName(); 28 | file = file.substring(0, file.length() - 5); 29 | String method = ele.getMethodName(); 30 | int line = ele.getLineNumber(); 31 | String result = String.format("%s-%s/%d : %s", file, method, line, str); 32 | System.out.println(result); 33 | } 34 | /** 35 | * 测试用 36 | * @param str 37 | */ 38 | public static void println(Object obj) { 39 | StackTraceElement ele = Thread.currentThread().getStackTrace()[2]; 40 | String file = ele.getFileName(); 41 | file = file.substring(0, file.length() - 5); 42 | String method = ele.getMethodName(); 43 | int line = ele.getLineNumber(); 44 | String result = String.format("%s-%s/%d : %s", file, method, line, obj.toString()); 45 | System.out.println(result); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/MD5.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | //import java.math.BigInteger; 4 | //import java.security.MessageDigest; 5 | //import java.security.NoSuchAlgorithmException; 6 | 7 | public class MD5 { 8 | 9 | // 10 | // private static String appkey(String key, int add) { 11 | // byte[] keyBytes = key.getBytes(); 12 | // for (int i = 0; i < keyBytes.length; i++) { 13 | // int index = 0; 14 | // int ch = keyBytes[i]; 15 | // int num = keyBytes[i] + add; 16 | // num = (num - 65) % 57 + 65; 17 | // while (90 < num && 97 > num) { 18 | // add = (index * add) + add; index ++; 19 | // num = ch + add; 20 | // num = (num - 65) % 57 + 65; 21 | // } 22 | // keyBytes[i] = (byte) num; 23 | // } 24 | // return new String(keyBytes); 25 | // } 26 | // 27 | // private static String encrypt(String param) { 28 | // byte[] secretBytes = null; 29 | // try { 30 | // // 生成一个MD5加密计算摘要 31 | // MessageDigest md = MessageDigest.getInstance("MD5"); 32 | // // 对字符串进行加密 33 | // md.update(param.getBytes()); 34 | // // 获得加密后的数据 35 | // secretBytes = md.digest(); 36 | // } catch (NoSuchAlgorithmException e) { 37 | // throw new RuntimeException("没有md5这个算法!"); 38 | // } 39 | // String md5code = new BigInteger(1,secretBytes).toString(16);// 16进制数字 40 | // for (int i = 0; i < 32 - md5code.length(); i++) { 41 | // md5code = "0" + md5code; 42 | // } 43 | // return md5code; 44 | // } 45 | // 46 | // public static String sign(String param, String appSecret) { 47 | // return encrypt(param + appSecret); 48 | // } 49 | // // 主函数调用测试 50 | // public static void main(String[] args) { 51 | // System.out.println(sign("actionkey=appkey&aid=2478750&appkey=YvirImLGlLANCLvM&build=5423000&cid=3876154&device=android&expire=0&fnval=80&fnver=0&force_host=0&fourk=0&mid=0&mobi_app=android&npcybs=0&otype=json&platform=android&qn=80&quality=3&ts=1561814729", 52 | // "JNlZNgfNGKZEpaDTkCdPQVXntXhuiJEM")); 53 | // 54 | // System.out.println("4ae40cf1894d2c30bb0b364c599f09c7"); 55 | // } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/QrCodeUtil.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | import java.awt.Color; 4 | import java.awt.Graphics2D; 5 | import java.awt.image.BufferedImage; 6 | import java.io.File; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.io.OutputStream; 10 | import java.util.Hashtable; 11 | 12 | import javax.imageio.ImageIO; 13 | 14 | import com.google.zxing.BarcodeFormat; 15 | import com.google.zxing.EncodeHintType; 16 | import com.google.zxing.WriterException; 17 | import com.google.zxing.common.BitMatrix; 18 | import com.google.zxing.common.CharacterSetECI; 19 | import com.google.zxing.qrcode.QRCodeWriter; 20 | import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; 21 | 22 | /** 23 | * 二维码生成和读的工具类 24 | * 25 | */ 26 | public class QrCodeUtil { 27 | 28 | /** 29 | * 生成包含字符串信息的二维码图片 30 | * 31 | * @param outputStream 文件输出流路径 32 | * @param content 二维码携带信息 33 | * @param qrCodeSize 二维码图片大小 34 | * @param imageFormat 二维码的格式 35 | * @throws WriterException 36 | * @throws IOException 37 | */ 38 | public static boolean createQrCode(OutputStream outputStream, String content, int qrCodeSize, String imageFormat) 39 | throws WriterException, IOException { 40 | BufferedImage image = createQrCode(content, qrCodeSize); 41 | return ImageIO.write(image, imageFormat, outputStream); 42 | } 43 | 44 | /** 45 | * 生成包含字符串信息的二维码图片 46 | * 47 | * @param outputStream 文件输出流路径 48 | * @param content 二维码携带信息 49 | * @param qrCodeSize 二维码图片大小 50 | * @throws WriterException 51 | * @throws IOException 52 | */ 53 | public static BufferedImage createQrCode(String content, int qrCodeSize) throws WriterException { 54 | // 设置二维码纠错级别MAP 55 | Hashtable hintMap = new Hashtable(); 56 | hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); // 矫错级别 57 | hintMap.put(EncodeHintType.CHARACTER_SET, CharacterSetECI.UTF8); 58 | QRCodeWriter qrCodeWriter = new QRCodeWriter(); 59 | // 创建比特矩阵(位矩阵)的QR码编码的字符串 60 | BitMatrix byteMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap); 61 | // 使BufferedImage勾画QRCode (matrixWidth 是行二维码像素点) 62 | int matrixWidth = byteMatrix.getWidth(); 63 | BufferedImage image = new BufferedImage(matrixWidth - 100, matrixWidth - 100, BufferedImage.TYPE_INT_RGB); 64 | image.createGraphics(); 65 | Graphics2D graphics = (Graphics2D) image.getGraphics(); 66 | graphics.setColor(Color.WHITE); 67 | graphics.fillRect(0, 0, matrixWidth, matrixWidth); 68 | // 使用比特矩阵画并保存图像 69 | graphics.setColor(Color.BLACK); 70 | for (int i = 0; i < matrixWidth; i++) { 71 | for (int j = 0; j < matrixWidth; j++) { 72 | if (byteMatrix.get(i, j)) { 73 | graphics.fillRect(i - 50, j - 50, 1, 1); 74 | } 75 | } 76 | } 77 | return image; 78 | } 79 | 80 | /** 81 | * 测试代码 82 | * 83 | * @throws WriterException 84 | */ 85 | public static void main(String[] args) throws IOException, WriterException { 86 | 87 | createQrCode(new FileOutputStream(new File("d:\\qrcode.jpg")), 88 | "WE1231238239128sASDASDSADSDWEWWREWRERWSDFDFSDSDF123123123123213123", 900, "JPEG"); 89 | //readQrCode(new FileInputStream(new File("d:\\qrcode.jpg"))); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/RepoUtil.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.util.Set; 9 | import java.util.concurrent.CopyOnWriteArraySet; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | import nicelee.ui.Global; 14 | 15 | public class RepoUtil { 16 | // avinfo 必须符合avId-qn-p的形式 17 | // av1234-60-p2 18 | static Pattern standardAvPattern; 19 | static Pattern standardBangumiPattern; 20 | // 存在某一清晰度后, 在下另一种清晰度时是否判断已完成 21 | // true : 同一视频两种清晰度算不同文件 22 | // false : 同一视频两种清晰度算相同文件 23 | static boolean definitionStrictMode; 24 | 25 | static File fRepo; // 持久化文件,存放于config/repo.config 26 | static Set downRepo; // 已下载完成的av集合 27 | 28 | 29 | public static void init(boolean refresh) { 30 | definitionStrictMode = Global.repoInDefinitionStrictMode; 31 | if(fRepo == null || refresh) { 32 | fRepo = new File("config/repo.config"); 33 | standardAvPattern = Pattern.compile("^(a[vabc][0-9_]+)-([0-9]+)(-p[0-9]+)$"); 34 | standardBangumiPattern = Pattern.compile("^a[vabc]([0-9]+)_([0-9]+)_([0-9]+)-([0-9]+)(-p[0-9]+)$"); 35 | downRepo = new CopyOnWriteArraySet(); 36 | if(!fRepo.exists()) { 37 | try { 38 | fRepo.createNewFile(); 39 | } catch (IOException e) { 40 | e.printStackTrace(); 41 | } 42 | } 43 | } 44 | // 先初始化downRepo 45 | BufferedReader buReader = null; 46 | try { 47 | buReader = new BufferedReader(new FileReader(fRepo)); 48 | String avRecord; 49 | while ((avRecord = buReader.readLine()) != null) { 50 | // 处理历史遗留事项,保持版本兼容(aa5024886_36188_330164 所有番的groupID都是36188已经不作数了) 51 | // 如果是番剧 52 | Matcher matcherBangumi = standardBangumiPattern.matcher(avRecord); 53 | if(matcherBangumi.find()) { 54 | //Pattern.compile("^a[vabc]([0-9]+)_([0-9]+)(_[0-9]+)(-[0-9]+)(-p[0-9]+)$"); 55 | String aaid = "aa" + matcherBangumi.group(1); 56 | String idWithUnderline = matcherBangumi.group(3); 57 | String qnWithMinus = matcherBangumi.group(4); 58 | if(definitionStrictMode) { 59 | downRepo.add(aaid + idWithUnderline + qnWithMinus); 60 | }else { 61 | downRepo.add(aaid + idWithUnderline); 62 | } 63 | }else { 64 | Matcher matcher = standardAvPattern.matcher(avRecord); 65 | if (matcher.find()) { 66 | if(definitionStrictMode) { 67 | downRepo.add(avRecord); 68 | }else { 69 | downRepo.add(matcher.group(1) + matcher.group(3)); 70 | } 71 | } 72 | } 73 | } 74 | buReader.close(); 75 | } catch (IOException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | 80 | /** 81 | * 仓库里是否存在该条记录 82 | * @param avRecord 83 | * @return 84 | */ 85 | public static boolean isInRepo(String avRecord) { 86 | System.out.println("查询记录" + avRecord); 87 | // 对于番剧特殊处理 88 | Matcher matcherBangumi = standardBangumiPattern.matcher(avRecord); 89 | if(matcherBangumi.find()) { 90 | //Pattern.compile("^a[vabc]([0-9]+)_([0-9]+)(_[0-9]+)(-[0-9]+)(-p[0-9]+)$"); 91 | String aaid = "aa" + matcherBangumi.group(1); 92 | String idWithUnderline = matcherBangumi.group(3); 93 | String qnWithMinus = matcherBangumi.group(4); 94 | if(definitionStrictMode) { 95 | return downRepo.contains(aaid + idWithUnderline + qnWithMinus); 96 | }else { 97 | return downRepo.contains(aaid + idWithUnderline); 98 | } 99 | } 100 | 101 | if(definitionStrictMode) { 102 | return downRepo.contains(avRecord); 103 | }else { 104 | Matcher matcher = standardAvPattern.matcher(avRecord); 105 | if (matcher.find()) { 106 | return downRepo.contains(matcher.group(1) + matcher.group(3)); 107 | } 108 | } 109 | return false; 110 | } 111 | 112 | /** 113 | * 加入并持久化保存到记录仓库 114 | *

avRecord 必须符合avId-p-qn的形式

115 | * @param avinfo 116 | */ 117 | public static void appendAndSave(String avRecord) { 118 | System.out.println("已完成下载: " + avRecord); 119 | if(!isInRepo(avRecord)) { 120 | Logger.println("不在记录中"); 121 | Matcher matcher = standardAvPattern.matcher(avRecord); 122 | if (matcher.find()) { 123 | if(definitionStrictMode) { 124 | downRepo.add(avRecord); 125 | }else { 126 | downRepo.add(matcher.group(1) + matcher.group(3)); 127 | } 128 | appendRecordToFile(avRecord); 129 | }else { 130 | Logger.println("不匹配standardAvPattern"); 131 | } 132 | } 133 | } 134 | 135 | 136 | 137 | synchronized static void appendRecordToFile(String line) { 138 | //System.out.println(Thread.currentThread().getName() + "开始记录"); 139 | try { 140 | FileWriter fw = new FileWriter(fRepo, true); 141 | fw.write(line); 142 | fw.write("\n"); 143 | fw.close(); 144 | } catch (Exception e) { 145 | e.printStackTrace(); 146 | } 147 | //System.out.println(Thread.currentThread().getName() + "记录完成"); 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/net/TrustAllCertSSLUtil.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util.net; 2 | 3 | import javax.net.ssl.SSLContext; 4 | import javax.net.ssl.SSLSocketFactory; 5 | import javax.net.ssl.TrustManager; 6 | import javax.net.ssl.X509TrustManager; 7 | 8 | public class TrustAllCertSSLUtil implements TrustManager, X509TrustManager { 9 | 10 | private TrustAllCertSSLUtil() { 11 | } 12 | 13 | private static SSLSocketFactory sslFactory = null; 14 | 15 | public static SSLSocketFactory getFactory() throws Exception { 16 | if (sslFactory == null) { 17 | TrustManager[] trustAllCerts = new TrustManager[1]; 18 | TrustManager tm = new TrustAllCertSSLUtil(); 19 | trustAllCerts[0] = tm; 20 | SSLContext sc = SSLContext.getInstance("SSL"); 21 | sc.init(null, trustAllCerts, null); 22 | sslFactory = sc.getSocketFactory(); 23 | } 24 | return sslFactory; 25 | } 26 | 27 | public java.security.cert.X509Certificate[] getAcceptedIssuers() { 28 | return null; 29 | } 30 | 31 | public boolean isServerTrusted(java.security.cert.X509Certificate[] certs) { 32 | return true; 33 | } 34 | 35 | public boolean isClientTrusted(java.security.cert.X509Certificate[] certs) { 36 | return true; 37 | } 38 | 39 | public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) 40 | throws java.security.cert.CertificateException { 41 | return; 42 | } 43 | 44 | public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) 45 | throws java.security.cert.CertificateException { 46 | return; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/net/stream/ChunkedInputStream.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util.net.stream; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | // 未使用 7 | public class ChunkedInputStream extends InputStream { 8 | 9 | private InputStream in; 10 | // private int len = -1; // 用于读取长度 11 | // private int[] length = new int[8]; // 用于保存长度 12 | // private int size = 0; // 用于只是length有效字位数 13 | // int remain = 0;// 还剩多少 14 | // 15 | // public ChunkedInputStream(InputStream in) { 16 | // this.in = in; 17 | // } 18 | // 19 | @Override 20 | public int read() throws IOException { 21 | // int value, cnt = 0; 22 | // while ((value = in.read()) != -1) { 23 | // //Logger.println(Integer.toHexString(value)); 24 | // System.out.print(Integer.toHexString(value)); 25 | // System.out.print(" "); 26 | // if(cnt == 30) { 27 | // System.out.print("\r\n"); 28 | // cnt = 0; 29 | // } 30 | // cnt ++; 31 | // } 32 | // return -1; 33 | // 如果没有获取长度, 那么先获取长度 34 | // if (len == -1) { 35 | // while (true) { 36 | // value = in.read(); 37 | // Logger.println(Integer.toHexString(value)); 38 | // if (value == -1) { 39 | // return -1; 40 | // } else if (value == 13) {// 回车键 41 | // value = in.read(); // 换行键 42 | // // 此时, 已经读完长度 43 | // len = getLen(); 44 | // remain = len; 45 | // break; 46 | // } else { 47 | // length[size] = value; 48 | // size ++; 49 | // } 50 | // } 51 | // } 52 | // // 读取一个字节 53 | // remain --; 54 | // //重置 55 | // if(remain == 0) { 56 | // len = -1; 57 | // in.read(); 58 | // in.read(); 59 | // } 60 | return in.read(); 61 | } 62 | 63 | /* 64 | * 获取读到的长度 65 | */ 66 | // private int getLen() throws IOException { 67 | // 68 | // int sum = 0; 69 | // int weight = 1; 70 | // while (size > 0) { 71 | // sum += (asciiToHex(length[size - 1]) << weight); 72 | // weight += 4; 73 | // size--; 74 | // } 75 | // return sum; 76 | // } 77 | 78 | // private int asciiToHex(int axcii) throws IOException { 79 | // if (axcii >= 0x30 && axcii <= 0x39) { 80 | // return axcii - 0x30; 81 | // } else if (axcii >= 0x41 && axcii <= 0x46) { 82 | // return axcii - 0x41 + 10; 83 | // } else if (axcii >= 0x61 && axcii <= 0x66) { 84 | // return axcii - 0x61 + 10; 85 | // } else { 86 | // throw new IOException("ascii 码转换错误"); 87 | // } 88 | // } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/net/stream/InflateWithHeaderInputStream.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util.net.stream; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.zip.Adler32; 6 | 7 | //未使用 8 | public class InflateWithHeaderInputStream extends InputStream { 9 | 10 | private InputStream in; 11 | private boolean isHeaderRead; 12 | private int[] header = {0x78, 0x9c};//{0x01, 0x78};//{0x01, 0x78};//{0x78, 0x01};// {0x78, 0x9c} 13 | private int pHeader = 0; 14 | 15 | private boolean isTailToRead; 16 | private int[] tail = {1, 0}; 17 | private int pTail = 0; 18 | Adler32 ad; 19 | long adler = 1; 20 | public InflateWithHeaderInputStream(InputStream in) { 21 | this.in = in; 22 | isHeaderRead = false; 23 | isTailToRead = false; 24 | ad = new Adler32(); 25 | } 26 | 27 | @Override 28 | public int read() throws IOException { 29 | int value; 30 | value = resultAddHeadAndTail(); 31 | System.out.println(Integer.toHexString(value)); 32 | if(isTailToRead) { 33 | System.out.println("----------" + Integer.toHexString(value)); 34 | } 35 | return value; 36 | } 37 | 38 | /** 39 | * @return 40 | * @throws IOException 41 | */ 42 | private int resultAddHeadAndTail() throws IOException { 43 | int value; 44 | if(!isHeaderRead) { 45 | value = header[pHeader]; 46 | pHeader++; 47 | if(pHeader > header.length - 1) { 48 | isHeaderRead = true; 49 | } 50 | return value; 51 | }else if(!isTailToRead){ 52 | value = in.read(); 53 | // value = -1; 54 | if(value == -1) { 55 | return -1; 56 | // isTailToRead = true; 57 | // pTail ++; 58 | // return ((tail[1] & 0xff00) >> 8); 59 | //// return (tail[1] & 0xff); 60 | }else { 61 | adler_32Check(value); 62 | return value; 63 | } 64 | }else { 65 | if(pTail > 3) { 66 | return -1; 67 | }else if(pTail == 1){ 68 | pTail ++; 69 | return (tail[1] & 0xff); 70 | // return ((tail[1] & 0xff00) >> 8); 71 | }else if(pTail == 2){ 72 | pTail ++; 73 | return ((tail[0] & 0xff00) >> 8); 74 | // return (tail[0] & 0xff); 75 | }else{ 76 | pTail ++; 77 | return (tail[0] & 0xff); 78 | // return ((tail[0] & 0xff00) >> 8); 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * @param value 85 | */ 86 | private void adler_32Check(int value) { 87 | // long s1 = adler & 0xffff; 88 | // long s2 = (adler >> 16) & 0xffff; 89 | // s1 = (s1 + value) % 65521; 90 | // s2 = (s2 + s1) % 65521; 91 | // adler = (s2 << 16) + s1; 92 | // tail[0] = (int) (adler & 0xffff); 93 | // tail[1] = (int) ((adler >> 16) & 0xffff); 94 | ad.update(value); 95 | tail[0] = (int) (ad.getValue() & 0xffff); 96 | tail[1] = (int) ((ad.getValue() >> 16) & 0xffff); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/nicelee/acfun/util/net/stream/TestStream.java: -------------------------------------------------------------------------------- 1 | package nicelee.acfun.util.net.stream; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | //未使用 7 | public class TestStream extends InputStream{ 8 | 9 | byte[] content; 10 | int pointer; 11 | public TestStream(String testStr) { 12 | content = testStr.getBytes(); 13 | pointer = 0; 14 | } 15 | 16 | public TestStream(byte[] test) { 17 | content = test; 18 | pointer = 0; 19 | } 20 | 21 | 22 | @Override 23 | public int read() throws IOException { 24 | if(pointer > content.length -1) { 25 | return -1; 26 | }else { 27 | int value = content[pointer]; 28 | pointer ++; 29 | return value; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/nicelee/test/junit/INeedLoginTest.java: -------------------------------------------------------------------------------- 1 | package nicelee.test.junit; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.net.HttpCookie; 6 | import java.util.List; 7 | 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import nicelee.acfun.INeedLogin; 13 | import nicelee.acfun.util.HttpCookies; 14 | 15 | public class INeedLoginTest { 16 | 17 | @Before 18 | public void setUp() throws Exception { 19 | } 20 | 21 | @After 22 | public void tearDown() throws Exception { 23 | } 24 | 25 | @Test 26 | // 测试Cookie保存和与String之间的转换 27 | public void testSaveCookie() { 28 | INeedLogin inl = new INeedLogin(); 29 | String origin = "[sid=123, bili_jct=ff44, SESSDATA=grgrerer, DedeUserID__ckMd5=ggg, DedeUserID=hhh]"; 30 | System.out.println(origin); 31 | List oringinSet = HttpCookies.convertCookies(origin); 32 | 33 | System.out.println(oringinSet.toString()); 34 | 35 | inl.saveCookies(oringinSet.toString()); 36 | String lastSet = (String) inl.readCookies(); 37 | System.out.println(lastSet.toString()); 38 | 39 | List str0 = HttpCookies.convertCookies(lastSet); 40 | System.out.println(str0); 41 | 42 | assertEquals(oringinSet.toString(), str0.toString()); 43 | } 44 | 45 | @Test 46 | public void testGetLoginStatus() { 47 | INeedLogin inl = new INeedLogin(); 48 | // 应该为有效cookie, 已经换掉了 49 | String cookies = "[sid=123, bili_jct=ff44, SESSDATA=grgrerer, DedeUserID__ckMd5=ggg, DedeUserID=hhh]"; 50 | boolean succ = inl.getLoginStatus(HttpCookies.convertCookies(cookies)); 51 | assertEquals(false, succ); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/nicelee/test/junit/RepoTest.java: -------------------------------------------------------------------------------- 1 | package nicelee.test.junit; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.FileWriter; 7 | import java.io.IOException; 8 | import java.util.HashSet; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | import org.junit.After; 13 | import org.junit.AfterClass; 14 | import org.junit.Before; 15 | import org.junit.BeforeClass; 16 | import org.junit.Test; 17 | 18 | import nicelee.acfun.util.RepoUtil; 19 | 20 | public class RepoTest { 21 | 22 | @BeforeClass 23 | public static void setUpBeforeClass() throws Exception { 24 | } 25 | 26 | @AfterClass 27 | public static void tearDownAfterClass() throws Exception { 28 | } 29 | 30 | @Before 31 | public void setUp() throws Exception { 32 | } 33 | 34 | @After 35 | public void tearDown() throws Exception { 36 | } 37 | 38 | @Test 39 | public void testCheck() { 40 | System.out.println("初始化开始"); 41 | RepoUtil.init(false); 42 | System.out.println("初始化完毕"); 43 | RepoUtil.appendAndSave("ab5024869_34168_328173-0-p1"); 44 | } 45 | 46 | //@Test 47 | public void testThreadRun() { 48 | System.out.println("初始化开始"); 49 | RepoUtil.init(false); 50 | System.out.println("初始化完毕"); 51 | for(int i = 0; i < 1000; i++) { 52 | final int cnt = i; 53 | new Thread(new Runnable() { 54 | @Override 55 | public void run() { 56 | RepoUtil.appendAndSave("av1234-32-p" + cnt); 57 | } 58 | }).start(); 59 | } 60 | } 61 | //@Test 62 | public void renameBatToRepo() { 63 | Pattern standardAvPattern = Pattern.compile("(av[0-9]+)-([0-9]+)-(p[0-9]+)"); 64 | File file = new File("download/rename.bat"); 65 | HashSet downRepo = new HashSet(); 66 | // 先初始化downRepo 67 | BufferedReader buReader = null; 68 | try { 69 | buReader = new BufferedReader(new FileReader(file)); 70 | String avRecord; 71 | while ((avRecord = buReader.readLine()) != null) { 72 | Matcher matcher = standardAvPattern.matcher(avRecord); 73 | if (matcher.find()) { 74 | System.out.println(avRecord.toString()); 75 | downRepo.add(matcher.group()); 76 | } 77 | } 78 | buReader.close(); 79 | } catch (IOException e) { 80 | e.printStackTrace(); 81 | } 82 | try { 83 | File fRepo = new File("config/repo.config"); 84 | FileWriter fw = new FileWriter(fRepo, true); 85 | for(Object avRecord: downRepo.toArray()) { 86 | System.out.println(avRecord.toString()); 87 | fw.write(avRecord.toString()); 88 | fw.write("\r\n"); 89 | } 90 | fw.close(); 91 | } catch (Exception e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/nicelee/test/junit/UtilTest.java: -------------------------------------------------------------------------------- 1 | package nicelee.test.junit; 2 | 3 | 4 | import static org.junit.Assert.assertEquals; 5 | 6 | import java.awt.Desktop; 7 | import java.io.BufferedReader; 8 | import java.io.File; 9 | import java.io.FileReader; 10 | import java.io.FileWriter; 11 | import java.io.IOException; 12 | import java.net.URI; 13 | import java.util.HashSet; 14 | 15 | import org.junit.After; 16 | import org.junit.AfterClass; 17 | import org.junit.Before; 18 | import org.junit.BeforeClass; 19 | import org.junit.Test; 20 | 21 | import nicelee.acfun.util.CmdUtil; 22 | import nicelee.ui.Global; 23 | 24 | public class UtilTest { 25 | 26 | @BeforeClass 27 | public static void setUpBeforeClass() throws Exception { 28 | } 29 | 30 | @AfterClass 31 | public static void tearDownAfterClass() throws Exception { 32 | } 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | } 37 | 38 | @After 39 | public void tearDown() throws Exception { 40 | } 41 | 42 | /** 43 | * 测试 删除已经生效过的临时cmd 命令文件 44 | */ 45 | //@Test 46 | public void testDeleteAllInactiveCmdTemp() { 47 | CmdUtil.deleteAllInactiveCmdTemp(); 48 | } 49 | 50 | /** 51 | * 测试 删除repo重复记录 52 | */ 53 | //@Test 54 | public void testDeleteRecords() { 55 | try { 56 | BufferedReader bReader = new BufferedReader(new FileReader("D:\\Workspace\\javaweb-springboot\\BilibiliDown\\release\\config\\repo.config")); 57 | String line = null; 58 | HashSet set = new HashSet<>(); 59 | while((line = bReader.readLine())!= null) { 60 | set.add(line); 61 | } 62 | bReader.close(); 63 | 64 | FileWriter fW = new FileWriter("D:\\Workspace\\javaweb-springboot\\BilibiliDown\\release\\config\\repo2.config"); 65 | for(Object str: set.toArray()) { 66 | fW.write(str.toString()); 67 | fW.write("\r\n"); 68 | } 69 | fW.close(); 70 | } catch (Exception e) { 71 | // TODO Auto-generated catch block 72 | e.printStackTrace(); 73 | } 74 | } 75 | 76 | /** 77 | * 测试 打开文件夹并选择文件 78 | */ 79 | //@Test 80 | public void testOpenFolder() { 81 | // 打开并选中 82 | try { 83 | String cmd[] = { "explorer", "/e,/select,", "D:\\Workspace\\javaweb-springboot\\BilibiliDown\\test test1 test.txt" }; 84 | //String cmd = "explorer /e,/select," +"D:\\Workspace\\javaweb-springboot\\BilibiliDown\\test test1 test.txt"; 85 | Runtime.getRuntime().exec(cmd); 86 | } catch (IOException e) { 87 | // TODO Auto-generated catch block 88 | e.printStackTrace(); 89 | } 90 | } 91 | 92 | /** 93 | * 测试 打开bat 94 | */ 95 | //@Test 96 | public void testOpenBat() { 97 | // 打开并选中 98 | try { 99 | Desktop desktop = Desktop.getDesktop(); 100 | desktop.open(new File("run.bat")); 101 | } catch (Exception e) { 102 | e.printStackTrace(); 103 | } 104 | } 105 | /** 106 | * 测试 打开URI 107 | */ 108 | //@Test 109 | public void testOpenURI() { 110 | // 打开并选中 111 | try { 112 | Desktop desktop = Desktop.getDesktop(); 113 | desktop.browse(new URI("https://www.baidu.com")); 114 | //desktop.open(new File("D:\\Workspace\\javaweb-springboot\\BilibiliDown\\test test1 test.txt")); 115 | } catch (Exception e) { 116 | e.printStackTrace(); 117 | } 118 | } 119 | 120 | /** 121 | * 测试 根据格式生成文件名 122 | */ 123 | //@Test 124 | public void testTitleUnderCondition() { 125 | Global.formatStr = "avTitle-pDisplay-clipTitle-qn-(:listName 我在前面-listName-我在后面-)ddd"; 126 | String formatedName = CmdUtil.genFormatedName("av12345", "p1", "pn2", 80, "av的标题", "片段的标题",null,null); 127 | System.out.println(formatedName); 128 | assertEquals("av的标题-pn2-片段的标题-80-ddd", formatedName); 129 | formatedName = CmdUtil.genFormatedName("av12345", "p1", "pn2", 80, "av的标题", "片段的标题","哈哈哈",null); 130 | System.out.println(formatedName); 131 | assertEquals("av的标题-pn2-片段的标题-80-我在前面-哈哈哈-我在后面-ddd", formatedName); 132 | } 133 | /** 134 | * 测试 根据格式生成文件名 135 | */ 136 | //@Test 137 | public void testTitle() { 138 | Global.formatStr = "avTitle-pDisplay-clipTitle-qn"; 139 | String formatedName = CmdUtil.genFormatedName("av12345", "p1", "pn2", 80, "av的标题", "片段的标题",null,null); 140 | System.out.println(formatedName); 141 | assertEquals("av的标题-pn2-片段的标题-80", formatedName); 142 | 143 | Global.formatStr = "开头avTitle-pDisplay-clipTitle-qn-pAvkkk666"; 144 | formatedName = CmdUtil.genFormatedName("av12345", "p1", "pn2", 80, "av的标题", "片段的标题",null,null); 145 | System.out.println(formatedName); 146 | assertEquals("开头av的标题-pn2-片段的标题-80-p1kkk666", formatedName); 147 | } 148 | 149 | /** 150 | * 测试 根据文件名 151 | */ 152 | //@Test 153 | public void testRename() { 154 | String str = "test$]WHAT "; 155 | str = str.replaceAll("[\\\\|\\/|:\\*\\?|<|>|\\||\\\"$]", "."); 156 | System.out.println(str); 157 | str = "av21449435-16-p1.flv".replaceFirst("av[0-9]+-[0-9]+-p[0-9]+", str); 158 | } 159 | 160 | /** 161 | * 测试 ffmpeg环境是否可用 162 | */ 163 | @Test 164 | public void testFFmpeg() { 165 | String[] cmd = {"ffmpeg", "-version"}; 166 | CmdUtil.run(cmd); 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /src/nicelee/test/junit/VersionManagerTest.java: -------------------------------------------------------------------------------- 1 | package nicelee.test.junit; 2 | 3 | 4 | 5 | import java.lang.management.ManagementFactory; 6 | 7 | import org.junit.After; 8 | import org.junit.AfterClass; 9 | import org.junit.Before; 10 | import org.junit.BeforeClass; 11 | import org.junit.Test; 12 | 13 | import nicelee.acfun.util.CmdUtil; 14 | import nicelee.acfun.util.Logger; 15 | import nicelee.acfun.util.VersionManagerUtil; 16 | 17 | public class VersionManagerTest { 18 | 19 | @BeforeClass 20 | public static void setUpBeforeClass() throws Exception { 21 | } 22 | 23 | @AfterClass 24 | public static void tearDownAfterClass() throws Exception { 25 | } 26 | 27 | @Before 28 | public void setUp() throws Exception { 29 | } 30 | 31 | @After 32 | public void tearDown() throws Exception { 33 | } 34 | 35 | 36 | /** 37 | * 测试 获取最新版本的tag 38 | */ 39 | //@Test 40 | public void testQueryLatestVersion() { 41 | // 打开并选中 42 | try { 43 | VersionManagerUtil.queryLatestVersion(); 44 | System.out.println(VersionManagerUtil.downName); 45 | System.out.println(VersionManagerUtil.downUrl); 46 | System.out.println(VersionManagerUtil.versionName); 47 | System.out.println(VersionManagerUtil.versionTag); 48 | } catch (Exception e) { 49 | e.printStackTrace(); 50 | } 51 | } 52 | /** 53 | * 测试 获取程序pid 54 | */ 55 | @Test 56 | public void testGetPID() { 57 | try { 58 | Logger.println(ManagementFactory.getRuntimeMXBean().getName()); 59 | } catch (Exception e) { 60 | e.printStackTrace(); 61 | } 62 | } 63 | /** 64 | * 测试 下载最新版本 65 | */ 66 | //@Test 67 | public void testDownloadLatestVersion() { 68 | // 打开并选中 69 | try { 70 | VersionManagerUtil.downloadLatestVersion(); 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } 74 | } 75 | /** 76 | * 测试 打开bat 77 | */ 78 | //@Test 79 | public void testOpenBat() { 80 | // 打开并选中 81 | try { 82 | // Desktop desktop = Desktop.getDesktop(); 83 | // desktop.open(new File("update.bat")); 84 | String cmd[] ={"cmd", "/c","start", "update.bat", "0"}; 85 | CmdUtil.run(cmd); 86 | } catch (Exception e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/nicelee/ui/FrameAbout.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui; 2 | 3 | import java.awt.Desktop; 4 | import java.awt.Dimension; 5 | import java.awt.FlowLayout; 6 | import java.io.File; 7 | import java.net.URL; 8 | 9 | import javax.swing.JEditorPane; 10 | import javax.swing.JFrame; 11 | import javax.swing.JPanel; 12 | import javax.swing.JScrollPane; 13 | import javax.swing.event.HyperlinkEvent; 14 | import javax.swing.event.HyperlinkListener; 15 | 16 | import nicelee.acfun.util.Logger; 17 | import nicelee.ui.item.MJTitleBar; 18 | import nicelee.ui.item.impl.TextTransferHandler; 19 | 20 | public class FrameAbout extends JFrame implements HyperlinkListener { 21 | 22 | private static final long serialVersionUID = -5017130575041108799L; 23 | private static FrameAbout frame; 24 | 25 | private FrameAbout() { 26 | } 27 | 28 | private void initUI() { 29 | // this.setBounds(300, 200, 500, 400); 30 | // this.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); 31 | this.setSize(800, 480); 32 | this.setResizable(false); 33 | this.setLocationRelativeTo(null); 34 | this.setTitle("用爱发电 bilibili~~"); 35 | //this.getRootPane().setWindowDecorationStyle(JRootPane.PLAIN_DIALOG); 36 | 37 | // 作为内容容器 38 | JPanel pane = new JPanel(); 39 | pane.setLayout(new FlowLayout()); 40 | // 添加标题栏 41 | MJTitleBar titleBar = new MJTitleBar(this, true); 42 | pane.add(titleBar); 43 | 44 | // 添加内容 45 | JEditorPane editorPane = new JEditorPane(); 46 | editorPane.setEditable(false); 47 | editorPane.setTransferHandler(new TextTransferHandler()); 48 | try { 49 | editorPane.setPage(this.getClass().getResource("/resources/about.html")); 50 | } catch (Exception ex) { 51 | ex.printStackTrace(); 52 | } 53 | editorPane.addHyperlinkListener(this); 54 | JScrollPane scrollPane = new JScrollPane(editorPane, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, 55 | JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); 56 | scrollPane.setPreferredSize(new Dimension(800, 460)); 57 | pane.add(scrollPane); 58 | 59 | this.setContentPane(pane); 60 | } 61 | 62 | public static void showAbout() { 63 | if (frame == null) { 64 | frame = new FrameAbout(); 65 | frame.initUI(); 66 | } 67 | frame.setVisible(true); 68 | } 69 | 70 | @Override 71 | public void hyperlinkUpdate(HyperlinkEvent e) { 72 | if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { 73 | URL url = e.getURL(); 74 | Logger.println(url.toString()); 75 | //Logger.println(url.getProtocol()); 76 | try { 77 | Desktop desktop = Desktop.getDesktop(); 78 | if (url.getProtocol().startsWith("http")) { 79 | desktop.browse(url.toURI()); 80 | }else { 81 | File file = new File(url.toString().substring(7)); 82 | Logger.println(file.getAbsolutePath()); 83 | desktop.open(file); 84 | } 85 | } catch (Exception e1) { 86 | e1.printStackTrace(); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/nicelee/ui/FrameMain.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Dimension; 5 | import java.awt.Font; 6 | import java.awt.event.WindowAdapter; 7 | import java.awt.event.WindowEvent; 8 | import java.net.URL; 9 | import java.util.Enumeration; 10 | 11 | import javax.swing.BorderFactory; 12 | import javax.swing.ImageIcon; 13 | import javax.swing.JFrame; 14 | import javax.swing.JOptionPane; 15 | import javax.swing.JPanel; 16 | import javax.swing.JTabbedPane; 17 | import javax.swing.UIManager; 18 | 19 | import nicelee.acfun.INeedLogin; 20 | import nicelee.acfun.PackageScanLoader; 21 | import nicelee.acfun.util.CmdUtil; 22 | import nicelee.acfun.util.ConfigUtil; 23 | import nicelee.acfun.util.RepoUtil; 24 | import nicelee.ui.item.MJTitleBar; 25 | import nicelee.ui.thread.LoginThread; 26 | import nicelee.ui.thread.MonitoringThread; 27 | 28 | public class FrameMain extends JFrame { 29 | 30 | /** 31 | * 32 | */ 33 | private static final long serialVersionUID = 1L; 34 | JTabbedPane jTabbedpane;// 存放选项卡的组件 35 | MJTitleBar titleBar;// 标题栏组件 36 | 37 | public static void main(String[] args) { 38 | // System.getProperties().setProperty("file.encoding", "utf-8"); 39 | ConfigUtil.initConfigs(); 40 | // 初始化主题 41 | initUITheme(); 42 | 43 | // 初始化UI 44 | FrameMain main = new FrameMain(); 45 | main.InitUI(); 46 | 47 | // 初始化监控线程,用于刷新下载面板 48 | MonitoringThread th = new MonitoringThread(); 49 | th.start(); 50 | 51 | // 初始化 - 登录 52 | INeedLogin inl = new INeedLogin(); 53 | if (inl.readCookies() != null) { 54 | Global.needToLogin = true; 55 | } 56 | LoginThread loginTh = new LoginThread(); 57 | loginTh.start(); 58 | 59 | // 初始化 - ffmpeg环境判断 60 | String[] cmd = {"ffmpeg", "-version"}; 61 | if(!CmdUtil.run(cmd)) { 62 | JOptionPane.showMessageDialog(null, "当前没有ffmpeg环境,大部分mp4及小部分flv文件将无法转码或合并", "请注意!!", JOptionPane.WARNING_MESSAGE); 63 | } 64 | 65 | // 66 | if (Global.saveToRepo) { 67 | RepoUtil.init(false); 68 | } 69 | // 预扫描加载类 70 | PackageScanLoader.validParserClasses.isEmpty(); 71 | // FrameQRCode qr = new FrameQRCode("https://www.bilibili.com/"); 72 | // qr.initUI(); 73 | // qr.dispose(); 74 | } 75 | 76 | /** 77 | * 78 | */ 79 | static void initUITheme() { 80 | try { 81 | if (!Global.themeDefault) { 82 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 83 | Font font = new Font("Dialog", Font.PLAIN, 12); 84 | Enumeration keys = UIManager.getDefaults().keys(); 85 | while (keys.hasMoreElements()) { 86 | Object key = keys.nextElement(); 87 | Object value = UIManager.get(key); 88 | if (value instanceof javax.swing.plaf.FontUIResource) { 89 | UIManager.put(key, font); 90 | } 91 | } 92 | } 93 | } catch (Exception e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | 98 | /** 99 | * 100 | */ 101 | public void InitUI() { 102 | 103 | this.setTitle("AcFun Down~~" + Global.version); 104 | this.setSize(1200, 745); 105 | this.setResizable(false); 106 | this.setLocationRelativeTo(null); 107 | this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 108 | URL iconURL = this.getClass().getResource("/resources/favicon.png"); 109 | ImageIcon icon = new ImageIcon(iconURL); 110 | this.setIconImage(icon.getImage()); 111 | 112 | // pane 作为内容容器 113 | JPanel pane = new JPanel(); 114 | pane.setBackground(Color.WHITE); 115 | pane.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1, Color.GRAY)); 116 | // 添加标题栏 117 | titleBar = new MJTitleBar(this, true, true); 118 | pane.add(titleBar); 119 | 120 | jTabbedpane = new JTabbedPane(); 121 | jTabbedpane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); 122 | jTabbedpane.setPreferredSize(new Dimension(1194, 706)); 123 | // Index Tab 124 | Global.index = new TabIndex(jTabbedpane); 125 | jTabbedpane.addTab("主页", Global.index); 126 | // 下载页 127 | Global.downloadTab = new TabDownload(); 128 | jTabbedpane.addTab("下载页", Global.downloadTab); 129 | // 作品页 130 | // JLabel label = new JLabel("作品页"); 131 | // TabVideo tab = new TabVideo(label); 132 | // jTabbedpane.addTab("作品页", tab); 133 | // jTabbedpane.setTabComponentAt(jTabbedpane.indexOfComponent(tab), label); 134 | 135 | pane.add(jTabbedpane); 136 | this.setContentPane(pane); 137 | // 关闭窗口时 138 | this.addWindowListener(new WindowAdapter() { 139 | public void windowClosing(WindowEvent e) { 140 | super.windowClosing(e); 141 | CmdUtil.deleteAllInactiveCmdTemp(); 142 | } 143 | }); 144 | this.setVisible(true); 145 | } 146 | 147 | @Override 148 | public void setTitle(String title) { 149 | super.setTitle(title); 150 | if(titleBar != null) { 151 | titleBar.setTitle(title); 152 | } 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /src/nicelee/ui/FrameQRCode.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Image; 5 | import java.awt.event.WindowAdapter; 6 | import java.awt.event.WindowEvent; 7 | 8 | import javax.swing.ImageIcon; 9 | import javax.swing.JFrame; 10 | import javax.swing.JLabel; 11 | 12 | import nicelee.acfun.util.QrCodeUtil; 13 | import nicelee.ui.item.MJTitleBar; 14 | 15 | public class FrameQRCode extends JFrame{ 16 | 17 | /** 18 | * 19 | */ 20 | private static final long serialVersionUID = 1659460151786653307L; 21 | String QRcodeStr; 22 | public FrameQRCode(String qrcode ) { 23 | QRcodeStr = qrcode; 24 | } 25 | 26 | public void initUI() { 27 | System.out.println("正在生成二维码图片Frame"); 28 | this.setTitle("请扫描二维码..."); 29 | this.setSize(450, 500); 30 | //this.getRootPane().setWindowDecorationStyle(JRootPane.PLAIN_DIALOG); 31 | MJTitleBar titleBar = new MJTitleBar(this); 32 | this.setGlassPane(titleBar); 33 | titleBar.setVisible(true); 34 | // this.setResizable(false); 35 | this.setLocationRelativeTo(null); 36 | this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 37 | this.setBackground(Color.WHITE); 38 | this.getContentPane().setBackground(Color.WHITE); 39 | //网址链接 ==> 二维码图片 40 | try { 41 | ImageIcon imgIcon = new ImageIcon(QrCodeUtil.createQrCode(QRcodeStr, 900)); 42 | imgIcon = new ImageIcon(imgIcon.getImage().getScaledInstance(350, 350, Image.SCALE_SMOOTH)); 43 | JLabel jLabel = new JLabel(imgIcon); 44 | this.add(jLabel); 45 | this.setVisible(true); 46 | System.out.println("二维码图片已生成"); 47 | } catch (Exception e) { 48 | e.printStackTrace(); 49 | this.dispose(); 50 | } 51 | 52 | //关闭后将后台线程同时关闭 53 | this.addWindowListener(new WindowAdapter() { 54 | @Override 55 | public void windowClosing(WindowEvent e) { 56 | Global.needToLogin = false; 57 | super.windowClosing(e); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/nicelee/ui/Global.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui; 2 | 3 | import java.util.concurrent.ConcurrentHashMap; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | 7 | import javax.swing.ImageIcon; 8 | 9 | import nicelee.acfun.downloaders.IDownloader; 10 | import nicelee.ui.item.DownloadInfoPanel; 11 | 12 | public class Global { 13 | // 界面显示相关 14 | public static String version = "v1.3"; 15 | public static boolean themeDefault = true; 16 | 17 | public static boolean isAlertIfDownloded = true; 18 | public static int maxAlertPrompt = 5; 19 | public static ImageIcon backgroundImg; 20 | // 下载相关 21 | public final static int MP4 = 0; 22 | public final static int FLV = 1; 23 | 24 | public static int maxFailRetry = 3; // 下载异常后重试次数 25 | public static int downloadFormat = MP4; //优先下载格式,如不存在该类型的源,那么将默认转为下载另一种格式 26 | public static String savePath = "./download/"; // 下载文件保存路径 27 | public static ExecutorService downLoadThreadPool;// 下载线程池 28 | public static ExecutorService queryThreadPool = Executors.newFixedThreadPool(1);// 查询线程池(同一时间并发不能太多, 为了保证任务面板的顺序,采用fixed(1)) 29 | public static TabDownload downloadTab; // 下载显示界面 30 | public static ConcurrentHashMap downloadTaskList = new ConcurrentHashMap(); 31 | 32 | public static boolean useRepo = true; //从仓库判断是否需要下载 33 | public static boolean saveToRepo = true; //使用仓库保存下载成功的记录 34 | public static boolean noQualityRequest = true; //不尝试获取视频的清晰度,直接提供所有选项 35 | public static boolean debugFFmpeg = true; //输出ffmpeg信息 36 | 37 | public static String formatStr = "avTitle-pDisplay-clipTitle-qn"; 38 | public static boolean doRenameAfterComplete = true; 39 | /* 存在某一清晰度后, 在下载另一种清晰度时是否判断已完成*/ 40 | public static boolean repoInDefinitionStrictMode = false; // 41 | //public static int totalTask = 0, activeTask = 0, pauseTask = 0, doneTask = 0, queuingTask = 0;//用于下载任务统计 42 | // 登录相关 43 | public static boolean needToLogin = false; 44 | public static boolean isLogin = false; 45 | public static FrameQRCode qr; // 二维码图片显示界面 46 | public static TabIndex index; // 主页界面 47 | 48 | // 信息查询相关 49 | public static int pageSize = 5; // 当有分页时,每页显示个数 50 | public static String pageDisplay; // 当有分页时,每页显示个数 51 | 52 | // 临时文件相关 53 | public static boolean restrictTempMode = true; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/nicelee/ui/TabDownload.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui; 2 | 3 | import java.awt.Color; 4 | import java.awt.Component; 5 | import java.awt.Dimension; 6 | import java.awt.Graphics; 7 | import java.awt.Image; 8 | import java.awt.event.ActionEvent; 9 | import java.awt.event.ActionListener; 10 | 11 | import javax.swing.BorderFactory; 12 | import javax.swing.ImageIcon; 13 | import javax.swing.JButton; 14 | import javax.swing.JLabel; 15 | import javax.swing.JPanel; 16 | import javax.swing.JScrollPane; 17 | 18 | import nicelee.ui.item.DownloadInfoPanel; 19 | 20 | public class TabDownload extends JPanel implements ActionListener { 21 | 22 | /** 23 | * 24 | */ 25 | private static final long serialVersionUID = 8714599826187286737L; 26 | private static boolean stopAll = false; 27 | ImageIcon backgroundIcon = Global.backgroundImg; 28 | 29 | JPanel jpContent; 30 | JScrollPane jpScorll; 31 | JLabel lbStatus; 32 | JButton btnContinue, btnStop, btnDeleteAll, btnDeleteDown; 33 | public TabDownload() { 34 | initUI(); 35 | } 36 | 37 | public int activeTask; 38 | public void refreshStatus(int totalTask,int activeTask,int pauseTask,int doneTask,int queuingTask) { 39 | this.activeTask = activeTask; 40 | String txt = String.format(" 总计: %d / 下载中 : %d / 暂停 : %d / 下载完 : %d / 队列中 : %d", 41 | totalTask, activeTask, pauseTask, doneTask, queuingTask); 42 | if (lbStatus != null) { 43 | lbStatus.setText(txt); 44 | } 45 | } 46 | 47 | public void initUI() { 48 | // //占位 49 | // JLabel lbBlank1 = new JLabel(); 50 | // lbBlank1.setPreferredSize(new Dimension(300, 30)); 51 | // this.add(lbBlank1); 52 | 53 | // 状态 totalTask, activeTask, pauseTask, doneTask, queuingTask 54 | lbStatus = new JLabel(); 55 | lbStatus.setPreferredSize(new Dimension(350, 30)); 56 | lbStatus.setOpaque(true); 57 | lbStatus.setBackground(new Color(204, 255, 255)); 58 | lbStatus.setBorder(BorderFactory.createLineBorder(Color.BLUE)); 59 | this.add(lbStatus); 60 | 61 | // 功能按钮 62 | btnContinue = new JButton("全部继续"); 63 | btnStop = new JButton("全部暂停"); 64 | btnDeleteAll = new JButton("全部删除"); 65 | btnDeleteDown = new JButton("删除已完成"); 66 | btnContinue.addActionListener(this); 67 | btnStop.addActionListener(this); 68 | btnDeleteAll.addActionListener(this); 69 | btnDeleteDown.addActionListener(this); 70 | this.add(btnContinue); 71 | this.add(btnStop); 72 | this.add(btnDeleteAll); 73 | this.add(btnDeleteDown); 74 | 75 | // 下载任务Panel 76 | jpContent = new JPanel(); 77 | jpContent.setPreferredSize(new Dimension(1100, 300)); 78 | jpContent.setOpaque(false); 79 | 80 | // DownloadInfoPanel downPan = new DownloadInfoPanel(); 81 | // jpContent.add(downPan); 82 | jpScorll = new JScrollPane(jpContent); 83 | // 分别设置水平和垂直滚动条出现方式 84 | jpScorll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); 85 | jpScorll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); 86 | // jpScorll.setBorder(BorderFactory.createLineBorder(Color.red)); 87 | jpScorll.setPreferredSize(new Dimension(1150, 620)); 88 | jpScorll.setOpaque(false); 89 | jpScorll.getViewport().setOpaque(false); 90 | this.add(jpScorll); 91 | } 92 | 93 | @Override 94 | public void paintComponent(Graphics g) { 95 | // // super.paintComponent(g); 96 | Image img = backgroundIcon.getImage(); 97 | int width = img.getWidth(this.getParent()); 98 | int height = img.getHeight(this.getParent()); 99 | int xGap = 5; 100 | int xCnt = this.getSize().width / (width + xGap) + 1; 101 | int yGap = 5; 102 | int yCnt = this.getSize().height / (height + yGap) + 1; 103 | if( xCnt >= 3) { 104 | for(int x = 0; x <= xCnt; x++) { 105 | int xp = xGap + (width + xGap) * x; 106 | for(int y = 0; y < yCnt; y++) { 107 | int yp = yGap + (height + yGap) * y; 108 | g.drawImage(backgroundIcon.getImage(), xp, yp, width, height, this.getParent()); 109 | } 110 | } 111 | }else { 112 | g.drawImage(backgroundIcon.getImage(), 0, 0, this.getSize().width, this.getSize().height, this.getParent()); 113 | } 114 | this.setOpaque(false); 115 | } 116 | 117 | @Override 118 | public void actionPerformed(ActionEvent e) { 119 | if (e.getSource() == btnContinue) { 120 | stopAll = false; 121 | for(int i = 0; i < jpContent.getComponentCount(); i++) { 122 | Component comp = jpContent.getComponent(i); 123 | if(comp instanceof DownloadInfoPanel ) { 124 | ((DownloadInfoPanel)comp).setFailCnt(0); 125 | ((DownloadInfoPanel)comp).continueTask(); 126 | } 127 | } 128 | } else if (e.getSource() == btnStop) { 129 | // 约3s后置false 130 | stopAll = true; 131 | btnContinue.setEnabled(false); 132 | btnStop.setEnabled(false); 133 | btnDeleteAll.setEnabled(false); 134 | for(DownloadInfoPanel dp : Global.downloadTaskList.keySet()) { 135 | dp.stopTask(); 136 | } 137 | // 停止进程需要时间, 期间最好不进行其他操作 138 | new Thread(new Runnable() { 139 | @Override 140 | public void run() { 141 | try { 142 | Thread.sleep(3000); 143 | } catch (InterruptedException e) { 144 | } 145 | //双保险 146 | for(DownloadInfoPanel dp : Global.downloadTaskList.keySet()) { 147 | dp.stopTask(); 148 | } 149 | btnContinue.setEnabled(true); 150 | btnStop.setEnabled(true); 151 | btnDeleteAll.setEnabled(true); 152 | stopAll = false; 153 | } 154 | }).start(); 155 | } else if (e.getSource() == btnDeleteAll) { 156 | for(DownloadInfoPanel dp : Global.downloadTaskList.keySet()) { 157 | dp.removeTask(true); 158 | } 159 | } else if (e.getSource() == btnDeleteDown) { 160 | for(DownloadInfoPanel dp : Global.downloadTaskList.keySet()) { 161 | dp.removeTask(false); 162 | } 163 | } 164 | } 165 | 166 | public JPanel getJpContent() { 167 | return jpContent; 168 | } 169 | 170 | public void setJpContent(JPanel jpContent) { 171 | this.jpContent = jpContent; 172 | } 173 | 174 | public static boolean isStopAll() { 175 | return stopAll; 176 | } 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/nicelee/ui/item/ClipInfoPanel.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item; 2 | 3 | import java.awt.Color; 4 | import java.awt.Dimension; 5 | import java.awt.Image; 6 | import java.awt.Toolkit; 7 | import java.awt.datatransfer.Clipboard; 8 | import java.awt.datatransfer.StringSelection; 9 | import java.awt.datatransfer.Transferable; 10 | import java.awt.event.ActionEvent; 11 | import java.awt.event.ActionListener; 12 | import java.awt.event.MouseEvent; 13 | import java.awt.event.MouseListener; 14 | import java.net.URL; 15 | 16 | import javax.swing.BorderFactory; 17 | import javax.swing.ImageIcon; 18 | import javax.swing.JButton; 19 | import javax.swing.JLabel; 20 | import javax.swing.JPanel; 21 | 22 | import nicelee.acfun.enums.VideoQualityEnum; 23 | import nicelee.acfun.model.ClipInfo; 24 | import nicelee.acfun.util.Logger; 25 | import nicelee.ui.Global; 26 | import nicelee.ui.TabVideo; 27 | import nicelee.ui.thread.DownloadRunnable; 28 | 29 | public class ClipInfoPanel extends JPanel implements MouseListener { 30 | 31 | /** 32 | * 33 | */ 34 | private static final long serialVersionUID = -752743062676819403L; 35 | String avTitle; 36 | ClipInfo clip; 37 | 38 | private JLabel labelTitle; 39 | private long lastMousePressed; 40 | public ClipInfoPanel(ClipInfo clip) { 41 | this.clip = clip; 42 | this.avTitle = clip.getAvTitle(); 43 | initUI(); 44 | } 45 | 46 | void initUI() { 47 | this.setBorder(BorderFactory.createLineBorder(Color.red)); 48 | this.setPreferredSize(new Dimension(340, 110)); 49 | // 分情况显示 50 | if(clip.getListName() != null) { 51 | labelTitle = new JLabel(clip.getRemark() + " - " + clip.getAvTitle() +clip.getTitle(), JLabel.CENTER); 52 | }else { 53 | labelTitle = new JLabel(clip.getRemark() + " - " + clip.getTitle(), JLabel.CENTER); 54 | } 55 | labelTitle.addMouseListener(this); 56 | //labelTitle.setBorder(BorderFactory.createLineBorder(Color.red)); 57 | //labelTitle.setToolTipText("双击复制title文本 + avId,长按查看更换预览图片"); 58 | labelTitle.setToolTipText(clip.getAvTitle() + clip.getTitle()); 59 | labelTitle.setPreferredSize(new Dimension(280, 30)); 60 | this.setOpaque(false); 61 | this.add(labelTitle); 62 | 63 | // JButton btnDanmuku = new JButton("弹幕"); 64 | // btnDanmuku.addActionListener(new ActionListener() { 65 | // @Override 66 | // public void actionPerformed(ActionEvent e) { 67 | // // 68 | // String url = "https://api.bilibili.com/x/v1/dm/list.so?oid="; 69 | // Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 70 | // Transferable trans = new StringSelection(url + clip.getcId()); 71 | // clipboard.setContents(trans, null); 72 | // } 73 | // }); 74 | // this.add(btnDanmuku); 75 | for (final int qn : clip.getLinks().keySet()) { 76 | // JButton btn = new JButton("清晰度: " + qn); 77 | String qnName = VideoQualityEnum.getQualityDescript(qn); 78 | JButton btn = null; 79 | if (qnName != null) { 80 | btn = new JButton(qnName); 81 | } else { 82 | btn = new JButton("清晰度: " + qn); 83 | } 84 | btn.addActionListener(new ActionListener() { 85 | 86 | @Override 87 | public void actionPerformed(ActionEvent e) { 88 | DownloadRunnable downThread = new DownloadRunnable(clip, qn); 89 | Logger.println("清晰度: " + qn); 90 | // new Thread(downThread).start(); 91 | Global.queryThreadPool.execute(downThread); 92 | } 93 | }); 94 | this.add(btn); 95 | } 96 | } 97 | 98 | @Override 99 | public void mouseClicked(MouseEvent e) { 100 | String txtToCopy = null; 101 | if (e.getClickCount() == 1) { 102 | //txtToCopy = clip.getAvTitle() + clip.getTitle(); 103 | } else { 104 | txtToCopy = clip.getAvTitle() + clip.getTitle() + " " +clip.getAvId(); 105 | } 106 | // 获取系统剪贴板 107 | Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); 108 | // 封装文本内容 109 | Transferable trans = new StringSelection(txtToCopy); 110 | // 把文本内容设置到系统剪贴板 111 | clipboard.setContents(trans, null); 112 | } 113 | 114 | @Override 115 | public void mousePressed(MouseEvent e) { 116 | lastMousePressed = System.currentTimeMillis(); 117 | labelTitle.setBorder(BorderFactory.createLineBorder(Color.red)); 118 | } 119 | 120 | @Override 121 | public void mouseReleased(MouseEvent e) { 122 | labelTitle.setBorder(null); 123 | long timeTouched = System.currentTimeMillis() - lastMousePressed; 124 | Logger.println("长按了" + timeTouched +"ms"); 125 | if(timeTouched >= 500) { 126 | try { 127 | //获取父对象 128 | TabVideo tVideo = (TabVideo)this.getParent().getParent().getParent().getParent(); 129 | //设置更换预览图片 130 | String toDisplay = clip.getPicPreview(); 131 | if(toDisplay != null && !toDisplay.equals(tVideo.getCurrentDisplayPic())) { 132 | URL fileURL = new URL(toDisplay); 133 | ImageIcon imag1 = new ImageIcon(fileURL); 134 | imag1 = new ImageIcon(imag1.getImage().getScaledInstance(700, 460, Image.SCALE_DEFAULT) ); 135 | tVideo.getLbAvPrivew().setText(""); 136 | tVideo.getLbAvPrivew().setIcon(imag1); 137 | tVideo.getLbAvPrivew().setToolTipText("单击获取图片链接" + fileURL); 138 | tVideo.setCurrentDisplayPic(toDisplay); 139 | } 140 | } catch (Exception e1) { 141 | e1.printStackTrace(); 142 | } 143 | 144 | } 145 | } 146 | 147 | @Override 148 | public void mouseEntered(MouseEvent e) { 149 | 150 | } 151 | 152 | @Override 153 | public void mouseExited(MouseEvent e) { 154 | 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/nicelee/ui/item/JOptionPaneManager.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.swing.JOptionPane; 7 | 8 | import nicelee.ui.Global; 9 | 10 | public class JOptionPaneManager { 11 | 12 | private static List promptThreads = new ArrayList(); 13 | 14 | public static void showMsg(String title, String msg) { 15 | if(Global.isAlertIfDownloded && promptThreads.size() < Global.maxAlertPrompt) { 16 | promptThreads.add(Thread.currentThread()); 17 | 18 | Object[] options = { "关闭", "关闭所有" }; 19 | int m = JOptionPane.showOptionDialog(null, msg, title, JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, 20 | null, options, options[0]); 21 | synchronized (promptThreads) { 22 | if (m == 1) { 23 | interruptAllThread(); 24 | } else { 25 | promptThreads.remove(Thread.currentThread()); 26 | } 27 | } 28 | } 29 | } 30 | 31 | public static void showMsgWithNewThread(String title, String msg) { 32 | if(Global.isAlertIfDownloded && promptThreads.size() < Global.maxAlertPrompt) { 33 | Thread t = new Thread(new Runnable() { 34 | public void run() { 35 | Object[] options = { "关闭", "关闭所有" }; 36 | int m = JOptionPane.showOptionDialog(null, msg, title, JOptionPane.YES_NO_OPTION, 37 | JOptionPane.PLAIN_MESSAGE, null, options, options[0]); 38 | // System.out.println(m); 39 | 40 | synchronized (promptThreads) { 41 | if (m == 1) { 42 | interruptAllThread(); 43 | } else { 44 | promptThreads.remove(Thread.currentThread()); 45 | } 46 | } 47 | } 48 | }); 49 | promptThreads.add(t); 50 | t.start(); 51 | } 52 | } 53 | 54 | private static void interruptAllThread() { 55 | // 不管怎样,先移除当前线程 56 | promptThreads.remove(Thread.currentThread()); 57 | for (Thread t : promptThreads) { 58 | if (t.isAlive()) { 59 | t.interrupt(); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/nicelee/ui/item/MJMenuBar.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item; 2 | 3 | import java.awt.FlowLayout; 4 | import java.awt.event.ActionEvent; 5 | import java.awt.event.ActionListener; 6 | import java.util.Enumeration; 7 | 8 | import javax.swing.AbstractButton; 9 | import javax.swing.ButtonGroup; 10 | import javax.swing.JFrame; 11 | import javax.swing.JMenu; 12 | import javax.swing.JMenuBar; 13 | import javax.swing.JMenuItem; 14 | import javax.swing.JOptionPane; 15 | import javax.swing.JRadioButtonMenuItem; 16 | 17 | import nicelee.acfun.enums.VideoQualityEnum; 18 | import nicelee.acfun.util.ConfigUtil; 19 | import nicelee.acfun.util.Logger; 20 | import nicelee.acfun.util.RepoUtil; 21 | import nicelee.acfun.util.VersionManagerUtil; 22 | import nicelee.ui.FrameAbout; 23 | import nicelee.ui.Global; 24 | 25 | public class MJMenuBar extends JMenuBar { 26 | 27 | /** 28 | * 29 | */ 30 | private static final long serialVersionUID = -344077300590858072L; 31 | 32 | private JFrame frame; 33 | 34 | public MJMenuBar(JFrame frame) { 35 | super(); 36 | this.frame = frame; 37 | init(); 38 | } 39 | 40 | private void init() { 41 | this.setLayout(new FlowLayout(FlowLayout.CENTER, 5, 0)); 42 | /* 43 | * 创建一级菜单 44 | */ 45 | JMenu operMenu = new JMenu("操作"); 46 | JMenu configMenu = new JMenu("配置"); 47 | JMenu aboutMenu = new JMenu("关于"); 48 | this.add(operMenu); 49 | this.add(configMenu); 50 | this.add(aboutMenu); 51 | 52 | /** 53 | * 创建二级 操作 子菜单 54 | */ 55 | JMenuItem reloadConfig = new JMenuItem("重新加载配置"); 56 | JMenuItem reloadRepo = new JMenuItem("重新加载仓库"); 57 | JMenuItem closeAllMenuItem = new JMenuItem("关闭全部Tab页"); 58 | JMenuItem doMultiDownMenuItem = new JMenuItem("批量下载Tab页"); 59 | operMenu.add(reloadConfig); 60 | operMenu.add(reloadRepo); 61 | operMenu.addSeparator(); 62 | operMenu.add(closeAllMenuItem); 63 | operMenu.add(doMultiDownMenuItem); 64 | 65 | /** 66 | * 创建二级 配置 子菜单 67 | */ 68 | JMenu dTypeMenuItem = new JMenu("下载策略"); 69 | JMenu dQNMenuItem = new JMenu("优先清晰度"); 70 | configMenu.add(dTypeMenuItem); 71 | configMenu.add(dQNMenuItem); 72 | 73 | /** 74 | * 创建二级 关于 子菜单 75 | */ 76 | JMenuItem infoMenuItem = new JMenuItem("作品信息"); 77 | JMenuItem updateMenuItem = new JMenuItem("检查更新"); 78 | aboutMenu.add(infoMenuItem); 79 | aboutMenu.add(updateMenuItem); 80 | 81 | /** 82 | * 创建三级 配置-下载策略 83 | */ 84 | final JRadioButtonMenuItem dType01 = new JRadioButtonMenuItem("仅第一"); 85 | final JRadioButtonMenuItem dType02 = new JRadioButtonMenuItem("全部"); 86 | ButtonGroup btnTypeGroup = new ButtonGroup(); 87 | btnTypeGroup.add(dType01); 88 | btnTypeGroup.add(dType02); 89 | dTypeMenuItem.add(dType01); 90 | dTypeMenuItem.add(dType02); 91 | dType01.setSelected(true); 92 | 93 | /** 94 | * 创建三级 配置-优先清晰度 95 | */ 96 | ButtonGroup btnQnGroup = new ButtonGroup(); 97 | for (VideoQualityEnum item : VideoQualityEnum.values()) { 98 | final JRadioButtonMenuItem dQN = new JRadioButtonMenuItem(item.getQuality()); 99 | dQNMenuItem.add(dQN); 100 | btnQnGroup.add(dQN); 101 | if (item.getQn() == 80 || item.getQn() == 3) { 102 | dQN.setSelected(true); 103 | } 104 | } 105 | 106 | /** 107 | * 添加动作 108 | */ 109 | // 修改app.config后,重新加载配置使生效 110 | reloadConfig.addActionListener(new ActionListener() { 111 | @Override 112 | public void actionPerformed(ActionEvent e) { 113 | ConfigUtil.initConfigs(); 114 | } 115 | }); 116 | 117 | // 修改repo.config后,重新加载仓库使生效 118 | reloadRepo.addActionListener(new ActionListener() { 119 | @Override 120 | public void actionPerformed(ActionEvent e) { 121 | RepoUtil.init(true); 122 | } 123 | }); 124 | 125 | // 关闭Tab页 126 | closeAllMenuItem.addActionListener(new ActionListener() { 127 | @Override 128 | public void actionPerformed(ActionEvent e) { 129 | Global.index.closeAllVideoTabs(); 130 | } 131 | }); 132 | 133 | // 批量下载 134 | doMultiDownMenuItem.addActionListener(new ActionListener() { 135 | @Override 136 | public void actionPerformed(ActionEvent e) { 137 | boolean downAll = dType02.isSelected(); 138 | Enumeration btns = btnQnGroup.getElements(); 139 | while (btns.hasMoreElements()) { 140 | JRadioButtonMenuItem item = (JRadioButtonMenuItem) btns.nextElement(); 141 | 142 | Logger.println(item.isSelected()); 143 | if(item.isSelected()) { 144 | Logger.println(item.getText()); 145 | int qn = VideoQualityEnum.getQN(item.getText()); 146 | Global.index.downVideoTabs(downAll, qn); 147 | break; 148 | } 149 | } 150 | } 151 | }); 152 | 153 | // 作品信息 154 | infoMenuItem.addActionListener(new ActionListener() { 155 | @Override 156 | public void actionPerformed(ActionEvent e) { 157 | FrameAbout.showAbout(); 158 | } 159 | }); 160 | // 更新版本 161 | updateMenuItem.addActionListener(new ActionListener() { 162 | @Override 163 | public void actionPerformed(ActionEvent e) { 164 | new Thread(new Runnable() { 165 | @Override 166 | public void run() { 167 | frame.setTitle(frame.getTitle() + " 版本更新中"); 168 | try { 169 | if (VersionManagerUtil.queryLatestVersion()) { 170 | JOptionPane.showMessageDialog(null, "当前版本为 " + Global.version + " ,已是最新", "成功", 171 | JOptionPane.PLAIN_MESSAGE); 172 | } else { 173 | VersionManagerUtil.downloadLatestVersion(); 174 | 175 | } 176 | } catch (Exception e1) { 177 | JOptionPane.showMessageDialog(null, "出现了异常,异常原因为:" + e1.toString(), "异常", 178 | JOptionPane.PLAIN_MESSAGE); 179 | } 180 | frame.setTitle(frame.getTitle().replace(" 版本更新中", "")); 181 | } 182 | }, "更新线程").start(); 183 | } 184 | }); 185 | 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/nicelee/ui/item/MJTextField.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item; 2 | 3 | import java.awt.Color; 4 | import java.awt.datatransfer.Clipboard; 5 | import java.awt.datatransfer.DataFlavor; 6 | import java.awt.datatransfer.Transferable; 7 | import java.awt.event.ActionEvent; 8 | import java.awt.event.ActionListener; 9 | import java.awt.event.FocusEvent; 10 | import java.awt.event.FocusListener; 11 | import java.awt.event.InputEvent; 12 | import java.awt.event.KeyEvent; 13 | import java.awt.event.KeyListener; 14 | import java.awt.event.MouseEvent; 15 | import java.awt.event.MouseListener; 16 | 17 | import javax.swing.JMenuItem; 18 | import javax.swing.JPopupMenu; 19 | import javax.swing.JTextField; 20 | import javax.swing.KeyStroke; 21 | 22 | import nicelee.ui.Global; 23 | 24 | //实现JTextfield 的全选、复制、剪切、粘贴功能。 25 | //实现Place Holder。 26 | public class MJTextField extends JTextField implements MouseListener, KeyListener, FocusListener { 27 | 28 | private static final long serialVersionUID = 10110L; 29 | 30 | private JPopupMenu pop = null; // 弹出菜单 31 | 32 | private JMenuItem selectAll = null, copy = null, paste = null, cut = null; // 功能菜单 33 | 34 | private String placeHolder; 35 | public MJTextField() { 36 | super(); 37 | placeHolder = ""; 38 | init(); 39 | } 40 | 41 | public MJTextField(String placeHolder) { 42 | super(placeHolder); 43 | this.setForeground(Color.GRAY); 44 | this.placeHolder = placeHolder; 45 | init(); 46 | } 47 | 48 | private void init() { 49 | this.addMouseListener(this); 50 | this.addKeyListener(this); 51 | this.addFocusListener(this); 52 | pop = new JPopupMenu(); 53 | pop.add(selectAll = new JMenuItem("全选")); 54 | pop.add(copy = new JMenuItem("复制")); 55 | pop.add(paste = new JMenuItem("粘贴")); 56 | pop.add(cut = new JMenuItem("剪切")); 57 | selectAll.setAccelerator(KeyStroke.getKeyStroke('A', InputEvent.CTRL_MASK)); 58 | copy.setAccelerator(KeyStroke.getKeyStroke('C', InputEvent.CTRL_MASK)); 59 | paste.setAccelerator(KeyStroke.getKeyStroke('V', InputEvent.CTRL_MASK)); 60 | cut.setAccelerator(KeyStroke.getKeyStroke('X', InputEvent.CTRL_MASK)); 61 | selectAll.addActionListener(new ActionListener() { 62 | public void actionPerformed(ActionEvent e) { 63 | action(e); 64 | } 65 | }); 66 | copy.addActionListener(new ActionListener() { 67 | public void actionPerformed(ActionEvent e) { 68 | action(e); 69 | } 70 | }); 71 | paste.addActionListener(new ActionListener() { 72 | public void actionPerformed(ActionEvent e) { 73 | action(e); 74 | } 75 | }); 76 | cut.addActionListener(new ActionListener() { 77 | public void actionPerformed(ActionEvent e) { 78 | action(e); 79 | } 80 | }); 81 | this.add(pop); 82 | } 83 | 84 | /** 85 | * 菜单动作 86 | * 87 | * @param e 88 | */ 89 | public void action(ActionEvent e) { 90 | if (e.getSource() == copy) { // 复制 91 | this.copy(); 92 | } else if (e.getSource() == paste) { // 粘贴 93 | if(this.getText().equals(placeHolder)) { 94 | this.setText(""); 95 | } 96 | this.setForeground(Color.BLACK); 97 | this.paste(); 98 | } else if (e.getSource() == cut) { // 剪切 99 | this.cut(); 100 | } else if (e.getSource() == selectAll) { // 全选 101 | this.selectAll(); 102 | } 103 | } 104 | 105 | public JPopupMenu getPop() { 106 | return pop; 107 | } 108 | 109 | public void setPop(JPopupMenu pop) { 110 | this.pop = pop; 111 | } 112 | 113 | /** 114 | * 剪切板中是否有文本数据可供粘贴 115 | * 116 | * @return true为有文本数据 117 | */ 118 | public boolean isClipboardString() { 119 | boolean b = false; 120 | Clipboard clipboard = this.getToolkit().getSystemClipboard(); 121 | Transferable content = clipboard.getContents(this); 122 | try { 123 | if (content.getTransferData(DataFlavor.stringFlavor) instanceof String) { 124 | b = true; 125 | } 126 | } catch (Exception e) { 127 | } 128 | return b; 129 | } 130 | 131 | /** 132 | * 文本组件中是否具备复制的条件 133 | * 134 | * @return true为具备 135 | */ 136 | public boolean isCanCopy() { 137 | boolean b = false; 138 | int start = this.getSelectionStart(); 139 | int end = this.getSelectionEnd(); 140 | if (start != end) 141 | b = true; 142 | return b; 143 | } 144 | 145 | public void mouseClicked(MouseEvent e) { 146 | } 147 | 148 | public void mouseEntered(MouseEvent e) { 149 | } 150 | 151 | public void mouseExited(MouseEvent e) { 152 | } 153 | 154 | public void mousePressed(MouseEvent e) { 155 | if (e.getButton() == MouseEvent.BUTTON3) { 156 | copy.setEnabled(isCanCopy()); 157 | paste.setEnabled(isClipboardString()); 158 | cut.setEnabled(isCanCopy()); 159 | pop.show(this, e.getX(), e.getY()); 160 | } 161 | } 162 | 163 | public void mouseReleased(MouseEvent e) { 164 | } 165 | 166 | @Override 167 | public void keyTyped(KeyEvent e) { 168 | } 169 | 170 | @Override 171 | public void keyPressed(KeyEvent e) { 172 | if(e.getKeyCode() == KeyEvent.VK_ENTER) { 173 | System.out.println("输入了Enter键"); 174 | Global.index.search(); 175 | } 176 | } 177 | 178 | @Override 179 | public void keyReleased(KeyEvent e) { 180 | } 181 | 182 | @Override 183 | public void focusGained(FocusEvent e) { 184 | if (this.getText().startsWith(placeHolder)) { 185 | this.setText(""); 186 | this.setForeground(Color.BLACK); 187 | } 188 | 189 | } 190 | 191 | @Override 192 | public void focusLost(FocusEvent e) { 193 | if (this.getText().isEmpty()) { 194 | this.setForeground(Color.GRAY); 195 | this.setText(placeHolder); 196 | } 197 | 198 | } 199 | 200 | } -------------------------------------------------------------------------------- /src/nicelee/ui/item/OperationPanel.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item; 2 | 3 | import java.awt.Color; 4 | import java.awt.Dimension; 5 | import java.awt.FlowLayout; 6 | import java.awt.event.ActionEvent; 7 | import java.awt.event.ActionListener; 8 | 9 | import javax.swing.BorderFactory; 10 | import javax.swing.JButton; 11 | import javax.swing.JComboBox; 12 | import javax.swing.JLabel; 13 | import javax.swing.JPanel; 14 | 15 | import nicelee.acfun.enums.VideoQualityEnum; 16 | import nicelee.ui.Global; 17 | 18 | /** 19 | * 内嵌于主页 nicelee.ui.TabIndex 20 | * 21 | * 舍弃,使用菜单栏FunctionMenuBar代替 22 | */ 23 | public class OperationPanel extends JPanel { 24 | private static final long serialVersionUID = -752743062676819407L; 25 | 26 | JComboBox cbType; // 全部视频 / 第一个视频 27 | JComboBox cbQn; // 清晰度 28 | /** 29 | * 30 | */ 31 | public OperationPanel() { 32 | super(); 33 | initUI(); 34 | } 35 | 36 | void initUI() { 37 | this.setBorder(BorderFactory.createLineBorder(Color.red)); 38 | this.setPreferredSize(new Dimension(355, 80)); 39 | FlowLayout f=(FlowLayout)getLayout(); 40 | f.setHgap(0);//水平间距 41 | f.setVgap(0);//组件垂直间距 42 | this.setLayout(f); 43 | 44 | JPanel jp1 = new JPanel(); 45 | jp1.setPreferredSize(new Dimension(350, 36)); 46 | 47 | 48 | JLabel label = new JLabel("关闭全部Tab"); 49 | label.setPreferredSize(new Dimension(277, 36)); 50 | JButton btn = new JButton("执行"); 51 | btn.addActionListener(new ActionListener() { 52 | @Override 53 | public void actionPerformed(ActionEvent e) { 54 | Global.index.closeAllVideoTabs(); 55 | } 56 | 57 | }); 58 | jp1.add(label); 59 | jp1.add(btn); 60 | this.add(jp1); 61 | 62 | JPanel jp2 = new JPanel(); 63 | jp2.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, Color.red)); 64 | jp2.setPreferredSize(new Dimension(350, 36)); 65 | JLabel label2 = new JLabel("下载策略"); 66 | jp2.add(label2); 67 | cbType = new JComboBox(); 68 | cbType.addItem("全部"); 69 | cbType.addItem("仅第一"); 70 | cbType.setSelectedIndex(1); 71 | jp2.add(cbType); 72 | 73 | JLabel label3 = new JLabel("优先清晰度"); 74 | jp2.add(label3); 75 | cbQn = new JComboBox<>(); 76 | for(VideoQualityEnum item: VideoQualityEnum.values()) { 77 | cbQn.addItem(item.getQuality()); 78 | } 79 | cbQn.setSelectedIndex(0); 80 | jp2.add(cbQn); 81 | 82 | JButton btnDownload = new JButton("执行"); 83 | btnDownload.addActionListener(new ActionListener() { 84 | @Override 85 | public void actionPerformed(ActionEvent e) { 86 | boolean downAll = cbType.getSelectedItem().toString().startsWith("全部"); 87 | int qn = VideoQualityEnum.getQN(cbQn.getSelectedItem().toString()); 88 | Global.index.downVideoTabs(downAll, qn); 89 | } 90 | 91 | }); 92 | jp2.add(btnDownload); 93 | this.add(jp2); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/nicelee/ui/item/impl/TextTransferHandler.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item.impl; 2 | 3 | import java.awt.datatransfer.Clipboard; 4 | import java.awt.datatransfer.Transferable; 5 | 6 | import javax.swing.JComponent; 7 | import javax.swing.JEditorPane; 8 | import javax.swing.TransferHandler; 9 | 10 | /** 11 | * https://codeday.me/bug/20181022/332533.html 12 | * 有做修改,用于 JEditorPane 中文本内容的复制 13 | */ 14 | public class TextTransferHandler extends TransferHandler{ 15 | 16 | private static final long serialVersionUID = 9053062598699556138L; 17 | 18 | protected Transferable createTransferable(JComponent c) { 19 | final JEditorPane pane = (JEditorPane) c; 20 | final String plainText = pane.getSelectedText(); 21 | return new TextTransferable(plainText); 22 | } 23 | 24 | 25 | @Override 26 | public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException { 27 | if (action == COPY) { 28 | clip.setContents(this.createTransferable(comp), null); 29 | } 30 | } 31 | 32 | @Override 33 | public int getSourceActions(JComponent c) { 34 | return COPY; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/nicelee/ui/item/impl/TextTransferable.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.item.impl; 2 | 3 | import java.awt.datatransfer.DataFlavor; 4 | import java.awt.datatransfer.Transferable; 5 | import java.awt.datatransfer.UnsupportedFlavorException; 6 | import java.io.IOException; 7 | 8 | /** 9 | * https://codeday.me/bug/20181022/332533.html 10 | * 有做修改,用于 JEditorPane 中文本内容的复制 11 | */ 12 | public class TextTransferable implements Transferable{ 13 | private static final DataFlavor[] supportedFlavors; 14 | 15 | static { 16 | try { 17 | supportedFlavors = new DataFlavor[] { new DataFlavor("text/plain;class=java.lang.String") }; 18 | } catch (ClassNotFoundException e) { 19 | throw new ExceptionInInitializerError(e); 20 | } 21 | } 22 | 23 | private final String plainData; 24 | 25 | public TextTransferable(String plainData) { 26 | this.plainData = plainData; 27 | } 28 | 29 | @Override 30 | public DataFlavor[] getTransferDataFlavors() { 31 | return supportedFlavors; 32 | } 33 | 34 | @Override 35 | public boolean isDataFlavorSupported(DataFlavor flavor) { 36 | for (DataFlavor supportedFlavor : supportedFlavors) { 37 | if (supportedFlavor == flavor) { 38 | return true; 39 | } 40 | } 41 | return false; 42 | } 43 | 44 | @Override 45 | public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { 46 | if (flavor.equals(supportedFlavors[0])) { 47 | return plainData; 48 | } 49 | throw new UnsupportedFlavorException(flavor); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/nicelee/ui/thread/DownloadRunnable.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.thread; 2 | 3 | import java.awt.Dimension; 4 | 5 | import javax.swing.JPanel; 6 | 7 | import nicelee.acfun.INeedAV; 8 | import nicelee.acfun.enums.StatusEnum; 9 | import nicelee.acfun.model.ClipInfo; 10 | import nicelee.acfun.util.CmdUtil; 11 | import nicelee.acfun.util.Logger; 12 | import nicelee.acfun.util.RepoUtil; 13 | import nicelee.ui.Global; 14 | import nicelee.ui.TabDownload; 15 | import nicelee.ui.item.DownloadInfoPanel; 16 | import nicelee.ui.item.JOptionPaneManager; 17 | 18 | public class DownloadRunnable implements Runnable { 19 | 20 | ClipInfo clip; 21 | String displayName; 22 | String avid; 23 | String cid; 24 | int page; 25 | int remark; 26 | String record; 27 | int qn; //想要申请的链接视频质量 28 | 29 | public DownloadRunnable(ClipInfo clip, int qn) { 30 | this.displayName = clip.getAvTitle() + "p" + clip.getRemark() + "-" +clip.getTitle(); 31 | this.clip = clip; 32 | this.avid = clip.getAvId(); 33 | this.cid = String.valueOf(clip.getcId()); 34 | this.page = clip.getPage(); 35 | this.remark = clip.getRemark(); 36 | this.qn = qn; 37 | this.record = avid + "-" + qn + "-p" + page; 38 | } 39 | 40 | @Override 41 | public void run() { 42 | System.out.println("你点击了一次下载按钮..."); 43 | // 如果点击了全部暂停按钮,而此时在队列中 44 | if(TabDownload.isStopAll()) { 45 | System.out.println("你点击了一次暂停按钮..."); 46 | return; 47 | } 48 | //判断是否已经下载过 49 | if(Global.useRepo && RepoUtil.isInRepo(record)) { 50 | JOptionPaneManager.showMsgWithNewThread("提示", "您已经下载过视频" + record); 51 | System.out.println("已经下载过 " + record); 52 | return; 53 | } 54 | // 新建下载部件 55 | DownloadInfoPanel downPanel = new DownloadInfoPanel(clip, qn); 56 | // 判断是否在下载任务中 57 | if (Global.downloadTaskList.get(downPanel) != null) { 58 | System.out.println("已经存在相关下载"); 59 | return; 60 | } 61 | // 查询下载链接 62 | INeedAV iNeedAV = new INeedAV(); 63 | String url = iNeedAV.getInputParser(avid).getVideoLink(avid, cid, qn, Global.downloadFormat); //该步含网络查询, 可能较为耗时 64 | int realQN = iNeedAV.getInputParser(avid).getRealQN(); 65 | // 生成格式化名称 66 | String formattedTitle = CmdUtil.genFormatedName( 67 | avid, 68 | "p" + page, 69 | "pn" + remark, 70 | realQN, 71 | clip.getAvTitle(), 72 | clip.getTitle(), 73 | clip.getListName(), 74 | clip.getListOwnerName()); 75 | String avid_qn = avid + "-" + realQN; 76 | this.record = avid_qn + "-p" + page; 77 | //如果清晰度不符合预期,再判断一次记录 78 | //判断是否已经下载过 79 | if (qn != realQN && Global.useRepo && RepoUtil.isInRepo(record)) { 80 | JOptionPaneManager.showMsgWithNewThread("提示", "您已经下载过视频" + record); 81 | System.out.println("已经下载过 " + record); 82 | return; 83 | } 84 | //获取实际清晰度后,初始化下载部件参数 85 | downPanel.initDownloadParams(iNeedAV, url, avid_qn, formattedTitle, realQN); 86 | // 再进行一次判断,看下载列表是否已经存在相应任务(防止并发误判) 87 | if (Global.downloadTaskList.get(downPanel) != null) { 88 | System.out.println("已经存在相关下载"); 89 | return; 90 | } 91 | // 将下载任务(HttpRequestUtil + DownloadInfoPanel)添加至全局列表, 让监控进程周期获取信息并刷新 92 | Global.downloadTaskList.put(downPanel, iNeedAV.getDownloader()); 93 | // 根据信息初始化绘制下载部件 94 | JPanel jpContent = Global.downloadTab.getJpContent(); 95 | jpContent.add(downPanel); 96 | jpContent.setPreferredSize(new Dimension(1100, 128 * Global.downloadTaskList.size())); 97 | Global.downLoadThreadPool.execute(new Runnable() { 98 | @Override 99 | public void run() { 100 | try { 101 | if(iNeedAV.getDownloader().currentStatus() == StatusEnum.STOP) { 102 | Logger.println("已经人工停止,无需再下载"); 103 | return; 104 | } 105 | // 开始下载 106 | if(iNeedAV.downloadClip(url, avid, iNeedAV.getInputParser(avid).getVideoLinkQN(), page)) { 107 | // 下载成功后保存到仓库 108 | if(Global.saveToRepo) { 109 | RepoUtil.appendAndSave(record); 110 | } 111 | CmdUtil.convertOrAppendCmdToRenameBat(avid_qn, formattedTitle, page); 112 | } 113 | } catch (Exception e) { 114 | e.printStackTrace(); 115 | } 116 | } 117 | }); 118 | 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/nicelee/ui/thread/GetVideoDetailThread.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.thread; 2 | 3 | import java.awt.Dimension; 4 | import java.awt.Image; 5 | import java.net.URL; 6 | 7 | import javax.swing.ImageIcon; 8 | import javax.swing.JPanel; 9 | 10 | import nicelee.acfun.INeedAV; 11 | import nicelee.acfun.model.ClipInfo; 12 | import nicelee.acfun.model.VideoInfo; 13 | import nicelee.ui.Global; 14 | import nicelee.ui.TabVideo; 15 | import nicelee.ui.item.ClipInfoPanel; 16 | 17 | public class GetVideoDetailThread extends Thread{ 18 | 19 | TabVideo video; 20 | String avId; 21 | public GetVideoDetailThread(TabVideo video, String avId) { 22 | this.video = video; 23 | this.avId = avId; 24 | //this.setName("Thread-GetVideoInfo"); 25 | } 26 | public void run() { 27 | try { 28 | //获取当前av详细信息 29 | INeedAV avs = new INeedAV(); 30 | //更新当前Tab页面 31 | VideoInfo avInfo =avs.getVideoDetail(avId, Global.downloadFormat, false); 32 | video.setAvInfo(avInfo); 33 | video.getLbAvID().setText(avInfo.getVideoId()); 34 | video.setCurrentDisplayPic(avInfo.getVideoPreview()); 35 | URL fileURL = new URL(avInfo.getVideoPreview()); 36 | ImageIcon imag1 = new ImageIcon(fileURL); 37 | imag1 = new ImageIcon(imag1.getImage().getScaledInstance(700, 460, Image.SCALE_DEFAULT) ); 38 | video.getLbAvPrivew().setText(""); 39 | video.getLbAvPrivew().setIcon(imag1); 40 | video.getLbBreif().setText(avInfo.getBrief()); 41 | video.getLbBreif().setToolTipText(avInfo.getBrief()); 42 | video.getLbVideoTitle().setText(avInfo.getVideoName()); 43 | video.getLbVideoTitle().setToolTipText(avInfo.getVideoName()); 44 | String title = avInfo.getVideoName(); 45 | if(title.length() >= 12) { 46 | title = title.substring(0, 9) + "..."; 47 | } 48 | video.getLbTabTitle().setText(title); 49 | 50 | JPanel jpContent = video.getJpContent(); 51 | jpContent.setPreferredSize(new Dimension(340, 116 * avInfo.getClips().size())); 52 | for(ClipInfo cInfo: avInfo.getClips().values()) { 53 | ClipInfoPanel cp = new ClipInfoPanel(cInfo); 54 | jpContent.add(cp); 55 | } 56 | jpContent.updateUI(); 57 | jpContent.repaint(); 58 | } catch (Exception e) { 59 | e.printStackTrace(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/nicelee/ui/thread/LoginThread.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.thread; 2 | 3 | 4 | import org.json.JSONArray; 5 | import org.json.JSONObject; 6 | 7 | import java.awt.Image; 8 | import java.io.UnsupportedEncodingException; 9 | import java.net.HttpCookie; 10 | import java.net.MalformedURLException; 11 | import java.net.URL; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | 17 | import javax.swing.ImageIcon; 18 | 19 | import nicelee.acfun.INeedLogin; 20 | import nicelee.acfun.model.FavList; 21 | import nicelee.acfun.util.HttpCookies; 22 | import nicelee.ui.FrameQRCode; 23 | import nicelee.ui.Global; 24 | 25 | public class LoginThread extends Thread { 26 | 27 | static boolean isFirstRun = true; 28 | @Override 29 | public void run() { 30 | // System.out.println("登录线程被调用..."); 31 | // if(isFirstRun) { 32 | // isFirstRun = false; 33 | // }else { 34 | // JOptionPane.showMessageDialog(null, "尚未涉及到登录功能"); 35 | // } 36 | // return; 37 | //Global.index.jlHeader.removeMouseListener(Global.index); 38 | INeedLogin inl = new INeedLogin(); 39 | /** 40 | * 0. 登录状态查询 41 | */ 42 | if (Global.isLogin || !Global.needToLogin) { 43 | //Global.index.jlHeader.addMouseListener(Global.index); 44 | System.out.println("已经登录,或没有发起登录请求"); 45 | return; 46 | } 47 | String cookiesStr = inl.readCookies(); 48 | // 检查有没有本地cookie配置 49 | if (cookiesStr != null) { 50 | System.out.println("检查到存在本地Cookies..."); 51 | List cookies = HttpCookies.convertCookies(cookiesStr); 52 | // 成功登录后即返回,不再进行二维码扫码工作 53 | if (inl.getLoginStatus(cookies)) { 54 | System.out.println("本地Cookies验证有效..."); 55 | // 设置全局Cookie 56 | HttpCookies.setGlobalCookies(cookies); 57 | // 设置当前头像 58 | try { 59 | // System.out.println(inl.user.getPoster()); 60 | URL fileURL = new URL(inl.user.getPoster()); 61 | ImageIcon imag1 = new ImageIcon(fileURL); 62 | imag1 = new ImageIcon(imag1.getImage().getScaledInstance(80, 80, Image.SCALE_SMOOTH)); 63 | Global.index.jlHeader.setToolTipText("当前用户为: " + inl.user.getName()); 64 | Global.index.jlHeader.setIcon(imag1); 65 | //Global.index.jlHeader.removeMouseListener(Global.index); 66 | } catch (MalformedURLException e) { 67 | e.printStackTrace(); 68 | } 69 | // 设置收藏夹 70 | getFavList(inl); 71 | System.out.println("成功登录..."); 72 | return; 73 | }else { 74 | System.out.println("本地Cookies验证无效..."); 75 | // 置空全局Cookie 76 | HttpCookies.setGlobalCookies(null); 77 | } 78 | } 79 | System.out.println("没有检查到本地Cookies..."); 80 | /** 81 | * 1. 访问 Get 访问 https://passport.bilibili.com/qrcode/getLoginUrl 获取 oauthKey ==> 82 | * 链接 ==> 二维码 83 | */ 84 | System.out.println("正在获取验证AuthKey以生成二维码..."); 85 | String authKey[] = inl.getAuthKey(); 86 | System.out.println("authKey: " + authKey); 87 | // 显示二维码图片 88 | FrameQRCode qr = new FrameQRCode(inl.qrCodeStr); 89 | qr.initUI(); 90 | 91 | /** 92 | * 2. 周期性Post 访问 https://passport.bilibili.com/qrcode/getLoginInfo 直至扫码成功 93 | * 成功后保存Cookie 94 | */ 95 | long start = System.currentTimeMillis(); 96 | while (!Global.isLogin && Global.needToLogin && System.currentTimeMillis() - start < 1 * 1000) { 97 | try { 98 | Global.isLogin = inl.getAuthStatus(authKey); 99 | System.out.println("------------"); 100 | Thread.sleep(1000); 101 | } catch (InterruptedException e) { 102 | e.printStackTrace(); 103 | } catch (UnsupportedEncodingException e) { 104 | e.printStackTrace(); 105 | } 106 | } 107 | if (Global.isLogin) { 108 | // 保存cookie到本地 109 | inl.saveCookies(inl.iCookies.toString()); 110 | // 设置全局Cookie 111 | HttpCookies.setGlobalCookies(inl.iCookies); 112 | // 获取用户信息, 设置当前头像 113 | inl.getLoginStatus(inl.iCookies); 114 | try { 115 | // System.out.println(inl.user.getPoster()); 116 | URL fileURL = new URL(inl.user.getPoster()); 117 | ImageIcon imag1 = new ImageIcon(fileURL); 118 | imag1 = new ImageIcon(imag1.getImage().getScaledInstance(80, 80, Image.SCALE_SMOOTH)); 119 | Global.index.jlHeader.setToolTipText("当前用户为: " + inl.user.getName()); 120 | Global.index.jlHeader.setIcon(imag1); 121 | //Global.index.jlHeader.removeMouseListener(Global.index); 122 | } catch (MalformedURLException e) { 123 | e.printStackTrace(); 124 | } 125 | // 设置收藏夹 126 | getFavList(inl); 127 | System.out.println("成功登录..."); 128 | } else { 129 | //Global.index.jlHeader.addMouseListener(Global.index); 130 | Global.needToLogin = true; 131 | } 132 | // 销毁图片 133 | System.out.println("登录线程结束..."); 134 | qr.dispose(); 135 | 136 | } 137 | 138 | private void getFavList(INeedLogin inl) { 139 | try { 140 | if (Global.index.cbChannel.getItemCount() == 1) { 141 | String favUrl = "https://www.acfun.cn/member/favourite"; 142 | String htmlStr = inl.getUtil().getContent(favUrl, new HashMap(), 143 | HttpCookies.getGlobalCookies()); 144 | Pattern p = Pattern.compile("window.__INITIAL_STATE__=(.*?});\\(function"); 145 | Matcher m = p.matcher(htmlStr); 146 | m.find(); 147 | String jsonStr = m.group(1); 148 | JSONArray list = new JSONObject(jsonStr).getJSONObject("member").getJSONObject("favorite").getJSONArray("dougaFolders"); 149 | for (int i = 0; i < list.length(); i++) { 150 | JSONObject favlist = list.getJSONObject(i); 151 | FavList fav = new FavList(favlist.getLong("folderId"), 152 | favlist.getInt("resourceCount"), favlist.getString("name")); 153 | Global.index.cbChannel.addItem(fav); 154 | } 155 | } 156 | } catch (Exception e) { 157 | e.printStackTrace(); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/nicelee/ui/thread/StreamManager.java: -------------------------------------------------------------------------------- 1 | package nicelee.ui.thread; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.io.InputStreamReader; 7 | 8 | public class StreamManager extends Thread{ 9 | Process process; 10 | InputStream inputStream; 11 | public StreamManager(Process process, InputStream inputStream) { 12 | this.process = process; 13 | this.inputStream = inputStream; 14 | } 15 | 16 | public void run () { 17 | InputStreamReader inputStreamReader = new InputStreamReader(inputStream); 18 | BufferedReader bufferedReader = new BufferedReader(inputStreamReader); 19 | String line = null; 20 | try { 21 | while((line = bufferedReader.readLine()) !=null ) { 22 | System.out.println(line); 23 | } 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | } 27 | process.destroy(); 28 | //System.out.println("转码完毕."); 29 | } 30 | } -------------------------------------------------------------------------------- /src/org/json/.gitignore: -------------------------------------------------------------------------------- 1 | # ignore eclipse project files 2 | .project 3 | .classpath 4 | # ignore Intellij Idea project files 5 | .idea 6 | *.iml 7 | -------------------------------------------------------------------------------- /src/org/json/CookieList.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Convert a web browser cookie list string to a JSONObject and back. 29 | * @author JSON.org 30 | * @version 2015-12-09 31 | */ 32 | public class CookieList { 33 | 34 | /** 35 | * Convert a cookie list into a JSONObject. A cookie list is a sequence 36 | * of name/value pairs. The names are separated from the values by '='. 37 | * The pairs are separated by ';'. The names and the values 38 | * will be unescaped, possibly converting '+' and '%' sequences. 39 | * 40 | * To add a cookie to a cookie list, 41 | * cookielistJSONObject.put(cookieJSONObject.getString("name"), 42 | * cookieJSONObject.getString("value")); 43 | * @param string A cookie list string 44 | * @return A JSONObject 45 | * @throws JSONException 46 | */ 47 | public static JSONObject toJSONObject(String string) throws JSONException { 48 | JSONObject jo = new JSONObject(); 49 | JSONTokener x = new JSONTokener(string); 50 | while (x.more()) { 51 | String name = Cookie.unescape(x.nextTo('=')); 52 | x.next('='); 53 | jo.put(name, Cookie.unescape(x.nextTo(';'))); 54 | x.next(); 55 | } 56 | return jo; 57 | } 58 | 59 | /** 60 | * Convert a JSONObject into a cookie list. A cookie list is a sequence 61 | * of name/value pairs. The names are separated from the values by '='. 62 | * The pairs are separated by ';'. The characters '%', '+', '=', and ';' 63 | * in the names and values are replaced by "%hh". 64 | * @param jo A JSONObject 65 | * @return A cookie list string 66 | * @throws JSONException 67 | */ 68 | public static String toString(JSONObject jo) throws JSONException { 69 | boolean b = false; 70 | final StringBuilder sb = new StringBuilder(); 71 | // Don't use the new entrySet API to maintain Android support 72 | for (final String key : jo.keySet()) { 73 | final Object value = jo.opt(key); 74 | if (!JSONObject.NULL.equals(value)) { 75 | if (b) { 76 | sb.append(';'); 77 | } 78 | sb.append(Cookie.escape(key)); 79 | sb.append("="); 80 | sb.append(Cookie.escape(value.toString())); 81 | b = true; 82 | } 83 | } 84 | return sb.toString(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/org/json/HTTPTokener.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The HTTPTokener extends the JSONTokener to provide additional methods 29 | * for the parsing of HTTP headers. 30 | * @author JSON.org 31 | * @version 2015-12-09 32 | */ 33 | public class HTTPTokener extends JSONTokener { 34 | 35 | /** 36 | * Construct an HTTPTokener from a string. 37 | * @param string A source string. 38 | */ 39 | public HTTPTokener(String string) { 40 | super(string); 41 | } 42 | 43 | 44 | /** 45 | * Get the next token or string. This is used in parsing HTTP headers. 46 | * @throws JSONException 47 | * @return A String. 48 | */ 49 | public String nextToken() throws JSONException { 50 | char c; 51 | char q; 52 | StringBuilder sb = new StringBuilder(); 53 | do { 54 | c = next(); 55 | } while (Character.isWhitespace(c)); 56 | if (c == '"' || c == '\'') { 57 | q = c; 58 | for (;;) { 59 | c = next(); 60 | if (c < ' ') { 61 | throw syntaxError("Unterminated string."); 62 | } 63 | if (c == q) { 64 | return sb.toString(); 65 | } 66 | sb.append(c); 67 | } 68 | } 69 | for (;;) { 70 | if (c == 0 || Character.isWhitespace(c)) { 71 | return sb.toString(); 72 | } 73 | sb.append(c); 74 | c = next(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/org/json/JSONException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /** 4 | * The JSONException is thrown by the JSON.org classes when things are amiss. 5 | * 6 | * @author JSON.org 7 | * @version 2015-12-09 8 | */ 9 | public class JSONException extends RuntimeException { 10 | /** Serialization ID */ 11 | private static final long serialVersionUID = 0; 12 | 13 | /** 14 | * Constructs a JSONException with an explanatory message. 15 | * 16 | * @param message 17 | * Detail about the reason for the exception. 18 | */ 19 | public JSONException(final String message) { 20 | super(message); 21 | } 22 | 23 | /** 24 | * Constructs a JSONException with an explanatory message and cause. 25 | * 26 | * @param message 27 | * Detail about the reason for the exception. 28 | * @param cause 29 | * The cause. 30 | */ 31 | public JSONException(final String message, final Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | /** 36 | * Constructs a new JSONException with the specified cause. 37 | * 38 | * @param cause 39 | * The cause. 40 | */ 41 | public JSONException(final Throwable cause) { 42 | super(cause.getMessage(), cause); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/org/json/JSONPointerException.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /** 28 | * The JSONPointerException is thrown by {@link JSONPointer} if an error occurs 29 | * during evaluating a pointer. 30 | * 31 | * @author JSON.org 32 | * @version 2016-05-13 33 | */ 34 | public class JSONPointerException extends JSONException { 35 | private static final long serialVersionUID = 8872944667561856751L; 36 | 37 | public JSONPointerException(String message) { 38 | super(message); 39 | } 40 | 41 | public JSONPointerException(String message, Throwable cause) { 42 | super(message, cause); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/org/json/JSONPropertyIgnore.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2018 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import static java.lang.annotation.ElementType.METHOD; 28 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 29 | 30 | import java.lang.annotation.Documented; 31 | import java.lang.annotation.Retention; 32 | import java.lang.annotation.Target; 33 | 34 | @Documented 35 | @Retention(RUNTIME) 36 | @Target({METHOD}) 37 | /** 38 | * Use this annotation on a getter method to override the Bean name 39 | * parser for Bean -> JSONObject mapping. If this annotation is 40 | * present at any level in the class hierarchy, then the method will 41 | * not be serialized from the bean into the JSONObject. 42 | */ 43 | public @interface JSONPropertyIgnore { } 44 | -------------------------------------------------------------------------------- /src/org/json/JSONPropertyName.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2018 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import static java.lang.annotation.ElementType.METHOD; 28 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 29 | 30 | import java.lang.annotation.Documented; 31 | import java.lang.annotation.Retention; 32 | import java.lang.annotation.Target; 33 | 34 | @Documented 35 | @Retention(RUNTIME) 36 | @Target({METHOD}) 37 | /** 38 | * Use this annotation on a getter method to override the Bean name 39 | * parser for Bean -> JSONObject mapping. A value set to empty string "" 40 | * will have the Bean parser fall back to the default field name processing. 41 | */ 42 | public @interface JSONPropertyName { 43 | /** 44 | * @return The name of the property as to be used in the JSON Object. 45 | */ 46 | String value(); 47 | } 48 | -------------------------------------------------------------------------------- /src/org/json/JSONString.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | /** 3 | * The JSONString interface allows a toJSONString() 4 | * method so that a class can change the behavior of 5 | * JSONObject.toString(), JSONArray.toString(), 6 | * and JSONWriter.value(Object). The 7 | * toJSONString method will be used instead of the default behavior 8 | * of using the Object's toString() method and quoting the result. 9 | */ 10 | public interface JSONString { 11 | /** 12 | * The toJSONString method allows a class to produce its own JSON 13 | * serialization. 14 | * 15 | * @return A strictly syntactically correct JSON text. 16 | */ 17 | public String toJSONString(); 18 | } 19 | -------------------------------------------------------------------------------- /src/org/json/JSONStringer.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2006 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.io.StringWriter; 28 | 29 | /** 30 | * JSONStringer provides a quick and convenient way of producing JSON text. 31 | * The texts produced strictly conform to JSON syntax rules. No whitespace is 32 | * added, so the results are ready for transmission or storage. Each instance of 33 | * JSONStringer can produce one JSON text. 34 | *

35 | * A JSONStringer instance provides a value method for appending 36 | * values to the 37 | * text, and a key 38 | * method for adding keys before values in objects. There are array 39 | * and endArray methods that make and bound array values, and 40 | * object and endObject methods which make and bound 41 | * object values. All of these methods return the JSONWriter instance, 42 | * permitting cascade style. For example,

43 |  * myString = new JSONStringer()
44 |  *     .object()
45 |  *         .key("JSON")
46 |  *         .value("Hello, World!")
47 |  *     .endObject()
48 |  *     .toString();
which produces the string
49 |  * {"JSON":"Hello, World!"}
50 | *

51 | * The first method called must be array or object. 52 | * There are no methods for adding commas or colons. JSONStringer adds them for 53 | * you. Objects and arrays can be nested up to 20 levels deep. 54 | *

55 | * This can sometimes be easier than using a JSONObject to build a string. 56 | * @author JSON.org 57 | * @version 2015-12-09 58 | */ 59 | public class JSONStringer extends JSONWriter { 60 | /** 61 | * Make a fresh JSONStringer. It can be used to build one JSON text. 62 | */ 63 | public JSONStringer() { 64 | super(new StringWriter()); 65 | } 66 | 67 | /** 68 | * Return the JSON text. This method is used to obtain the product of the 69 | * JSONStringer instance. It will return null if there was a 70 | * problem in the construction of the JSON text (such as the calls to 71 | * array were not properly balanced with calls to 72 | * endArray). 73 | * @return The JSON text. 74 | */ 75 | @Override 76 | public String toString() { 77 | return this.mode == 'd' ? this.writer.toString() : null; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/org/json/LICENSE: -------------------------------------------------------------------------------- 1 | ============================================================================ 2 | 3 | Copyright (c) 2002 JSON.org 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | The Software shall be used for Good, not Evil. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/org/json/Property.java: -------------------------------------------------------------------------------- 1 | package org.json; 2 | 3 | /* 4 | Copyright (c) 2002 JSON.org 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | The Software shall be used for Good, not Evil. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | import java.util.Enumeration; 28 | import java.util.Properties; 29 | 30 | /** 31 | * Converts a Property file data into JSONObject and back. 32 | * @author JSON.org 33 | * @version 2015-05-05 34 | */ 35 | public class Property { 36 | /** 37 | * Converts a property file object into a JSONObject. The property file object is a table of name value pairs. 38 | * @param properties java.util.Properties 39 | * @return JSONObject 40 | * @throws JSONException 41 | */ 42 | public static JSONObject toJSONObject(java.util.Properties properties) throws JSONException { 43 | // can't use the new constructor for Android support 44 | // JSONObject jo = new JSONObject(properties == null ? 0 : properties.size()); 45 | JSONObject jo = new JSONObject(); 46 | if (properties != null && !properties.isEmpty()) { 47 | Enumeration enumProperties = properties.propertyNames(); 48 | while(enumProperties.hasMoreElements()) { 49 | String name = (String)enumProperties.nextElement(); 50 | jo.put(name, properties.getProperty(name)); 51 | } 52 | } 53 | return jo; 54 | } 55 | 56 | /** 57 | * Converts the JSONObject into a property file object. 58 | * @param jo JSONObject 59 | * @return java.util.Properties 60 | * @throws JSONException 61 | */ 62 | public static Properties toProperties(JSONObject jo) throws JSONException { 63 | Properties properties = new Properties(); 64 | if (jo != null) { 65 | // Don't use the new entrySet API to maintain Android support 66 | for (final String key : jo.keySet()) { 67 | Object value = jo.opt(key); 68 | if (!JSONObject.NULL.equals(value)) { 69 | properties.put(key, value.toString()); 70 | } 71 | } 72 | } 73 | return properties; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/resources/_.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/_.jpg -------------------------------------------------------------------------------- /src/resources/_h.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/_h.jpg -------------------------------------------------------------------------------- /src/resources/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 作品信息 6 | 19 | 20 | 21 |

作品信息

22 |
23 | 29 |

感谢 (不分先后( ´_ゝ`)

30 |
31 |
    32 |
  • Json解析使用JSON.org
  • 33 |
  • 转码由ffmpeg提供技术支持
  • 34 |
  • 登录二维码由zxing生成
  • 35 |
36 |

本作以及第三方LICENSE详见本地文件夹github

37 | 38 | -------------------------------------------------------------------------------- /src/resources/app.config: -------------------------------------------------------------------------------- 1 | # 下载文件命名格式 2 | ## avId - av号 e.g. av1234567 3 | ## pAv - av 的第几个视频 e.g. p1/p2 4 | ## pDisplay - 合集的第几个视频 e.g. pn1/pn2 5 | ## qn - 清晰度值 e.g. 32/64/80 6 | ## avTitle - av标题 7 | ## clipTitle - 视频小标题 8 | ### 以下可能不存在,仅在收藏夹/UP主视频搜索时有效 9 | ### listName - 集合名称 e.g. 某收藏夹的名称 10 | ### listOwnerName - 集合的拥有者 e.g. 某某某 (假设搜索的是某人的收藏夹) 11 | #### pDisplay 和 pAv 可能不一致, 比如有的ss是分布在不同的av的第一个视频, 有的则是分布在同一av的不同p 12 | ##acfun.name.format = avTitle-pDisplay-clipTitle-qn 13 | # (:条件 格式字符串) 当条件成立时,文件名将增加括号内的格式字符串(注意条件和格式字符串中间的空格) 14 | acfun.name.format = (:listName listName-)avTitle-pDisplay-clipTitle-qn 15 | 16 | # 下载完成后是否马上重命名 17 | # 若为false, 那么会追加到重命名文件, 可以人工运行rename.bat 重命名 18 | acfun.name.doAfterComplete = true 19 | ####################################################################################################### 20 | # 下载异常后尝试次数, 0 则异常后不再尝试 21 | acfun.download.maxFailRetry = 3 22 | 23 | # 优先下载格式 24 | # 0: MP4 1:FLV 25 | acfun.format = 0 26 | 27 | # 不尝试获取视频的清晰度,直接提供所有选项 issue#17 28 | acfun.quality.noQualityRequest = true 29 | 30 | # 在控制栏输出ffmpeg运行信息 31 | acfun.debug.ffmpeg = true 32 | ####################################################################################################### 33 | # 分页查询时,每页最大显示个数 34 | acfun.pageSize = 5 35 | 36 | # 分页查询时,结果展示方式 37 | ## promptAll 每个av弹出一个Tab页 38 | ## listAll 所有选项在一个Tab页面里呈现 39 | acfun.pageDisplay = listAll 40 | ####################################################################################################### 41 | #下载文件保存路径, 可以是相对路径,也可以是绝对路径 42 | acfun.savePath = download/ 43 | #acfun.savePath = D:\Workspace\acfun\ 44 | 45 | ####################################################################################################### 46 | #最大的同时下载任务数 47 | acfun.download.poolSize = 3 48 | 49 | ####################################################################################################### 50 | #UI主题 51 | # default swing默认 52 | # system 跟随系统 53 | acfun.theme = default 54 | 55 | ####################################################################################################### 56 | #临时文件严格模式开启与否 57 | #开启后,如果已经存在下载好的视频(无论视频损坏与否),该视频对应的临时文件将会被删除 58 | #关闭后,当下载完成后,如果视频大小达标,该视频对应的临时文件将会被删除。某些异常可能会导致临时文件未被删除而一直存在。 59 | # on / off 60 | acfun.restrictTempMode = on 61 | 62 | ####################################################################################################### 63 | #是否使用仓库功能 64 | # 开启后,每次下载前都会先从仓库查询记录。 若存在,则不开始任务 65 | acfun.repo = on 66 | 67 | # 仓库功能关闭时,是否仍保存下载成功的记录(即只保存成功的下载记录而不作其它操作) 68 | acfun.repo.save = on 69 | 70 | # 同一视频的不同清晰度算不算同一记录 71 | ## on : 同一视频两种清晰度算不同记录 72 | ## off : 同一视频两种清晰度算相同记录 73 | acfun.repo.definitionStrictMode = off 74 | 75 | ####################################################################################################### 76 | # 下载已完成的视频时,是否弹出提示 true / false 77 | acfun.alert.isAlertIfDownloded = true 78 | 79 | # 批量下载时,最大提示框弹出数 80 | acfun.alert.maxAlertPrompt = 5 81 | ####################################################################################################### 82 | # 同时支持HTTP + HTTPS 代理 83 | #proxyHost = 127.0.0.1 84 | #proxyPort = 1080 85 | 86 | # 仅代理HTTP 87 | #http.proxyHost = 127.0.0.1 88 | #http.proxyHost = 1080 89 | 90 | # 仅代理HTTPS 91 | #https.proxyHost = 127.0.0.1 92 | #https.proxyPort = 1080 93 | 94 | # SOCKS 代理,支持 HTTP 和 HTTPS 请求 95 | # 注意:如果设置了 SOCKS 代理就不要设 HTTP/HTTPS 代理 96 | #socksProxyHost = 127.0.0.1 97 | #socksProxyPort = 1080 -------------------------------------------------------------------------------- /src/resources/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/background.png -------------------------------------------------------------------------------- /src/resources/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/favicon.png -------------------------------------------------------------------------------- /src/resources/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/header.png -------------------------------------------------------------------------------- /src/resources/title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/title.png -------------------------------------------------------------------------------- /src/resources/x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/x.jpg -------------------------------------------------------------------------------- /src/resources/xh.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nICEnnnnnnnLee/AcFunDown/48bb354755074b5b51087dd12df0f501b5ca6434/src/resources/xh.jpg --------------------------------------------------------------------------------