Repository: hupo376787/WeiboAlbumDownloader
Branch: master
Commit: 3aa5390d12a1
Files: 32
Total size: 159.4 KB
Directory structure:
gitextract_8y3zn10b/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ └── bug反馈.md
│ └── workflows/
│ └── dotnet-desktop.yml
├── .gitignore
├── LICENSE
├── README.md
├── WeiboAlbumDownloader/
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── AssemblyInfo.cs
│ ├── Converters/
│ │ └── ColorConverter.cs
│ ├── Enums/
│ │ ├── MessageEnum.cs
│ │ ├── PicEnum.cs
│ │ └── WeiboDataSource.cs
│ ├── GlobalVar.cs
│ ├── Helpers/
│ │ ├── GithubHelper.cs
│ │ ├── HttpHelper.cs
│ │ ├── PushPlusHelper.cs
│ │ ├── SeleniumHelper.cs
│ │ └── WeiboMidHelper.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── Models/
│ │ ├── AlbumDetailModel.cs
│ │ ├── AlbumDetailModel2.cs
│ │ ├── MessageModel.cs
│ │ ├── SettingsModel.cs
│ │ ├── UserAlbumModel.cs
│ │ ├── UserAlbumModel2.cs
│ │ ├── VideoDetailModel.cs
│ │ └── WeiboCnMobileModel.cs
│ ├── SettingsWindow.xaml
│ ├── SettingsWindow.xaml.cs
│ └── WeiboAlbumDownloader.csproj
└── WeiboAlbumDownloader.sln
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug反馈.md
================================================
---
name: Bug反馈
about: 反馈Bug
title: ''
labels: ''
assignees: ''
---
**Bug描述**
简单描述一下bug,最好附带uid,方便具体排查原因。
**如何复现**
复现步骤:
1.
2.
3.
**截图**
错误信息截图
**软件版本:**
- OS: [e.g. iOS]
- 版本 [e.g. 22]
**额外描述**
额外描述信息
================================================
FILE: .github/workflows/dotnet-desktop.yml
================================================
# 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:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
strategy:
matrix:
configuration: [Debug, 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: WeiboAlbumDownloader.sln # Replace with your solution name, i.e. MyWpfApp.sln.
Test_Project_Path: your-test-project-path # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
Wap_Project_Directory: your-wap-project-directory-name # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package.
Wap_Project_Path: your-wap-project-path # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj.
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
# Install the .NET Core workload
- name: Install .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: 6.0.x
# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.2
# Execute all unit tests in the solution
- name: Execute unit tests
run: dotnet test
# Upload bin directory
- name: Upload bin directory
uses: actions/upload-artifact@main
if: ${{ success() }}
with:
name: WeiboAlbumDownloader_Build
path: ./bin/Release/net6.0-windows/
================================================
FILE: .gitignore
================================================
## 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/master/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
*.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 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/
# 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
/Demos/Demos.Algorithm/Demos.Algorithm.Toolbox.MaojiaExamine/CameraDevices.cs
/Demos/Demos.Algorithm/Demo.Algorithm.Toolbox.Maojia/Demo.Algorithm.Toolbox.Maojia.csproj
/WeiboAlbumDownloader/DownLoad
Settings.json
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2023 Vincent Wang
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# WeiboAlbumDownloader
微博相册下载工具C#版,界面全新设计
# 项目说明
本项目可以批量采集指定微博账号下的所有图片/视频/LivePhoto。
和其他语言比如python版本类似,都可以实现用户相册的采集下载工作。不过本软件UI更加美观好看。下载文件名以日期+博文+编号命名。本软件还额外增加自动修改文件日期为发博日期的功能,方便用户按照日期就行分组和排序。这个是其他工具暂时没有的功能。软件采用Selenium自动化扫码获取cookie,大大提高采集成功率。以及随机延时模拟人类刷微博的操作。


# 推荐类似
[银杏下载器(抖音/快手/小红书/推特作者主页作品下载) 抖音解析、快手解析、小红书解析、推特解析](https://github.com/hupo376787/GinkgoDownloader)
# 最佳CP
[银杏美女吧(基于深度学习和计算机视觉领域的尖端YOLO11模型,快速检测并筛选出需要的美女图片。) ](https://github.com/hupo376787/GinkgoBeautySelector)
# 软件优势
开源的不开源的微博下载工具很多,但是大部分都有一个致命的缺点,就是下载的图片,并没有修改日期为发博时间。就是当我想按照日期筛选排序的时候,就很尴尬了。他们的日期一般都是默认图片下载日期,这会给使用者造成很大的困扰。
本软件基于这一点,在下载完图片后,自动获取发博时间(年月日时分秒),然后**将图片的创建日期、修改日期、访问日期都修改为发博时间**。虽然就是一个很简单的功能,但是为后期图片分析节约了大量的时间。
# 使用说明
获取微博用户uid以及web版微博Cookie,~~填入到软件根目录的Settings.json中即可~~,点击设置里面扫码获取即可。
如果想要在下载完发送push+通知,请填写PushPlusToken字段。不填就不发送。
如果想要开启定时任务,即在某个时间自动触发批量下载任务,那么需要填写EnableCrontab和Crontab字段。
| 字段 | 说明 |
| -------------- | ------------------------------------------------------------ |
| WeiboCnCookie | [weibo.cn](https://weibo.cn/) Cookie |
| WeiboComCookie | [weibo.com](https://weibo.com/) Cookie |
| PushPlusToken | 推送到微信,填了就会发送 |
| ShowHeadImage | 首页是否显示头像 |
| EnableCrontab | 否开启Crontab定时任务 |
| Crontab | Crontab定时任务,例如"14 2 * * *"表示每天凌晨2点14分开始执行 |
| EnableDatetimeRange | 是否启用时间范围 |
| StartDateTime | 设置截至时间,例如"2025/1/1"表示下载2025年1月1日至今的所有图片 |
| CountDownloadedSkipToNextUser | 自动跳到下一个用户的计数器,比如设置20,那么程序会检测本地如果达到20个已经存在的图片了,就认为当前用户已经下载过了,接下来就停止程序或者跳到下一个用户 |
# 数据源
推荐使用m.weibo.cn。
数据源区分[weibo.com](https://weibo.com/) 和[weibo.cn](https://weibo.cn/) ,[weibo.com](https://weibo.com/) 是获取用户相册的数据(不包含视频),返回的是json格式。 [weibo.cn](https://weibo.cn/) 是获取的用户的时间流数据(包含视频),返回的是html格式。
对于某些用户(可能时间线很长的用户)来说,数据源选择[weibo.com](https://weibo.com/) 可能采集到之前的某一个时间点,就没有数据了。我遇到过一个用户就是这样。
还有一部分视频,无法下载,用网页访问发现无权限,暂时不知道怎么下载,以后有空再研究。
# 获取uid以及Cookie
PC打开[weibo.com](https://weibo.com/),点击某一用户头像,进入主页。uid就是地址栏中的最后一串数字,比如https://weibo.com/u/1000000000。
Cookie可以通过点击上方按钮打开页面扫码获取,或者按F12进入控制台,网络-全部,在名称栏选择uid,标头-请求标头-Cookie。右键复制后请填入到Seetings.json。
[weibo.com](https://weibo.com/)和[weibo.cn](https://weibo.cn/) cookie不一样,请注意区分。
# 参考
本软件的实现参考/使用了一下项目/技术:
1. [微软WPF](https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/?view=netdesktop-8.0),本程序的基础。
2. [MicaWPF](https://github.com/Simnico99/MicaWPF),实现窗体Mica/Acrylic效果。
3. [Newtonsoft.Json](https://www.newtonsoft.com/),解析api返回的json数据。
4. [HtmlAgilityPack](https://html-agility-pack.net/),解析网页返回的html数据。
5. [Selenium](https://www.selenium.dev/),开源的浏览器自动化工具。
6. [PushPlus](https://www.pushplus.plus/),发送消息到微信等即时工具。
7. [CronExpressionDescriptor](https://github.com/bradymholt/cron-expression-descriptor),翻译crontab数据为可阅读的文本。
8. [TimeCrontab](https://github.com/MonkSoul/TimeCrontab),解析crontab时间数据。
9. [Weibo Spider](https://github.com/dataabc/weiboSpider),一个开源微博爬虫。
10. 图标来自[FlatIcon](https://www.flaticon.com/)。
11. [免责声明](https://github.com/JoeanAmier/TikTokDownloader/blob/master/README.md)
# 是否支持项目个性化定制
支持,具体需求可以联系作者,或者提issue。
# 免责声明(Disclaimers)
- 使用者对本项目的使用由使用者自行决定,并自行承担风险。作者对使用者使用本项目所产生的任何损失、责任、或风险概不负责。
- 本项目的作者提供的代码和功能是基于现有知识和技术的开发成果。作者尽力确保代码的正确性和安全性,但不保证代码完全没有错误或缺陷。
- 使用者在任何情况下均不得将本项目的作者、贡献者或其他相关方与使用者的使用行为联系起来,或要求其对使用者使用本项目所产生的任何损失或损害负责。
- 使用者在使用本项目的代码和功能时,必须自行研究相关法律法规,并确保其使用行为合法合规。任何因违反法律法规而导致的法律责任和风险,均由使用者自行承担。
- 基于本项目进行的任何二次开发、修改或编译的程序与原创作者无关,原创作者不承担与二次开发行为或其结果相关的任何责任,使用者应自行对因二次开发可能带来的各种情况负全部责任。
**在使用本项目的代码和功能之前,请您认真考虑并接受以上免责声明。如果您对上述声明有任何疑问或不同意,请不要使用本项目的代码和功能。如果您使用了本项目的代码和功能,则视为您已完全理解并接受上述免责声明,并自愿承担使用本项目的一切风险和后果。**
================================================
FILE: WeiboAlbumDownloader/App.xaml
================================================
<Application
x:Class="WeiboAlbumDownloader.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WeiboAlbumDownloader"
xmlns:mica="clr-namespace:MicaWPF.Styles;assembly=MicaWPF"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MicaWPF;component/Styles/Themes/MicaDark.xaml" />
<ResourceDictionary Source="pack://application:,,,/MicaWPF;component/Styles/MicaWPF.xaml" />
<mica:ThemeDictionary Theme="Light" />
<!-- And Here (You can change to Light or Dark here) -->
<mica:ControlsDictionary />
<!-- This is mandatory -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
================================================
FILE: WeiboAlbumDownloader/App.xaml.cs
================================================
using Sentry;
using System;
using System.Reflection;
using System.Windows;
using System.Windows.Threading;
namespace WeiboAlbumDownloader
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public App()
{
DispatcherUnhandledException += App_DispatcherUnhandledException;
SentrySdk.Init(o =>
{
// Tells which project in Sentry to send events to:
o.Dsn = "https://dd07c62f4012a941162c1101ab58cf06@o1189682.ingest.us.sentry.io/4508093180411904";
// When configuring for the first time, to see what the SDK is doing:
o.Debug = true;
// Set TracesSampleRate to 1.0 to capture 100% of transactions for tracing.
// We recommend adjusting this value in production.
o.TracesSampleRate = 1.0;
});
}
void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
SentrySdk.AddBreadcrumb(
message: $"weibo.com/u/{GlobalVar.gId},uid: {GlobalVar.gId},DataSource: {GlobalVar.gDataSource},Page:{GlobalVar.gPage},SinceId: {GlobalVar.gSinceId}",
category: "error",
level: BreadcrumbLevel.Error
);
SentrySdk.CaptureException(e.Exception);
// If you want to avoid the application from crashing:
e.Handled = true;
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
SentrySdk.ConfigureScope(scope =>
{
scope.SetTag("AppName", Assembly.GetExecutingAssembly().GetName().Name!);
scope.SetTag("DeviceName", Environment.MachineName);
scope.SetTag("DeviceName", WeiboAlbumDownloader.MainWindow.currentVersion.ToString("#0.0"));
});
}
}
}
================================================
FILE: WeiboAlbumDownloader/AssemblyInfo.cs
================================================
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)
)]
================================================
FILE: WeiboAlbumDownloader/Converters/ColorConverter.cs
================================================
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using WeiboAlbumDownloader.Enums;
namespace WeiboAlbumDownloader.Converters
{
public class ColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var type = (MessageEnum)value;
if (type == MessageEnum.Error)
return new SolidColorBrush(Colors.Red);
else if (type == MessageEnum.Warning)
return new SolidColorBrush(Colors.Yellow);
else if (type == MessageEnum.Success)
return new SolidColorBrush(Colors.Green);
return new SolidColorBrush(Colors.Orange);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
================================================
FILE: WeiboAlbumDownloader/Enums/MessageEnum.cs
================================================
namespace WeiboAlbumDownloader.Enums
{
public enum MessageEnum
{
Info,
Warning,
Success,
Error
}
}
================================================
FILE: WeiboAlbumDownloader/Enums/PicEnum.cs
================================================
namespace WeiboAlbumDownloader.Enums
{
public enum PicEnum
{
//单图
Picture,
//组图
Pictures,
//视频
Video
}
}
================================================
FILE: WeiboAlbumDownloader/Enums/WeiboDataSource.cs
================================================
using System.ComponentModel;
namespace WeiboAlbumDownloader.Enums
{
public enum WeiboDataSource
{
[Description("m.weibo.cn")]
WeiboCnMobile = 0,
[Description("weibo.cn")]
WeiboCn = 1,
[Description("weibo.com1")]
WeiboCom1 = 2,
[Description("weibo.com2")]
WeiboCom2 = 3,
}
}
================================================
FILE: WeiboAlbumDownloader/GlobalVar.cs
================================================
using WeiboAlbumDownloader.Enums;
namespace WeiboAlbumDownloader
{
public class GlobalVar
{
public static string gId = "";
public static long gSinceId;
public static int gPage;
public static WeiboDataSource gDataSource;
}
}
================================================
FILE: WeiboAlbumDownloader/Helpers/GithubHelper.cs
================================================
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace WeiboAlbumDownloader.Helpers
{
public class GithubHelper
{
public static async Task<string?> GetLatestVersion()
{
try
{
HttpClient client = new HttpClient();
var resp = await client.GetAsync("https://github.com/hupo376787/WeiboAlbumDownloader/tags");
var body = await resp.Content.ReadAsStringAsync();
string pattern = "tags.*zip";
//匹配多个
//MatchCollection list = Regex.Matches(body, pattern);
//匹配第一个
Match match = Regex.Match(body, pattern);
if (match.Success)
{
var version = match.Value.Replace("tags/", "").Replace(".zip", "");
return version;
}
return null;
}
catch (Exception ex)
{
return null;
}
}
public static async Task<string> GetGiteeLatestVersion()
{
try
{
HttpClient client = new HttpClient();
var resp = await client.GetAsync("https://gitee.com/hupo376787/weibo-album-downloader/tags");
var body = await resp.Content.ReadAsStringAsync();
string pattern = @"<a\s+title=""(\d+\.\d+)""";
Regex regex = new Regex(pattern);
//MatchCollection matches = regex.Matches(body);
Match match = Regex.Match(body, pattern);
if (match.Success)
{
var version = match.Value.Replace("<a title=\"", "").Replace("\"", "");
return version;
}
return null;
}
catch (Exception ex)
{
return null;
}
}
}
}
================================================
FILE: WeiboAlbumDownloader/Helpers/HttpHelper.cs
================================================
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using WeiboAlbumDownloader.Enums;
using WeiboAlbumDownloader.Models;
namespace WeiboAlbumDownloader.Helpers
{
public class HttpHelper
{
static HttpClient client;
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="url"></param>
/// <param name="fileName">不为空的时候,表示存图</param>
/// <returns></returns>
public static async Task<T> GetAsync<T>(string url, WeiboDataSource dataSource, string cookie, string fileName = "", Action<string, MessageEnum> logAction = null)
{
string responseBody = null;
try
{
//logAction?.Invoke($"[Request URL]: {url}", MessageEnum.Info);
//logAction?.Invoke($"[Request Cookie]: {cookie}", MessageEnum.Info);
var handler = new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
client = new HttpClient(handler);
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Referer", "https://m.weibo.cn/");
if (string.IsNullOrEmpty(fileName))
{
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36");
request.Headers.Add("Accept", "application/json, text/plain, */*");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br, zstd");
request.Headers.Add("Accept-Language", "zh-CN,zh;q=0.9");
request.Headers.Add("Cookie", cookie);
}
var response = await client.SendAsync(request);
var contentEncoding = response.Content.Headers.ContentEncoding.ToString();
if (contentEncoding.Contains("gzip"))
{
using (var stream = await response.Content.ReadAsStreamAsync())
using (var decompressedStream = new GZipStream(stream, CompressionMode.Decompress))
using (var reader = new StreamReader(decompressedStream))
{
responseBody = await reader.ReadToEndAsync();
}
}
else
{
responseBody = await response.Content.ReadAsStringAsync();
}
logAction?.Invoke($"[Response Status Code]: {response.StatusCode}", MessageEnum.Info);
//logAction?.Invoke($"[Response Body]: {responseBody}", MessageEnum.Info);
response.EnsureSuccessStatusCode();
if (!string.IsNullOrEmpty(fileName))
{
string directory = Path.GetDirectoryName(fileName);
string fileNameOnly = Path.GetFileName(fileName);
var invalidChar = Path.GetInvalidFileNameChars();
var cleanedFileNameOnly = invalidChar.Aggregate(fileNameOnly, (o, r) => (o.Replace(r.ToString(), string.Empty)));
string finalFullPath;
if (cleanedFileNameOnly.Length > 200)
{
// Truncate the file name part, then recombine with the original directory
string truncatedFileName = cleanedFileNameOnly.Substring(0, 200) + Path.GetExtension(cleanedFileNameOnly);
finalFullPath = Path.Combine(directory, truncatedFileName);
}
else
{
// Recombine the original directory with the cleaned file name
finalFullPath = Path.Combine(directory, cleanedFileNameOnly);
}
string uniquePath = GetUniqueFileName(finalFullPath);
var stream = response.Content.ReadAsStream();
FileStream lxFS = File.Create(uniquePath);
await stream.CopyToAsync(lxFS);
lxFS.Close();
lxFS.Dispose();
stream.Dispose();
return default(T);
}
Type type = typeof(T);
if (type == typeof(string))
return (T)Convert.ChangeType(responseBody, typeof(T));
else
return JsonConvert.DeserializeObject<T>(responseBody);
}
catch (Exception ex)
{
logAction?.Invoke($"[Request Failed]: URL: {url}", MessageEnum.Error);
if (responseBody != null && responseBody.Contains("<title>登录 - 微博</title>"))
{
logAction?.Invoke($"[Cookie可能失效]: {responseBody}", MessageEnum.Error);
}
logAction?.Invoke($"[Exception]: {ex.ToString()}", MessageEnum.Error);
throw;
}
}
public static string GetUniqueFileName(string filePath)
{
// 获取文件目录和扩展名
string directory = Path.GetDirectoryName(filePath)!;
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
string extension = Path.GetExtension(filePath);
int count = 1;
// 初始文件路径
string uniqueFilePath = filePath;
// 检查文件是否存在,如果存在则循环添加编号直到找到一个不存在的文件名
while (File.Exists(uniqueFilePath))
{
string newFileName = $"{fileNameWithoutExtension}({count}){extension}";
uniqueFilePath = Path.Combine(directory, newFileName);
count++;
}
return uniqueFilePath;
}
}
}
================================================
FILE: WeiboAlbumDownloader/Helpers/PushPlusHelper.cs
================================================
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
namespace WeiboAlbumDownloader.Helpers
{
public class PushPlusHelper
{
private static readonly HttpClient client = new HttpClient();
public static async Task SendMessage(string token, string title, string content)
{
if (string.IsNullOrEmpty(token))
{
return;
}
using HttpResponseMessage response = await client.GetAsync($"http://www.pushplus.plus/send?token={token}&title={title}&content={content}");
response.EnsureSuccessStatusCode();
var jsonResponse = await response.Content.ReadAsStringAsync();
Debug.WriteLine($"{jsonResponse}");
}
}
}
================================================
FILE: WeiboAlbumDownloader/Helpers/SeleniumHelper.cs
================================================
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using WeiboAlbumDownloader.Enums;
namespace WeiboAlbumDownloader.Helpers
{
internal class SeleniumHelper
{
public static string? GetCookie(WeiboDataSource dataSource)
{
string loginUrl = "https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https://weibo.cn";
if (dataSource == WeiboDataSource.WeiboCn)
loginUrl = "https://passport.weibo.com/sso/signin?entry=wapsso&source=wapssowb&url=https://weibo.cn";
else if (dataSource == WeiboDataSource.WeiboCnMobile)
loginUrl = "https://passport.weibo.com/sso/signin?entry=wapsso&source=wapsso&url=https://m.weibo.cn";
else
loginUrl = "https://passport.weibo.com/sso/signin?entry=miniblog&source=miniblog&url=https://weibo.com/";
IWebDriver driver = new ChromeDriver();
driver.Url = loginUrl;
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromHours(8))
{
PollingInterval = TimeSpan.FromMilliseconds(500),
};
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
//wait.Until(d => d.FindElement(By.LinkText("title")));
// 等待页面加载完成并获取页面标题
wait.Until(d => d.Title.Equals("微博 – 随时随地发现新鲜事") || d.Title.Equals("我的首页") || d.Title.Equals("微博"));
// 获取页面标题并进行检查
string pageTitle = driver.Title;
if (pageTitle.Equals("微博 – 随时随地发现新鲜事") || pageTitle.Equals("我的首页") || pageTitle.Equals("微博"))
{
//AppendLog("扫码登陆成功", MessageEnum.Success);
// 获取所有的 Cookie 对象
IReadOnlyCollection<Cookie> cookies = driver.Manage().Cookies.AllCookies;
// 将 Cookie 对象转换为一个字符串,格式类似于 HTTP 请求头的 Cookie 字符串
string cookie = string.Join("; ", cookies.Select(c => $"{c.Name}={c.Value}"));
// 打印 Cookie 字符串
Debug.WriteLine(cookie);
driver.Quit();
return cookie;
}
else
{
Debug.WriteLine("未登录");
}
// 程序结束时,手动关闭浏览器
driver.Quit();
return null;
}
}
}
================================================
FILE: WeiboAlbumDownloader/Helpers/WeiboMidHelper.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
namespace WeiboAlbumDownloader.Helpers
{
public static class WeiboMidHelper
{
private static readonly HttpClient Http = CreateClient();
public static async Task<List<string>> GetImageIdsByMidAsync(string mid, string? cookie = null, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(mid))
return new List<string>();
// 1) PC ӿ
var pc = await TryFromPcAsync(mid, cookie, ct);
if (pc.Count > 0) return pc;
// 2) ƶӿ
var mobile = await TryFromMobileAsync(mid, cookie, ct);
return mobile;
}
private static async Task<List<string>> TryFromPcAsync(string mid, string? cookie, CancellationToken ct)
{
var url = $"https://weibo.com/ajax/statuses/show?id={mid}&locale=zh-CN&isGetLongText=true";
using var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Referrer = new Uri("https://weibo.com/");
req.Headers.TryAddWithoutValidation("X-Requested-With", "XMLHttpRequest");
if (!string.IsNullOrWhiteSpace(cookie))
{
req.Headers.TryAddWithoutValidation("Cookie", cookie);
var xsrf = TryGetCookieValue(cookie, "XSRF-TOKEN");
if (!string.IsNullOrEmpty(xsrf))
req.Headers.TryAddWithoutValidation("X-XSRF-TOKEN", xsrf);
}
using var resp = await Http.SendAsync(req, ct);
if (!resp.IsSuccessStatusCode) return new List<string>();
var json = await resp.Content.ReadAsStringAsync(ct);
return ExtractPicIds(json);
}
private static async Task<List<string>> TryFromMobileAsync(string mid, string? cookie, CancellationToken ct)
{
var url = $"https://m.weibo.cn/statuses/show?id={mid}";
using var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Referrer = new Uri($"https://m.weibo.cn/detail/{mid}");
req.Headers.TryAddWithoutValidation("X-Requested-With", "XMLHttpRequest");
if (!string.IsNullOrWhiteSpace(cookie))
req.Headers.TryAddWithoutValidation("Cookie", cookie);
using var resp = await Http.SendAsync(req, ct);
if (!resp.IsSuccessStatusCode) return new List<string>();
var json = await resp.Content.ReadAsStringAsync(ct);
return ExtractPicIds(json);
}
private static List<string> ExtractPicIds(string json)
{
var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (string.IsNullOrWhiteSpace(json)) return result.ToList();
var root = JToken.Parse(json);
// m.weibo.cn: { ok: 1, data: {...} }
if (root["ok"] != null && root["data"] != null)
root = root["data"]!;
void CollectFromNode(JToken? node)
{
if (node == null) return;
// pic_ids
var picIds = node["pic_ids"] as JArray;
if (picIds != null)
{
foreach (var id in picIds.Values<string>())
if (!string.IsNullOrWhiteSpace(id)) result.Add(id);
}
// pics[].pid
var pics = node["pics"] as JArray;
if (pics != null)
{
foreach (var p in pics)
{
var pid = p?["pid"]?.Value<string>();
if (!string.IsNullOrWhiteSpace(pid)) result.Add(pid!);
}
}
// ת
if (node["retweeted_status"] != null)
CollectFromNode(node["retweeted_status"]);
}
CollectFromNode(root);
return result.ToList();
}
private static string? TryGetCookieValue(string cookieHeader, string key)
{
// Cookie ıȡijֵ
// : "SUB=...; XSRF-TOKEN=abc; SUBP=...;"
if (string.IsNullOrWhiteSpace(cookieHeader) || string.IsNullOrWhiteSpace(key))
return null;
var parts = cookieHeader.Split(';');
foreach (var p in parts)
{
var kv = p.Split('=', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (kv.Length == 2 && kv[0].Equals(key, StringComparison.OrdinalIgnoreCase))
return kv[1];
}
return null;
}
private static HttpClient CreateClient()
{
var handler = new HttpClientHandler
{
AllowAutoRedirect = true,
AutomaticDecompression = DecompressionMethods.All
};
var c = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(15)
};
c.DefaultRequestHeaders.UserAgent.ParseAdd(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36");
c.DefaultRequestHeaders.Accept.ParseAdd("application/json, text/plain, */*");
c.DefaultRequestHeaders.AcceptLanguage.ParseAdd("zh-CN,zh;q=0.9,en;q=0.8");
return c;
}
}
}
================================================
FILE: WeiboAlbumDownloader/MainWindow.xaml
================================================
<mica:MicaWindow
x:Class="WeiboAlbumDownloader.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:conv="clr-namespace:WeiboAlbumDownloader.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WeiboAlbumDownloader"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mica="clr-namespace:MicaWPF.Controls;assembly=MicaWPF"
Title="微博相册/视频/LivePhoto下载"
Width="1200"
Height="800"
FontFamily="微软雅黑"
Icon="/weibo.ico"
SizeChanged="MicaWindow_SizeChanged"
SystemBackdropType="Acrylic"
TitleBarType="WinUI"
WindowStartupLocation="CenterScreen"
mc:Ignorable="d">
<Window.Resources>
<conv:ColorConverter x:Key="colorConverter" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Margin="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<mica:TextBox
x:Name="TextBox_WeiboId"
Grid.Column="0"
Height="32"
KeyUp="TextBox_WeiboId_KeyUp"
ToolTip="填写uid进行单用户下载或者uidList.txt进行批量下载"
Watermark="微博uid" />
<mica:TextBox
x:Name="TextBox_StartPage"
Grid.Column="1"
Height="32"
Margin="8,0,0,0"
Watermark="起始页码(可选)" />
<mica:TextBox
x:Name="TextBox_StartSinceId"
Grid.Column="2"
Height="32"
Margin="8,0,0,0"
Watermark="起始SinceID(可选)" />
<StackPanel Grid.Column="3" Orientation="Horizontal">
<mica:Button
Width="110"
Height="32"
Margin="8,0,0,0"
Click="StartDownLoad"
CornerRadius="2 0 0 2"
ToolTip="开始/停止下载,停止下载的时候可能有延后,因为需要等待分页任务下载完才能停">
<StackPanel Orientation="Horizontal">
<Image Width="18" Source="/Assets/cloud-computing.png" />
<TextBlock
x:Name="tbDownload"
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="开始下载" />
</StackPanel>
</mica:Button>
<mica:Button
Grid.Column="1"
Width="30"
Height="32"
Margin="-2,0,0,0"
Click="BatchDownLoad"
CornerRadius="0 2 2 0"
FontSize="10"
ToolTip="自动循环uidList.txt中所有用户,执行批量下载任务">
<StackPanel Orientation="Horizontal">
<Image Width="18" Source="/Assets/batch.png" />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="批量
下载"
Visibility="Collapsed" />
</StackPanel>
</mica:Button>
</StackPanel>
<mica:Button
Grid.Column="4"
Width="64"
Height="32"
Margin="8,0"
Click="OpenSettings"
ToolTip="设置">
<StackPanel Orientation="Horizontal">
<Image Width="18" Source="/Assets/settings.png" />
</StackPanel>
</mica:Button>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="Column_LeftGrid" Width="*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<StackPanel VerticalAlignment="Center">
<Border x:Name="Border_Head" Margin="0">
<Border.Background>
<ImageBrush
x:Name="Image_Head"
ImageSource="/weibo.ico"
Stretch="Fill" />
</Border.Background>
</Border>
<TextBox
x:Name="TextBlock_UID"
Width="256"
Margin="16,16,16,0"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
d:Text="uid"
Background="Transparent"
BorderThickness="0"
FontSize="22"
FontWeight="Bold"
Foreground="Gray" />
<TextBox
x:Name="TextBlock_NickName"
Width="256"
Margin="16"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center"
d:Text="昵称"
Background="Transparent"
BorderThickness="0"
FontSize="32"
FontWeight="Bold"
Foreground="Gray" />
<TextBlock
x:Name="TextBlock_WeiboDesc"
Margin="8,0"
HorizontalAlignment="Center"
d:Text="微博[888] 关注[274] 粉丝[7.9万] 分组[1]"
FontSize="16"
Foreground="Gray"
TextWrapping="Wrap" />
</StackPanel>
<ListView
x:Name="ListView_Messages"
Grid.Column="1"
Margin="6"
Background="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Click="ListView_CopyLog" Header="复制" />
<MenuItem Click="ListView_ClearLog" Header="清空" />
<MenuItem Click="ListView_ExportLog" Header="导出" />
<MenuItem Click="ListView_OpenDownloadFolder" Header="打开下载文件目录" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="50" />
<ColumnDefinition Width="60" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse
Width="14"
Height="14"
VerticalAlignment="Top"
Fill="{Binding Path=MessageType, Converter={StaticResource colorConverter}}" />
<TextBlock Grid.Column="1" Text="{Binding MessageType, Mode=OneWay}" />
<TextBlock Grid.Column="2" Text="{Binding Time, Mode=OneWay}" />
<TextBlock
Grid.Column="3"
Text="{Binding Message, Mode=OneWay}"
TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</mica:MicaWindow>
================================================
FILE: WeiboAlbumDownloader/MainWindow.xaml.cs
================================================
using HtmlAgilityPack;
using MicaWPF.Controls;
using Newtonsoft.Json;
using Sentry;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Imaging;
using TimeCrontab;
using WeiboAlbumDownloader.Enums;
using WeiboAlbumDownloader.Helpers;
using WeiboAlbumDownloader.Models;
namespace WeiboAlbumDownloader
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : MicaWindow
{
//发行说明:
//①此处升级一下GlobalVar版本号
//②Github/Gitee release新建一个新版本Tag
//③上传压缩包删除Settings.json以及uidList.txt
public static double currentVersion = 7.3;
/// <summary>
/// com1是根据uid获取相册id,https://photo.weibo.com/albums/get_all?uid=10000000000&page=1;根据uid和相册id以及相册type获取图片列表,https://photo.weibo.com/photos/get_all?uid=10000000000&album_id=3959362334782071&page=1&type=3
/// com2是根据uid获取相册id,https://weibo.com/ajax/profile/getImageWall?uid=10000000000&sinceid=0&has_album=true;根据相册id和sinceid获取图片列表,https://weibo.com/ajax/profile/getAlbumDetail?containerid=123456789000123456_-_pc_profile_album_-_photo_-_camera_-_0_-_%25E5%258E%259F%25E5%2588%259B&since_id=0
/// cn是从 https://weibo.cn/10000000000/profile?page=2获取html解析发布的微博
/// m.cn是移动端api,可以从 https://m.weibo.cn/api/container/getIndex?type=uid&value=10000000000&containerid=10760310000000000&since_id=5040866311798795,since_id第一次为空,containerid以107603开头是获取时间线,100505开头是个人资料
/// </summary>
private WeiboDataSource dataSource = WeiboDataSource.WeiboCnMobile;
private int countPerPage = 90;
private string downloadFolder = Environment.CurrentDirectory + "//DownLoad//";
private SettingsModel? settings;
private string? cookie;
private List<string> uids = new List<string>();
//用来跳过到下一个uid的计数。如果当前uid下载的时候已存在文件超过此计数,则判定下载过了。
private int countDownloadedSkipToNextUser;
private bool isDownloading;
private CancellationTokenSource? cancellationTokenSource;
public ObservableCollection<MessageModel> Messages { get; set; } = new ObservableCollection<MessageModel>();
public MainWindow()
{
InitializeComponent();
_ = GetVersion();
InitUidsData();
InitSettingsData();
Directory.CreateDirectory(downloadFolder);
ListView_Messages.ItemsSource = Messages;
//定时任务
if (settings?.Crontab != null)
{
var cron = Crontab.Parse(settings?.Crontab);
Task.Factory.StartNew(async () =>
{
while (true)
{
await Task.Delay((int)cron.GetSleepMilliseconds(DateTime.Now));
if (settings?.EnableCrontab != true)
{
continue;
}
AppendLog(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + "开启定时任务");
foreach (var item in uids)
{
await Start(item.Trim());
AppendLog("等待60s,下载下一个用户相册数据");
await Task.Delay(60 * 1000);
}
AppendLog(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + "结束定时任务");
}
}, TaskCreationOptions.LongRunning);
}
}
private async Task GetVersion()
{
//AppendLog($"当前程序版本 V{GlobalVar.currentVersion}");
var latestVersionString = await GithubHelper.GetLatestVersion();
if (string.IsNullOrEmpty(latestVersionString) || latestVersionString?.Length > 5)
{
latestVersionString = await GithubHelper.GetGiteeLatestVersion();
}
AppendLog($"当前程序版本 V{currentVersion},最新版为 V{latestVersionString}");
var res = double.TryParse(latestVersionString, out double latestVersion);
if (res)
{
if (latestVersion > currentVersion)
{
await Application.Current.Dispatcher.InvokeAsync(async () =>
{
var uiMessageBox = new MicaWPF.Dialogs.ContentDialog
{
Title = "提示",
Content = $"检测到新版本 V{latestVersionString},当前程序版本 V{currentVersion},点击确定下载",
PrimaryButtonText = "OK"
};
var res = await uiMessageBox.ShowAsync();
if (res == MicaWPF.Core.Enums.ContentDialogResult.PrimaryButton)
{
Process.Start(new ProcessStartInfo("https://github.com/hupo376787/WeiboAlbumDownloader/releases") { UseShellExecute = true });
}
});
}
}
}
private async Task Start(string userId, int startPage = 1, long startSinceId = 0)
{
try
{
cancellationTokenSource?.Cancel();
cancellationTokenSource = new CancellationTokenSource();
var conv = long.TryParse(userId, out long temp);
if (string.IsNullOrEmpty(userId) || !conv)
{
AppendLog("错误的微博uid,参考上面说明获取uid", MessageEnum.Error);
return;
}
//用于Sentry崩溃收集
GlobalVar.gId = userId;
GlobalVar.gDataSource = dataSource = settings.DataSource;
//读取用户列表和设置
InitSettingsData();
if (startPage <= 1) startPage = 1;
if (startSinceId <= 0) startSinceId = 0;
await Task.Run(async () =>
{
bool isSkip = false;
//四种api下载
{
string nickName = string.Empty;
string headUrl = string.Empty;
int page = startPage;
long sinceId = startSinceId;
countDownloadedSkipToNextUser = 0;
isSkip = false;
AppendLog("开始下载" + userId, MessageEnum.Info);
//源是weibo.com的时候,获取的是获取相册列表,json格式
if (dataSource == WeiboDataSource.WeiboCom1)
{
string albumsUrl = $"https://photo.weibo.com/albums/get_all?uid={userId}&page=1";
TextBlock_UID?.Dispatcher.InvokeAsync(() =>
{
TextBlock_UID.Text = userId;
});
var albums = await HttpHelper.GetAsync<UserAlbumModel>(albumsUrl, dataSource, cookie!);
Directory.CreateDirectory(downloadFolder + userId);
if (albums != null)
{
foreach (var item in albums?.data?.album_list!)
{
if (isSkip)
break;
Directory.CreateDirectory(downloadFolder + userId + "//" + item.caption);
if (item.caption == "头像相册")
{
headUrl = item.cover_pic;
}
page = startPage;
int consecutiveEmptyPages = 0;
while (true)
{
if (isSkip)
break;
GlobalVar.gPage = page;
string albumUrl = $"https://photo.weibo.com/photos/get_all?uid={userId}&album_id={item.album_id}&count={countPerPage}&page={page}&type={item.type}";
var photos = await HttpHelper.GetAsync<AlbumDetailModel>(albumUrl, dataSource, cookie!, logAction: AppendLog);
if (photos != null && photos.data?.photo_list != null && photos.data?.photo_list.Count > 0)
{
consecutiveEmptyPages = 0;
int photoCount = 1;
string oldCaption = "";
foreach (var photo in photos.data?.photo_list!)
{
if (cancellationTokenSource.IsCancellationRequested)
{
AppendLog($"用户手动终止,当前页码: {page}", MessageEnum.Info);
return;
}
if (oldCaption.Equals(photo.caption_render))
{
photoCount++;
}
else
{
photoCount = 1;
}
oldCaption = photo.caption_render;
string photoUrl = photo.pic_host + "/large/" + photo.pic_name;
DateTime timestamp = DateTime.UnixEpoch.AddSeconds(photo.timestamp + 8 * 3600);
//时间范围过滤,比设置日期早的跳过
if ((bool)settings?.EnableDatetimeRange)
{
if (item.caption == "微博配图" && timestamp < settings?.StartDateTime)
{
AppendLog($"翻页到截至日期{settings?.StartDateTime},停止下载");
return;
}
}
var invalidChar = System.IO.Path.GetInvalidFileNameChars();
var newCaption = invalidChar.Aggregate(photo.caption_render, (o, r) => (o.Replace(r.ToString(), string.Empty)));
var fileName = downloadFolder + userId + "//" + item.caption + "//"
+ timestamp.ToString("yyyy-MM-dd HH_mm_ss") + newCaption + "_" + photoCount + ".jpg";
Debug.WriteLine(fileName);
if (File.Exists(fileName))
{
AppendLog("文件已存在,跳过下载" + Path.GetFullPath(fileName), MessageEnum.Warning);
countDownloadedSkipToNextUser++;
await Task.Delay(500);
continue;
}
//传入图片/视频的名字,开始下载图片/视频
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(photoUrl, dataSource, cookie!, fileName);
AppendLog("已完成 " + Path.GetFileName(fileName), MessageEnum.Success);
SetFileTime(fileName, timestamp);
}
catch (Exception ex)
{
AppendLog($"文件下载失败,原始url:{item},下载路径{fileName}", MessageEnum.Error);
}
}
}
else
{
consecutiveEmptyPages++;
AppendLog($"第 {consecutiveEmptyPages} 次遇到空页面,页码: {page},准备尝试下一页...", MessageEnum.Warning);
if (consecutiveEmptyPages >= 3)
{
AppendLog("连续 3 次遇到空页面,停止下载当前相册。", MessageEnum.Info);
break;
}
}
//已存在的文件超过设置值,判定该用户下载过了
if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser)
{
AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info);
isSkip = true;
break;
}
page++;
//通过加入随机等待避免被限制。爬虫速度过快容易被系统限制(一段时间后限制会自动解除),加入随机等待模拟人的操作,可降低被系统限制的风险。默认是每爬取1到5页随机等待6到10秒,如果仍然被限,可适当增加sleep时间
Random rd = new Random();
int rnd = rd.Next(5000, 10000);
AppendLog($"随机等待{rnd}ms,避免爬虫速度过快被系统限制", MessageEnum.Info);
await Task.Delay(rd.Next(5000, 10000));
}
}
}
}
//ajax获取相册方法
else if (dataSource == WeiboDataSource.WeiboCom2)
{
string albumsUrl = $"https://weibo.com/ajax/profile/getImageWall?uid={userId}&sinceid=0&has_album=true";
TextBlock_UID?.Dispatcher.InvokeAsync(() =>
{
TextBlock_UID.Text = userId;
});
var albums = await HttpHelper.GetAsync<UserAlbumModel2>(albumsUrl, dataSource, cookie!);
Directory.CreateDirectory(downloadFolder + userId);
if (albums != null)
{
foreach (var item in albums?.Data?.AlbumList!)
{
if (isSkip)
break;
if (item.PicTitle == "头像相册")
{
headUrl = item.Pic;
}
Directory.CreateDirectory(downloadFolder + userId + "//" + item.PicTitle);
sinceId = startSinceId;
while (true)
{
if (isSkip)
break;
string albumUrl = $"https://weibo.com/ajax/profile/getAlbumDetail?containerid={item.Containerid}&since_id={sinceId}";
var photos = await HttpHelper.GetAsync<AlbumDetailModel2>(albumUrl, dataSource, cookie!, logAction: AppendLog);
if (photos != null && photos.PhotoListData2?.PhotoListItem2 != null && photos.PhotoListData2?.PhotoListItem2.Count > 0)
{
GlobalVar.gSinceId = sinceId = photos.PhotoListData2.SinceId;
foreach (var photo in photos.PhotoListData2?.PhotoListItem2!)
{
if (cancellationTokenSource.IsCancellationRequested)
{
AppendLog($"用户手动终止,当前页码: {page}", MessageEnum.Info);
return;
}
string photoUrl = "https://wx4.sinaimg.cn/large/" + photo.Pid + ".jpg";
var fileName = downloadFolder + userId + "//" + item.PicTitle + "//" + photo.Pid + ".jpg";
Debug.WriteLine(fileName);
if (File.Exists(fileName))
{
AppendLog("文件已存在,跳过下载" + Path.GetFullPath(fileName), MessageEnum.Warning);
countDownloadedSkipToNextUser++;
await Task.Delay(500);
continue;
}
//传入图片/视频的名字,开始下载图片/视频
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(photoUrl, dataSource, cookie!, fileName);
AppendLog("已完成 " + Path.GetFileName(fileName), MessageEnum.Success);
}
catch (Exception ex)
{
AppendLog($"文件下载失败,原始url:{item},下载路径{fileName}", MessageEnum.Error);
}
}
}
else
{
break;
}
//已存在的文件超过设置值,判定该用户下载过了
if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser)
{
AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info);
isSkip = true;
break;
}
//通过加入随机等待避免被限制。爬虫速度过快容易被系统限制(一段时间后限制会自动解除),加入随机等待模拟人的操作,可降低被系统限制的风险。默认是每爬取1到5页随机等待6到10秒,如果仍然被限,可适当增加sleep时间
Random rd = new Random();
int rnd = rd.Next(5000, 10000);
AppendLog($"随机等待{rnd}ms,避免爬虫速度过快被系统限制", MessageEnum.Info);
await Task.Delay(rd.Next(5000, 10000));
}
}
}
}
//源是weibo.cn的时候,获取的是微博时间线列表,解析html格式
else if (dataSource == WeiboDataSource.WeiboCn)
{
page = startPage;
int totalPage = -1;
bool cachedUserInfo = false;
Directory.CreateDirectory(downloadFolder + userId + "//" + "微博配图");
while (true)
{
if (isSkip)
break;
GlobalVar.gPage = page;
//filter,0-全部;1-原创;2-图片
//https://weibo.cn/xxxxxxxxxxxxx?page=2
//https://weibo.cn/xxxxxxxxxxxxx/profile?page=2
string url = $"https://weibo.cn/{userId}/profile?page={page}&filter=1";
string text = await HttpHelper.GetAsync<string>(url, dataSource, cookie!, logAction: AppendLog);
var doc = new HtmlDocument();
doc.LoadHtml(text);
//获取总页数
if (totalPage == -1)
{
var totalPageHtml = doc.DocumentNode.Descendants("input").Where(x => x.Attributes["type"]?.Value == "hidden").ToList();
if (totalPageHtml.Count > 0)
{
totalPage = Convert.ToInt32(totalPageHtml[totalPageHtml.Count - 1].Attributes["value"].Value);
AppendLog($"获取到{totalPage}页数据", MessageEnum.Info);
}
}
//当只有一页数据的时候,totalPage获取不到。需要叠加判定doc.DocumentNode.Descendants("div").Where(x => x.Attributes["class"]?.Value == "c").ToList().Count
if (totalPage == -1 && doc.DocumentNode.Descendants("div").Where(x => x.Attributes["class"]?.Value == "c").ToList().Count == 0)
{
AppendLog("获取总页数失败,请重新登录https://weibo.cn/获取Cookie重试", MessageEnum.Error);
return;
}
//获取用户资料
if (!cachedUserInfo)
{
var userInfoXmlString = doc.DocumentNode.Descendants("div").Where(x => x.Attributes["class"]?.Value == "u").ToList()?[0].OuterHtml;
var docUser = new HtmlDocument();
docUser.LoadHtml(userInfoXmlString);
string temp = docUser.DocumentNode
.Descendants("img")
.FirstOrDefault(x => x.Attributes["alt"]?.Value == "头像")
?.Attributes["src"]?.Value
?? "";
nickName = docUser.DocumentNode.Descendants("span").FirstOrDefault(x => x.Attributes["class"]?.Value == "ctt")?.InnerText.Split(" ")[0] ?? "";
if (!string.IsNullOrEmpty(nickName))
using (File.Create(downloadFolder + userId + "//" + nickName)) { }
var desc = docUser.DocumentNode.Descendants("div").Where(x => x.Attributes["class"]?.Value == "tip2").ToList()?[0].InnerText.Split(" ");
string weiboDesc = string.Join(" ", desc!);
headUrl = "https://tvax2.sinaimg.cn/large/" + Path.GetFileName(temp).Split("?")[0];
var fileName = downloadFolder + userId + "//" + Path.GetFileName(headUrl);
//下载头像
if (!File.Exists(fileName) && temp != "")
{
await HttpHelper.GetAsync<AlbumDetailModel>(headUrl, dataSource, cookie!, fileName);
}
Image_Head?.Dispatcher.InvokeAsync(() =>
{
if (settings.ShowHeadImage && temp != "")
{
var bytes = File.ReadAllBytes(fileName);
MemoryStream ms = new MemoryStream(bytes);
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
Image_Head.ImageSource = bi;
//Image_Head.ImageSource = new BitmapImage(new Uri(fileName));
}
TextBlock_UID!.Text = userId;
TextBlock_NickName.Text = nickName;
TextBlock_WeiboDesc.Text = weiboDesc;
});
cachedUserInfo = true;
}
//获取当前页的微博
var nodes = doc.DocumentNode.Descendants("div").Where(x => x.Attributes["class"]?.Value == "c").ToList();
foreach (var node in nodes)
{
if (isSkip)
break;
if (cancellationTokenSource.IsCancellationRequested)
{
AppendLog($"用户手动终止,当前页码: {page}", MessageEnum.Info);
return;
}
// 使用OuterXml属性将HtmlNode对象转换为Xml字符串
string xmlString = node.OuterHtml;
//页尾html调过解析
if (xmlString.Contains("设置") && xmlString.Contains("图片") && xmlString.Contains("条数") && xmlString.Contains("隐私"))
continue;
var doc1 = new HtmlDocument();
doc1.LoadHtml(xmlString);
//微博内容
var weiboContent = doc1.DocumentNode.Descendants("span").Where(x => x.Attributes["class"]?.Value == "ctt").ToList()[0].InnerText;
//微博来源
var temp = doc1.DocumentNode.Descendants("span").Where(x => x.Attributes["class"]?.Value == "ct").ToList()[0].InnerText;
var sourceDevice = temp.Split(" ").Length > 1 ? temp.Split(" ")[1] : "";
//发布时间
//15分钟前 来自iPhone客户端
string pattern = @"(\d+分钟前|今天)";
Regex regex = new Regex(pattern);
DateTime timestamp = DateTime.Now;
if (temp.Contains("分钟前"))
{
int minute = int.Parse(temp.Split(" ")[0].Replace("分钟前", ""));
timestamp = DateTime.Now.AddMinutes(-1 * minute);
}
else
{
timestamp = DateTime.Parse(temp.Split(" ")[0].Replace("今天", ""));
}
//图片列表链接
string photoListUrl = string.Empty;
//视频链接
string videoUrl = string.Empty;
//转评赞
int likeCount, repostCount, commentCount;
//是单图、组图、视频
PicEnum picType = PicEnum.Picture;
var list = doc1.DocumentNode.Descendants("a").ToList();
foreach (var item in list)
{
if (isSkip)
break;
if (item.InnerText.Contains("组图共"))
{
photoListUrl = item.Attributes["href"].Value;
picType = PicEnum.Pictures;
}
else if (item.Attributes["href"].Value.Contains("s/video/show"))
{
videoUrl = item.Attributes["href"].Value.Replace("s/video/show", "s/video/object");
picType = PicEnum.Video;
}
else if (item.InnerText.Contains("赞"))
{
//likeCount = Convert.ToInt32(item.InnerText.Replace("赞[", "").Replace("]", ""));
}
else if (item.InnerText.Contains("转发"))
{
//repostCount = Convert.ToInt32(item.InnerText.Replace("转发[", "").Replace("]", ""));
}
else if (item.InnerText.Contains("评论"))
{
//commentCount = Convert.ToInt32(item.InnerText.Replace("评论[", "").Replace("]", ""));
}
}
//如果已赞,那么获取方式是下面的
try
{
//likeCount = Convert.ToInt32(doc1.DocumentNode.Descendants("span").Where(x => x.Attributes["class"]?.Value == "cmt").ToList()[0].InnerText.Replace("已赞[", "").Replace("]", ""));
}
catch { }
//获取图片列表中的每一个图片的原图超链接url
List<string> originalPics = new List<string>();
if (picType == PicEnum.Pictures)
{
text = await HttpHelper.GetAsync<string>(photoListUrl, dataSource, cookie!);
var doc2 = new HtmlDocument();
doc2.LoadHtml(text);
list = doc2.DocumentNode.Descendants("img").ToList().ToList();
foreach (var item in list)
{
var photoUrl = "https://wx4.sinaimg.cn/large/" + Path.GetFileName(item.Attributes["src"]?.Value);
originalPics.Add(photoUrl);
}
}
else if (picType == PicEnum.Picture)
{
list = doc1.DocumentNode.Descendants("a").ToList();
foreach (var item in list)
{
if (item.InnerText.Contains("原图"))
{
originalPics.Add("https://wx4.sinaimg.cn/large/" + item.Attributes["href"].Value.Split("u=")[1] + ".jpg");
break;
}
}
}
else if (picType == PicEnum.Video && settings!.EnableDownloadVideo)
{
try
{
var res = await HttpHelper.GetAsync<VideoDetailModel>(videoUrl, dataSource, cookie!);
if (res != null && res.ok == 1)
{
originalPics.Add(res?.data?.@object?.stream?.hd_url!);
}
}
catch
{
AppendLog($"视频解析错误,原始url:{videoUrl}", MessageEnum.Error);
}
}
//下载获取图片列表中的图片原图
int photoCount = 1;
foreach (var item in originalPics)
{
if (isSkip)
break;
if (string.IsNullOrEmpty(item))
continue;
//替换非法字符
var invalidChar = Path.GetInvalidFileNameChars();
var newCaption = settings!.EnableShortenName ? "" : invalidChar.Aggregate(weiboContent, (o, r) => (o.Replace(r.ToString(), string.Empty)));
var fileName = downloadFolder + userId + "//" + "微博配图" + "//"
+ timestamp.ToString("yyyy-MM-dd HH_mm_ss") + newCaption + "_" + photoCount;
//后缀名区分图片/视频
if (picType == PicEnum.Video)
fileName += ".mp4";
else
fileName += ".jpg";
Debug.WriteLine(fileName);
//已存在的文件超过设置值,判定该用户下载过了
if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser)
{
AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info);
isSkip = true;
}
//已经下载过的跳过
if (File.Exists(fileName))
{
AppendLog("文件已存在,跳过下载" + Path.GetFullPath(fileName), MessageEnum.Warning);
countDownloadedSkipToNextUser++;
await Task.Delay(500);
continue;
}
//传入图片/视频的名字,开始下载图片/视频
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(item, dataSource, cookie!, fileName);
//修改文件日期时间为发博的时间
SetFileTime(fileName, timestamp);
AppendLog("已完成 " + Path.GetFileName(fileName), MessageEnum.Success);
}
catch (Exception ex)
{
AppendLog($"文件下载失败,原始url:{item},下载路径{fileName}", MessageEnum.Error);
}
photoCount++;
}
}
page++;
if (page > totalPage)
{
break;
}
//通过加入随机等待避免被限制。爬虫速度过快容易被系统限制(一段时间后限制会自动解除),加入随机等待模拟人的操作,可降低被系统限制的风险。默认是每爬取1到5页随机等待6到10秒,如果仍然被限,可适当增加sleep时间
Random rd = new Random();
int rnd = rd.Next(5000, 10000);
AppendLog($"随机等待{rnd}ms,避免爬虫速度过快被系统限制", MessageEnum.Info);
await Task.Delay(rnd);
}
}
//通过m.weibo.cn移动端api获取
else if (dataSource == WeiboDataSource.WeiboCnMobile)
{
//https://m.weibo.cn/api/container/getIndex?type=uid&value=10000000000&containerid=10760310000000000&since_id=5040866311798795
sinceId = startSinceId;
bool cachedUserInfo = false;
string personalFolder = userId;
page = startPage;
while (true)
{
if (isSkip)
break;
string url = $"https://m.weibo.cn/api/container/getIndex?type=uid&value={userId}&containerid=107603{userId}&since_id={sinceId}&page={page}";
var res = await HttpHelper.GetAsync<WeiboCnMobileModel>(url, dataSource, cookie!, logAction: AppendLog);
if (res != null && res?.Ok == 1 && res?.Data != null && res?.Data?.Cards != null && res?.Data?.Cards?.Count > 0)
{
if (res?.Data?.CardlistInfo?.SinceId != null)
GlobalVar.gSinceId = sinceId = (long)res?.Data?.CardlistInfo?.SinceId!;
AppendLog($"获取到{res?.Data?.CardlistInfo?.Total}条数据,正在下载第{page}页,SinceId: {sinceId}", MessageEnum.Info);
if (res?.Data?.Cards?[res.Data.Cards.Count - 1]?.Mblog?.User?.ScreenName == null)
return;
//获取用户资料
if (!cachedUserInfo)
{
nickName = res?.Data?.Cards?[res.Data.Cards.Count - 1]?.Mblog?.User?.ScreenName!;
personalFolder = $"{nickName}({userId})";
Directory.CreateDirectory(downloadFolder + "//" + personalFolder);
using (File.Create(downloadFolder + "//" + personalFolder + "//" + nickName)) { }
headUrl = "https://tvax2.sinaimg.cn/large/" + Path.GetFileName(res?.Data?.Cards?[res.Data.Cards.Count - 1]?.Mblog?.User?.AvatarHd!).Split("?")[0];
var fileName = downloadFolder + "//" + personalFolder + "//" + Path.GetFileName(headUrl);
bool downloadHeadImageSuccess = true;
//下载头像
if (!File.Exists(fileName))
{
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(headUrl, dataSource, cookie!, fileName);
}
catch
{
downloadHeadImageSuccess = false;
AppendLog($"头像下载失败", MessageEnum.Warning);
}
}
Image_Head?.Dispatcher.InvokeAsync(() =>
{
if (settings.ShowHeadImage && downloadHeadImageSuccess)
{
var bytes = File.ReadAllBytes(fileName);
MemoryStream ms = new MemoryStream(bytes);
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = ms;
bi.EndInit();
Image_Head.ImageSource = bi;
//Image_Head.ImageSource = new BitmapImage(new Uri(fileName));
}
TextBlock_UID!.Text = userId;
TextBlock_NickName.Text = nickName;
TextBlock_WeiboDesc.Text = res?.Data?.Cards?[res.Data.Cards.Count - 1]?.Mblog?.User?.Description!;
});
cachedUserInfo = true;
}
//获取图片列表中的每一个图片的原图超链接url
List<string> originalPics = new List<string>();
List<string> originalVideos = new List<string>();
List<string> originalLivePhotos = new List<string>();
string weiboContent = "";
DateTime timestamp = DateTime.Now;
foreach (var card in res?.Data?.Cards!)
{
if (isSkip)
break;
//9是微博,RetweetedStatus是转发
if (card?.CardType != 9 || card?.Mblog?.RetweetedStatus != null)
continue;
if (cancellationTokenSource.IsCancellationRequested)
{
AppendLog($"用户手动终止,当前页码: {page}, SinceID: {sinceId}", MessageEnum.Info);
return;
}
originalPics.Clear();
originalVideos.Clear();
originalLivePhotos.Clear();
//weiboContent = card?.Mblog?.Text!;
// 使用正则表达式去除 <a> 和 <span> 标签及其内容
string result = Regex.Replace(card?.Mblog?.Text!, @"<a.*?>.*?</a>|<span.*?>.*?</span>", string.Empty);
// 去除其他不需要的标签(如 <br />)
weiboContent = Regex.Replace(result, @"<.*?>", string.Empty);
string format = "ddd MMM dd HH:mm:ss K yyyy"; // 定义日期格式
timestamp = DateTime.ParseExact(card?.Mblog?.CreatedAt!, format, System.Globalization.CultureInfo.InvariantCulture);
//时间范围过滤,比设置日期早的跳过
if ((bool)settings?.EnableDatetimeRange)
{
if (card?.ProfileTypeId == "proweibotop_" && timestamp < settings?.StartDateTime)
continue;
else if (card?.ProfileTypeId == "proweibotop_" && timestamp >= settings?.StartDateTime)
{
}
else if (timestamp < settings?.StartDateTime)
{
AppendLog($"翻页到截至日期{settings?.StartDateTime},停止下载");
return;
}
}
Debug.WriteLine(card?.Mblog?.Text);
//如果PicNum大于PicIds.Count,那么图片可能超过9张图。
if (card?.Mblog?.PicIds != null && (bool)card?.Mblog?.PicIds?.Any()!)
{
if (card?.Mblog?.PicIds!.Count == card?.Mblog?.PicNum)
{
foreach (var item in card?.Mblog?.PicIds!)
{
var photoUrl = "https://wx4.sinaimg.cn/large/" + Path.GetFileName(item) + ".jpg";
originalPics.Add(photoUrl);
}
}
//多于9图
else
{
var picIds = await WeiboMidHelper.GetImageIdsByMidAsync(card?.Mblog?.Mid!, settings?.WeiboComCookie);
foreach (var item in picIds)
{
var photoUrl = "https://wx4.sinaimg.cn/large/" + Path.GetFileName(item) + ".jpg";
originalPics.Add(photoUrl);
}
}
}
if (card?.Mblog?.LivePhoto != null && (bool)(card?.Mblog?.LivePhoto?.Any()!))
{
foreach (var item in card?.Mblog?.LivePhoto!)
{
originalLivePhotos.Add(item);
}
}
//选最高清晰度
if (card?.Mblog?.PageInfo?.Urls?.Mp48kMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp48kMp4!);
}
else if (card?.Mblog?.PageInfo?.Urls?.Mp44kMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp44kMp4!);
}
else if (card?.Mblog?.PageInfo?.Urls?.Mp42kMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp42kMp4!);
}
else if (card?.Mblog?.PageInfo?.Urls?.Mp41080pMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp41080pMp4!);
}
else if (card?.Mblog?.PageInfo?.Urls?.Mp4720pMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp4720pMp4!);
}
else if (card?.Mblog?.PageInfo?.Urls?.Mp4HDMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp4HDMp4!);
}
else if (card?.Mblog?.PageInfo?.Urls?.Mp4LDMp4 != null)
{
originalVideos.Add(card?.Mblog?.PageInfo?.Urls?.Mp4LDMp4!);
}
//替换非法字符
var invalidChar = Path.GetInvalidFileNameChars();
var newCaption = settings!.EnableShortenName ? "" : invalidChar.Aggregate(weiboContent, (o, r) => (o.Replace(r.ToString(), string.Empty)));
var fileName = downloadFolder + "//" + personalFolder + "//" + "//"
+ timestamp.ToString("yyyy-MM-dd HH_mm_ss") + newCaption;
Debug.WriteLine(fileName);
int id = 1;
//下载获取图片列表中的图片原图
foreach (var item in originalPics)
{
if (isSkip)
break;
if (string.IsNullOrEmpty(item))
continue;
var fileNamee = fileName + $"_{id}.jpg";
//已存在的文件超过设置值,判定该用户下载过了
if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser)
{
AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info);
isSkip = true;
}
//已经下载过的跳过
if (File.Exists(fileNamee))
{
AppendLog("文件已存在,跳过下载" + Path.GetFullPath(fileNamee), MessageEnum.Warning);
countDownloadedSkipToNextUser++;
await Task.Delay(500);
continue;
}
//传入图片/视频的名字,开始下载图片/视频
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(item, dataSource, cookie!, fileNamee);
//修改文件日期时间为发博的时间
SetFileTime(fileNamee, timestamp);
AppendLog("已完成 " + Path.GetFileName(fileNamee), MessageEnum.Success);
}
catch (Exception ex)
{
AppendLog($"文件下载失败,原始url:{item},下载路径{fileNamee}", MessageEnum.Error);
}
id++;
}
if (settings!.EnableDownloadVideo)
{
foreach (var item in originalVideos)
{
if (isSkip)
break;
if (string.IsNullOrEmpty(item))
continue;
var fileNamee = fileName + $"_{id}.mp4";
//已存在的文件超过设置值,判定该用户下载过了
if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser)
{
AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info);
isSkip = true;
}
//已经下载过的跳过
if (File.Exists(fileNamee))
{
AppendLog("文件已存在,跳过下载" + Path.GetFullPath(fileNamee), MessageEnum.Warning);
countDownloadedSkipToNextUser++;
await Task.Delay(500);
continue;
}
//传入图片/视频的名字,开始下载图片/视频
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(item, dataSource, cookie!, fileNamee);
//修改文件日期时间为发博的时间
SetFileTime(fileNamee, timestamp);
AppendLog("已完成 " + Path.GetFileName(fileNamee), MessageEnum.Success);
}
catch (Exception ex)
{
AppendLog($"文件下载失败,原始url:{item},下载路径{fileNamee}", MessageEnum.Error);
}
id++;
}
}
if (settings!.EnableDownloadLivePhoto)
{
foreach (var item in originalLivePhotos)
{
if (isSkip)
break;
if (string.IsNullOrEmpty(item))
continue;
var fileNamee = fileName + $"_{id}.mov";
//已存在的文件超过设置值,判定该用户下载过了
if (settings!.CountDownloadedSkipToNextUser > 0 && countDownloadedSkipToNextUser > settings.CountDownloadedSkipToNextUser)
{
AppendLog($"已存在的文件{countDownloadedSkipToNextUser}超过设置值{settings.CountDownloadedSkipToNextUser},跳到下一个用户", MessageEnum.Info);
isSkip = true;
}
//已经下载过的跳过
if (File.Exists(fileNamee))
{
AppendLog("文件已存在,跳过下载" + Path.GetFullPath(fileNamee), MessageEnum.Warning);
countDownloadedSkipToNextUser++;
await Task.Delay(500);
continue;
}
//传入图片/视频的名字,开始下载图片/视频
try
{
await HttpHelper.GetAsync<AlbumDetailModel>(item, dataSource, cookie!, fileNamee);
//修改文件日期时间为发博的时间
SetFileTime(fileNamee, timestamp);
AppendLog("已完成 " + Path.GetFileName(fileNamee), MessageEnum.Success);
}
catch (Exception ex)
{
AppendLog($"文件下载失败,原始url:{item},下载路径{fileNamee}", MessageEnum.Error);
}
id++;
}
}
}
page++;
}
else
{
break;
}
//通过加入随机等待避免被限制。爬虫速度过快容易被系统限制(一段时间后限制会自动解除),加入随机等待模拟人的操作,可降低被系统限制的风险。默认是每爬取1到5页随机等待6到10秒,如果仍然被限,可适当增加sleep时间
Random rd = new Random();
int rnd = rd.Next(5000, 10000);
AppendLog($"随机等待{rnd}ms,避免爬虫速度过快被系统限制", MessageEnum.Info);
await Task.Delay(rnd);
}
}
//单个用户结束下载
if (!string.IsNullOrEmpty(nickName))
{
string info = $"{nickName} <a href=\"//weibo.com/u/{userId}\">{userId}{nickName}</a>于{DateTime.Now.ToString("HH:mm:ss")}结束下载,程序版本V{currentVersion}<img src=\"{headUrl}\">";
await PushPlusHelper.SendMessage(settings?.PushPlusToken!, "微博相册下载", info);
SentrySdk.CaptureMessage(info);
AddToUserIdList(userId, nickName);
AppendLog(info, MessageEnum.Info);
}
else
{
AppendLog($"未能获取到用户 {userId} 的基本信息(如昵称),下载失败。最后尝试位置 Page: {page}, SinceID: {sinceId}。可能原因:用户不存在、暂无微博内容、Cookie失效或网络问题。", MessageEnum.Error);
}
}
});
//所有用户结束下载
AppendLog("结束下载。", MessageEnum.Info);
}
catch (Exception ex)
{
string msg = $"遇到错误,uid: {GlobalVar.gId},DataSource: {dataSource},Page:{GlobalVar.gPage},SinceId: {GlobalVar.gSinceId}," + ex.ToString() + ",请稍后重试。";
AppendLog(msg, MessageEnum.Error);
SentrySdk.CaptureMessage(msg, SentryLevel.Error);
}
finally
{
isDownloading = false;
Application.Current.Dispatcher.Invoke(() =>
{
tbDownload.Text = "开始下载";
});
}
}
public void AppendLog(string text, MessageEnum messageEnum = MessageEnum.Info)
{
string msg = $"{DateTime.Now} {text}";
Debug.WriteLine(msg);
ListView_Messages?.Dispatcher.InvokeAsync(() =>
{
Messages.Insert(0, new MessageModel()
{
Time = DateTime.Now.ToString("HH:mm:ss"),
Message = text,
MessageType = messageEnum
});
});
}
private void AddToUserIdList(string userId, string nickName)
{
//不在列表的,才写入文件
if (!uids.Contains(userId))
{
uids.Add(userId);
File.AppendAllText("uidList.txt", Environment.NewLine + $"{userId},{nickName}");
}
}
private void InitUidsData()
{
//配置文件不存在就创建
if (!File.Exists("uidList.txt"))
{
File.WriteAllText("uidList.txt", "//可以是多用户,换行隔开。\r\n//行内用英文逗号隔开,用户id(必填),用户名(可选)\r\n");
}
uids.Clear();
//文件中可以是多用户,换行隔开。行内用英文逗号隔开,用户id(必填),用户名(可选)
var lines = File.ReadAllLines("uidList.txt");
foreach (var line in lines)
{
if (line.StartsWith("//") || string.IsNullOrEmpty(line.Trim()))
continue;
var temp = line.Split(',');
uids.Add(temp[0]);
}
}
private void InitSettingsData()
{
if (!File.Exists("Settings.json"))
{
File.WriteAllText("Settings.json", JsonConvert.SerializeObject(new SettingsModel(), Formatting.Indented));
}
string settingsContent = File.ReadAllText("Settings.json");
settings = JsonConvert.DeserializeObject<SettingsModel>(settingsContent);
if (settings == null)
{
AppendLog("Settings.json文件缺失,请重新下载程序", MessageEnum.Error);
return;
}
if (string.IsNullOrEmpty(settings.PushPlusToken))
{
AppendLog("没有检测到PushPlus token,程序将不会推送消息到微信。如需推送,请登录https://www.pushplus.plus/设置", MessageEnum.Info);
}
if (dataSource == WeiboDataSource.WeiboCn || dataSource == WeiboDataSource.WeiboCnMobile)
{
cookie = settings.WeiboCnCookie;
}
else
{
cookie = settings.WeiboComCookie;
}
if (string.IsNullOrEmpty(cookie))
{
AppendLog("没有检测到cookie,程序将无法抓取数据,请在设置里面扫码获取", MessageEnum.Error);
return;
}
if (!settings.EnableCrontab)
{
AppendLog("没有开启Crontab定时任务,程序将不会自动执行", MessageEnum.Info);
}
if (settings.EnableCrontab && !string.IsNullOrEmpty(settings.Crontab))
{
AppendLog($"已开启Crontab定时任务,{CronExpressionDescriptor.ExpressionDescriptor.GetDescription(settings.Crontab)}", MessageEnum.Info);
}
}
private void SetFileTime(string filename, DateTime timestamp)
{
File.SetCreationTime(filename, timestamp);
File.SetLastWriteTime(filename, timestamp);
File.SetLastAccessTime(filename, timestamp);
}
#region UI操作
private async void StartDownLoad(object sender, RoutedEventArgs e)
{
if (tbDownload.Text == "开始下载")
{
if (isDownloading)
{
AppendLog("正在执行下载任务");
return;
}
isDownloading = true;
tbDownload.Text = "停止下载";
string pattern = @"\d+"; // 匹配一个或多个数字
Match match = Regex.Match(TextBox_WeiboId.Text.Trim(), pattern);
// 读取用户输入的起始页码和SinceId
int.TryParse(TextBox_StartPage.Text, out int startPage);
long.TryParse(TextBox_StartSinceId.Text, out long startSinceId);
if (match.Success)
{
await Start(match.Value, startPage, startSinceId);
}
else
{
AppendLog("没有找到微博账号", MessageEnum.Warning);
isDownloading = false;
tbDownload.Text = "开始下载";
}
}
else
{
isDownloading = false;
tbDownload.Text = "开始下载";
cancellationTokenSource?.Cancel();
}
}
private void StopDownLoad(object sender, RoutedEventArgs e)
{
cancellationTokenSource?.Cancel();
}
private async void BatchDownLoad(object sender, RoutedEventArgs e)
{
if (isDownloading)
{
AppendLog("正在执行下载任务");
return;
}
isDownloading = true;
AppendLog(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + "开启批量任务");
foreach (var item in uids)
{
await Start(item.Trim());
AppendLog("等待60s,下载下一个用户相册数据");
await Task.Delay(60 * 1000);
}
AppendLog(DateTime.Now.ToString("yyyy-MM-dd HH-mm-ss") + "结束批量任务");
isDownloading = false;
}
private void ListView_CopyLog(object sender, RoutedEventArgs e)
{
if (ListView_Messages.SelectedItem != null)
Clipboard.SetText((ListView_Messages.SelectedItem! as MessageModel)!.Message);
}
private void ListView_ClearLog(object sender, RoutedEventArgs e)
{
Messages.Clear();
}
private void ListView_ExportLog(object sender, RoutedEventArgs e)
{
var dialog = new Microsoft.Win32.SaveFileDialog();
dialog.FileName = $"{GlobalVar.gId}-log";
dialog.DefaultExt = ".txt";
dialog.Filter = "下载日志 (.txt)|*.txt";
bool? result = dialog.ShowDialog();
if (result == true)
{
StringBuilder sb = new StringBuilder();
foreach (var message in Messages)
{
string line = $"{message.Time},{message.Message},{message.MessageType}";
sb.AppendLine(line);
}
File.WriteAllText(dialog.FileName, sb.ToString());
}
}
private void ListView_OpenDownloadFolder(object sender, RoutedEventArgs e)
{
Process.Start("explorer.exe", Path.GetFullPath(downloadFolder));
}
private async void TextBox_WeiboId_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
StartDownLoad(null, null);
}
}
private void OpenSettings(object sender, RoutedEventArgs e)
{
var set = new SettingsWindow();
set.Owner = this;
set.ShowDialog();
AppendLog("设置已更新");
InitSettingsData();
}
private void MicaWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
Border_Head.Width = Border_Head.Height = Column_LeftGrid.ActualWidth * 0.8;
Border_Head.CornerRadius = new CornerRadius(Column_LeftGrid.ActualWidth * 0.8);
}
#endregion
}
}
================================================
FILE: WeiboAlbumDownloader/Models/AlbumDetailModel.cs
================================================
using System.Collections.Generic;
namespace WeiboAlbumDownloader.Models
{
public class AlbumDetailModel
{
/// <summary>
///
/// </summary>
public string result { get; set; }
/// <summary>
///
/// </summary>
public int code { get; set; }
/// <summary>
///
/// </summary>
public string msg { get; set; }
/// <summary>
///
/// </summary>
public int timestamp { get; set; }
/// <summary>
///
/// </summary>
public PhotoListData data { get; set; }
}
public class PhotoListCount
{
/// <summary>
/// 评论数
/// </summary>
public int comments { get; set; }
/// <summary>
/// 查数
/// </summary>
public int clicks { get; set; }
/// <summary>
/// 转发数
/// </summary>
public int retweets { get; set; }
/// <summary>
/// 点赞数
/// </summary>
public int likes { get; set; }
}
public class PhotoListItem
{
/// <summary>
/// 相册ID
/// </summary>
public string? album_id { get; set; }
/// <summary>
/// 推文内容
/// </summary>
public string caption { get; set; }
/// <summary>
/// 推文内容
/// </summary>
public string caption_render { get; set; }
/// <summary>
///
/// </summary>
public PhotoListCount count { get; set; }
/// <summary>
/// 创建日期
/// </summary>
public string created_at { get; set; }
/// <summary>
///
/// </summary>
public int @type { get; set; }
/// <summary>
///
/// </summary>
public string @from { get; set; }
/// <summary>
///
/// </summary>
public string is_favorited { get; set; }
/// <summary>
///
/// </summary>
public string is_liked { get; set; }
/// <summary>
///
/// </summary>
public string oid { get; set; }
/// <summary>
///
/// </summary>
public string photo_id { get; set; }
/// <summary>
///
/// </summary>
public string pic_host { get; set; }
/// <summary>
///
/// </summary>
public string pic_name { get; set; }
/// <summary>
///
/// </summary>
public string pic_pid { get; set; }
/// <summary>
///
/// </summary>
public int pic_type { get; set; }
/// <summary>
///
/// </summary>
public string source { get; set; }
/// <summary>
///
/// </summary>
public string tags { get; set; }
/// <summary>
///
/// </summary>
public int timestamp { get; set; }
/// <summary>
/// 用户id
/// </summary>
public long uid { get; set; }
/// <summary>
///
/// </summary>
public string updated_at { get; set; }
/// <summary>
///
/// </summary>
public int property { get; set; }
/// <summary>
///
/// </summary>
public int? visible_type { get; set; }
/// <summary>
///
/// </summary>
public string mid { get; set; }
/// <summary>
///
/// </summary>
public string is_private { get; set; }
/// <summary>
///
/// </summary>
public string feed_id { get; set; }
/// <summary>
///
/// </summary>
public double? latitude { get; set; }
/// <summary>
///
/// </summary>
public double? longitude { get; set; }
/// <summary>
///
/// </summary>
public string is_paid { get; set; }
/// <summary>
///
/// </summary>
public int mblog_vip_type { get; set; }
}
public class PhotoListData
{
/// <summary>
/// 相册ID
/// </summary>
public string? album_id { get; set; }
/// <summary>
/// 照片总数
/// </summary>
public int total { get; set; }
/// <summary>
/// 照片列表,分页
/// </summary>
public List<PhotoListItem>? photo_list { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/Models/AlbumDetailModel2.cs
================================================
using Newtonsoft.Json;
using System.Collections.Generic;
namespace WeiboAlbumDownloader.Models
{
public partial class AlbumDetailModel2
{
[JsonProperty("data")]
public PhotoListData2? PhotoListData2 { get; set; }
[JsonProperty("ok")]
public long Ok { get; set; }
}
public partial class PhotoListData2
{
[JsonProperty("type")]
public string? Type { get; set; }
[JsonProperty("list")]
public List<PhotoListItem2>? PhotoListItem2 { get; set; }
[JsonProperty("since_id")]
public long SinceId { get; set; }
}
public partial class PhotoListItem2
{
[JsonProperty("pid")]
public string? Pid { get; set; }
[JsonProperty("mid")]
public string? Mid { get; set; }
[JsonProperty("is_paid")]
public bool IsPaid { get; set; }
[JsonProperty("timeline_month")]
public string? TimelineMonth { get; set; }
[JsonProperty("timeline_year")]
public string? TimelineYear { get; set; }
[JsonProperty("object_id")]
public string? ObjectId { get; set; }
[JsonProperty("type")]
public string? Type { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/Models/MessageModel.cs
================================================
using System;
using WeiboAlbumDownloader.Enums;
namespace WeiboAlbumDownloader.Models
{
public class MessageModel
{
public string? Message { get; set; }
public string? Time { get; set; }
public MessageEnum MessageType { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/Models/SettingsModel.cs
================================================
using System;
using WeiboAlbumDownloader.Enums;
namespace WeiboAlbumDownloader.Models
{
public class SettingsModel
{
//数据源
public WeiboDataSource DataSource { get; set; } = WeiboDataSource.WeiboCnMobile;
//是否显示作者头像
public bool ShowHeadImage { get; set; } = true;
//weibo.cn cookie
public string? WeiboCnCookie { get; set; }
//weibo.com cookie
public string? WeiboComCookie { get; set; }
//推送到微信,填了就会发送
public string? PushPlusToken { get; set; }
//是否开启Crontab定时任务
public bool EnableCrontab { get; set; } = true;
//Crontab定时任务
public string? Crontab { get; set; } = "14 2 * * *";
//用来跳过到下一个uid的计数。如果当前uid下载的时候已存在文件超过此计数,则判定下载过了。-1表示不判定
public int CountDownloadedSkipToNextUser { get; set; } = 20;
//是否开启时间范围
public bool EnableDatetimeRange { get; set; } = false;
public DateTime? StartDateTime { get; set; }
//开启下载视频功能,默认开启
public bool EnableDownloadVideo { get; set; } = true;
//开启下载LivePhoto
public bool EnableDownloadLivePhoto { get; set; } = true;
//启用后图片仅以日期+编号命名,文件名中不在包含博文内容
public bool EnableShortenName { get; set; } = false;
}
}
================================================
FILE: WeiboAlbumDownloader/Models/UserAlbumModel.cs
================================================
using System.Collections.Generic;
namespace WeiboAlbumDownloader.Models
{
internal class UserAlbumModel
{
/// <summary>
///
/// </summary>
public string result { get; set; }
/// <summary>
///
/// </summary>
public int code { get; set; }
/// <summary>
///
/// </summary>
public string msg { get; set; }
/// <summary>
///
/// </summary>
public int timestamp { get; set; }
/// <summary>
///
/// </summary>
public AlbumData data { get; set; }
}
public class AlbumCount
{
/// <summary>
///
/// </summary>
public int photos { get; set; }
/// <summary>
///
/// </summary>
public int likes { get; set; }
/// <summary>
///
/// </summary>
public int comments { get; set; }
/// <summary>
///
/// </summary>
public int retweets { get; set; }
}
public class AlbumListItem
{
/// <summary>
///
/// </summary>
public string album_id { get; set; }
/// <summary>
///
/// </summary>
public string uid { get; set; }
/// <summary>
///
/// </summary>
public string property { get; set; }
/// <summary>
///
/// </summary>
public int status { get; set; }
/// <summary>
///
/// </summary>
public int type { get; set; }
/// <summary>
///
/// </summary>
public string source { get; set; }
/// <summary>
///
/// </summary>
public string album_order { get; set; }
/// <summary>
///
/// </summary>
public string created_at { get; set; }
/// <summary>
///
/// </summary>
public string usort { get; set; }
/// <summary>
/// 头像相册
/// </summary>
public string caption { get; set; }
/// <summary>
///
/// </summary>
public string description { get; set; }
/// <summary>
///
/// </summary>
public string cover_pic { get; set; }
/// <summary>
///
/// </summary>
public string cover_photo_id { get; set; }
/// <summary>
///
/// </summary>
public string question { get; set; }
/// <summary>
///
/// </summary>
public string answer { get; set; }
/// <summary>
///
/// </summary>
public string updated_at { get; set; }
/// <summary>
///
/// </summary>
public string timestamp { get; set; }
/// <summary>
///
/// </summary>
public int updated_at_int { get; set; }
/// <summary>
///
/// </summary>
public string is_favorited { get; set; }
/// <summary>
///
/// </summary>
public string is_private { get; set; }
/// <summary>
///
/// </summary>
public string thumb120_pic { get; set; }
/// <summary>
///
/// </summary>
public string thumb300_pic { get; set; }
/// <summary>
///
/// </summary>
public string sq612_pic { get; set; }
/// <summary>
///
/// </summary>
public AlbumCount count { get; set; }
}
public class AlbumData
{
/// <summary>
///
/// </summary>
public int total { get; set; }
/// <summary>
///
/// </summary>
public List<AlbumListItem> album_list { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/Models/UserAlbumModel2.cs
================================================
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace WeiboAlbumDownloader.Models
{
public partial class UserAlbumModel2
{
[JsonProperty("data")]
public AlbumData2 Data { get; set; }
[JsonProperty("bottom_tips_visible")]
public bool BottomTipsVisible { get; set; }
[JsonProperty("bottom_tips_text")]
public string BottomTipsText { get; set; }
[JsonProperty("ok")]
public long Ok { get; set; }
}
public partial class AlbumData2
{
[JsonProperty("album_list")]
public List<AlbumList2> AlbumList { get; set; }
[JsonProperty("album_since_id")]
public long AlbumSinceId { get; set; }
[JsonProperty("since_id")]
public string SinceId { get; set; }
[JsonProperty("list")]
public List<List2> List { get; set; }
}
public partial class AlbumList2
{
[JsonProperty("pic_title")]
public string PicTitle { get; set; }
[JsonProperty("containerid")]
public string Containerid { get; set; }
[JsonProperty("pic")]
public string Pic { get; set; }
}
public partial class List2
{
[JsonProperty("pid")]
public string Pid { get; set; }
[JsonProperty("mid")]
public string Mid { get; set; }
[JsonProperty("is_paid")]
public bool IsPaid { get; set; }
[JsonProperty("timeline_month")]
public string TimelineMonth { get; set; }
[JsonProperty("timeline_year")]
public string TimelineYear { get; set; }
[JsonProperty("object_id")]
public string ObjectId { get; set; }
[JsonProperty("type")]
public string Type { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/Models/VideoDetailModel.cs
================================================
namespace WeiboAlbumDownloader.Models
{
public class VideoDetailModel
{
/// <summary>
///
/// </summary>
public int ok { get; set; }
/// <summary>
///
/// </summary>
public VideoData data { get; set; }
}
public class VideoData
{
/// <summary>
///
/// </summary>
public string object_id { get; set; }
/// <summary>
///
/// </summary>
public string object_type { get; set; }
/// <summary>
///
/// </summary>
public @object @object { get; set; }
}
public class @object
{
/// <summary>
///
/// </summary>
public string summary { get; set; }
/// <summary>
///
/// </summary>
public Author author { get; set; }
/// <summary>
///
/// </summary>
public @Stream stream { get; set; }
/// <summary>
///
/// </summary>
public string created_at { get; set; }
/// <summary>
///
/// </summary>
public Image image { get; set; }
}
public class Author
{
/// <summary>
///
/// </summary>
public long id { get; set; }
/// <summary>
///
/// </summary>
public string screen_name { get; set; }
/// <summary>
///
/// </summary>
public string profile_image_url { get; set; }
/// <summary>
///
/// </summary>
public string profile_url { get; set; }
/// <summary>
///
/// </summary>
public int statuses_count { get; set; }
/// <summary>
///
/// </summary>
public string verified { get; set; }
/// <summary>
///
/// </summary>
public int verified_type { get; set; }
/// <summary>
///
/// </summary>
public string close_blue_v { get; set; }
/// <summary>
///
/// </summary>
public string description { get; set; }
/// <summary>
///
/// </summary>
public string gender { get; set; }
/// <summary>
///
/// </summary>
public int mbtype { get; set; }
/// <summary>
///
/// </summary>
public int svip { get; set; }
/// <summary>
///
/// </summary>
public int urank { get; set; }
/// <summary>
///
/// </summary>
public int mbrank { get; set; }
/// <summary>
///
/// </summary>
public string follow_me { get; set; }
/// <summary>
///
/// </summary>
public string following { get; set; }
/// <summary>
///
/// </summary>
public int follow_count { get; set; }
/// <summary>
///
/// </summary>
public string followers_count { get; set; }
/// <summary>
///
/// </summary>
public string followers_count_str { get; set; }
/// <summary>
///
/// </summary>
public string cover_image_phone { get; set; }
/// <summary>
///
/// </summary>
public string avatar_hd { get; set; }
/// <summary>
///
/// </summary>
public string like { get; set; }
/// <summary>
///
/// </summary>
public string like_me { get; set; }
/// <summary>
///
/// </summary>
public string special_follow { get; set; }
}
public class @Stream
{
/// <summary>
///
/// </summary>
public double duration { get; set; }
/// <summary>
///
/// </summary>
public string format { get; set; }
/// <summary>
///
/// </summary>
public int width { get; set; }
/// <summary>
///
/// </summary>
public string hd_url { get; set; }
/// <summary>
///
/// </summary>
public string url { get; set; }
/// <summary>
///
/// </summary>
public int height { get; set; }
}
public class Image
{
/// <summary>
///
/// </summary>
public int width { get; set; }
/// <summary>
///
/// </summary>
public string pid { get; set; }
/// <summary>
///
/// </summary>
public int source { get; set; }
/// <summary>
///
/// </summary>
public int is_self_cover { get; set; }
/// <summary>
///
/// </summary>
public int type { get; set; }
/// <summary>
///
/// </summary>
public string url { get; set; }
/// <summary>
///
/// </summary>
public int height { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs
================================================
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
namespace WeiboAlbumDownloader.Models
{
public partial class WeiboCnMobileModel
{
[JsonProperty("ok")]
public long? Ok { get; set; }
[JsonProperty("data")]
public Data? Data { get; set; }
}
public partial class Data
{
[JsonProperty("cardlistInfo")]
public CardlistInfo? CardlistInfo { get; set; }
[JsonProperty("cards")]
public List<Card>? Cards { get; set; }
[JsonProperty("scheme")]
public string? Scheme { get; set; }
[JsonProperty("showAppTips")]
public long? ShowAppTips { get; set; }
}
public partial class CardlistInfo
{
[JsonProperty("containerid")]
public string? Containerid { get; set; }
[JsonProperty("v_p")]
public long? VP { get; set; }
[JsonProperty("show_style")]
public long? ShowStyle { get; set; }
[JsonProperty("total")]
public long? Total { get; set; }
[JsonProperty("autoLoadMoreIndex")]
public long? AutoLoadMoreIndex { get; set; }
[JsonProperty("since_id")]
public long? SinceId { get; set; }
}
public partial class Card
{
[JsonProperty("card_type")]
public long? CardType { get; set; }
[JsonProperty("profile_type_id")]
public string? ProfileTypeId { get; set; }
[JsonProperty("itemid")]
public string? Itemid { get; set; }
[JsonProperty("scheme")]
public Uri? Scheme { get; set; }
[JsonProperty("mblog")]
public Mblog? Mblog { get; set; }
}
public partial class Mblog
{
[JsonProperty("visible")]
public Visible? Visible { get; set; }
[JsonProperty("created_at")]
public string? CreatedAt { get; set; }
[JsonProperty("id")]
public string? Id { get; set; }
[JsonProperty("mid")]
public string? Mid { get; set; }
[JsonProperty("can_edit")]
public bool? CanEdit { get; set; }
[JsonProperty("text")]
public string? Text { get; set; }
[JsonProperty("textLength")]
public long? TextLength { get; set; }
[JsonProperty("source")]
public string? Source { get; set; }
[JsonProperty("favorited")]
public bool? Favorited { get; set; }
[JsonProperty("pic_ids")]
public List<string>? PicIds { get; set; }
[JsonProperty("thumbnail_pic")]
public string? ThumbnailPic { get; set; }
[JsonProperty("bmiddle_pic")]
public string? BmiddlePic { get; set; }
[JsonProperty("original_pic")]
public string? OriginalPic { get; set; }
[JsonProperty("is_paid")]
public bool? IsPaid { get; set; }
[JsonProperty("mblog_vip_type")]
public long? MblogVipType { get; set; }
[JsonProperty("user")]
public User? User { get; set; }
[JsonProperty("retweeted_status")]
public RetweetedStatus? RetweetedStatus { get; set; }
[JsonProperty("reposts_count")]
public long? RepostsCount { get; set; }
[JsonProperty("comments_count")]
public long? CommentsCount { get; set; }
[JsonProperty("reprint_cmt_count")]
public long? ReprintCmtCount { get; set; }
[JsonProperty("attitudes_count")]
public long? AttitudesCount { get; set; }
[JsonProperty("mixed_count")]
public long? MixedCount { get; set; }
[JsonProperty("pending_approval_count")]
public long? PendingApprovalCount { get; set; }
[JsonProperty("isLongText")]
public bool? IsLongText { get; set; }
[JsonProperty("show_mlevel")]
public long? ShowMlevel { get; set; }
[JsonProperty("darwin_tags")]
public List<DarwinTag>? DarwinTags { get; set; }
[JsonProperty("ad_marked")]
public bool? AdMarked { get; set; }
[JsonProperty("mblogtype")]
public long? Mblogtype { get; set; }
[JsonProperty("item_category")]
public string? ItemCategory { get; set; }
[JsonProperty("rid")]
public string? Rid { get; set; }
[JsonProperty("extern_safe")]
public long? ExternSafe { get; set; }
[JsonProperty("number_display_strategy")]
public NumberDisplayStrategy? NumberDisplayStrategy { get; set; }
[JsonProperty("content_auth")]
public long? ContentAuth { get; set; }
[JsonProperty("is_show_mixed")]
public bool? IsShowMixed { get; set; }
[JsonProperty("comment_manage_info")]
public CommentManageInfo? CommentManageInfo { get; set; }
[JsonProperty("pic_num")]
public long? PicNum { get; set; }
[JsonProperty("mlevel")]
public long? Mlevel { get; set; }
[JsonProperty("region_name")]
public string? RegionName { get; set; }
[JsonProperty("region_opt")]
public long? RegionOpt { get; set; }
[JsonProperty("analysis_extra")]
public string? AnalysisExtra { get; set; }
[JsonProperty("mblog_menu_new_style")]
public long? MblogMenuNewStyle { get; set; }
[JsonProperty("edit_config")]
public EditConfig? EditConfig { get; set; }
[JsonProperty("page_info")]
public PageInfo? PageInfo { get; set; }
[JsonProperty("pics")]
//public List<Pic>? Pics { get; set; }
public object? Pics { get; set; }
[JsonProperty("live_photo")]
public List<string>? LivePhoto { get; set; }
[JsonProperty("bid")]
public string? Bid { get; set; }
[JsonProperty("safe_tags")]
public long? SafeTags { get; set; }
}
public partial class RetweetedStatus
{
}
public partial class CommentManageInfo
{
[JsonProperty("comment_permission_type")]
public long? CommentPermissionType { get; set; }
[JsonProperty("approval_comment_type")]
public long? ApprovalCommentType { get; set; }
[JsonProperty("comment_sort_type")]
public long? CommentSortType { get; set; }
}
public partial class DarwinTag
{
[JsonProperty("object_type")]
public string? ObjectType { get; set; }
[JsonProperty("object_id")]
public string? ObjectId { get; set; }
[JsonProperty("display_name")]
public string? DisplayName { get; set; }
[JsonProperty("enterprise_uid")]
public string? EnterpriseUid { get; set; }
[JsonProperty("bd_object_type")]
public string? BdObjectType { get; set; }
}
public partial class EditConfig
{
[JsonProperty("edited")]
public bool? Edited { get; set; }
}
public partial class NumberDisplayStrategy
{
[JsonProperty("apply_scenario_flag")]
public long? ApplyScenarioFlag { get; set; }
[JsonProperty("display_text_min_number")]
public long? DisplayTextMinNumber { get; set; }
[JsonProperty("display_text")]
public string? DisplayText { get; set; }
}
public partial class PageInfo
{
[JsonProperty("type")]
public string? Type { get; set; }
[JsonProperty("icon")]
public string? Icon { get; set; }
[JsonProperty("page_pic")]
public PagePic? PagePic { get; set; }
[JsonProperty("page_url")]
public string? PageUrl { get; set; }
[JsonProperty("page_title")]
public string? PageTitle { get; set; }
[JsonProperty("content1")]
public string? Content1 { get; set; }
[JsonProperty("content2")]
public string? Content2 { get; set; }
[JsonProperty("video_orientation")]
public string? VideoOrientation { get; set; }
[JsonProperty("play_count")]
public string? PlayCount { get; set; }
[JsonProperty("media_info")]
public Mediainfo? Mediainfo { get; set; }
[JsonProperty("urls")]
public Urls? Urls { get; set; }
}
public partial class Mediainfo
{
[JsonProperty("stream_url")]
public string? StreamUrl { get; set; }
[JsonProperty("stream_url_hd")]
public string? StreamUrlHd { get; set; }
[JsonProperty("duration")]
public string? Duration { get; set; }
}
public partial class Urls
{
[JsonProperty("mp4_8k_mp4")]
public string? Mp48kMp4 { get; set; }
[JsonProperty("mp4_4k_mp4")]
public string? Mp44kMp4 { get; set; }
[JsonProperty("mp4_2k_mp4")]
public string? Mp42kMp4 { get; set; }
[JsonProperty("mp4_1080p_mp4")]
public string? Mp41080pMp4 { get; set; }
[JsonProperty("mp4_720p_mp4")]
public string? Mp4720pMp4 { get; set; }
[JsonProperty("mp4_hd_mp4")]
public string? Mp4HDMp4 { get; set; }
[JsonProperty("mp4_ld_mp4")]
public string? Mp4LDMp4 { get; set; }
}
public partial class PagePic
{
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("width")]
public int? Width { get; set; }
[JsonProperty("height")]
public int? Height { get; set; }
}
public partial class Pic
{
[JsonProperty("pid")]
public string? Pid { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("size")]
public string? Size { get; set; }
[JsonProperty("geo")]
public object? Geo { get; set; }
[JsonProperty("large")]
public Large? Large { get; set; }
[JsonProperty("videoSrc")]
public string? VideoSrc { get; set; }
[JsonProperty("type")]
public string? Type { get; set; }
}
public partial class PicGeo
{
[JsonProperty("width")]
public long? Width { get; set; }
[JsonProperty("height")]
public long? Height { get; set; }
[JsonProperty("croped")]
public bool? Croped { get; set; }
}
public partial class Large
{
[JsonProperty("size")]
public string? Size { get; set; }
[JsonProperty("url")]
public string? Url { get; set; }
[JsonProperty("geo")]
public object? Geo { get; set; }
}
public partial class LargeGeo
{
[JsonProperty("width")]
public long? Width { get; set; }
[JsonProperty("height")]
public long? Height { get; set; }
[JsonProperty("croped")]
public bool? Croped { get; set; }
}
public partial class User
{
[JsonProperty("id")]
public long? Id { get; set; }
[JsonProperty("screen_name")]
public string? ScreenName { get; set; }
[JsonProperty("profile_image_url")]
public string? ProfileImageUrl { get; set; }
[JsonProperty("profile_url")]
public string? ProfileUrl { get; set; }
[JsonProperty("close_blue_v")]
public bool? CloseBlueV { get; set; }
[JsonProperty("description")]
public string? Description { get; set; }
[JsonProperty("follow_me")]
public bool? FollowMe { get; set; }
[JsonProperty("following")]
public bool? Following { get; set; }
[JsonProperty("follow_count")]
public long? FollowCount { get; set; }
[JsonProperty("followers_count")]
public string? FollowersCount { get; set; }
[JsonProperty("cover_image_phone")]
public string? CoverImagePhone { get; set; }
[JsonProperty("avatar_hd")]
public string? AvatarHd { get; set; }
[JsonProperty("badge")]
public Dictionary<string, long?>? Badge { get; set; }
[JsonProperty("statuses_count")]
public long? StatusesCount { get; set; }
[JsonProperty("verified")]
public bool? Verified { get; set; }
[JsonProperty("verified_type")]
public long? VerifiedType { get; set; }
[JsonProperty("gender")]
public string? Gender { get; set; }
[JsonProperty("mbtype")]
public long? Mbtype { get; set; }
[JsonProperty("svip")]
public long? Svip { get; set; }
[JsonProperty("urank")]
public long? Urank { get; set; }
[JsonProperty("mbrank")]
public long? Mbrank { get; set; }
[JsonProperty("followers_count_str")]
public string? FollowersCountStr { get; set; }
[JsonProperty("verified_reason")]
public string? VerifiedReason { get; set; }
[JsonProperty("like")]
public bool? Like { get; set; }
[JsonProperty("like_me")]
public bool? LikeMe { get; set; }
[JsonProperty("special_follow")]
public bool? SpecialFollow { get; set; }
}
public partial class Visible
{
[JsonProperty("type")]
public long? Type { get; set; }
[JsonProperty("list_id")]
public long? ListId { get; set; }
}
}
================================================
FILE: WeiboAlbumDownloader/SettingsWindow.xaml
================================================
<mica:MicaWindow
x:Class="WeiboAlbumDownloader.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WeiboAlbumDownloader"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mica="clr-namespace:MicaWPF.Controls;assembly=MicaWPF"
Title="设置"
Width="1200"
Height="567"
FontFamily="微软雅黑"
Icon="/weibo.ico"
ResizeMode="NoResize"
SystemBackdropType="Acrylic"
TitleBarType="WinUI"
WindowStartupLocation="CenterOwner"
mc:Ignorable="d">
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
<Grid>
<StackPanel Margin="8">
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Foreground="Gray"
Text="数据源" />
<ComboBox
x:Name="ComboBox_DataSource"
Margin="16,8,0,8"
ToolTip="不懂的话选择m.weibo.cn" />
<TextBlock
Margin="8,8,0,0"
VerticalAlignment="Center"
Foreground="Gray"
Text="Cookie" />
<Grid Margin="8,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<mica:Button
Width="200"
Height="32"
Margin="8,0"
HorizontalAlignment="Left"
Background="LightYellow"
Click="GetCookie"
Tag="cn"
ToolTip="打开手机版微博,扫描二维码确认登陆">
<StackPanel Orientation="Horizontal">
<Image Width="18" Source="/Assets/cookie.png" />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="扫码获取weibo.cn Cookie" />
</StackPanel>
</mica:Button>
<mica:TextBox
x:Name="TextBox_WeiboCnCookie"
Grid.Column="1"
Height="32"
Margin="8,0,0,0"
Watermark="weibo.cn cookie" />
</Grid>
<Grid Margin="8,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<mica:Button
Width="200"
Height="32"
Margin="8,0"
HorizontalAlignment="Left"
Background="LightYellow"
Click="GetCookie"
Tag="com"
ToolTip="打开手机版微博,扫描二维码确认登陆">
<StackPanel Orientation="Horizontal">
<Image Width="18" Source="/Assets/cookie.png" />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="扫码获取weibo.com Cookie" />
</StackPanel>
</mica:Button>
<mica:TextBox
x:Name="TextBox_WeiboComCookie"
Grid.Column="1"
Height="32"
Margin="8,0,0,0"
Watermark="weibo.com cookie" />
</Grid>
<TextBlock
Grid.Row="1"
Margin="16,8,8,0"
VerticalAlignment="Center"
Foreground="Gray"
TextWrapping="Wrap">
<Run Text="扫码无法打开浏览器时,可自行在PC浏览器打开" />
<Hyperlink NavigateUri="https://weibo.com/" RequestNavigate="Hyperlink_RequestNavigate">weibo.com</Hyperlink>
<Run Text=",点击某一用户头像,进入主页。uid就是地址栏中的最后一串数字,比如https://weibo.com/u/1000000000。Cookie可以通过点击上方按钮打开页面扫码获取,或者按F12进入控制台,网络-全部,在名称栏选择uid,标头-请求标头-Cookie。" />
<Hyperlink NavigateUri="https://weibo.com/" RequestNavigate="Hyperlink_RequestNavigate">weibo.com</Hyperlink>
<Run Text="和" />
<Hyperlink NavigateUri="https://weibo.cn" RequestNavigate="Hyperlink_RequestNavigate">weibo.cn</Hyperlink>
<Run Text="cookie不一样,请注意区分。" />
</TextBlock>
<TextBlock
Margin="8,16,0,0"
VerticalAlignment="Center"
Foreground="Gray"
Text="消息推送" />
<Grid Margin="8,8,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<mica:Button
Width="200"
Height="32"
Margin="8,0"
HorizontalAlignment="Left"
Background="LightYellow"
Click="OpenPushPlus"
Tag="com"
ToolTip="打开PushPlus官网获取">
<StackPanel Orientation="Horizontal">
<Image Width="18" Source="/Assets/pushplus.png" />
<TextBlock
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="打开PushPlus官网获取 " />
</StackPanel>
</mica:Button>
<mica:TextBox
x:Name="TextBox_PushPlusToken"
Grid.Column="1"
Width="554"
Height="32"
Margin="8,0,0,0"
HorizontalAlignment="Left"
Watermark="Push plus token" />
</Grid>
<TextBlock
Margin="8,16,0,0"
VerticalAlignment="Center"
Foreground="Gray"
Text="下载设置" />
<UniformGrid Margin="16,8,0,0" Columns="3">
<mica:ToggleSwitch
x:Name="ToggleSwitch_ShowHeadImage"
Content="显示作者头像"
Foreground="Gray" />
<mica:ToggleSwitch
x:Name="ToggleSwitch_EnableDownloadVideo"
Content="下载视频"
Foreground="Gray" />
<mica:ToggleSwitch
x:Name="ToggleSwitch_EnableDownloadLivePhoto"
Content="下载Live Photo"
Foreground="Gray" />
<StackPanel Orientation="Horizontal">
<mica:ToggleSwitch
x:Name="ToggleSwitch_DatetimeRange"
Margin="0,8,0,0"
Checked="ToggleSwitch_DatetimeRangeChecked"
Content="启用时间范围"
Foreground="Gray"
ToolTip="开启后仅下载指定时间范围内的微博数据"
Unchecked="ToggleSwitch_DatetimeRangeUnchecked" />
<TextBlock
Margin="16,14,0,0"
FontSize="13"
Foreground="Gray"
Text="自" />
<DatePicker
x:Name="DatePicker_Start"
Width="100"
Margin="2,12,0,0"
BorderThickness="0"
Foreground="Gray" />
<TextBlock
Margin="2,13,0,0"
FontSize="13"
Foreground="Gray"
Text="至最新日期" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<mica:ToggleSwitch
x:Name="ToggleSwitch_Crontab"
Margin="0,8,0,0"
Checked="ToggleSwitch_Checked"
Content="启用定时下载"
Foreground="Gray"
ToolTip="定时任务会在设置的Crontab时间启动,自动循环uidList.txt中所有用户,执行下载任务"
Unchecked="ToggleSwitch_Unchecked" />
<mica:TextBox
x:Name="TextBox_Crontab"
Width="160"
Height="32"
Margin="16,8,0,0"
TextWrapping="Wrap"
ToolTip="例如,14 2 * * *,代表每天凌晨2点14分开始下载" />
</StackPanel>
<mica:ToggleSwitch
x:Name="ToggleSwitch_EnableShortenName"
Margin="0,8,0,0"
Content="精简图片命名"
Foreground="Gray"
ToolTip="启用后图片仅以日期+编号命名,文件名中不在包含博文内容" />
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="0,8,0,0"
VerticalAlignment="Center"
Foreground="Gray"
Text="自动跳到下一个用户下载的计数" />
<mica:TextBox
x:Name="TextBox_SkipCount"
Width="126"
Height="32"
Margin="8,8,0,0"
TextWrapping="Wrap"
ToolTip="下载用户的文件如果在本地已经存在设置的个数,软件就认为当前用户已经下载完成" />
</StackPanel>
</UniformGrid>
<StackPanel
Margin="0,16,0,0"
HorizontalAlignment="Center"
Orientation="Horizontal">
<mica:Button
Width="100"
Height="32"
Background="#FFBF00"
Click="Confirm_Click"
Content="OK" />
<mica:Button
Width="100"
Height="32"
Margin="24,0,0,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="OpenGithub"
ToolTip="Fork me on Github">
<StackPanel Orientation="Horizontal">
<Image Width="20" Source="/Assets/github.png" />
</StackPanel>
</mica:Button>
</StackPanel>
</StackPanel>
</Grid>
</ScrollViewer>
</mica:MicaWindow>
================================================
FILE: WeiboAlbumDownloader/SettingsWindow.xaml.cs
================================================
using MicaWPF.Controls;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using WeiboAlbumDownloader.Enums;
using WeiboAlbumDownloader.Helpers;
using WeiboAlbumDownloader.Models;
namespace WeiboAlbumDownloader
{
/// <summary>
/// SettingsWindow.xaml 的交互逻辑
/// </summary>
public partial class SettingsWindow : MicaWindow
{
public SettingsWindow()
{
InitializeComponent();
this.Loaded += SettingsWindow_Loaded;
}
private void SettingsWindow_Loaded(object sender, RoutedEventArgs e)
{
ComboBox_DataSource.ItemsSource = new List<string>()
{
"m.weibo.cn(获取用户的时间流,推荐使用!!!)",
"weibo.cn (获取用户的时间流,不过只能获取原创微博相册)",
"weibo.com (获取相册信息,可以获取原创微博相册、头像相册、自拍相册等。少数用户存在获取失败的问题,怀疑是微博内部api不统一造成的。截止日期仅对微博配图生效)",
"weibo.com (获取用户的ajax相册,可以获取原创微博相册、头像相册、自拍相册等。但是获取不到博文信息,所以无法重命名图片和修改图片日期。貌似还获取不全。截止日期不生效,不推荐使用!!!)",
};
if (File.Exists("Settings.json"))
{
string settingsContent = File.ReadAllText("Settings.json");
var settings = JsonConvert.DeserializeObject<SettingsModel>(settingsContent);
if (settings?.DataSource == WeiboDataSource.WeiboCnMobile) ComboBox_DataSource.SelectedIndex = 0;
else if (settings?.DataSource == WeiboDataSource.WeiboCn) ComboBox_DataSource.SelectedIndex = 1;
else if (settings?.DataSource == WeiboDataSource.WeiboCom1) ComboBox_DataSource.SelectedIndex = 2;
else if (settings?.DataSource == WeiboDataSource.WeiboCom2) ComboBox_DataSource.SelectedIndex = 3;
TextBox_WeiboCnCookie.Text = settings?.WeiboCnCookie;
TextBox_WeiboComCookie.Text = settings?.WeiboComCookie;
TextBox_PushPlusToken.Text = settings?.PushPlusToken;
ToggleSwitch_ShowHeadImage.IsChecked = settings?.ShowHeadImage;
ToggleSwitch_EnableDownloadVideo.IsChecked = settings?.EnableDownloadVideo;
ToggleSwitch_EnableDownloadLivePhoto.IsChecked = settings?.EnableDownloadLivePhoto;
ToggleSwitch_EnableShortenName.IsChecked = settings?.EnableShortenName;
ToggleSwitch_Crontab.IsChecked = settings?.EnableCrontab;
TextBox_Crontab.Text = settings?.Crontab;
TextBox_SkipCount.Text = settings?.CountDownloadedSkipToNextUser.ToString();
ToggleSwitch_DatetimeRange.IsChecked = settings?.EnableDatetimeRange;
DatePicker_Start.SelectedDate = settings?.StartDateTime;
}
}
private void GetCookie(object sender, RoutedEventArgs e)
{
var tag = (sender as MicaWPF.Controls.Button)?.Tag as string;
if (tag == "cn")
{
TextBox_WeiboCnCookie.Text = SeleniumHelper.GetCookie(Enums.WeiboDataSource.WeiboCnMobile);
}
else if (tag == "com")
{
TextBox_WeiboComCookie.Text = SeleniumHelper.GetCookie(Enums.WeiboDataSource.WeiboCom1);
}
}
private void Confirm_Click(object sender, RoutedEventArgs e)
{
//先判断日期是否合法
if (ToggleSwitch_DatetimeRange.IsChecked == true)
{
if (DatePicker_Start.SelectedDate == null)
{
MessageBox.Show("启用时间范围后,必须要设置起始日期。");
return;
}
else
{
if (DatePicker_Start.SelectedDate > DateTime.Now)
{
MessageBox.Show("启用时间范围后,起始日期不能是未来的时间。");
return;
}
}
}
var ds = WeiboDataSource.WeiboCnMobile;
if (ComboBox_DataSource.SelectedIndex == 0)
{
ds = WeiboDataSource.WeiboCnMobile;
}
else if (ComboBox_DataSource.SelectedIndex == 1)
{
ds = WeiboDataSource.WeiboCn;
}
else if (ComboBox_DataSource.SelectedIndex == 2)
{
ds = WeiboDataSource.WeiboCom1;
}
else if (ComboBox_DataSource.SelectedIndex == 3)
{
ds = WeiboDataSource.WeiboCom2;
}
//再写入Json
var settings = new SettingsModel()
{
DataSource = ds,
WeiboCnCookie = TextBox_WeiboCnCookie.Text,
WeiboComCookie = TextBox_WeiboComCookie.Text,
PushPlusToken = TextBox_PushPlusToken.Text,
ShowHeadImage = (bool)ToggleSwitch_ShowHeadImage.IsChecked!,
EnableDownloadVideo = (bool)ToggleSwitch_EnableDownloadVideo.IsChecked!,
EnableDownloadLivePhoto = (bool)ToggleSwitch_EnableDownloadLivePhoto.IsChecked!,
EnableShortenName = (bool)ToggleSwitch_EnableShortenName.IsChecked!,
EnableCrontab = (bool)ToggleSwitch_Crontab.IsChecked!,
Crontab = TextBox_Crontab.Text,
CountDownloadedSkipToNextUser = string.IsNullOrEmpty(TextBox_SkipCount.Text) ? 20 : Convert.ToInt32(TextBox_SkipCount.Text),
EnableDatetimeRange = (bool)ToggleSwitch_DatetimeRange.IsChecked!,
StartDateTime = DatePicker_Start.SelectedDate,
};
File.WriteAllText("Settings.json", JsonConvert.SerializeObject(settings, Formatting.Indented));
this.Close();
}
private void ToggleSwitch_Checked(object sender, RoutedEventArgs e)
{
TextBox_Crontab.IsEnabled = true;
}
private void ToggleSwitch_Unchecked(object sender, RoutedEventArgs e)
{
TextBox_Crontab.IsEnabled = false;
}
private void OpenPushPlus(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo("https://www.pushplus.plus/uc.html") { UseShellExecute = true });
}
private void ToggleSwitch_DatetimeRangeChecked(object sender, RoutedEventArgs e)
{
DatePicker_Start.IsEnabled = true;
}
private void ToggleSwitch_DatetimeRangeUnchecked(object sender, RoutedEventArgs e)
{
DatePicker_Start.IsEnabled = false;
}
private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start("explorer.exe", e.Uri.AbsoluteUri);
}
private void OpenGithub(object sender, RoutedEventArgs e)
{
Process.Start(new ProcessStartInfo("https://github.com/hupo376787/WeiboAlbumDownloader") { UseShellExecute = true });
}
}
}
================================================
FILE: WeiboAlbumDownloader/WeiboAlbumDownloader.csproj
================================================
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<ApplicationIcon>weibo.ico</ApplicationIcon>
<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
<Company>Caiwei Studio</Company>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\batch.png" />
<None Remove="Assets\cloud-computing.png" />
<None Remove="Assets\cookie.png" />
<None Remove="Assets\download.png" />
<None Remove="Assets\github.png" />
<None Remove="Assets\pushplus.png" />
<None Remove="Assets\settings.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\batch.png" />
<Resource Include="Assets\github.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Assets\cloud-computing.png" />
<Resource Include="Assets\cookie.png" />
<Resource Include="Assets\download.png" />
<Resource Include="Assets\pushplus.png" />
<Resource Include="Assets\settings.png" />
<Resource Include="weibo.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CronExpressionDescriptor" Version="2.44.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
<PackageReference Include="MicaWPF" Version="6.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Selenium.WebDriver" Version="4.39.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="143.0.7499.4000" />
<PackageReference Include="Sentry" Version="5.16.2" />
<PackageReference Include="TimeCrontab" Version="3.7.0" />
</ItemGroup>
</Project>
================================================
FILE: WeiboAlbumDownloader.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34221.43
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WeiboAlbumDownloader", "WeiboAlbumDownloader\WeiboAlbumDownloader.csproj", "{3A020274-F6B7-459D-9FB7-2AF6AFF680B9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3A020274-F6B7-459D-9FB7-2AF6AFF680B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3A020274-F6B7-459D-9FB7-2AF6AFF680B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3A020274-F6B7-459D-9FB7-2AF6AFF680B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3A020274-F6B7-459D-9FB7-2AF6AFF680B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7CBDAC70-9D3A-4CF9-9DF3-BA04FD4C2B74}
EndGlobalSection
EndGlobal
gitextract_8y3zn10b/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ └── bug反馈.md │ └── workflows/ │ └── dotnet-desktop.yml ├── .gitignore ├── LICENSE ├── README.md ├── WeiboAlbumDownloader/ │ ├── App.xaml │ ├── App.xaml.cs │ ├── AssemblyInfo.cs │ ├── Converters/ │ │ └── ColorConverter.cs │ ├── Enums/ │ │ ├── MessageEnum.cs │ │ ├── PicEnum.cs │ │ └── WeiboDataSource.cs │ ├── GlobalVar.cs │ ├── Helpers/ │ │ ├── GithubHelper.cs │ │ ├── HttpHelper.cs │ │ ├── PushPlusHelper.cs │ │ ├── SeleniumHelper.cs │ │ └── WeiboMidHelper.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Models/ │ │ ├── AlbumDetailModel.cs │ │ ├── AlbumDetailModel2.cs │ │ ├── MessageModel.cs │ │ ├── SettingsModel.cs │ │ ├── UserAlbumModel.cs │ │ ├── UserAlbumModel2.cs │ │ ├── VideoDetailModel.cs │ │ └── WeiboCnMobileModel.cs │ ├── SettingsWindow.xaml │ ├── SettingsWindow.xaml.cs │ └── WeiboAlbumDownloader.csproj └── WeiboAlbumDownloader.sln
SYMBOL INDEX (102 symbols across 21 files)
FILE: WeiboAlbumDownloader/App.xaml.cs
class App (line 12) | public partial class App : Application
method App (line 14) | public App()
method App_DispatcherUnhandledException (line 29) | void App_DispatcherUnhandledException(object sender, DispatcherUnhandl...
method OnStartup (line 42) | protected override void OnStartup(StartupEventArgs e)
FILE: WeiboAlbumDownloader/Converters/ColorConverter.cs
class ColorConverter (line 9) | public class ColorConverter : IValueConverter
method Convert (line 11) | public object Convert(object value, Type targetType, object parameter,...
method ConvertBack (line 24) | public object ConvertBack(object value, Type targetType, object parame...
FILE: WeiboAlbumDownloader/Enums/MessageEnum.cs
type MessageEnum (line 3) | public enum MessageEnum
FILE: WeiboAlbumDownloader/Enums/PicEnum.cs
type PicEnum (line 3) | public enum PicEnum
FILE: WeiboAlbumDownloader/Enums/WeiboDataSource.cs
type WeiboDataSource (line 5) | public enum WeiboDataSource
FILE: WeiboAlbumDownloader/GlobalVar.cs
class GlobalVar (line 5) | public class GlobalVar
FILE: WeiboAlbumDownloader/Helpers/GithubHelper.cs
class GithubHelper (line 9) | public class GithubHelper
method GetLatestVersion (line 11) | public static async Task<string?> GetLatestVersion()
method GetGiteeLatestVersion (line 38) | public static async Task<string> GetGiteeLatestVersion()
FILE: WeiboAlbumDownloader/Helpers/HttpHelper.cs
class HttpHelper (line 16) | public class HttpHelper
method GetAsync (line 26) | public static async Task<T> GetAsync<T>(string url, WeiboDataSource da...
method GetUniqueFileName (line 124) | public static string GetUniqueFileName(string filePath)
FILE: WeiboAlbumDownloader/Helpers/PushPlusHelper.cs
class PushPlusHelper (line 7) | public class PushPlusHelper
method SendMessage (line 10) | public static async Task SendMessage(string token, string title, strin...
FILE: WeiboAlbumDownloader/Helpers/SeleniumHelper.cs
class SeleniumHelper (line 12) | internal class SeleniumHelper
method GetCookie (line 14) | public static string? GetCookie(WeiboDataSource dataSource)
FILE: WeiboAlbumDownloader/Helpers/WeiboMidHelper.cs
class WeiboMidHelper (line 12) | public static class WeiboMidHelper
method GetImageIdsByMidAsync (line 16) | public static async Task<List<string>> GetImageIdsByMidAsync(string mi...
method TryFromPcAsync (line 30) | private static async Task<List<string>> TryFromPcAsync(string mid, str...
method TryFromMobileAsync (line 51) | private static async Task<List<string>> TryFromMobileAsync(string mid,...
method ExtractPicIds (line 67) | private static List<string> ExtractPicIds(string json)
method TryGetCookieValue (line 111) | private static string? TryGetCookieValue(string cookieHeader, string key)
method CreateClient (line 128) | private static HttpClient CreateClient()
FILE: WeiboAlbumDownloader/MainWindow.xaml.cs
class MainWindow (line 27) | public partial class MainWindow : MicaWindow
method MainWindow (line 53) | public MainWindow()
method GetVersion (line 94) | private async Task GetVersion()
method Start (line 128) | private async Task Start(string userId, int startPage = 1, long startS...
method AppendLog (line 1047) | public void AppendLog(string text, MessageEnum messageEnum = MessageEn...
method AddToUserIdList (line 1063) | private void AddToUserIdList(string userId, string nickName)
method InitUidsData (line 1073) | private void InitUidsData()
method InitSettingsData (line 1095) | private void InitSettingsData()
method SetFileTime (line 1136) | private void SetFileTime(string filename, DateTime timestamp)
method StartDownLoad (line 1144) | private async void StartDownLoad(object sender, RoutedEventArgs e)
method StopDownLoad (line 1183) | private void StopDownLoad(object sender, RoutedEventArgs e)
method BatchDownLoad (line 1188) | private async void BatchDownLoad(object sender, RoutedEventArgs e)
method ListView_CopyLog (line 1210) | private void ListView_CopyLog(object sender, RoutedEventArgs e)
method ListView_ClearLog (line 1216) | private void ListView_ClearLog(object sender, RoutedEventArgs e)
method ListView_ExportLog (line 1221) | private void ListView_ExportLog(object sender, RoutedEventArgs e)
method ListView_OpenDownloadFolder (line 1243) | private void ListView_OpenDownloadFolder(object sender, RoutedEventArg...
method TextBox_WeiboId_KeyUp (line 1248) | private async void TextBox_WeiboId_KeyUp(object sender, System.Windows...
method OpenSettings (line 1256) | private void OpenSettings(object sender, RoutedEventArgs e)
method MicaWindow_SizeChanged (line 1265) | private void MicaWindow_SizeChanged(object sender, SizeChangedEventArg...
FILE: WeiboAlbumDownloader/Models/AlbumDetailModel.cs
class AlbumDetailModel (line 5) | public class AlbumDetailModel
class PhotoListCount (line 29) | public class PhotoListCount
class PhotoListItem (line 49) | public class PhotoListItem
class PhotoListData (line 169) | public class PhotoListData
FILE: WeiboAlbumDownloader/Models/AlbumDetailModel2.cs
class AlbumDetailModel2 (line 6) | public partial class AlbumDetailModel2
class PhotoListData2 (line 15) | public partial class PhotoListData2
class PhotoListItem2 (line 27) | public partial class PhotoListItem2
FILE: WeiboAlbumDownloader/Models/MessageModel.cs
class MessageModel (line 6) | public class MessageModel
FILE: WeiboAlbumDownloader/Models/SettingsModel.cs
class SettingsModel (line 6) | public class SettingsModel
FILE: WeiboAlbumDownloader/Models/UserAlbumModel.cs
class UserAlbumModel (line 5) | internal class UserAlbumModel
class AlbumCount (line 28) | public class AlbumCount
class AlbumListItem (line 48) | public class AlbumListItem
class AlbumData (line 148) | public class AlbumData
FILE: WeiboAlbumDownloader/Models/UserAlbumModel2.cs
class UserAlbumModel2 (line 7) | public partial class UserAlbumModel2
class AlbumData2 (line 22) | public partial class AlbumData2
class AlbumList2 (line 37) | public partial class AlbumList2
class List2 (line 49) | public partial class List2
FILE: WeiboAlbumDownloader/Models/VideoDetailModel.cs
class VideoDetailModel (line 3) | public class VideoDetailModel
class VideoData (line 15) | public class VideoData
class @object (line 31) | public class @object
class Author (line 55) | public class Author
class @Stream (line 155) | public class @Stream
class Image (line 183) | public class Image
FILE: WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs
class WeiboCnMobileModel (line 7) | public partial class WeiboCnMobileModel
class Data (line 16) | public partial class Data
class CardlistInfo (line 31) | public partial class CardlistInfo
class Card (line 52) | public partial class Card
class Mblog (line 70) | public partial class Mblog
class RetweetedStatus (line 215) | public partial class RetweetedStatus
class CommentManageInfo (line 219) | public partial class CommentManageInfo
class DarwinTag (line 231) | public partial class DarwinTag
class EditConfig (line 249) | public partial class EditConfig
class NumberDisplayStrategy (line 255) | public partial class NumberDisplayStrategy
class PageInfo (line 267) | public partial class PageInfo
class Mediainfo (line 303) | public partial class Mediainfo
class Urls (line 315) | public partial class Urls
class PagePic (line 339) | public partial class PagePic
class Pic (line 351) | public partial class Pic
class PicGeo (line 375) | public partial class PicGeo
class Large (line 387) | public partial class Large
class LargeGeo (line 399) | public partial class LargeGeo
class User (line 411) | public partial class User
class Visible (line 492) | public partial class Visible
FILE: WeiboAlbumDownloader/SettingsWindow.xaml.cs
class SettingsWindow (line 20) | public partial class SettingsWindow : MicaWindow
method SettingsWindow (line 22) | public SettingsWindow()
method SettingsWindow_Loaded (line 28) | private void SettingsWindow_Loaded(object sender, RoutedEventArgs e)
method GetCookie (line 62) | private void GetCookie(object sender, RoutedEventArgs e)
method Confirm_Click (line 75) | private void Confirm_Click(object sender, RoutedEventArgs e)
method ToggleSwitch_Checked (line 134) | private void ToggleSwitch_Checked(object sender, RoutedEventArgs e)
method ToggleSwitch_Unchecked (line 139) | private void ToggleSwitch_Unchecked(object sender, RoutedEventArgs e)
method OpenPushPlus (line 144) | private void OpenPushPlus(object sender, RoutedEventArgs e)
method ToggleSwitch_DatetimeRangeChecked (line 149) | private void ToggleSwitch_DatetimeRangeChecked(object sender, RoutedEv...
method ToggleSwitch_DatetimeRangeUnchecked (line 154) | private void ToggleSwitch_DatetimeRangeUnchecked(object sender, Routed...
method Hyperlink_RequestNavigate (line 159) | private void Hyperlink_RequestNavigate(object sender, System.Windows.N...
method OpenGithub (line 164) | private void OpenGithub(object sender, RoutedEventArgs e)
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (182K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug反馈.md",
"chars": 213,
"preview": "---\nname: Bug反馈\nabout: 反馈Bug\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Bug描述**\n简单描述一下bug,最好附带uid,方便具体排查原因。\n\n**如何复现**\n复现"
},
{
"path": ".github/workflows/dotnet-desktop.yml",
"chars": 3678,
"preview": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n"
},
{
"path": ".gitignore",
"chars": 6431,
"preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2023 Vincent Wang\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 3848,
"preview": "# WeiboAlbumDownloader\n微博相册下载工具C#版,界面全新设计\n\n\n# 项目说明\n\n本项目可以批量采集指定微博账号下的所有图片/视频/LivePhoto。\n\n和其他语言比如python版本类似,都可以实现用户相册的采集下"
},
{
"path": "WeiboAlbumDownloader/App.xaml",
"chars": 930,
"preview": "<Application\n x:Class=\"WeiboAlbumDownloader.App\"\n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n "
},
{
"path": "WeiboAlbumDownloader/App.xaml.cs",
"chars": 1990,
"preview": "using Sentry;\nusing System;\nusing System.Reflection;\nusing System.Windows;\nusing System.Windows.Threading;\n\nnamespace W"
},
{
"path": "WeiboAlbumDownloader/AssemblyInfo.cs",
"chars": 595,
"preview": "using System.Windows;\n\n[assembly: ThemeInfo(\n ResourceDictionaryLocation.None, //where theme specific resource dictio"
},
{
"path": "WeiboAlbumDownloader/Converters/ColorConverter.cs",
"chars": 950,
"preview": "using System;\nusing System.Globalization;\nusing System.Windows.Data;\nusing System.Windows.Media;\nusing WeiboAlbumDownlo"
},
{
"path": "WeiboAlbumDownloader/Enums/MessageEnum.cs",
"chars": 144,
"preview": "namespace WeiboAlbumDownloader.Enums\n{\n public enum MessageEnum\n {\n Info,\n Warning,\n Success"
},
{
"path": "WeiboAlbumDownloader/Enums/PicEnum.cs",
"chars": 166,
"preview": "namespace WeiboAlbumDownloader.Enums\n{\n public enum PicEnum\n {\n //单图\n Picture,\n //组图\n "
},
{
"path": "WeiboAlbumDownloader/Enums/WeiboDataSource.cs",
"chars": 352,
"preview": "using System.ComponentModel;\n\nnamespace WeiboAlbumDownloader.Enums\n{\n public enum WeiboDataSource\n {\n [Des"
},
{
"path": "WeiboAlbumDownloader/GlobalVar.cs",
"chars": 270,
"preview": "using WeiboAlbumDownloader.Enums;\n\nnamespace WeiboAlbumDownloader\n{\n public class GlobalVar\n {\n public sta"
},
{
"path": "WeiboAlbumDownloader/Helpers/GithubHelper.cs",
"chars": 2027,
"preview": "using System;\nusing System.Diagnostics;\nusing System.Net.Http;\nusing System.Text.RegularExpressions;\nusing System.Threa"
},
{
"path": "WeiboAlbumDownloader/Helpers/HttpHelper.cs",
"chars": 6096,
"preview": "using Newtonsoft.Json;\nusing System;\nusing System.Diagnostics;\nusing System.IO;\nusing System.IO.Compression;\nusing Syst"
},
{
"path": "WeiboAlbumDownloader/Helpers/PushPlusHelper.cs",
"chars": 769,
"preview": "using System.Diagnostics;\nusing System.Net.Http;\nusing System.Threading.Tasks;\n\nnamespace WeiboAlbumDownloader.Helpers\n"
},
{
"path": "WeiboAlbumDownloader/Helpers/SeleniumHelper.cs",
"chars": 2420,
"preview": "using OpenQA.Selenium;\nusing OpenQA.Selenium.Chrome;\nusing OpenQA.Selenium.Support.UI;\nusing System;\nusing System.Colle"
},
{
"path": "WeiboAlbumDownloader/Helpers/WeiboMidHelper.cs",
"chars": 5590,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Net;\nusing System.Net.Http;\nusing System"
},
{
"path": "WeiboAlbumDownloader/MainWindow.xaml",
"chars": 6415,
"preview": "<mica:MicaWindow\n x:Class=\"WeiboAlbumDownloader.MainWindow\"\n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/pres"
},
{
"path": "WeiboAlbumDownloader/MainWindow.xaml.cs",
"chars": 69455,
"preview": "using HtmlAgilityPack;\nusing MicaWPF.Controls;\nusing Newtonsoft.Json;\nusing Sentry;\nusing System;\nusing System.Collecti"
},
{
"path": "WeiboAlbumDownloader/Models/AlbumDetailModel.cs",
"chars": 4493,
"preview": "using System.Collections.Generic;\n\nnamespace WeiboAlbumDownloader.Models\n{\n public class AlbumDetailModel\n {\n "
},
{
"path": "WeiboAlbumDownloader/Models/AlbumDetailModel2.cs",
"chars": 1229,
"preview": "using Newtonsoft.Json;\nusing System.Collections.Generic;\n\nnamespace WeiboAlbumDownloader.Models\n{\n public partial cl"
},
{
"path": "WeiboAlbumDownloader/Models/MessageModel.cs",
"chars": 274,
"preview": "using System;\nusing WeiboAlbumDownloader.Enums;\n\nnamespace WeiboAlbumDownloader.Models\n{\n public class MessageModel\n"
},
{
"path": "WeiboAlbumDownloader/Models/SettingsModel.cs",
"chars": 1252,
"preview": "using System;\nusing WeiboAlbumDownloader.Enums;\n\nnamespace WeiboAlbumDownloader.Models\n{\n public class SettingsModel"
},
{
"path": "WeiboAlbumDownloader/Models/UserAlbumModel.cs",
"chars": 3845,
"preview": "using System.Collections.Generic;\n\nnamespace WeiboAlbumDownloader.Models\n{\n internal class UserAlbumModel\n {\n "
},
{
"path": "WeiboAlbumDownloader/Models/UserAlbumModel2.cs",
"chars": 1780,
"preview": "using Newtonsoft.Json;\nusing System;\nusing System.Collections.Generic;\n\nnamespace WeiboAlbumDownloader.Models\n{\n pub"
},
{
"path": "WeiboAlbumDownloader/Models/VideoDetailModel.cs",
"chars": 5082,
"preview": "namespace WeiboAlbumDownloader.Models\n{\n public class VideoDetailModel\n {\n /// <summary>\n /// \n "
},
{
"path": "WeiboAlbumDownloader/Models/WeiboCnMobileModel.cs",
"chars": 13185,
"preview": "using Newtonsoft.Json;\nusing System;\nusing System.Collections.Generic;\n\nnamespace WeiboAlbumDownloader.Models\n{\n pub"
},
{
"path": "WeiboAlbumDownloader/SettingsWindow.xaml",
"chars": 8782,
"preview": "<mica:MicaWindow\n x:Class=\"WeiboAlbumDownloader.SettingsWindow\"\n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/"
},
{
"path": "WeiboAlbumDownloader/SettingsWindow.xaml.cs",
"chars": 7002,
"preview": "using MicaWPF.Controls;\nusing Newtonsoft.Json;\nusing System;\nusing System.Collections.Generic;\nusing System.ComponentMo"
},
{
"path": "WeiboAlbumDownloader/WeiboAlbumDownloader.csproj",
"chars": 1726,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <OutputType>WinExe</OutputType>\n <TargetFramework>net6.0-wi"
},
{
"path": "WeiboAlbumDownloader.sln",
"chars": 1138,
"preview": "\nMicrosoft Visual Studio Solution File, Format Version 12.00\n# Visual Studio Version 17\nVisualStudioVersion = 17.7.3422"
}
]
About this extraction
This page contains the full source code of the hupo376787/WeiboAlbumDownloader GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (159.4 KB), approximately 35.3k tokens, and a symbol index with 102 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.