diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml new file mode 100644 index 0000000..9b5ed4c --- /dev/null +++ b/.github/workflows/dotnet-desktop.yml @@ -0,0 +1,117 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow will build, test, sign and package a WPF or Windows Forms desktop application +# built on .NET Core. +# To learn how to migrate your existing application to .NET Core, +# refer to https://docs.microsoft.com/en-us/dotnet/desktop-wpf/migration/convert-project-from-net-framework +# +# To configure this workflow: +# +# 1. Configure environment variables +# GitHub sets default environment variables for every workflow run. +# Replace the variables relative to your project in the "env" section below. +# +# 2. Signing +# Generate a signing certificate in the Windows Application +# Packaging Project or add an existing signing certificate to the project. +# Next, use PowerShell to encode the .pfx file using Base64 encoding +# by running the following Powershell script to generate the output string: +# +# $pfx_cert = Get-Content '.\SigningCertificate.pfx' -Encoding Byte +# [System.Convert]::ToBase64String($pfx_cert) | Out-File 'SigningCertificate_Encoded.txt' +# +# Open the output file, SigningCertificate_Encoded.txt, and copy the +# string inside. Then, add the string to the repo as a GitHub secret +# and name it "Base64_Encoded_Pfx." +# For more information on how to configure your signing certificate for +# this workflow, refer to https://github.com/microsoft/github-actions-for-desktop-apps#signing +# +# Finally, add the signing certificate password to the repo as a secret and name it "Pfx_Key". +# See "Build the Windows Application Packaging project" below to see how the secret is used. +# +# For more information on GitHub Actions, refer to https://github.com/features/actions +# For a complete CI/CD sample to get started with GitHub Action workflows for Desktop Applications, +# refer to https://github.com/microsoft/github-actions-for-desktop-apps + +name: .NET Core Desktop + +on: + push: + tags: + - 'v*.*.*' # 仅在推送版本标签时触发 + +jobs: + + build: + + strategy: + matrix: + configuration: [ Release] + + runs-on: windows-latest # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + env: + Solution_Name: src/BlueCatKoKo.sln # Replace with your solution name, i.e. MyWpfApp.sln. + Wap_Project_Directory: src/BlueCatKoKo.Ui/ # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package. + Wap_Project_Path: src/BlueCatKoKo.Ui/BlueCatKoKo.Ui.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj. + Wap_Project_Release_Directory: src/BlueCatKoKo.Ui/bin/Release/net8.0-windows + App_Name: BlueCatKoKo + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install .NET Core + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + + - name: Restore NuGet Packages + run: dotnet restore $env:Solution_Name + + - name: Setup MSBuild.exe + uses: microsoft/setup-msbuild@v2 + + - name: Restore the application + run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration + env: + Configuration: ${{ matrix.configuration }} + + - name: Decode the pfx + run: | + $pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}") + $certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx + [IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte) + + - name: Create the app package + run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }} + env: + Appx_Bundle: Always + Appx_Bundle_Platforms: x86|x64 + Appx_Package_Build_Mode: StoreUpload + Configuration: ${{ matrix.configuration }} + + - name: Remove the pfx + run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx + + - name: Create ZIP file + run: | + Compress-Archive -Path $env:Wap_Project_Release_Directory/* -DestinationPath $env:Wap_Project_Release_Directory/$env:App_Name.zip + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ env.App_Name }} + path: ${{ env.Wap_Project_Release_Directory }}/${{ env.App_Name }}.zip + + - name: Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: ${{ env.Wap_Project_Release_Directory }}/${{ env.App_Name }}.zip \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f27a85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +### Example user template template +### Example user template + +# IntelliJ project files +.idea +*.iml +out +gen diff --git a/README.md b/README.md new file mode 100644 index 0000000..89ae041 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# 蓝猫KoKo下载器 + +![_20240824121208.png](https://s2.loli.net/2024/08/24/DnSyxsaIHd972oL.jpg) + +蓝猫KoKo下载器(BlueCatKoKo)是一个简单易用的抖音视频下载工具,具有简洁的界面,流畅的操作逻辑。可以下载几乎所有的抖音视频,并输出mp4格式的文件。 +> 因为包含了LibVLC库播放视频,所以比较大 +## 下载 +https://github.com/K12f/BlueCatKoKo/releases + +## 免责申明 + +1. 本软件只提供视频解析,不提供任何资源上传、存储到服务器的功能。 +2. 本软件仅解析来自抖音的内容,不会对解析到的音视频进行二次编码,部分视频会进行有限的格式转换、拼接等操作。 +3. 本软件解析得到的所有内容均来自抖音UP主上传、分享,其版权均归原作者所有。内容提供者、上传者(UP主)应对其提供、上传的内容承担全部责任。 +4. **本软件提供的所有内容,仅可用作学习交流使用,未经原作者授权,禁止用于其他用途。请在下载24小时内删除。为尊重作者版权,请前往资源的原始发布网站观看,支持原创,谢谢。** +5. 因使用本软件产生的版权问题,软件作者概不负责。 diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..0890165 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,401 @@ +### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +.idea diff --git a/src/BlueCatKoKo.Ui/.gitignore b/src/BlueCatKoKo.Ui/.gitignore new file mode 100644 index 0000000..5e8067b --- /dev/null +++ b/src/BlueCatKoKo.Ui/.gitignore @@ -0,0 +1,801 @@ +### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +.idea### Csharp template +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +.idea \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/App.xaml b/src/BlueCatKoKo.Ui/App.xaml new file mode 100644 index 0000000..bd6d013 --- /dev/null +++ b/src/BlueCatKoKo.Ui/App.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/App.xaml.cs b/src/BlueCatKoKo.Ui/App.xaml.cs new file mode 100644 index 0000000..4230440 --- /dev/null +++ b/src/BlueCatKoKo.Ui/App.xaml.cs @@ -0,0 +1,120 @@ +using System.Windows; +using System.Windows.Threading; + +using BlueCatKoKo.Ui.Models; +using BlueCatKoKo.Ui.Services; +using BlueCatKoKo.Ui.ViewModels; +using BlueCatKoKo.Ui.ViewModels.Pages; +using BlueCatKoKo.Ui.Views; +using BlueCatKoKo.Ui.Views.Pages; + +using CommunityToolkit.Mvvm.Messaging; + +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +using Serilog; + +using Wpf.Ui; + +using HomeViewModel = BlueCatKoKo.Ui.ViewModels.Pages.HomeViewModel; +using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +namespace BlueCatKoKo.Ui +{ + using HomeViewModel = ViewModels.Pages.HomeViewModel; + + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + private static readonly IHost _host = Host.CreateDefaultBuilder() + .ConfigureHostConfiguration(builder => + { + // builder.AddJsonFile("appsettings.json"); + }) + .ConfigureLogging(logging => + { + logging.ClearProviders(); + Log.Logger = new LoggerConfiguration() + .WriteTo.Console() + .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day) + .CreateLogger(); + logging.Services.AddSingleton(Log.Logger); + }) + .ConfigureServices((context, container) => + { + container.AddHostedService(); + // configuration + container.Configure(context.Configuration.GetSection(nameof(AppConfig))); + container.AddSingleton(); + + // Service containing + container.AddSingleton(); + container.AddSingleton(); + + // Main window with navigation + container.AddSingleton(); + container.AddSingleton(); + + + //view and viewmodels + container.AddSingleton(); + container.AddSingleton(); + + container.AddSingleton(); + container.AddSingleton(); + + container.AddSingleton(); + container.AddSingleton(); + + container.AddSingleton(); + container.AddSingleton(); + + container.AddSingleton(); + container.AddSingleton(); + + // messager + container.AddSingleton(); + container.AddSingleton(sp => sp.GetRequiredService()); + + container.AddSingleton(_ => Current.Dispatcher); + }).Build(); + + public static new App Current => (App)Application.Current; + + /// + /// Gets services. + /// + public static IServiceProvider Services => _host.Services; + + + /// + /// Occurs when the application is loading. + /// + private async void OnStartup(object sender, StartupEventArgs e) + { + await _host.StartAsync(); + } + + /// + /// Occurs when the application is closing. + /// + private async void OnExit(object sender, ExitEventArgs e) + { + await _host.StopAsync(); + + _host.Dispose(); + } + + /// + /// Occurs when an exception is thrown by an application but not handled. + /// + private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) + { + // For more info see https://docs.microsoft.com/en-us/dotnet/api/system.windows.application.dispatcherunhandledexception?view=windowsdesktop-6.0 + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/AssemblyInfo.cs b/src/BlueCatKoKo.Ui/AssemblyInfo.cs new file mode 100644 index 0000000..4a05c7d --- /dev/null +++ b/src/BlueCatKoKo.Ui/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Windows; + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Assets/applicationicon.jpg b/src/BlueCatKoKo.Ui/Assets/applicationicon.jpg new file mode 100644 index 0000000..774677a Binary files /dev/null and b/src/BlueCatKoKo.Ui/Assets/applicationicon.jpg differ diff --git a/src/BlueCatKoKo.Ui/Assets/douyin.js b/src/BlueCatKoKo.Ui/Assets/douyin.js new file mode 100644 index 0000000..357c508 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Assets/douyin.js @@ -0,0 +1,434 @@ +// All the content in this article is only for learning and communication use, not for any other purpose, strictly prohibited for commercial use and illegal use, otherwise all the consequences are irrelevant to the author! +function rc4_encrypt(plaintext, key) { + var s = []; + for (var i = 0; i < 256; i++) { + s[i] = i; + } + var j = 0; + for (var i = 0; i < 256; i++) { + j = (j + s[i] + key.charCodeAt(i % key.length)) % 256; + var temp = s[i]; + s[i] = s[j]; + s[j] = temp; + } + + var i = 0; + var j = 0; + var cipher = []; + for (var k = 0; k < plaintext.length; k++) { + i = (i + 1) % 256; + j = (j + s[i]) % 256; + var temp = s[i]; + s[i] = s[j]; + s[j] = temp; + var t = (s[i] + s[j]) % 256; + cipher.push(String.fromCharCode(s[t] ^ plaintext.charCodeAt(k))); + } + return cipher.join(''); +} + +function le(e, r) { + return (e << (r %= 32) | e >>> 32 - r) >>> 0 +} + +function de(e) { + return 0 <= e && e < 16 ? 2043430169 : 16 <= e && e < 64 ? 2055708042 : void console['error']("invalid j for constant Tj") +} + +function pe(e, r, t, n) { + return 0 <= e && e < 16 ? (r ^ t ^ n) >>> 0 : 16 <= e && e < 64 ? (r & t | r & n | t & n) >>> 0 : (console['error']('invalid j for bool function FF'), + 0) +} + +function he(e, r, t, n) { + return 0 <= e && e < 16 ? (r ^ t ^ n) >>> 0 : 16 <= e && e < 64 ? (r & t | ~r & n) >>> 0 : (console['error']('invalid j for bool function GG'), + 0) +} + +function reset() { + this.reg[0] = 1937774191, + this.reg[1] = 1226093241, + this.reg[2] = 388252375, + this.reg[3] = 3666478592, + this.reg[4] = 2842636476, + this.reg[5] = 372324522, + this.reg[6] = 3817729613, + this.reg[7] = 2969243214, + this["chunk"] = [], + this["size"] = 0 +} + +function write(e) { + var a = "string" == typeof e ? function (e) { + n = encodeURIComponent(e)['replace'](/%([0-9A-F]{2})/g, (function (e, r) { + return String['fromCharCode']("0x" + r) + } + )) + , a = new Array(n['length']); + return Array['prototype']['forEach']['call'](n, (function (e, r) { + a[r] = e.charCodeAt(0) + } + )), + a + }(e) : e; + this.size += a.length; + var f = 64 - this['chunk']['length']; + if (a['length'] < f) + this['chunk'] = this['chunk'].concat(a); + else + for (this['chunk'] = this['chunk'].concat(a.slice(0, f)); this['chunk'].length >= 64;) + this['_compress'](this['chunk']), + f < a['length'] ? this['chunk'] = a['slice'](f, Math['min'](f + 64, a['length'])) : this['chunk'] = [], + f += 64 +} + +function sum(e, t) { + e && (this['reset'](), + this['write'](e)), + this['_fill'](); + for (var f = 0; f < this.chunk['length']; f += 64) + this._compress(this['chunk']['slice'](f, f + 64)); + var i = null; + if (t == 'hex') { + i = ""; + for (f = 0; f < 8; f++) + i += se(this['reg'][f]['toString'](16), 8, "0") + } else + for (i = new Array(32), + f = 0; f < 8; f++) { + var c = this.reg[f]; + i[4 * f + 3] = (255 & c) >>> 0, + c >>>= 8, + i[4 * f + 2] = (255 & c) >>> 0, + c >>>= 8, + i[4 * f + 1] = (255 & c) >>> 0, + c >>>= 8, + i[4 * f] = (255 & c) >>> 0 + } + return this['reset'](), + i +} + +function _compress(t) { + if (t < 64) + console.error("compress error: not enough data"); + else { + for (var f = function (e) { + for (var r = new Array(132), t = 0; t < 16; t++) + r[t] = e[4 * t] << 24, + r[t] |= e[4 * t + 1] << 16, + r[t] |= e[4 * t + 2] << 8, + r[t] |= e[4 * t + 3], + r[t] >>>= 0; + for (var n = 16; n < 68; n++) { + var a = r[n - 16] ^ r[n - 9] ^ le(r[n - 3], 15); + a = a ^ le(a, 15) ^ le(a, 23), + r[n] = (a ^ le(r[n - 13], 7) ^ r[n - 6]) >>> 0 + } + for (n = 0; n < 64; n++) + r[n + 68] = (r[n] ^ r[n + 4]) >>> 0; + return r + }(t), i = this['reg'].slice(0), c = 0; c < 64; c++) { + var o = le(i[0], 12) + i[4] + le(de(c), c) + , s = ((o = le(o = (4294967295 & o) >>> 0, 7)) ^ le(i[0], 12)) >>> 0 + , u = pe(c, i[0], i[1], i[2]); + u = (4294967295 & (u = u + i[3] + s + f[c + 68])) >>> 0; + var b = he(c, i[4], i[5], i[6]); + b = (4294967295 & (b = b + i[7] + o + f[c])) >>> 0, + i[3] = i[2], + i[2] = le(i[1], 9), + i[1] = i[0], + i[0] = u, + i[7] = i[6], + i[6] = le(i[5], 19), + i[5] = i[4], + i[4] = (b ^ le(b, 9) ^ le(b, 17)) >>> 0 + } + for (var l = 0; l < 8; l++) + this['reg'][l] = (this['reg'][l] ^ i[l]) >>> 0 + } +} + +function _fill() { + var a = 8 * this['size'] + , f = this['chunk']['push'](128) % 64; + for (64 - f < 8 && (f -= 64); f < 56; f++) + this.chunk['push'](0); + for (var i = 0; i < 4; i++) { + var c = Math['floor'](a / 4294967296); + this['chunk'].push(c >>> 8 * (3 - i) & 255) + } + for (i = 0; i < 4; i++) + this['chunk']['push'](a >>> 8 * (3 - i) & 255) + +} + +function SM3() { + this.reg = []; + this.chunk = []; + this.size = 0; + this.reset() +} +SM3.prototype.reset = reset; +SM3.prototype.write = write; +SM3.prototype.sum = sum; +SM3.prototype._compress = _compress; +SM3.prototype._fill = _fill; + +function result_encrypt(long_str, num = null) { + let s_obj = { + "s0": "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + "s1": "Dkdpgh4ZKsQB80/Mfvw36XI1R25+WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=", + "s2": "Dkdpgh4ZKsQB80/Mfvw36XI1R25-WUAlEi7NLboqYTOPuzmFjJnryx9HVGcaStCe=", + "s3": "ckdp1h4ZKsUB80/Mfvw36XIgR25+WQAlEi7NLboqYTOPuzmFjJnryx9HVGDaStCe", + "s4": "Dkdpgh2ZmsQB80/MfvV36XI1R45-WUAlEixNLwoqYTOPuzKFjJnry79HbGcaStCe" + } + let constant = { + "0": 16515072, + "1": 258048, + "2": 4032, + "str": s_obj[num], + } + + let result = ""; + let lound = 0; + let long_int = get_long_int(lound, long_str); + for (let i = 0; i < long_str.length / 3 * 4; i++) { + if (Math.floor(i / 4) !== lound) { + lound += 1; + long_int = get_long_int(lound, long_str); + } + let key = i % 4; + switch (key) { + case 0: + temp_int = (long_int & constant["0"]) >> 18; + result += constant["str"].charAt(temp_int); + break; + case 1: + temp_int = (long_int & constant["1"]) >> 12; + result += constant["str"].charAt(temp_int); + break; + case 2: + temp_int = (long_int & constant["2"]) >> 6; + result += constant["str"].charAt(temp_int); + break; + case 3: + temp_int = long_int & 63; + result += constant["str"].charAt(temp_int); + break; + default: + break; + } + } + return result; +} + +function get_long_int(round, long_str) { + round = round * 3; + return (long_str.charCodeAt(round) << 16) | (long_str.charCodeAt(round + 1) << 8) | (long_str.charCodeAt(round + 2)); +} + +function gener_random(random, option) { + return [ + (random & 255 & 170) | option[0] & 85, // 163 + (random & 255 & 85) | option[0] & 170, //87 + (random >> 8 & 255 & 170) | option[1] & 85, //37 + (random >> 8 & 255 & 85) | option[1] & 170, //41 + ] +} + +////////////////////////////////////////////// +function generate_rc4_bb_str(url_search_params, user_agent, window_env_str, suffix = "cus", Arguments = [0, 1, 14]) { + let sm3 = new SM3() + let start_time = Date.now() + /** + * 进行3次加密处理 + * 1: url_search_params两次sm3之的结果 + * 2: 对后缀两次sm3之的结果 + * 3: 对ua处理之后的结果 + */ + // url_search_params两次sm3之的结果 + let url_search_params_list = sm3.sum(sm3.sum(url_search_params + suffix)) + // 对后缀两次sm3之的结果 + let cus = sm3.sum(sm3.sum(suffix)) + // 对ua处理之后的结果 + let ua = sm3.sum(result_encrypt(rc4_encrypt(user_agent, String.fromCharCode.apply(null, [0.00390625, 1, Arguments[2]])), "s3")) + // + let end_time = Date.now() + // b + let b = { + 8: 3, // 固定 + 10: end_time, //3次加密结束时间 + 15: { + "aid": 6383, + "pageId": 6241, + "boe": false, + "ddrt": 7, + "paths": { + "include": [ + {}, + {}, + {}, + {}, + {}, + {}, + {} + ], + "exclude": [] + }, + "track": { + "mode": 0, + "delay": 300, + "paths": [] + }, + "dump": true, + "rpU": "" + }, + 16: start_time, //3次加密开始时间 + 18: 44, //固定 + 19: [1, 0, 1, 5], + } + + //3次加密开始时间 + b[20] = (b[16] >> 24) & 255 + b[21] = (b[16] >> 16) & 255 + b[22] = (b[16] >> 8) & 255 + b[23] = b[16] & 255 + b[24] = (b[16] / 256 / 256 / 256 / 256) >> 0 + b[25] = (b[16] / 256 / 256 / 256 / 256 / 256) >> 0 + + // 参数Arguments [0, 1, 14, ...] + // let Arguments = [0, 1, 14] + b[26] = (Arguments[0] >> 24) & 255 + b[27] = (Arguments[0] >> 16) & 255 + b[28] = (Arguments[0] >> 8) & 255 + b[29] = Arguments[0] & 255 + + b[30] = (Arguments[1] / 256) & 255 + b[31] = (Arguments[1] % 256) & 255 + b[32] = (Arguments[1] >> 24) & 255 + b[33] = (Arguments[1] >> 16) & 255 + + b[34] = (Arguments[2] >> 24) & 255 + b[35] = (Arguments[2] >> 16) & 255 + b[36] = (Arguments[2] >> 8) & 255 + b[37] = Arguments[2] & 255 + + // (url_search_params + "cus") 两次sm3之的结果 + /**let url_search_params_list = [ + 91, 186, 35, 86, 143, 253, 6, 76, + 34, 21, 167, 148, 7, 42, 192, 219, + 188, 20, 182, 85, 213, 74, 213, 147, + 37, 155, 93, 139, 85, 118, 228, 213 + ]*/ + b[38] = url_search_params_list[21] + b[39] = url_search_params_list[22] + + // ("cus") 对后缀两次sm3之的结果 + /** + * let cus = [ + 136, 101, 114, 147, 58, 77, 207, 201, + 215, 162, 154, 93, 248, 13, 142, 160, + 105, 73, 215, 241, 83, 58, 51, 43, + 255, 38, 168, 141, 216, 194, 35, 236 + ]*/ + b[40] = cus[21] + b[41] = cus[22] + + // 对ua处理之后的结果 + /** + * let ua = [ + 129, 190, 70, 186, 86, 196, 199, 53, + 99, 38, 29, 209, 243, 17, 157, 69, + 147, 104, 53, 23, 114, 126, 66, 228, + 135, 30, 168, 185, 109, 156, 251, 88 + ]*/ + b[42] = ua[23] + b[43] = ua[24] + + //3次加密结束时间 + b[44] = (b[10] >> 24) & 255 + b[45] = (b[10] >> 16) & 255 + b[46] = (b[10] >> 8) & 255 + b[47] = b[10] & 255 + b[48] = b[8] + b[49] = (b[10] / 256 / 256 / 256 / 256) >> 0 + b[50] = (b[10] / 256 / 256 / 256 / 256 / 256) >> 0 + + + // object配置项 + b[51] = b[15]['pageId'] + b[52] = (b[15]['pageId'] >> 24) & 255 + b[53] = (b[15]['pageId'] >> 16) & 255 + b[54] = (b[15]['pageId'] >> 8) & 255 + b[55] = b[15]['pageId'] & 255 + + b[56] = b[15]['aid'] + b[57] = b[15]['aid'] & 255 + b[58] = (b[15]['aid'] >> 8) & 255 + b[59] = (b[15]['aid'] >> 16) & 255 + b[60] = (b[15]['aid'] >> 24) & 255 + + // 中间进行了环境检测 + // 代码索引: 2496 索引值: 17 (索引64关键条件) + // '1536|747|1536|834|0|30|0|0|1536|834|1536|864|1525|747|24|24|Win32'.charCodeAt()得到65位数组 + /** + * let window_env_list = [49, 53, 51, 54, 124, 55, 52, 55, 124, 49, 53, 51, 54, 124, 56, 51, 52, 124, 48, 124, 51, + * 48, 124, 48, 124, 48, 124, 49, 53, 51, 54, 124, 56, 51, 52, 124, 49, 53, 51, 54, 124, 56, + * 54, 52, 124, 49, 53, 50, 53, 124, 55, 52, 55, 124, 50, 52, 124, 50, 52, 124, 87, 105, 110, + * 51, 50] + */ + let window_env_list = []; + for (let index = 0; index < window_env_str.length; index++) { + window_env_list.push(window_env_str.charCodeAt(index)) + } + b[64] = window_env_list.length + b[65] = b[64] & 255 + b[66] = (b[64] >> 8) & 255 + + b[69] = [].length + b[70] = b[69] & 255 + b[71] = (b[69] >> 8) & 255 + + b[72] = b[18] ^ b[20] ^ b[26] ^ b[30] ^ b[38] ^ b[40] ^ b[42] ^ b[21] ^ b[27] ^ b[31] ^ b[35] ^ b[39] ^ b[41] ^ b[43] ^ b[22] ^ + b[28] ^ b[32] ^ b[36] ^ b[23] ^ b[29] ^ b[33] ^ b[37] ^ b[44] ^ b[45] ^ b[46] ^ b[47] ^ b[48] ^ b[49] ^ b[50] ^ b[24] ^ + b[25] ^ b[52] ^ b[53] ^ b[54] ^ b[55] ^ b[57] ^ b[58] ^ b[59] ^ b[60] ^ b[65] ^ b[66] ^ b[70] ^ b[71] + let bb = [ + b[18], b[20], b[52], b[26], b[30], b[34], b[58], b[38], b[40], b[53], b[42], b[21], b[27], b[54], b[55], b[31], + b[35], b[57], b[39], b[41], b[43], b[22], b[28], b[32], b[60], b[36], b[23], b[29], b[33], b[37], b[44], b[45], + b[59], b[46], b[47], b[48], b[49], b[50], b[24], b[25], b[65], b[66], b[70], b[71] + ] + bb = bb.concat(window_env_list).concat(b[72]) + return rc4_encrypt(String.fromCharCode.apply(null, bb), String.fromCharCode.apply(null, [121])); +} + +function generate_random_str() { + let random_str_list = [] + random_str_list = random_str_list.concat(gener_random(Math.random() * 10000, [3, 45])) + random_str_list = random_str_list.concat(gener_random(Math.random() * 10000, [1, 0])) + random_str_list = random_str_list.concat(gener_random(Math.random() * 10000, [1, 5])) + return String.fromCharCode.apply(null, random_str_list) +} + +function sign(url_search_params, user_agent, arguments) { + /** + * url_search_params:"device_platform=webapp&aid=6383&channel=channel_pc_web&update_version_code=170400&pc_client_type=1&version_code=170400&version_name=17.4.0&cookie_enabled=true&screen_width=1536&screen_height=864&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=123.0.0.0&browser_online=true&engine_name=Blink&engine_version=123.0.0.0&os_name=Windows&os_version=10&cpu_core_num=16&device_memory=8&platform=PC&downlink=10&effective_type=4g&round_trip_time=50&webid=7362810250930783783&msToken=VkDUvz1y24CppXSl80iFPr6ez-3FiizcwD7fI1OqBt6IICq9RWG7nCvxKb8IVi55mFd-wnqoNkXGnxHrikQb4PuKob5Q-YhDp5Um215JzlBszkUyiEvR" + * user_agent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36" + */ + let result_str = generate_random_str() + generate_rc4_bb_str( + url_search_params, + user_agent, + "1536|747|1536|834|0|30|0|0|1536|834|1536|864|1525|747|24|24|Win32", + "cus", + arguments + ); + return result_encrypt(result_str, "s4") + "="; +} + +function sign_datail(params, userAgent) { + return sign(params, userAgent, [0, 1, 14]) +} + +function sign_reply(params, userAgent) { + return sign(params, userAgent, [0, 1, 8]) +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/BlueCatKoKo.Ui.csproj b/src/BlueCatKoKo.Ui/BlueCatKoKo.Ui.csproj new file mode 100644 index 0000000..1228399 --- /dev/null +++ b/src/BlueCatKoKo.Ui/BlueCatKoKo.Ui.csproj @@ -0,0 +1,58 @@ + + + + WinExe + net8.0-windows + enable + enable + applicationIcon.ico + AnyCPU;x64 + true + + + + + + + + + + + + + + + + + + + + + + + BlueCatKoKo + + + + + Always + + + + + + + + + + + + + + + + + + diff --git a/src/BlueCatKoKo.Ui/Constants/DownloaderEnum.cs b/src/BlueCatKoKo.Ui/Constants/DownloaderEnum.cs new file mode 100644 index 0000000..373e6f6 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Constants/DownloaderEnum.cs @@ -0,0 +1,11 @@ +namespace BlueCatKoKo.Ui.Constants +{ + public enum DownloaderEnum + { + Init, + Downloading, + Success, + Warning, + Error, + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Models/AppConfig.cs b/src/BlueCatKoKo.Ui/Models/AppConfig.cs new file mode 100644 index 0000000..8adf856 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Models/AppConfig.cs @@ -0,0 +1,13 @@ +namespace BlueCatKoKo.Ui.Models +{ + public class AppConfig + { + public string? Name { get; set; } + public string? TrayTitle { get; set; } + public string? Version { get; set; } + public string? Description { get; set; } + public string? DownloadPath { get; set; } + public string? RepositoryUrl { get; set; } + public List? Cookies { get; set; } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Models/DouyinShareRouterData.cs b/src/BlueCatKoKo.Ui/Models/DouyinShareRouterData.cs new file mode 100644 index 0000000..d4af007 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Models/DouyinShareRouterData.cs @@ -0,0 +1,297 @@ +using Newtonsoft.Json; + +namespace BlueCatKoKo.Ui.Models +{ + /// + /// 抖音 分享文本中的视频数据 + /// + public class DouyinShareRouterData + { + [JsonProperty("loaderData")] public LoaderData LoaderData { get; set; } + } + + public class LoaderData + { + [JsonProperty("video_(id)/page")] public VideoIdPage VideoIdPage { get; set; } + } + + public class VideoIdPage + { + [JsonProperty("isAutoOpenApp")] public bool IsAutoOpenApp { get; set; } + + [JsonProperty("appName")] public string AppName { get; set; } + + [JsonProperty("query")] public Query Query { get; set; } + + [JsonProperty("isSpider")] public bool IsSpider { get; set; } + + [JsonProperty("ua")] public string Ua { get; set; } + + [JsonProperty("isVideoOptimize")] public bool IsVideoOptimize { get; set; } + + [JsonProperty("commonContext")] public CommonContext CommonContext { get; set; } + + [JsonProperty("itemId")] public string ItemId { get; set; } + + [JsonProperty("webId")] public string WebId { get; set; } + + [JsonProperty("renderInSSR")] public long RenderInSsr { get; set; } + + [JsonProperty("host")] public string Host { get; set; } + + [JsonProperty("videoInfoRes")] public VideoInfoRes VideoInfoRes { get; set; } + + [JsonProperty("darkModeAdaptation")] public bool DarkModeAdaptation { get; set; } + + [JsonProperty("lastPath")] public string LastPath { get; set; } + + [JsonProperty("isNotSupportWebp")] public bool IsNotSupportWebp { get; set; } + + [JsonProperty("serverToken")] public string ServerToken { get; set; } + } + + public class CommonContext + { + [JsonProperty("webId")] public string WebId { get; set; } + + [JsonProperty("renderInSSR")] public long RenderInSsr { get; set; } + + [JsonProperty("appName")] public string AppName { get; set; } + + [JsonProperty("query")] public Query Query { get; set; } + + [JsonProperty("host")] public string Host { get; set; } + + [JsonProperty("isSpider")] public bool IsSpider { get; set; } + + [JsonProperty("lastPath")] public string LastPath { get; set; } + + [JsonProperty("isNotSupportWebp")] public bool IsNotSupportWebp { get; set; } + + [JsonProperty("ua")] public string Ua { get; set; } + } + + public class Query + { + [JsonProperty("titleType")] public string TitleType { get; set; } + + [JsonProperty("iid")] public string Iid { get; set; } + + [JsonProperty("u_code")] public string UCode { get; set; } + + [JsonProperty("from_ssr")] public long FromSsr { get; set; } + + [JsonProperty("mid")] public string Mid { get; set; } + + [JsonProperty("share_sign")] public string ShareSign { get; set; } + + [JsonProperty("with_sec_did")] public long WithSecDid { get; set; } + + [JsonProperty("from_aid")] public long FromAid { get; set; } + + [JsonProperty("from")] public string From { get; set; } + + [JsonProperty("region")] public string Region { get; set; } + + [JsonProperty("did")] public string Did { get; set; } + + [JsonProperty("share_version")] public long ShareVersion { get; set; } + + [JsonProperty("ts")] public long Ts { get; set; } + } + + public class VideoInfoRes + { + [JsonProperty("status_code")] public long StatusCode { get; set; } + + [JsonProperty("filter_list")] public List FilterList { get; set; } + + [JsonProperty("item_list")] public List ItemList { get; set; } + + [JsonProperty("extra")] public Extra Extra { get; set; } + } + + public class Extra + { + [JsonProperty("now")] public long Now { get; set; } + } + + public class ItemList + { + [JsonProperty("video")] public Video Video { get; set; } + + [JsonProperty("music")] public Music Music { get; set; } + + [JsonProperty("create_time")] public long CreateTime { get; set; } + + [JsonProperty("author")] public Author Author { get; set; } + + [JsonProperty("risk_infos")] public RiskInfos RiskInfos { get; set; } + + [JsonProperty("mix_info")] public MixInfo MixInfo { get; set; } + + [JsonProperty("aweme_id")] public string AwemeId { get; set; } + + [JsonProperty("group_id_str")] public string GroupIdStr { get; set; } + + [JsonProperty("text_extra")] public List TextExtra { get; set; } + + [JsonProperty("aweme_type")] public long AwemeType { get; set; } + + [JsonProperty("desc")] public string Desc { get; set; } + + [JsonProperty("statistics")] public Statistics Statistics { get; set; } + } + + public class Author + { + [JsonProperty("avatar_medium")] public AvatarMedium AvatarMedium { get; set; } + + [JsonProperty("unique_id")] public string UniqueId { get; set; } + + [JsonProperty("sec_uid")] public string SecUid { get; set; } + + [JsonProperty("signature")] public string Signature { get; set; } + + [JsonProperty("mplatform_followers_count")] + public long MplatformFollowersCount { get; set; } + + [JsonProperty("avatar_thumb")] public AvatarMedium AvatarThumb { get; set; } + + [JsonProperty("follow_status")] public long FollowStatus { get; set; } + + [JsonProperty("short_id")] public string ShortId { get; set; } + + [JsonProperty("following_count")] public long FollowingCount { get; set; } + + [JsonProperty("nickname")] public string Nickname { get; set; } + + [JsonProperty("favoriting_count")] public long FavoritingCount { get; set; } + } + + public class AvatarMedium + { + [JsonProperty("url_list")] public List UrlList { get; set; } + + [JsonProperty("uri")] public string Uri { get; set; } + } + + public class MixInfo + { + [JsonProperty("mix_id")] public string MixId { get; set; } + + [JsonProperty("cover_url")] public AvatarMedium CoverUrl { get; set; } + + [JsonProperty("next_info")] public NextInfo NextInfo { get; set; } + + [JsonProperty("statis")] public Statis Statis { get; set; } + + [JsonProperty("create_time")] public long CreateTime { get; set; } + + [JsonProperty("extra")] public string Extra { get; set; } + + [JsonProperty("mix_name")] public string MixName { get; set; } + + [JsonProperty("status")] public Status Status { get; set; } + + [JsonProperty("desc")] public string Desc { get; set; } + } + + public class NextInfo + { + [JsonProperty("cover_url")] public AvatarMedium CoverUrl { get; set; } + + [JsonProperty("mix_name")] public string MixName { get; set; } + + [JsonProperty("desc")] public string Desc { get; set; } + } + + public class Statis + { + [JsonProperty("collect_vv")] public long CollectVv { get; set; } + + [JsonProperty("current_episode")] public long CurrentEpisode { get; set; } + + [JsonProperty("play_vv")] public long PlayVv { get; set; } + + [JsonProperty("updated_to_episode")] public long UpdatedToEpisode { get; set; } + } + + public class Status + { + [JsonProperty("is_collected")] public long IsCollected { get; set; } + + [JsonProperty("status")] public long StatusStatus { get; set; } + } + + public class Music + { + [JsonProperty("duration")] public long Duration { get; set; } + + [JsonProperty("author")] public string Author { get; set; } + + [JsonProperty("cover_medium")] public AvatarMedium CoverMedium { get; set; } + + [JsonProperty("mid")] public string Mid { get; set; } + + [JsonProperty("title")] public string Title { get; set; } + + [JsonProperty("cover_hd")] public AvatarMedium CoverHd { get; set; } + + [JsonProperty("cover_large")] public AvatarMedium CoverLarge { get; set; } + + [JsonProperty("cover_thumb")] public AvatarMedium CoverThumb { get; set; } + + [JsonProperty("status")] public long Status { get; set; } + } + + public class RiskInfos + { + [JsonProperty("warn")] public bool Warn { get; set; } + + [JsonProperty("type")] public long Type { get; set; } + + [JsonProperty("content")] public string Content { get; set; } + + [JsonProperty("reflow_unplayable")] public long ReflowUnplayable { get; set; } + } + + public class Statistics + { + [JsonProperty("comment_count")] public int CommentCount { get; set; } + + [JsonProperty("share_count")] public int ShareCount { get; set; } + + [JsonProperty("aweme_id")] public string AwemeId { get; set; } + + [JsonProperty("digg_count")] public int DiggCount { get; set; } + + [JsonProperty("play_count")] public int PlayCount { get; set; } + + [JsonProperty("collect_count")] public int CollectCount { get; set; } + } + + public class TextExtra + { + [JsonProperty("hashtag_id")] public long HashtagId { get; set; } + + [JsonProperty("start")] public long Start { get; set; } + + [JsonProperty("end")] public long End { get; set; } + + [JsonProperty("type")] public long Type { get; set; } + + [JsonProperty("hashtag_name")] public string HashtagName { get; set; } + } + + public class Video + { + [JsonProperty("cover")] public AvatarMedium Cover { get; set; } + + [JsonProperty("play_addr")] public AvatarMedium PlayAddr { get; set; } + + [JsonProperty("width")] public long Width { get; set; } + + [JsonProperty("height")] public long Height { get; set; } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Models/DownloaderMessage.cs b/src/BlueCatKoKo.Ui/Models/DownloaderMessage.cs new file mode 100644 index 0000000..9d511a8 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Models/DownloaderMessage.cs @@ -0,0 +1,18 @@ +using BlueCatKoKo.Ui.Constants; + +namespace BlueCatKoKo.Ui.Models +{ + /// + /// 下载器消息 + /// + /// + /// + /// + public record DownloaderMessage( + DownloaderEnum Type, + // 消息文本 + string Message, + // 链接 + string Url + ); +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Models/Pages/HomePageModel.cs b/src/BlueCatKoKo.Ui/Models/Pages/HomePageModel.cs new file mode 100644 index 0000000..558607f --- /dev/null +++ b/src/BlueCatKoKo.Ui/Models/Pages/HomePageModel.cs @@ -0,0 +1,50 @@ +namespace BlueCatKoKo.Ui.Models.Pages +{ + /// + /// home page页面数据 + /// + public class HomePageModel + { + // 视频ID + public string? VideoId { get; set; } + + // 作者 + public string? AuthorName { get; set; } + + // 头像 + public string? AuthorAvatar { get; set; } + + // 视频标题 + public string? Title { get; set; } + + // 封面 + public string? Cover { get; set; } + + // 收藏数 + public int? CollectCount { get; set; } + + // 点赞数 + public int? DiggCount { get; set; } + + // 分享数 + public int? ShareCount { get; set; } + + // 评论数 + public int? CommentCount { get; set; } + + // 视频地址 + public string? VideoUrl { get; set; } + + // 音频地址 + public string? Mp3Url { get; set; } + + // 创建时间 + public string? CreatedTime { get; set; } + + // 视频描述 + public string? Desc { get; set; } + + // 视频时长 + public string? Duration { get; set; } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Services/AppConfigService.cs b/src/BlueCatKoKo.Ui/Services/AppConfigService.cs new file mode 100644 index 0000000..47a9eca --- /dev/null +++ b/src/BlueCatKoKo.Ui/Services/AppConfigService.cs @@ -0,0 +1,57 @@ +using System.IO; + +using BlueCatKoKo.Ui.Models; + +using Newtonsoft.Json; + +using Serilog; + +namespace BlueCatKoKo.Ui.Services +{ + /// + /// 配置服务 + /// + public class AppConfigService + { + private readonly ILogger _logger; + private readonly ReaderWriterLockSlim _rwLock = new(); + private const string _appSettingsFile = "appsettings.json"; + + private static string StartUpPath { get; set; } = AppContext.BaseDirectory; + + public AppConfigService(ILogger logger) + { + _logger = logger; + } + + private static string Absolute(string relativePath) + { + return Path.Combine(StartUpPath, relativePath); + } + + public void Write(AppConfig config) + { + _rwLock.EnterWriteLock(); + try + { + var path = Absolute(config.DownloadPath ?? "./"); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + var file = Path.Combine(StartUpPath, _appSettingsFile); + _logger.Information("保存配置文件:{file}", file); + File.WriteAllText(file, JsonConvert.SerializeObject(config)); + } + catch (Exception e) + { + _logger.Error("保存配置文件失败:{error}", e.Message); + } + finally + { + _rwLock.ExitWriteLock(); + } + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Services/ApplicationHostService.cs b/src/BlueCatKoKo.Ui/Services/ApplicationHostService.cs new file mode 100644 index 0000000..95c9b64 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Services/ApplicationHostService.cs @@ -0,0 +1,61 @@ +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT. +// Copyright (C) Leszek Pomianowski and WPF UI Contributors. +// All Rights Reserved. + + +using System.Windows; + +using BlueCatKoKo.Ui.Views; +using BlueCatKoKo.Ui.Views.Pages; + +using Microsoft.Extensions.Hosting; + +using Wpf.Ui; + +namespace BlueCatKoKo.Ui.Services +{ + /// + /// Managed host of the application. + /// + public class ApplicationHostService(IServiceProvider serviceProvider) : IHostedService + { + private INavigationWindow? _navigationWindow; + + /// + /// Triggered when the application host is ready to start the service. + /// + /// Indicates that the start process has been aborted. + public async Task StartAsync(CancellationToken cancellationToken) + { + await HandleActivationAsync(); + } + + /// + /// Triggered when the application host is performing a graceful shutdown. + /// + /// Indicates that the shutdown process should no longer be graceful. + public async Task StopAsync(CancellationToken cancellationToken) + { + await Task.CompletedTask; + } + + /// + /// Creates main window during activation. + /// + private async Task HandleActivationAsync() + { + await Task.CompletedTask; + + if (!Application.Current.Windows.OfType().Any()) + { + _navigationWindow = (serviceProvider.GetService(typeof(INavigationWindow)) as INavigationWindow)!; + _navigationWindow!.ShowWindow(); + + _ = _navigationWindow.Navigate(typeof(HomePage)); + } + + await Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Services/CheckUpdateService.cs b/src/BlueCatKoKo.Ui/Services/CheckUpdateService.cs new file mode 100644 index 0000000..ca13d87 --- /dev/null +++ b/src/BlueCatKoKo.Ui/Services/CheckUpdateService.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace BlueCatKoKo.Ui.Services +{ + public class CheckUpdateService:BackgroundService + { + private readonly ILogger _logger; + + public CheckUpdateService(ILogger logger) + { + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(3),stoppingToken); + + _logger.LogInformation("Checking for updated"); + } + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/Services/DouyinDownloaderService.cs b/src/BlueCatKoKo.Ui/Services/DouyinDownloaderService.cs new file mode 100644 index 0000000..9a3beac --- /dev/null +++ b/src/BlueCatKoKo.Ui/Services/DouyinDownloaderService.cs @@ -0,0 +1,151 @@ +using System.ComponentModel; +using System.IO; +using System.Net.Http; +using System.Text.RegularExpressions; + +using BlueCatKoKo.Ui.Models; + +using Downloader; + +using Newtonsoft.Json; + +using RestSharp; + +using Serilog; + +namespace BlueCatKoKo.Ui.Services +{ + public class DouyinDownloaderService + { + private static readonly Dictionary _defaultHeaders = new() + { + { + "User-Agent", + "Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36" + }, + { "sec-fetch-site", "same-origin" }, + { "sec-fetch-mode", "cors" }, + { "sec-fetch-dest", "empty" }, + { "sec-ch-ua-platform", "Windows" }, + { "sec-ch-ua-mobile", "?0" }, + { "sec-ch-ua", "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"" }, + { "referer", "https://www.douyin.com/?recommend=1" }, + { "priority", "u=1, i" }, + { "pragma", "no-cache" }, + { "cache-control", "no-cache" }, + { "accept-language", "zh-CN,zh;q=0.9,en;q=0.8" }, + { + "accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + }, + { "dnt", "1" } + }; + + private readonly ILogger _logger; + + public DouyinDownloaderService(ILogger logger) + { + _logger = logger; + } + + /// + /// 解析dy分享中的文本 6.17 05/18 U@L.wf fbn:/ 悬疑推理:亡者和自己的手机上午一同下葬,下午却给警察发来短信 本期的故事,来自于高分推理神剧《天堂岛疑云》中的谜案《亡灵的短信》。# 悬疑推理 # 每日推荐电影 + /// # 一剪到底 https://v.douyin.com/ircqoExo/ 复制此链接,打开Dou音搜索,直接观看视频! + /// https://v.douyin.com/ircqoExo/ + /// + /// + /// + public async Task ExtractUrlAsync(string text) + { + _logger.Information("开始解析文本 {text}", text); + return Regex.Match(text, @"https?://[^\s]+").Value; + } + + /// + /// 根据链接 https://v.douyin.com/ircqoExo/ 解析出页面中的数据 + /// + /// + /// + public async Task ExtractVideoDataAsync(string url) + { + try + { + _logger.Information("开始解析链接 {url}", url); + // 创建RestClient + RestClient client = new RestClient(url); + + // 创建请求对象 + RestRequest request = new RestRequest(); + + // 设置 User-Agent 模拟手机浏览器 + request.AddHeaders(_defaultHeaders); + + // 发送请求并获取响应 + RestResponse response = client.Execute(request); + if (!response.IsSuccessful) + { + throw new HttpRequestException("request is fail"); + } + + string? content = response.Content; + _logger.Information("开始解析响应内容 {content}", content); + if (content is null) + { + throw new InvalidDataException("content is null"); + } + + const string routerDataPattern = @"_ROUTER_DATA\s*=\s*(\{.*?\});"; + + Match matchJson = Regex.Match(content, routerDataPattern); + + _logger.Information("开始解析匹配到的json {matchJson}", matchJson); + if (matchJson.Groups.Count < 2) + { + throw new InvalidDataException("未匹配到合法的数据,matchJson.Groups.Count < 2"); + } + + string videoJson = matchJson.Groups[1].Value; + _logger.Information("开始解析匹配到的json {videoJson}", videoJson); + // 反序列化JSON字符串为C#对象 + return JsonConvert.DeserializeObject(videoJson); + } + catch (Exception e) + { + _logger.Error(e.Message); + throw; + } + } + + public async Task Download(string url, string savePath, string fileName, + EventHandler onProgressChanged, + EventHandler onProgressCompleted + ) + { + var downloadConfiguration = new DownloadConfiguration + { + ChunkCount = 8, // Download in 8 chunks (increase for larger files) + MaxTryAgainOnFailover = 5, // Retry up to 5 times on failure + Timeout = 10000, // 10 seconds timeout for each request + RequestConfiguration = new RequestConfiguration() + { + UserAgent = _defaultHeaders.GetValueOrDefault("User-Agent"), + } + }; + + var downloader = new DownloadService(downloadConfiguration); + + downloader.DownloadProgressChanged += onProgressChanged; + downloader.DownloadFileCompleted += onProgressCompleted; + + try + { + await downloader.DownloadFileTaskAsync(url, savePath + fileName); + } + catch (Exception ex) + { + _logger.Information("Download failed: {ex}", ex); + throw; + } + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/ViewModels/MainWindowViewModel.cs b/src/BlueCatKoKo.Ui/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..c0095fc --- /dev/null +++ b/src/BlueCatKoKo.Ui/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,135 @@ +using System.Collections.ObjectModel; + +using BlueCatKoKo.Ui.Constants; +using BlueCatKoKo.Ui.Models; +using BlueCatKoKo.Ui.Views.Pages; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using CommunityToolkit.Mvvm.Messaging.Messages; + +using HandyControl.Controls; +using HandyControl.Data; + +using Microsoft.Extensions.Options; + +using Serilog; + +using Wpf.Ui; +using Wpf.Ui.Controls; + +namespace BlueCatKoKo.Ui.ViewModels +{ + [ObservableRecipient] + public partial class MainWindowViewModel : ViewModelBase, IRecipient> + { + private readonly IOptions _appConfig; + private readonly ILogger _logger; + private readonly INavigationService _navigationService; + + [ObservableProperty] private string _applicationTitle = string.Empty; + [ObservableProperty] private string _applicationTrayTitle = string.Empty; + + private bool _isInitialized; + + [ObservableProperty] private ObservableCollection _navigationFooter = []; + + [ObservableProperty] private ObservableCollection _navigationItems = []; + + [ObservableProperty] private ObservableCollection _trayMenuItems = []; + + public MainWindowViewModel(INavigationService navigationService, IOptions appConfig, ILogger logger, + IMessenger messenger) + { + Messenger = messenger; + IsActive = true; + + _navigationService = navigationService; + + _appConfig = appConfig; + + _logger = logger; + + ApplicationTitle = _appConfig.Value.Name ?? ""; + ApplicationTrayTitle = _appConfig.Value.TrayTitle ?? ""; + if (!_isInitialized) + { + InitializeViewModel(); + } + } + + public void Receive(ValueChangedMessage message) + { + DownloaderEnum type = message.Value.Type; + string notifyMessage = message.Value.Message; + string url = message.Value.Url; + GrowlInfo growlInfo = new GrowlInfo { Message = notifyMessage, ShowDateTime = true, WaitTime = 2 }; + + _logger.Information("Receive: {Type}, Message: {Message}, Url: {Url}", type, notifyMessage, url); + + // 使用 switch 表达式 + switch (type) + { + case DownloaderEnum.Init or DownloaderEnum.Downloading: + Growl.Info(growlInfo); + break; + case DownloaderEnum.Success: + Growl.Success(growlInfo); + break; + case DownloaderEnum.Warning: + Growl.Warning(growlInfo); + break; + case DownloaderEnum.Error: + Growl.Error(growlInfo); + break; + default: + // 记录未知类型的信息 + Growl.Warning(growlInfo); + break; + } + } + + private void InitializeViewModel() + { + NavigationItems = + [ + new NavigationViewItem + { + Content = "首页", + Icon = new SymbolIcon { Symbol = SymbolRegular.Home24 }, + TargetPageType = typeof(HomePage) + }, + // new NavigationViewItem + // { + // Content = "视频", + // Icon = new SymbolIcon { Symbol = SymbolRegular.Video20 }, + // TargetPageType = typeof(VideoPage) + // }, + // new NavigationViewItem + // { + // Content = "Cookie管理", + // Icon = new SymbolIcon { Symbol = SymbolRegular.Cookies16 }, + // TargetPageType = typeof(CookiesPage) + // }, + new NavigationViewItem + { + Content = "关于", + Icon = new SymbolIcon { Symbol = SymbolRegular.Person12 }, + TargetPageType = typeof(AboutPage) + } + ]; + + NavigationFooter = + [ + new NavigationViewItem + { + Content = "设置", + Icon = new SymbolIcon { Symbol = SymbolRegular.Settings24 }, + TargetPageType = typeof(SettingsPage) + } + ]; + + _isInitialized = true; + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/ViewModels/Pages/AboutViewModel.cs b/src/BlueCatKoKo.Ui/ViewModels/Pages/AboutViewModel.cs new file mode 100644 index 0000000..d635b24 --- /dev/null +++ b/src/BlueCatKoKo.Ui/ViewModels/Pages/AboutViewModel.cs @@ -0,0 +1,54 @@ +using System.Diagnostics; + +using BlueCatKoKo.Ui.Services; + +using BlueCatKoKo.Ui.Models; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +using Microsoft.Extensions.Options; + +using Serilog; + +namespace BlueCatKoKo.Ui.ViewModels.Pages +{ + public partial class AboutViewModel : ViewModelBase + { + private readonly IOptions _appConfig; + private readonly ILogger _logger; + + [ObservableProperty] private string _aboutText; + + [ObservableProperty] private string _currentVersion; + + [ObservableProperty] private string _repositoryUrl; + + public AboutViewModel(ILogger logger, + IOptions appConfig) + { + _logger = logger; + _appConfig = appConfig; + + CurrentVersion = _appConfig.Value.Version; + RepositoryUrl = _appConfig.Value.RepositoryUrl; + + AboutText = @" + 1.本软件只提供视频解析,不提供任何资源上传、存储到服务器的功能。 + 2,本软件仅解析来自抖音的内容,不会对解析到的音视频进行二次编码,部分视频会进行有限的格式转换、拼接等操作 。 + 3.本软件解析得到的所有内容均来自抖音UP主上传、分享,其版权均归原作者所有。内容提供者、上传者(UP主)应对其提供、上传的内容承担全部表 + 任。 + 4.本软件提供的所有资源,仅可用作学习交流使用,未经原作者授权,禁止用于其他用途。请在下载24小时内删除。为尊重作者版权,请前往资源的原 + 始发布网站观看,支持原创,谢谢, + 5.任何涉及商业盈利目的均不得使用,否则产生的一切后果将由您自己承担。 + 6.因使用本软件产生的版权问题,软件作者概不负表。"; + } + + [RelayCommand] + private void OpenRepository(string parameter) + { + // 使用默认浏览器打开链接 + Process.Start(new ProcessStartInfo(RepositoryUrl + parameter) { UseShellExecute = true }); + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/ViewModels/Pages/CookiesViewModel.cs b/src/BlueCatKoKo.Ui/ViewModels/Pages/CookiesViewModel.cs new file mode 100644 index 0000000..d94c597 --- /dev/null +++ b/src/BlueCatKoKo.Ui/ViewModels/Pages/CookiesViewModel.cs @@ -0,0 +1,7 @@ +namespace BlueCatKoKo.Ui.ViewModels.Pages +{ + public partial class CookiesViewModel:ViewModelBase + { + + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/ViewModels/Pages/HomeViewModel.cs b/src/BlueCatKoKo.Ui/ViewModels/Pages/HomeViewModel.cs new file mode 100644 index 0000000..f0a31ce --- /dev/null +++ b/src/BlueCatKoKo.Ui/ViewModels/Pages/HomeViewModel.cs @@ -0,0 +1,208 @@ +using System.ComponentModel.DataAnnotations; +using System.Configuration; +using System.IO; + +using BlueCatKoKo.Ui.Constants; +using BlueCatKoKo.Ui.Models; +using BlueCatKoKo.Ui.Models.Pages; +using BlueCatKoKo.Ui.Services; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; +using CommunityToolkit.Mvvm.Messaging.Messages; + +using LibVLCSharp.Shared; + +using Microsoft.Extensions.Options; + +using Serilog; + +using MediaPlayer = LibVLCSharp.Shared.MediaPlayer; + +namespace BlueCatKoKo.Ui.ViewModels.Pages +{ + [ObservableRecipient] + public partial class HomeViewModel : ViewModelBase + { + private readonly DouyinDownloaderService _douyinDownloaderService; + private readonly IOptions _appConfig; + private readonly ILogger _logger; + + // 解析出的视频数据 + [ObservableProperty] private HomePageModel _data; + + // 下载链接 + [ObservableProperty] [Required(ErrorMessage = "缺少分享链接")] + private string _downloadUrlText; + + // 是否下载音频,默认false + [ObservableProperty] private bool _isDownloadAudio; + + // 是否下载视频,默认true + [ObservableProperty] private bool _isDownloadVideo; + + + // 视频是否已解析 + [ObservableProperty] private string _isParsed; + + // 视频是否已解析 + [ObservableProperty] private bool _isParsing; + + // 是否已经下载 + [ObservableProperty] private string _isDownload; + + // 下载进度 + [ObservableProperty] private double _downloadProcess; + + + public HomeViewModel(IMessenger messenger, ILogger logger, DouyinDownloaderService douyinDownloaderService, + IOptions appConfig) + { + Messenger = messenger; + IsActive = true; + IsParsed = "Hidden"; + IsParsing = false; + + IsDownloadAudio = false; + IsDownloadVideo = true; + + IsDownload = "Hidden"; + + _logger = logger; + _douyinDownloaderService = douyinDownloaderService; + _appConfig = appConfig; + + + LibVlc = new LibVLC(); + MediaPlayer = new MediaPlayer(LibVlc); + //通过设置宽高比为窗体宽高可达到视频铺满全屏的效果 + MediaPlayer.AspectRatio = MediaPlayerWidth + ":" + MediaPlayerHeight; + } + + public MediaPlayer MediaPlayer { get; init; } + public LibVLC LibVlc { get; init; } + public int MediaPlayerWidth => 480; + public int MediaPlayerHeight => 400; + + [RelayCommand] + private async Task Parse() + { + ValidateAllProperties(); + IsParsing = true; + + string message = "解析成功~"; + DownloaderEnum type = DownloaderEnum.Success; + + try + { + if (HasErrors) + { + string errorMessage = string.Join(Environment.NewLine, GetErrors()); + throw new ValidationException(errorMessage); + } + + if (!DownloadUrlText.Contains("https://v.douyin.com")) + { + throw new ValidationException("请输入正确的分享链接"); + } + + string downloadUrl = await _douyinDownloaderService.ExtractUrlAsync(DownloadUrlText); + DouyinShareRouterData? douYinRouterData = + await _douyinDownloaderService.ExtractVideoDataAsync(downloadUrl); + if (douYinRouterData is null) + { + throw new InvalidDataException("解析数据为空,请检查分享链接是否正确,如有更多问题请查看日志"); + } + + // 获取数据 + ItemList videoInfoData = douYinRouterData.LoaderData.VideoIdPage.VideoInfoRes.ItemList.First(); + + Data = new HomePageModel + { + VideoId = videoInfoData.AwemeId, + AuthorName = videoInfoData.Author.Nickname, + AuthorAvatar = videoInfoData.Author.AvatarThumb.UrlList.First().ToString(), + Title = videoInfoData.Author.Signature, + Cover = videoInfoData.Video.Cover.UrlList.Last().ToString(), + VideoUrl = videoInfoData.Video.PlayAddr.UrlList.First().ToString().Replace("playwm", "play"), + Mp3Url = "", + CreatedTime = + DateTimeOffset.FromUnixTimeSeconds(videoInfoData.CreateTime) + .ToString("yyyy-MM-dd HH:mm:ss"), + Desc = videoInfoData.Desc, + Duration = "", + DiggCount = videoInfoData.Statistics.DiggCount, + CollectCount = videoInfoData.Statistics.CollectCount, + CommentCount = videoInfoData.Statistics.CommentCount, + ShareCount = videoInfoData.Statistics.ShareCount + }; + + // 绑定视频 + using (Media media = new(LibVlc, new Uri(Data.VideoUrl))) + { + MediaPlayer.Play(media); + } + + IsParsed = "Visible"; + IsParsing = false; + } + catch (Exception ex) + { + type = DownloaderEnum.Warning; + message = ex.Message; + } + finally + { + DownloaderMessage downloadMessage = new(type, message, DownloadUrlText); + Messenger.Send(new ValueChangedMessage(downloadMessage)); + } + } + + [RelayCommand] + private async Task DownloadAll() + { + IsDownload = "Visible"; + string message = "下载中..."; + DownloaderEnum type = DownloaderEnum.Success; + try + { + if (string.IsNullOrEmpty(_appConfig.Value.DownloadPath)) + { + throw new InvalidDataException("请在配置文件中设置下载路径"); + } + + if (string.IsNullOrEmpty(Data.VideoUrl)) + { + throw new InvalidDataException("无效的下载链接"); + } + + var filename = _appConfig.Value.DownloadPath + Data.VideoId + ".mp4"; + await _douyinDownloaderService.Download(Data.VideoUrl, _appConfig.Value.DownloadPath, + Data.VideoId + ".mp4", + onProgressChanged: ( + (sender, e) => + { + Console.WriteLine($"Progress: {e.ProgressPercentage}%"); + DownloadProcess = e.ProgressPercentage; + }), onProgressCompleted: ((sender, e) => + { + DownloadProcess = 100; + IsDownload = "hidden"; + message = filename+"下载成功~"; + _logger.Error($"Download completed! Status: {e.Error}"); + })); + } + catch (Exception ex) + { + type = DownloaderEnum.Warning; + message = ex.Message; + } + finally + { + DownloaderMessage downloadMessage = new(type, message, DownloadUrlText); + Messenger.Send(new ValueChangedMessage(downloadMessage)); + } + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/ViewModels/Pages/SettingsViewModel.cs b/src/BlueCatKoKo.Ui/ViewModels/Pages/SettingsViewModel.cs new file mode 100644 index 0000000..4addfd2 --- /dev/null +++ b/src/BlueCatKoKo.Ui/ViewModels/Pages/SettingsViewModel.cs @@ -0,0 +1,58 @@ +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Windows.Forms; + +using BlueCatKoKo.Ui.Models; +using BlueCatKoKo.Ui.Services; + +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using CommunityToolkit.Mvvm.Messaging; + +using LibVLCSharp.Shared; + +using Microsoft.Extensions.Options; + +using Serilog; + +namespace BlueCatKoKo.Ui.ViewModels.Pages +{ + [ObservableRecipient] + public partial class SettingsViewModel : ViewModelBase + { + private readonly IOptions _appConfig; + private readonly AppConfigService _appConfigService; + private readonly ILogger _logger; + + [ObservableProperty] private string _downloadPath; + + public SettingsViewModel(IMessenger messenger, ILogger logger, + IOptions appConfig, AppConfigService appConfigService) + { + Messenger = messenger; + IsActive = true; + + _logger = logger; + _appConfig = appConfig; + _appConfigService = appConfigService; + + DownloadPath = _appConfig.Value.DownloadPath ?? "./"; + } + + [RelayCommand] + private void SelectDownloadPath() + { + // Configure open folder dialog box + Microsoft.Win32.OpenFolderDialog dialog = new() { Multiselect = false }; + if (dialog.ShowDialog() == true) + { + // 获取用户选择的目录路径 + string selectedPath = dialog.FolderName; + DownloadPath = selectedPath; + _appConfig.Value.DownloadPath = selectedPath; + + _appConfigService.Write(_appConfig.Value); + } + } + } +} \ No newline at end of file diff --git a/src/BlueCatKoKo.Ui/ViewModels/Pages/VideoViewModel.cs b/src/BlueCatKoKo.Ui/ViewModels/Pages/VideoViewModel.cs new file mode 100644 index 0000000..81c131c --- /dev/null +++ b/src/BlueCatKoKo.Ui/ViewModels/Pages/VideoViewModel.cs @@ -0,0 +1,11 @@ +using BlueCatKoKo.Ui.Models; + +using CommunityToolkit.Mvvm.ComponentModel; + +namespace BlueCatKoKo.Ui.ViewModels.Pages +{ + public partial class VideoViewModel:ViewModelBase + { + [ObservableProperty] private List