├── SharpBot_Test ├── Usings.cs ├── config │ └── config.yaml ├── appsettings.json ├── ProcessTest.cs ├── App.cs └── SharpBot_Test.csproj ├── SharpBot ├── src │ ├── Config │ │ ├── ConfigAttribute.cs │ │ ├── ConfigImpement │ │ │ ├── TelegramConfig.cs │ │ │ └── BaiduConfig.cs │ │ ├── MotionConfig.cs │ │ └── Config.cs │ ├── DB │ │ ├── IDB.cs │ │ ├── BaseDB.cs │ │ ├── DBServer.cs │ │ └── Baidu │ │ │ └── BaiduDB.cs │ ├── ExtentFunction │ │ ├── TorrentExtension.cs │ │ ├── ActionExtension.cs │ │ ├── AppExt.cs │ │ ├── DelegateExtension.cs │ │ ├── DictionaryExtension.cs │ │ ├── EncodingExtension.cs │ │ ├── CollectionExtension.cs │ │ └── ReflectExtension.cs │ ├── Singlonton │ │ ├── SingaltonAttribute.cs │ │ ├── SingaltonInstance.cs │ │ └── ISingalton.cs │ ├── Api │ │ └── v1 │ │ │ └── ConfigController.cs │ ├── Log │ │ └── Log.cs │ ├── Bot │ │ └── TelegramBot.cs │ ├── IOC │ │ └── SinglontonAttribute.cs │ ├── BotPlugin │ │ └── Baidu │ │ │ ├── Baidu.cs │ │ │ └── BaiduClient.cs │ └── StringTools │ │ └── StringTools.cs ├── config │ └── config.yaml ├── .dockerignore ├── appsettings.json ├── Dockerfile ├── Properties │ └── launchSettings.json ├── Program.cs └── SharpBot.csproj ├── global.json ├── .gitignore ├── .idea ├── .idea.SharpBot │ └── .idea │ │ ├── encodings.xml │ │ ├── vcs.xml │ │ ├── indexLayout.xml │ │ └── .gitignore └── .idea.Bangumi │ └── .idea │ └── workspace.xml ├── LICENSE ├── SharpBot.sln ├── README.md ├── .github └── workflows │ └── docker-push.yml └── SharpBot.sln.DotSettings.user /SharpBot_Test/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; -------------------------------------------------------------------------------- /SharpBot/src/Config/ConfigAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace SharpBot; 2 | 3 | public class ConfigAttribute : Attribute 4 | { 5 | } -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "7.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ 6 | /SharpBot/config/meta.db 7 | /SharpBot_Test/config/meta.db 8 | /SharpBot/logs/ -------------------------------------------------------------------------------- /SharpBot/config/config.yaml: -------------------------------------------------------------------------------- 1 | BaiduConfig: 2 | ClientID: '' 3 | ClientSecret: '' 4 | DownloadPath: '' 5 | TelegramConfig: 6 | BotToken: '' 7 | ChatID: '' 8 | TransFinishAddtionDesc: '' 9 | -------------------------------------------------------------------------------- /.idea/.idea.SharpBot/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /SharpBot_Test/config/config.yaml: -------------------------------------------------------------------------------- 1 | BaiduConfig: 2 | ClientID: '' 3 | ClientSecret: '' 4 | DownloadPath: /SharpBot 5 | TelegramConfig: 6 | BotToken: '' 7 | ChatID: '' 8 | TransFinishAddtionDesc: 到alist查看转存结果 9 | -------------------------------------------------------------------------------- /.idea/.idea.SharpBot/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SharpBot/src/DB/IDB.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace SharpBot.DB; 4 | 5 | public interface IDB 6 | { 7 | string DBName { get; } 8 | List GetAllRawData(); 9 | object UpdateData(JsonElement data); 10 | } -------------------------------------------------------------------------------- /.idea/.idea.SharpBot/.idea/indexLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SharpBot/src/Config/ConfigImpement/TelegramConfig.cs: -------------------------------------------------------------------------------- 1 | namespace SharpBot; 2 | [Config] 3 | public class TelegramConfig// : MotionConfig 4 | { 5 | public string BotToken { get; set; } 6 | public string ChatID { get; set; } 7 | public string TransFinishAddtionDesc { get; set; } 8 | } -------------------------------------------------------------------------------- /SharpBot/src/Config/ConfigImpement/BaiduConfig.cs: -------------------------------------------------------------------------------- 1 | using SharpBot.BotPlugin; 2 | 3 | namespace SharpBot; 4 | 5 | [Config] 6 | public class BaiduConfig 7 | { 8 | public string ClientID { get; set; } 9 | public string ClientSecret { get; set; } 10 | public string DownloadPath { get; set; } 11 | } -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/TorrentExtension.cs: -------------------------------------------------------------------------------- 1 | using QBittorrent.Client; 2 | 3 | namespace SharpBot.ExtentFunction; 4 | 5 | public static class TorrentExtension 6 | { 7 | public static string GetFileName(this TorrentContent content) 8 | { 9 | return content.Name.Split("/")[^1]; 10 | } 11 | } -------------------------------------------------------------------------------- /.idea/.idea.SharpBot/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Rider ignored files 5 | /.idea.Anime.iml 6 | /projectSettingsUpdater.xml 7 | /modules.xml 8 | /contentModel.xml 9 | # Editor-based HTTP Client requests 10 | /httpRequests/ 11 | # Datasource local storage ignored files 12 | /dataSources/ 13 | /dataSources.local.xml 14 | -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/ActionExtension.cs: -------------------------------------------------------------------------------- 1 | namespace SharpBot.ExtentFunction; 2 | 3 | public static class ActionExtension 4 | { 5 | public static void Pipe(this T t, Action act) 6 | { 7 | act.Invoke(t); 8 | } 9 | 10 | public static K Pipe(this T t, Func act) 11 | { 12 | return act.Invoke(t); 13 | } 14 | } -------------------------------------------------------------------------------- /SharpBot_Test/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "Console", 13 | "Args": { 14 | } 15 | } 16 | ] 17 | }, 18 | "AllowedHosts": "*" 19 | } -------------------------------------------------------------------------------- /SharpBot/src/Config/MotionConfig.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Options; 2 | 3 | namespace SharpBot; 4 | 5 | public class MotionConfig/* where T : MotionConfig*/ 6 | { 7 | private static IOptionsMonitor motion; 8 | public static T CurrentValue => motion.CurrentValue; 9 | 10 | public static IDisposable? OnChange(Action listener) 11 | { 12 | return motion.OnChange(listener); 13 | } 14 | } -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/AppExt.cs: -------------------------------------------------------------------------------- 1 | using SharpBot.ExtentFunction; 2 | using Catalyst.Models; 3 | using Microsoft.Recognizers.Text.DataTypes.TimexExpression; 4 | 5 | namespace SharpBot.Service; 6 | using System.Reflection; 7 | public static class AppExt 8 | { 9 | public static WebApplicationBuilder AddConfig(this WebApplicationBuilder app) 10 | { 11 | Config.Instance.Init(app); 12 | return app; 13 | } 14 | } -------------------------------------------------------------------------------- /SharpBot/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/DelegateExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System; 4 | public static class DelegateExtension 5 | { 6 | public static bool IsValid(this Delegate d) 7 | { 8 | return d != null && (d.Target != null || d.Method.IsStatic); 9 | } 10 | public static bool IsInvalid(this Delegate d) 11 | { 12 | return !d.IsValid(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /SharpBot/src/Singlonton/SingaltonAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | using Cysharp.Threading.Tasks; 7 | 8 | 9 | public class BaseSgArribute : Attribute 10 | { 11 | } 12 | 13 | public class RunningOnAwakeAttribute : BaseSgArribute 14 | { 15 | public void Invoke(ISingaltonAttribute singal, MethodInfo methodInfo) 16 | { 17 | methodInfo.Invoke(singal, new object?[] { }); 18 | } 19 | } -------------------------------------------------------------------------------- /SharpBot_Test/ProcessTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.Extensions.Options; 3 | using SharpBot; 4 | using SharpBot.BotPlugin; 5 | using SharpBot.DB.Baidu; 6 | 7 | namespace SharpBot_Test; 8 | 9 | public class ProcessTest 10 | { 11 | [Test] 12 | public async Task APITest() 13 | { 14 | var app = new App(); 15 | var api = app.GetService(); 16 | var res = await api.TransFile("https://pan.baidu.com/s/1tRqpIjwJfqFanObjMfI4tA", "jxrx"); 17 | Console.WriteLine(res); 18 | } 19 | } -------------------------------------------------------------------------------- /SharpBot_Test/App.cs: -------------------------------------------------------------------------------- 1 | using SharpBot.IOC; 2 | using SharpBot.Service; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace SharpBot_Test; 7 | 8 | public class App 9 | { 10 | private WebApplication app; 11 | 12 | public App() 13 | { 14 | var builder = WebApplication.CreateBuilder(); 15 | builder.AddConfig(); 16 | builder.AddSinglontons(); 17 | this.app = builder.Build(); 18 | } 19 | 20 | public T GetService() 21 | { 22 | return app.Services.GetService(); 23 | } 24 | } -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/DictionaryExtension.cs: -------------------------------------------------------------------------------- 1 | namespace SharpBot.ExtentFunction; 2 | 3 | public static class DictionaryExtension 4 | { 5 | public static V Get(this Dictionary d, K k) 6 | { 7 | if (d.TryGetValue(k, out var value)) 8 | { 9 | return value; 10 | } 11 | 12 | return default; 13 | } 14 | 15 | public static V GetOrDefault(this Dictionary d, K k, V def = default) 16 | { 17 | if (d.TryGetValue(k, out var value)) 18 | { 19 | return value; 20 | } 21 | 22 | return def; 23 | } 24 | } -------------------------------------------------------------------------------- /SharpBot/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Serilog": { 3 | "MinimumLevel": { 4 | "Default": "Debug", 5 | "Override": { 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | }, 10 | "WriteTo": [ 11 | { 12 | "Name": "File", 13 | "Args": { 14 | "path": "./logs/log-.txt", 15 | "rollingInterval": "Day" 16 | } 17 | }, 18 | { 19 | "Name": "Console", 20 | "Args": { 21 | } 22 | }, 23 | { 24 | "Name":"SQLite", 25 | "Args": { 26 | "sqliteDbPath": "./logs/log.db" 27 | } 28 | } 29 | ] 30 | }, 31 | "AllowedHosts": "*" 32 | } -------------------------------------------------------------------------------- /SharpBot/src/Api/v1/ConfigController.cs: -------------------------------------------------------------------------------- 1 | using SharpBot; 2 | using SharpBot.Service; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.Options; 5 | 6 | namespace SharpBot.Controllers; 7 | 8 | [ApiController] 9 | [Route("api/v1/[controller]")] 10 | public class ConfigController : ControllerBase 11 | { 12 | private readonly ILogger _logger; 13 | private IOptionsMonitor source; 14 | 15 | public ConfigController(ILogger logger,IOptionsMonitor source) 16 | { 17 | _logger = logger; 18 | this.source = source; 19 | } 20 | 21 | [HttpGet("source")] 22 | public BaiduConfig GetSourceConfig() 23 | { 24 | return source.CurrentValue; 25 | } 26 | } -------------------------------------------------------------------------------- /SharpBot/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base 2 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 3 | WORKDIR /app 4 | 5 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build 6 | WORKDIR /src 7 | COPY [".", "SharpBot/"] 8 | RUN dotnet restore "SharpBot/SharpBot.csproj" 9 | #COPY . . 10 | WORKDIR "/src/SharpBot" 11 | RUN dotnet build "SharpBot.csproj" -c Release -o /app/build 12 | 13 | FROM build AS publish 14 | RUN dotnet publish "SharpBot.csproj" -c Release -o /app/publish 15 | 16 | FROM base AS final 17 | WORKDIR /app 18 | COPY --from=publish /app/publish . 19 | ENV TZ=Asia/Shanghai 20 | #https://github.com/dotnet/aspnetcore/issues/32842 21 | ENV ASPNETCORE_URLS=http://0.0.0.0:5115 22 | ENTRYPOINT ["dotnet", "SharpBot.dll"] 23 | -------------------------------------------------------------------------------- /SharpBot/src/DB/BaseDB.cs: -------------------------------------------------------------------------------- 1 | using System.Data.SQLite; 2 | 3 | namespace SharpBot.DB; 4 | 5 | public class BaseDB : SingaltonInstance 6 | { 7 | public SQLiteConnection Connection { get; private set; } 8 | public SQLite.SQLiteConnection SqLiteConnection { get; set; } 9 | 10 | [RunningOnAwake] 11 | void Init() 12 | { 13 | string cs = @"URI=file:./config/meta.db"; 14 | Connection = new SQLiteConnection(cs); 15 | Connection.Open(); 16 | 17 | SqLiteConnection = new SQLite.SQLiteConnection("./config/meta.db"); 18 | } 19 | 20 | public void CreateTable() 21 | { 22 | SqLiteConnection.CreateTable(); 23 | } 24 | 25 | ~BaseDB() 26 | { 27 | Connection?.Close(); 28 | SqLiteConnection?.Close(); 29 | } 30 | } -------------------------------------------------------------------------------- /SharpBot/src/Singlonton/SingaltonInstance.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | 6 | public class SingaltonInstance : ISingaltonAttribute where T : SingaltonInstance, new() 7 | { 8 | private static T instance; 9 | 10 | public static T Instance => GetInstance(); 11 | 12 | private static T GetInstance() 13 | { 14 | if (instance == null) 15 | { 16 | lock (typeof(T)) 17 | { 18 | if (instance == null) 19 | { 20 | instance = new T(); 21 | instance.InitAttribute(); 22 | } 23 | } 24 | } 25 | 26 | return instance; 27 | } 28 | 29 | protected SingaltonInstance() 30 | { 31 | } 32 | } -------------------------------------------------------------------------------- /SharpBot/src/DB/DBServer.cs: -------------------------------------------------------------------------------- 1 | using SharpBot.IOC; 2 | using Dapper; 3 | using System.Data.SQLite; 4 | using System.Text.Json; 5 | 6 | namespace SharpBot.DB; 7 | 8 | [Singlonton] 9 | public class DBServer 10 | { 11 | private Dictionary dbMap; 12 | 13 | public DBServer(IEnumerable dbs) 14 | { 15 | dbMap = dbs.ToDictionary(x => x.DBName); 16 | } 17 | 18 | public List GetDBData(string db) 19 | { 20 | return this.GetDB(db)?.GetAllRawData(); 21 | } 22 | 23 | public object UpdateDBData(string db, JsonElement jsonObj) 24 | { 25 | return this.GetDB(db)?.UpdateData(jsonObj); 26 | } 27 | 28 | private IDB GetDB(string db) 29 | { 30 | if (!dbMap.ContainsKey(db)) 31 | { 32 | return null; 33 | } 34 | 35 | return dbMap[db]; 36 | } 37 | } -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/EncodingExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.InteropServices; 6 | using System.Text; 7 | 8 | public static class EncodingExtension 9 | { 10 | public static byte[] StringToCChar(this Encoding encoding, string str) 11 | { 12 | return encoding.GetBytes(str).Append((byte)0).ToArray(); 13 | } 14 | 15 | public static string CCharToString(this Encoding encoding, IntPtr ptr) 16 | { 17 | List bytesList = new List(); 18 | int i = 0; 19 | while (true) 20 | { 21 | byte b = Marshal.ReadByte(ptr, i++); 22 | if ((char)b == '\0') 23 | { 24 | break; 25 | } 26 | bytesList.Add(b); 27 | } 28 | return encoding.GetString(bytesList.ToArray()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /SharpBot/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:25756", 8 | "sslPort": 44337 9 | } 10 | }, 11 | "profiles": { 12 | "SharpBot": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "https://localhost:7115;http://localhost:5115", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SharpBot/Program.cs: -------------------------------------------------------------------------------- 1 | // See https://aka.ms/new-console-template for more information 2 | using SharpBot; 3 | using SharpBot.Service; 4 | using SharpBot.IOC; 5 | using Microsoft.AspNetCore.OpenApi; 6 | using Serilog; 7 | using Log = SharpBot.Log; 8 | 9 | var builder = WebApplication.CreateBuilder(args); 10 | builder.AddConfig(); 11 | builder.Services.AddLogging(loggerBuilder => 12 | { 13 | loggerBuilder.AddSerilog(Log.Logger); 14 | }); 15 | 16 | builder.Services.AddEndpointsApiExplorer(); 17 | builder.Services.AddControllers(); 18 | builder.Services.AddSwaggerGen(); 19 | 20 | builder.Services.AddSinglontons(); 21 | 22 | 23 | builder.Services.AddCors(options => 24 | { 25 | options.AddPolicy("CorsPolicy", opt => 26 | { 27 | opt.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); 28 | }); 29 | }); 30 | 31 | var app = builder.Build(); 32 | app.Setup(); 33 | app.UseCors("CorsPolicy"); 34 | app.UseSwagger(); 35 | app.UseSwaggerUI(); 36 | app.MapControllers(); 37 | // app.MapGet("/", () => "Hello World!").WithName("GetWeatherForecast").WithOpenApi();; 38 | 39 | app.Run(); -------------------------------------------------------------------------------- /SharpBot/src/Singlonton/ISingalton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using SharpBot; 7 | 8 | public interface ISingaltonAttribute 9 | { 10 | } 11 | 12 | internal static class ISingaltonExtension 13 | { 14 | public static void InitAttribute(this ISingaltonAttribute ins) 15 | { 16 | //Running Awake 17 | foreach (var method in ins.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Static | 18 | BindingFlags.Public | BindingFlags.NonPublic | 19 | BindingFlags.FlattenHierarchy)) 20 | { 21 | method.GetCustomAttributes(true).ForEach((x) => 22 | { 23 | try 24 | { 25 | x.Invoke(ins, method); 26 | } 27 | catch (System.Exception ex) 28 | { 29 | Log.Error(ex); 30 | } 31 | }); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Cnicehs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SharpBot.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpBot", "SharpBot\SharpBot.csproj", "{959C291D-7CCA-4BA0-9BB4-18732DAFCF14}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpBot_Test", "SharpBot_Test\SharpBot_Test.csproj", "{FE9A0083-D2C8-4A2F-97F9-C1B5292B3452}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {959C291D-7CCA-4BA0-9BB4-18732DAFCF14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {959C291D-7CCA-4BA0-9BB4-18732DAFCF14}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {959C291D-7CCA-4BA0-9BB4-18732DAFCF14}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {959C291D-7CCA-4BA0-9BB4-18732DAFCF14}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {FE9A0083-D2C8-4A2F-97F9-C1B5292B3452}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {FE9A0083-D2C8-4A2F-97F9-C1B5292B3452}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {FE9A0083-D2C8-4A2F-97F9-C1B5292B3452}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {FE9A0083-D2C8-4A2F-97F9-C1B5292B3452}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SharpBot 2 | 一个基于百度网盘开放接口的转存机器人 3 | 4 | ## 使用方法 5 | 1. 前往该链接创建自己的应用 https://pan.baidu.com/union/console/createapp 6 | 2. 前往 https://t.me/BotFather 创建自己的Telegram机器人 7 | ``` yaml 8 | version: "3" 9 | 10 | services: 11 | sharpbot: 12 | image: crinte/sharpbot:main 13 | restart: unless-stopped 14 | container_name: sharpbot 15 | environment: 16 | # 配置选项参考 https://github.com/Cnicehs/SharpBot/blob/master/SharpBot/config/config.yaml 17 | # 环境变量仅在配置项为空时进行覆盖,并写入配置 18 | # 运行时以/app/config/config.yaml中的数据为准 19 | SharpBot_BaiduConfig_ClientID: #百度应用的AppKey 20 | SharpBot_BaiduConfig_ClientSecret: #百度应用的SecretKey 21 | SharpBot_BaiduConfig_DownloadPath: #网盘内的存放位置,该位置必须是已经存在的文件夹 22 | SharpBot_TelegramConfig_BotToken: #TelegramBot的token 23 | SharpBot_TelegramConfig_ChatID: #用户自身的TelegramID,用于生成百度Token时的操作交互 24 | SharpBot_TelegramConfig_TransFinishAddtionDesc: #执行结束后的通知文本,如到Alist查看转存结果 25 | volumes: 26 | - ./sharpbot/config:/app/config 27 | ``` 28 | 3. 初次运行时会收到机器人申请应用的信息,按照提示操作即可 29 | 4. 若要清除应用数据,删除 ./sharpbot/config 下的所有数据即可 30 | 5. 使用时发送如下的链接格式即可,单次消息只能发送一条分享连接 31 | ``` 32 | 链接: https://pan.baidu.com/s/1GQuNCeAzb92rl2KB68X3wA 提取码: aww4 复制这段内容后打开百度网盘手机App,操作更方便哦 33 | 34 | 链接:https://pan.baidu.com/s/15yXtvMIKaozgfdw5dMRy0A 35 | 提取码:lsol 36 | --来自百度网盘超级会员V6的分享 37 | ``` 38 | ## Licence 39 | [MIT licence](https://github.com/Cnicehs/SharpBot/blob/main/LICENSE) -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/CollectionExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System; 4 | using System.Linq; 5 | 6 | public static class CollectionExtension 7 | { 8 | public static void ForEach(this IEnumerable enumer, Action action) 9 | { 10 | foreach (var em in enumer) 11 | { 12 | action?.Invoke(em); 13 | } 14 | } 15 | 16 | public static async Task ForEachAsync(this IEnumerable enumer, Func action) where K : Task 17 | { 18 | foreach (var em in enumer) 19 | { 20 | await action?.Invoke(em); 21 | } 22 | } 23 | 24 | public static T RandomChoose(this IList ls) 25 | { 26 | int i = System.Random.Shared.Next(0, ls.Count); 27 | return ls[i]; 28 | } 29 | 30 | public static int IndexOf(this IList ls, Func predicate) 31 | { 32 | int count = ls.Count; 33 | for (var i = 0; i < count; i++) 34 | { 35 | if (predicate(ls[i])) 36 | return i; 37 | } 38 | 39 | return -1; 40 | } 41 | 42 | public static bool TryRemove(this ICollection t, T obj) 43 | { 44 | return t.Remove(obj); 45 | } 46 | 47 | public static bool TryAddWithoutRepeat(this ICollection t, T obj) 48 | { 49 | if (!t.Contains(obj)) 50 | { 51 | t.Add(obj); 52 | return true; 53 | } 54 | 55 | return false; 56 | } 57 | } -------------------------------------------------------------------------------- /SharpBot/src/Log/Log.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Serilog; 3 | 4 | namespace SharpBot; 5 | 6 | public static class Log 7 | { 8 | public static Serilog.ILogger Logger => Serilog.Log.Logger; 9 | static Log() 10 | { 11 | static void BuildConfig(IConfigurationBuilder builder) 12 | { 13 | builder.SetBasePath(Directory.GetCurrentDirectory()) 14 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 15 | .AddEnvironmentVariables(); 16 | } 17 | 18 | var builder = new ConfigurationBuilder(); 19 | BuildConfig(builder); 20 | 21 | 22 | Serilog.Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(builder.Build()).CreateLogger(); 23 | // Serilog.Log.Logger = new LoggerConfiguration().WriteTo.File().CreateLogger(); 24 | } 25 | 26 | public static void Debug(object message) 27 | { 28 | Serilog.Log.Debug(message.ToString()); 29 | } 30 | 31 | public static void Debug(string format, params object[] args) 32 | { 33 | Serilog.Log.Debug(format, args); 34 | } 35 | 36 | public static void Warn(object message) 37 | { 38 | Serilog.Log.Warning(message.ToString()); 39 | } 40 | 41 | public static void Warn(string format, params object[] args) 42 | { 43 | Serilog.Log.Warning(format, args); 44 | } 45 | 46 | public static void Error(object message) 47 | { 48 | Serilog.Log.Error(message.ToString()); 49 | } 50 | 51 | public static void Error(string format, params object[] args) 52 | { 53 | Serilog.Log.Error(format, args); 54 | } 55 | } -------------------------------------------------------------------------------- /SharpBot/src/DB/Baidu/BaiduDB.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using Dapper; 3 | using SharpBot.IOC; 4 | using SQLite; 5 | 6 | namespace SharpBot.DB.Baidu; 7 | 8 | [Table("baidu")] 9 | public class BaiduProto 10 | { 11 | [PrimaryKey] public string id { get; set; } 12 | public string refresh_token { get; set; } 13 | [NotNull] public string access_token { get; set; } 14 | [NotNull] public DateTime last_update { get; set; } 15 | public string next_token { get; set; } 16 | } 17 | 18 | [Singlonton] 19 | public class BaiduDB : IDB 20 | { 21 | public string DBName => "baidu"; 22 | 23 | public BaiduDB() 24 | { 25 | BaseDB.Instance.CreateTable(); 26 | } 27 | 28 | public void AddNew(BaiduProto from, BaiduProto to) 29 | { 30 | if (from == null) 31 | { 32 | this.InsertOrReplace(to); 33 | return; 34 | } 35 | 36 | from.next_token = to.access_token; 37 | this.InsertOrReplace(from); 38 | this.InsertOrReplace(to); 39 | } 40 | 41 | public bool InsertOrReplace(BaiduProto proto) 42 | { 43 | var result = BaseDB.Instance.SqLiteConnection.InsertOrReplace(proto, typeof(BaiduProto)); 44 | if (result > 0) 45 | { 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | public BaiduProto GetProto(string id) 53 | { 54 | try 55 | { 56 | var record = BaseDB.Instance.SqLiteConnection.Get(id); 57 | return record; 58 | } 59 | catch (Exception e) 60 | { 61 | return null; 62 | } 63 | } 64 | 65 | public List GetAllRawData() 66 | { 67 | var select = @"SELECT * FROM baidu"; 68 | return BaseDB.Instance.Connection.Query(select).Select(x => x as object).ToList(); 69 | } 70 | 71 | public object UpdateData(JsonElement data) 72 | { 73 | var result = 74 | BaseDB.Instance.SqLiteConnection.Update(JsonSerializer.Deserialize(data), typeof(BaiduProto)); 75 | if (result > 0) 76 | { 77 | return data; 78 | } 79 | 80 | return data; 81 | } 82 | } -------------------------------------------------------------------------------- /SharpBot/src/Config/Config.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using SharpBot.ExtentFunction; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.FileProviders; 6 | using Microsoft.Extensions.Options; 7 | using Microsoft.Extensions.Primitives; 8 | 9 | namespace SharpBot; 10 | 11 | public class Config : SingaltonInstance 12 | { 13 | public void Init(WebApplicationBuilder builder) 14 | { 15 | var yaml = File.ReadAllText("./config/config.yaml"); 16 | var deserializer = new YamlDotNet.Serialization.DeserializerBuilder().Build(); 17 | var result = deserializer.Deserialize>(yaml); 18 | var envs = System.Environment.GetEnvironmentVariables(); 19 | foreach (string key in envs.Keys) 20 | { 21 | if (key.StartsWith("SharpBot_")) 22 | { 23 | var paths = key.Split("_").ToList(); 24 | paths.RemoveAt(0); 25 | var now = result; 26 | for (int i = 0; i < paths.Count; i++) 27 | { 28 | if (!now.ContainsKey(paths[i])) 29 | { 30 | break; 31 | } 32 | 33 | if (!(now is Dictionary)) 34 | { 35 | break; 36 | } 37 | 38 | if (i != paths.Count - 1) 39 | { 40 | now = now[paths[i]] as Dictionary; 41 | } 42 | else 43 | { 44 | if (now[paths[i]].ToString() == "") 45 | { 46 | now[paths[i]] = envs[key]; 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | var s = new YamlDotNet.Serialization.SerializerBuilder().Build(); 54 | File.WriteAllText("./config/config.yaml", s.Serialize(result)); 55 | 56 | builder.Configuration.AddYamlFile("config/config.yaml", true, true); 57 | var server = builder.Services; 58 | var method = typeof(OptionsConfigurationServiceCollectionExtensions) 59 | .GetMethods(BindingFlags.Static | BindingFlags.Public) 60 | .First(x => x.Name == "Configure" && x.GetParameters().Length == 2); 61 | Assembly.GetExecutingAssembly().GetTypes().ForEach(type => 62 | { 63 | if (type.GetAllAttribute().Count > 0) 64 | { 65 | method.MakeGenericMethod(type) 66 | .Invoke(null, new object[] { server, builder.Configuration.GetSection(type.Name) }); 67 | } 68 | }); 69 | } 70 | } -------------------------------------------------------------------------------- /SharpBot/src/Bot/TelegramBot.cs: -------------------------------------------------------------------------------- 1 | using Cysharp.Threading.Tasks; 2 | using SharpBot.IOC; 3 | using Microsoft.Extensions.Options; 4 | using Telegram.Bot; 5 | using Telegram.Bot.Types; 6 | 7 | namespace SharpBot.Notify; 8 | 9 | [Singlonton(RunningOnAwake = true)] 10 | public class TelegramBot 11 | { 12 | private TelegramBotClient client; 13 | public event Action OnRecvMessage; //接收到消息时 14 | public event Action AfterRecvMessage; //处理完接收的消息,可以删除或什么的 15 | static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); 16 | private ILogger logger; 17 | 18 | public TelegramBot(ILogger logger, IOptionsMonitor tgConfig) 19 | { 20 | this.logger = logger; 21 | this.InitTGBot(tgConfig); 22 | tgConfig.OnChange(((config, s) => { InitTGBot(tgConfig); })); 23 | 24 | async Task CheckHealth() 25 | { 26 | await UniTask.SwitchToTaskPool(); 27 | await semaphoreSlim.WaitAsync(); 28 | try 29 | { 30 | if (!await client.TestApiAsync()) 31 | { 32 | lock (client) 33 | { 34 | logger.LogWarning("Try Re Init TG Bot"); 35 | this.InitTGBot(tgConfig); 36 | } 37 | } 38 | } 39 | finally 40 | { 41 | semaphoreSlim.Release(); 42 | } 43 | 44 | await Task.Delay(1000); 45 | CheckHealth(); 46 | } 47 | 48 | CheckHealth(); 49 | } 50 | 51 | public void InitTGBot(IOptionsMonitor tgConfig) 52 | { 53 | client = new TelegramBotClient(tgConfig.CurrentValue.BotToken); 54 | client.StartReceiving(HandleUpdateAsync, ((botClient, exception, arg3) => { return default; })); 55 | } 56 | 57 | public async Task SendText(string msg, string chatId) 58 | { 59 | await semaphoreSlim.WaitAsync(); 60 | try 61 | { 62 | await client.SendTextMessageAsync(new ChatId(long.Parse(chatId)), msg); 63 | return true; 64 | } 65 | catch (Exception e) 66 | { 67 | Log.Error(e); 68 | return false; 69 | } 70 | finally 71 | { 72 | semaphoreSlim.Release(); 73 | } 74 | } 75 | 76 | private async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, 77 | CancellationToken cancellationToken) 78 | { 79 | try 80 | { 81 | this.logger.LogDebug("收到新消息了From " + update.Message.From.Id); 82 | OnRecvMessage?.Invoke(update.Message.From.Id, update.Message.Text); 83 | AfterRecvMessage?.Invoke(update.Message.From.Id, update.Message); 84 | } 85 | catch (Exception e) 86 | { 87 | this.logger.LogError(e.ToString()); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /SharpBot_Test/SharpBot_Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | SharpBot_Test 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | Always 47 | 48 | 49 | Always 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /SharpBot/src/IOC/SinglontonAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace SharpBot.IOC; 2 | 3 | using System.Reflection; 4 | 5 | [AttributeUsage(AttributeTargets.Class)] 6 | public class SinglontonAttribute : Attribute 7 | { 8 | public bool RunningOnAwake = false; 9 | public int Order = 0; 10 | } 11 | 12 | public static class SingletonsExt 13 | { 14 | public static IServiceCollection AddSinglontons(this WebApplicationBuilder builder) 15 | { 16 | return builder.Services.AddSinglontons(); 17 | } 18 | 19 | public static IServiceCollection AddSinglontons(this IServiceCollection server) 20 | { 21 | var method = typeof(ServiceCollectionServiceExtensions) 22 | .GetMethods(BindingFlags.Static | BindingFlags.Public) 23 | .First(x => x.Name == "AddSingleton" && x.GetParameters().Length == 1 && x.IsGenericMethod && 24 | x.GetGenericArguments().Length == 1); 25 | 26 | //builder.Services.AddSingleton(x=>x.GetServices()) 27 | var methodForward = typeof(ServiceCollectionServiceExtensions) 28 | .GetMethods(BindingFlags.Static | BindingFlags.Public) 29 | .First(x => x.Name == "AddSingleton" && x.GetParameters().Length == 3 && !x.IsGenericMethod && 30 | x.GetParameters().Last().ParameterType.IsGenericType && 31 | x.GetParameters().Last().ParameterType.GetGenericTypeDefinition() == typeof(Func<,>)); 32 | 33 | Assembly.GetExecutingAssembly().GetTypes().ForEach(type => 34 | { 35 | if (type.GetAllAttribute().Count > 0) 36 | { 37 | method.MakeGenericMethod(type) 38 | .Invoke(null, new object[] { server }); 39 | 40 | type.GetInterfaces().ForEach(i => 41 | { 42 | methodForward.Invoke(null, 43 | new object[] { server, i, (IServiceProvider provide) => provide.GetService(type) }); 44 | }); 45 | } 46 | }); 47 | return server; 48 | } 49 | 50 | public static WebApplication Setup(this WebApplication server) 51 | { 52 | Assembly assembly = Assembly.GetExecutingAssembly(); 53 | List<(Type, int)> toAwakes = new List<(Type, int)>(); 54 | assembly.GetTypes().ForEach(type => 55 | { 56 | var attribute = type.GetCustomAttribute(false); 57 | if (attribute != null && attribute.RunningOnAwake) 58 | { 59 | toAwakes.Add((type, attribute.Order)); 60 | } 61 | }); 62 | 63 | toAwakes.Sort((x, y) => 64 | { 65 | if (x.Item2 != y.Item2) 66 | { 67 | return x.Item2 - y.Item2; 68 | } 69 | 70 | return String.CompareOrdinal(x.ToString(), y.ToString()); 71 | }); 72 | 73 | toAwakes.ForEach(x => 74 | { 75 | server.Services.GetService(x.Item1); 76 | }); 77 | 78 | return server; 79 | } 80 | } -------------------------------------------------------------------------------- /SharpBot/src/BotPlugin/Baidu/Baidu.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using CliWrap; 6 | using Microsoft.Extensions.Options; 7 | using SharpBot.IOC; 8 | using SharpBot.Notify; 9 | 10 | namespace SharpBot.BotPlugin; 11 | 12 | [Singlonton(RunningOnAwake = true)] 13 | public class Baidu 14 | { 15 | private ILogger logger; 16 | private TelegramBot bot; 17 | private BaiduClient client; 18 | private IOptionsMonitor tgConfig; 19 | 20 | public Baidu(ILogger logger, IOptionsMonitor tgConfig, BaiduClient client, TelegramBot bot) 21 | { 22 | this.logger = logger; 23 | this.bot = bot; 24 | this.client = client; 25 | this.tgConfig = tgConfig; 26 | bot.OnRecvMessage += RecvMessage; 27 | 28 | var token = this.client.GetToken().GetAwaiter().GetResult(); 29 | if (string.IsNullOrEmpty(token)) 30 | { 31 | waitApply = true; 32 | bot.SendText($"请点击下方链接申请应用,并将授权码发送给该机器人\n{client.GetTokenRegisterUrl()}", tgConfig.CurrentValue.ChatID); 33 | return; 34 | } 35 | } 36 | 37 | private bool waitApply; 38 | 39 | void RecvMessage(long uid, string msg) 40 | { 41 | if (waitApply) 42 | { 43 | var code = Regex.Match(msg, "[a-z0-9]+"); 44 | if (code.Success) 45 | { 46 | var result = client.ApplyCode(code.Value).GetAwaiter().GetResult(); 47 | if (result != null) 48 | { 49 | bot.SendText("获取Token成功", tgConfig.CurrentValue.ChatID); 50 | } 51 | 52 | waitApply = false; 53 | } 54 | } 55 | 56 | var shareLinkMatch = Regex.Match(msg, "链接.\\s*(?https://pan.baidu.com/s/\\S+)"); 57 | if (shareLinkMatch.Success) 58 | { 59 | var token = this.client.GetToken().GetAwaiter().GetResult(); 60 | if (string.IsNullOrEmpty(token)) 61 | { 62 | waitApply = true; 63 | bot.SendText($"请点击下方链接申请应用,并将授权码发送给该机器人\n{client.GetTokenRegisterUrl()}", tgConfig.CurrentValue.ChatID); 64 | return; 65 | } 66 | 67 | var pwdMatch = Regex.Match(msg, "提取码.\\s*(?\\S+)"); 68 | if (pwdMatch.Success) 69 | { 70 | var shareLink = shareLinkMatch.Groups["t"].Value; 71 | 72 | //可能存在这种带query的,暂时统一一下 73 | //先不支持https://pan.baidu.com/s/1-rqkYYYzUE2NUsfkd9RCtQ?pwd=wdcv直接这样输入 74 | // 链接: https://pan.baidu.com/s/1-rqkYYYzUE2NUsfkd9RCtQ?pwd=wdcv 提取码: wdcv 复制这段内容后打开百度网盘手机App,操作更方便哦 75 | if (shareLink.Contains("?")) 76 | { 77 | shareLink = shareLink.Split("?")[0]; 78 | } 79 | 80 | var pwd = pwdMatch.Groups["t"].Value; 81 | logger.LogDebug($"{shareLink} -> {pwd}"); 82 | try 83 | { 84 | var result = client.TransFile(shareLink, pwd).GetAwaiter().GetResult(); 85 | this.bot.SendText($"转存成功\n{tgConfig.CurrentValue.TransFinishAddtionDesc}", uid.ToString()); 86 | } 87 | catch (Exception e) 88 | { 89 | this.bot.SendText($"{e.Message}\n{tgConfig.CurrentValue.TransFinishAddtionDesc}", uid.ToString()); 90 | } 91 | } 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /SharpBot/SharpBot.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | net7.0 6 | enable 7 | enable 8 | false 9 | Linux 10 | SharpBot 11 | preview 12 | 13 | 14 | 15 | 16 | Always 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | Always 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /.github/workflows/docker-push.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | # This workflow uses actions that are not certified by GitHub. 4 | # They are provided by a third-party and are governed by 5 | # separate terms of service, privacy policy, and support 6 | # documentation. 7 | 8 | on: 9 | schedule: 10 | - cron: '43 19 * * *' 11 | push: 12 | branches: [ "main" ] 13 | # Publish semver tags as releases. 14 | tags: [ 'v*.*.*' ] 15 | pull_request: 16 | branches: [ "main" ] 17 | 18 | env: 19 | # Use docker.io for Docker Hub if empty 20 | REGISTRY: docker.io 21 | # github.repository as / 22 | IMAGE_NAME: crinte/sharpbot 23 | 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | permissions: 30 | contents: read 31 | packages: write 32 | # This is used to complete the identity challenge 33 | # with sigstore/fulcio when running outside of PRs. 34 | id-token: write 35 | 36 | steps: 37 | - name: Checkout repository 38 | uses: actions/checkout@v3 39 | 40 | # Install the cosign tool except on PR 41 | # https://github.com/sigstore/cosign-installer 42 | - name: Install cosign 43 | if: github.event_name != 'pull_request' 44 | uses: sigstore/cosign-installer@f3c664df7af409cb4873aa5068053ba9d61a57b6 #v2.6.0 45 | with: 46 | cosign-release: 'v1.13.0' 47 | 48 | 49 | # Workaround: https://github.com/docker/build-push-action/issues/461 50 | - name: Setup Docker buildx 51 | uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf 52 | 53 | # Login against a Docker registry except on PR 54 | # https://github.com/docker/login-action 55 | - name: Log into registry ${{ env.REGISTRY }} 56 | if: github.event_name != 'pull_request' 57 | uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c 58 | with: 59 | registry: ${{ env.REGISTRY }} 60 | username: ${{ secrets.DOCKER_USER }} 61 | password: ${{ secrets.DOCKER_PWD }} 62 | 63 | # Extract metadata (tags, labels) for Docker 64 | # https://github.com/docker/metadata-action 65 | - name: Extract Docker metadata 66 | id: meta 67 | uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 68 | with: 69 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 70 | 71 | # Build and push Docker image with Buildx (don't push on PR) 72 | # https://github.com/docker/build-push-action 73 | - name: Build and push Docker image 74 | id: build-and-push 75 | uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a 76 | with: 77 | context: ./SharpBot 78 | push: ${{ github.event_name != 'pull_request' }} 79 | tags: ${{ steps.meta.outputs.tags }} 80 | labels: ${{ steps.meta.outputs.labels }} 81 | cache-from: type=gha 82 | cache-to: type=gha,mode=max 83 | 84 | 85 | # # Sign the resulting Docker image digest except on PRs. 86 | # # This will only write to the public Rekor transparency log when the Docker 87 | # # repository is public to avoid leaking data. If you would like to publish 88 | # # transparency data even for private images, pass --force to cosign below. 89 | # # https://github.com/sigstore/cosign 90 | # - name: Sign the published Docker image 91 | # if: ${{ github.event_name != 'pull_request' }} 92 | # env: 93 | # COSIGN_EXPERIMENTAL: "true" 94 | # # This step uses the identity token to provision an ephemeral certificate 95 | # # against the sigstore community Fulcio instance. 96 | # run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }} 97 | -------------------------------------------------------------------------------- /SharpBot.sln.DotSettings.user: -------------------------------------------------------------------------------- 1 | 2 | <AssemblyExplorer> 3 | <Assembly Path="C:\Users\crint\.nuget\packages\cliwrap\3.5.0\lib\netcoreapp3.0\CliWrap.dll" /> 4 | </AssemblyExplorer> 5 | <SessionState ContinuousTestingMode="0" Name="APITest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 6 | <TestAncestor> 7 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.APITest</TestId> 8 | </TestAncestor> 9 | </SessionState> 10 | <SessionState ContinuousTestingMode="0" Name="Test #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 11 | <TestAncestor> 12 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.Test</TestId> 13 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.APITest</TestId> 14 | </TestAncestor> 15 | </SessionState> 16 | <SessionState ContinuousTestingMode="0" IsActive="True" Name="APITest #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 17 | <TestAncestor> 18 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.APITest</TestId> 19 | </TestAncestor> 20 | </SessionState> 21 | <SessionState ContinuousTestingMode="0" Name="Test" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 22 | <TestAncestor> 23 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.Test</TestId> 24 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.APITest</TestId> 25 | </TestAncestor> 26 | </SessionState> 27 | <SessionState ContinuousTestingMode="0" Name="Test #3" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> 28 | <TestAncestor> 29 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.Test</TestId> 30 | <TestId>NUnit3x::FE9A0083-D2C8-4A2F-97F9-C1B5292B3452::net7.0::SharpBot_Test.ProcessTest.APITest</TestId> 31 | </TestAncestor> 32 | </SessionState> 33 | 122.f7cdb13569c3d3d836bfbd3a5a0eaa8f.YmdjBFDNAaNkrOeS868oRRVqSgWn9Hxf15WgqLe.CmGa1w 34 | 5685609452:AAHpEV4Y69ztgRfVo3h3-n3tnIv36QURy5E 35 | 874853399 -------------------------------------------------------------------------------- /SharpBot/src/StringTools/StringTools.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | public static class StringTools 4 | { 5 | /// 6 | /// 将中文数字转换成阿拉伯数字 7 | /// 8 | /// 9 | /// 10 | public static int ConvertToDigit(string cnNumber) 11 | { 12 | int result = 0; 13 | 14 | int temp = 0; 15 | 16 | foreach (char c in cnNumber) 17 | 18 | { 19 | int temp1 = ToDigit(c); 20 | 21 | if (temp1 == 10000) 22 | 23 | { 24 | result += temp; 25 | 26 | result *= 10000; 27 | 28 | temp = 0; 29 | } 30 | 31 | else if (temp1 > 9) 32 | 33 | { 34 | if (temp1 == 10 && temp == 0) temp = 1; 35 | 36 | result += temp * temp1; 37 | 38 | temp = 0; 39 | } 40 | 41 | else temp = temp1; 42 | } 43 | 44 | result += temp; 45 | 46 | return result; 47 | } 48 | 49 | 50 | /// 51 | /// 将中文数字转换成阿拉伯数字 52 | /// 53 | /// 54 | /// 55 | public static int ToDigit(char cn) 56 | { 57 | int number = 0; 58 | 59 | switch (cn) 60 | 61 | { 62 | case '壹': 63 | 64 | case '一': 65 | 66 | number = 1; 67 | 68 | break; 69 | 70 | case '两': 71 | 72 | case '贰': 73 | 74 | case '二': 75 | 76 | number = 2; 77 | 78 | break; 79 | 80 | case '叁': 81 | 82 | case '三': 83 | 84 | number = 3; 85 | 86 | break; 87 | 88 | case '肆': 89 | 90 | case '四': 91 | 92 | number = 4; 93 | 94 | break; 95 | 96 | case '伍': 97 | 98 | case '五': 99 | 100 | number = 5; 101 | 102 | break; 103 | 104 | case '陆': 105 | 106 | case '六': 107 | 108 | number = 6; 109 | 110 | break; 111 | 112 | case '柒': 113 | 114 | case '七': 115 | 116 | number = 7; 117 | 118 | break; 119 | 120 | case '捌': 121 | 122 | case '八': 123 | 124 | number = 8; 125 | 126 | break; 127 | 128 | case '玖': 129 | 130 | case '九': 131 | 132 | number = 9; 133 | 134 | break; 135 | 136 | case '拾': 137 | 138 | case '十': 139 | 140 | number = 10; 141 | 142 | break; 143 | 144 | case '佰': 145 | 146 | case '百': 147 | 148 | number = 100; 149 | 150 | break; 151 | 152 | case '仟': 153 | 154 | case '千': 155 | 156 | number = 1000; 157 | 158 | break; 159 | 160 | case '萬': 161 | 162 | case '万': 163 | 164 | number = 10000; 165 | 166 | break; 167 | 168 | case '零': 169 | 170 | default: 171 | 172 | number = 0; 173 | 174 | break; 175 | } 176 | 177 | return number; 178 | } 179 | 180 | 181 | /// 182 | /// 将中文数字转换成阿拉伯数字 183 | /// 184 | /// 185 | /// 186 | public static long ToLong(string cnDigit) 187 | { 188 | long result = 0; 189 | 190 | string[] str = cnDigit.Split('亿'); 191 | 192 | result = ConvertToDigit(str[0]); 193 | 194 | if (str.Length > 1) 195 | 196 | { 197 | result *= 100000000; 198 | 199 | result += ConvertToDigit(str[1]); 200 | } 201 | 202 | return result; 203 | } 204 | 205 | 206 | /// 207 | /// This class implements string comparison algorithm 208 | /// based on character pair similarity 209 | /// Source: http://www.catalysoft.com/articles/StrikeAMatch.html 210 | /// 211 | 212 | #region SimilarityTool 213 | 214 | /// 215 | /// Compares the two strings based on letter pair matches 216 | /// 217 | /// 218 | /// 219 | /// The percentage match from 0.0 to 1.0 where 1.0 is 100% 220 | public static double CompareStrings(string str1, string str2) 221 | { 222 | List pairs1 = WordLetterPairs(str1.ToUpper()); 223 | List pairs2 = WordLetterPairs(str2.ToUpper()); 224 | 225 | int intersection = 0; 226 | int union = pairs1.Count + pairs2.Count; 227 | 228 | for (int i = 0; i < pairs1.Count; i++) 229 | { 230 | for (int j = 0; j < pairs2.Count; j++) 231 | { 232 | if (pairs1[i] == pairs2[j]) 233 | { 234 | intersection++; 235 | pairs2.RemoveAt( 236 | j); //Must remove the match to prevent "GGGG" from appearing to match "GG" with 100% success 237 | 238 | break; 239 | } 240 | } 241 | } 242 | 243 | return (2.0 * intersection) / union; 244 | } 245 | 246 | /// 247 | /// Gets all letter pairs for each 248 | /// individual word in the string 249 | /// 250 | /// 251 | /// 252 | private static List WordLetterPairs(string str) 253 | { 254 | List AllPairs = new List(); 255 | 256 | // Tokenize the string and put the tokens/words into an array 257 | string[] Words = Regex.Split(str, @"\s"); 258 | 259 | // For each word 260 | for (int w = 0; w < Words.Length; w++) 261 | { 262 | if (!string.IsNullOrEmpty(Words[w])) 263 | { 264 | // Find the pairs of characters 265 | String[] PairsInWord = LetterPairs(Words[w]); 266 | 267 | for (int p = 0; p < PairsInWord.Length; p++) 268 | { 269 | AllPairs.Add(PairsInWord[p]); 270 | } 271 | } 272 | } 273 | 274 | return AllPairs; 275 | } 276 | 277 | /// 278 | /// Generates an array containing every 279 | /// two consecutive letters in the input string 280 | /// 281 | /// 282 | /// 283 | private static string[] LetterPairs(string str) 284 | { 285 | int numPairs = str.Length - 1; 286 | 287 | string[] pairs = new string[numPairs]; 288 | 289 | for (int i = 0; i < numPairs; i++) 290 | { 291 | pairs[i] = str.Substring(i, 2); 292 | } 293 | 294 | return pairs; 295 | } 296 | 297 | #endregion 298 | } -------------------------------------------------------------------------------- /SharpBot/src/ExtentFunction/ReflectExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Runtime.CompilerServices; 7 | 8 | public static class ReflectExtension 9 | { 10 | public const BindingFlags bindingParent = System.Reflection.BindingFlags.Public | 11 | System.Reflection.BindingFlags.NonPublic | 12 | System.Reflection.BindingFlags.Static | 13 | System.Reflection.BindingFlags.Instance | 14 | System.Reflection.BindingFlags.FlattenHierarchy; 15 | 16 | public const BindingFlags bindingSelf = System.Reflection.BindingFlags.Public | 17 | System.Reflection.BindingFlags.NonPublic | 18 | System.Reflection.BindingFlags.Static | 19 | System.Reflection.BindingFlags.Instance | 20 | System.Reflection.BindingFlags.DeclaredOnly; 21 | 22 | public const BindingFlags bindingPublicParent = System.Reflection.BindingFlags.Public | 23 | System.Reflection.BindingFlags.Static | 24 | System.Reflection.BindingFlags.Instance | 25 | System.Reflection.BindingFlags.FlattenHierarchy; 26 | 27 | public const BindingFlags bindingPublicSelf = System.Reflection.BindingFlags.Public | 28 | System.Reflection.BindingFlags.Static | 29 | System.Reflection.BindingFlags.Instance | 30 | System.Reflection.BindingFlags.DeclaredOnly; 31 | 32 | public static T GetFirstAttribute(this Type type, BindingFlags flags) where T : Attribute 33 | { 34 | var at = type.GetCustomAttribute(); 35 | if (at != null) 36 | return at; 37 | 38 | foreach (var field in type.GetFields(flags)) 39 | { 40 | at = field.GetCustomAttribute(); 41 | if (at != null) 42 | return at; 43 | } 44 | 45 | foreach (var method in type.GetMethods(flags)) 46 | { 47 | at = method.GetCustomAttribute(); 48 | if (at != null) 49 | return at; 50 | } 51 | 52 | foreach (var property in type.GetProperties(flags)) 53 | { 54 | at = property.GetCustomAttribute(); 55 | if (at != null) 56 | return at; 57 | } 58 | 59 | return null; 60 | } 61 | 62 | public static List GetAllAttribute(this Type type, BindingFlags flags = bindingSelf) where T : Attribute 63 | { 64 | List ret = new List(); 65 | var at = type.GetCustomAttributes(); 66 | if (at != null) 67 | ret.AddRange(at); 68 | 69 | foreach (var field in type.GetFields(flags)) 70 | { 71 | at = field.GetCustomAttributes(); 72 | if (at != null) 73 | ret.AddRange(at); 74 | } 75 | 76 | foreach (var method in type.GetMethods(flags)) 77 | { 78 | at = method.GetCustomAttributes(); 79 | if (at != null) 80 | ret.AddRange(at); 81 | } 82 | 83 | foreach (var property in type.GetProperties(flags)) 84 | { 85 | at = property.GetCustomAttributes(); 86 | if (at != null) 87 | ret.AddRange(at); 88 | } 89 | 90 | return ret; 91 | } 92 | 93 | public static List GetAllAttribute(this Type type, Type attribute, BindingFlags flags = bindingSelf) 94 | { 95 | List ret = new List(); 96 | var at = type.GetCustomAttributes(); 97 | if (at != null) 98 | ret.AddRange(at.Where(t => t.GetType().IsSubclassOf(attribute) || (t.GetType() == attribute))); 99 | 100 | foreach (var field in type.GetFields(flags)) 101 | { 102 | at = field.GetCustomAttributes(); 103 | if (at != null) 104 | ret.AddRange(at.Where(t => t.GetType().IsSubclassOf(attribute) || (t.GetType() == attribute))); 105 | } 106 | 107 | foreach (var method in type.GetMethods(flags)) 108 | { 109 | at = method.GetCustomAttributes(); 110 | if (at != null) 111 | ret.AddRange(at.Where(t => t.GetType().IsSubclassOf(attribute) || (t.GetType() == attribute))); 112 | } 113 | 114 | foreach (var property in type.GetProperties(flags)) 115 | { 116 | at = property.GetCustomAttributes(); 117 | if (at != null) 118 | ret.AddRange(at.Where(t => t.GetType().IsSubclassOf(attribute) || (t.GetType() == attribute))); 119 | } 120 | 121 | return ret; 122 | } 123 | 124 | //关于Flatten无效 125 | //https://stackoverflow.com/questions/9201859/why-doesnt-type-getfields-return-backing-fields-in-a-base-class 126 | public static FieldInfo GetFieldInfoInclueBase(this Type type, string name, BindingFlags flags = bindingSelf) 127 | { 128 | do 129 | { 130 | var field = type.GetField(name, flags); 131 | if (field != null) 132 | return field; 133 | type = type.BaseType; 134 | } while (type != null); 135 | 136 | return null; 137 | } 138 | 139 | public static object GetDefaultValue(this Type t) 140 | { 141 | if (t.IsValueType) 142 | return Activator.CreateInstance(t); 143 | 144 | return null; 145 | } 146 | 147 | public static object GetValueByPath(this object obj, string path, BindingFlags flags = bindingParent) 148 | { 149 | foreach (var t in path.Split('.')) 150 | { 151 | var filed = obj.GetType().GetField(t, flags); 152 | if (filed != null) 153 | { 154 | obj = filed.GetValue(obj); 155 | } 156 | else 157 | { 158 | var property = obj.GetType().GetProperty(t, flags); 159 | if (property != null) 160 | { 161 | obj = property.GetValue(obj); 162 | } 163 | else 164 | { 165 | throw new Exception("Can not find " + path); 166 | } 167 | } 168 | } 169 | 170 | return obj; 171 | } 172 | 173 | public static void SetValueByPath(this object obj, string path, object value, BindingFlags flags = bindingParent) 174 | { 175 | var pt = path.Split('.'); 176 | int count = pt.Length; 177 | for (int i = 0; i < count; i++) 178 | { 179 | var t = pt[i]; 180 | var filed = obj.GetType().GetField(t, flags); 181 | if (filed != null) 182 | { 183 | if (i == count - 1) 184 | { 185 | filed.SetValue(obj, value); 186 | } 187 | else 188 | { 189 | obj = filed.GetValue(obj); 190 | } 191 | } 192 | else 193 | { 194 | var property = obj.GetType().GetProperty(t, flags); 195 | if (property != null) 196 | { 197 | if (i == count - 1) 198 | { 199 | property.SetValue(obj, value); 200 | } 201 | else 202 | { 203 | obj = property.GetValue(obj); 204 | } 205 | } 206 | else 207 | { 208 | throw new Exception("Can not find " + path); 209 | } 210 | } 211 | } 212 | } 213 | 214 | /// 215 | /// This Methode extends the System.Type-type to get all extended methods. It searches hereby in all assemblies which are known by the current AppDomain. 216 | /// 217 | /// 218 | /// Insired by Jon Skeet from his answer on http://stackoverflow.com/questions/299515/c-sharp-reflection-to-identify-extension-methods 219 | /// 220 | /// returns MethodInfo[] with the extended Method 221 | public static MethodInfo[] GetExtensionMethods(this Type t) 222 | { 223 | List AssTypes = new List(); 224 | 225 | foreach (Assembly item in AppDomain.CurrentDomain.GetAssemblies()) 226 | { 227 | AssTypes.AddRange(item.GetTypes()); 228 | } 229 | 230 | var query = from type in AssTypes 231 | where type.IsSealed && !type.IsGenericType && !type.IsNested 232 | from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) 233 | where method.IsDefined(typeof(ExtensionAttribute), false) 234 | where method.GetParameters()[0].ParameterType == t 235 | select method; 236 | return query.ToArray(); 237 | } 238 | 239 | /// 240 | /// Extends the System.Type-type to search for a given extended MethodeName. 241 | /// 242 | /// Name of the Methode 243 | /// the found Methode or null 244 | public static MethodInfo GetExtensionMethod(this Type t, string MethodeName) 245 | { 246 | var mi = from methode in t.GetExtensionMethods() 247 | where methode.Name == MethodeName 248 | select methode; 249 | if (mi.Count() <= 0) 250 | return null; 251 | else 252 | return mi.First(); 253 | } 254 | } -------------------------------------------------------------------------------- /SharpBot/src/BotPlugin/Baidu/BaiduClient.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | using System.IO.Pipelines; 3 | using System.Text; 4 | using System.Text.Json.Nodes; 5 | using System.Text.RegularExpressions; 6 | using System.Web; 7 | using Microsoft.Extensions.Options; 8 | using Newtonsoft.Json; 9 | using Newtonsoft.Json.Linq; 10 | using SharpBot.DB.Baidu; 11 | using SharpBot.IOC; 12 | using JsonSerializer = System.Text.Json.JsonSerializer; 13 | 14 | namespace SharpBot.BotPlugin; 15 | 16 | public class VerifyResp 17 | { 18 | public int errno { get; set; } 19 | public string err_msg { get; set; } 20 | public long request_id { get; set; } 21 | public string randsk { get; set; } 22 | } 23 | 24 | [Singlonton] //先写成单例 25 | public class BaiduClient 26 | { 27 | public static Dictionary headers = new Dictionary() 28 | { 29 | { 30 | "User-Agent", 31 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36" 32 | }, 33 | { "Referer", "pan.baidu.com" } 34 | }; 35 | 36 | public static Dictionary ErrorCodeMap = new Dictionary() 37 | { 38 | { "2", "参数错误。检查必填字段;get/post 参数位置" }, 39 | { "-6", "身份验证失败。access_token 是否有效;部分接口需要申请对应的网盘权限" }, 40 | { "31034", "命中接口频控。核对频控规则;稍后再试;申请单独频控规则" }, 41 | { "42000", "访问过于频繁" }, 42 | { "42001", "rand校验失败" }, 43 | { "42999", "功能下线" }, 44 | { "9100", "一级封禁" }, 45 | { "9200", "二级封禁" }, 46 | { "9300", "三级封禁" }, 47 | { "9400", "四级封禁" }, 48 | { "9500", "五级封禁" } 49 | }; 50 | 51 | private IOptionsMonitor _config; 52 | private BaiduConfig config => _config.CurrentValue; 53 | private string accessToken; 54 | private string refreshToken; 55 | private DateTime lastUpdate = default; 56 | private BaiduDB baiduDB; 57 | 58 | 59 | public BaiduClient(IOptionsMonitor config, BaiduDB baiduDB) 60 | { 61 | this._config = config; 62 | this.baiduDB = baiduDB; 63 | } 64 | 65 | public string GetTokenRegisterUrl() 66 | { 67 | return 68 | $"https://openapi.baidu.com/oauth/2.0/authorize?response_type=code&client_id={this.config.ClientID}&redirect_uri=oob&scope=netdisk"; 69 | } 70 | 71 | public async Task ApplyCode(string code) 72 | { 73 | var url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=authorization_code"; 74 | 75 | NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty); 76 | 77 | queryString.Add("code", code); 78 | queryString.Add("client_id", this.config.ClientID); 79 | queryString.Add("client_secret", this.config.ClientSecret); 80 | queryString.Add("redirect_uri", "oob"); 81 | url = url + "&" + queryString.ToString(); 82 | 83 | var client = this.GetDefaultClient(); 84 | var jString = await client.GetStringAsync(url); 85 | var json = JObject.Parse(jString); 86 | if (json.SelectToken("error", false) != null) 87 | { 88 | Console.WriteLine("获取token失败"); 89 | return null; 90 | } 91 | 92 | if (json.SelectToken("access_token") != null && json.SelectToken("refresh_token") != null) 93 | { 94 | var proto = new BaiduProto(); 95 | proto.id = this.config.ClientID; 96 | proto.refresh_token = (string)json.SelectToken("refresh_token"); 97 | refreshToken = proto.refresh_token; 98 | proto.access_token = (string)json["access_token"]; 99 | this.accessToken = proto.access_token; 100 | proto.last_update = DateTime.Now; 101 | lastUpdate = proto.last_update; 102 | this.baiduDB.InsertOrReplace(proto); 103 | Console.WriteLine($"Token 获取成功 {this.accessToken}"); 104 | return this.accessToken; 105 | } 106 | 107 | return null; 108 | } 109 | 110 | public async Task GetToken() 111 | { 112 | if (lastUpdate == default) 113 | { 114 | var proto = this.baiduDB.GetProto(this.config.ClientID); 115 | if (proto != null) 116 | { 117 | this.lastUpdate = proto.last_update; 118 | this.refreshToken = proto.refresh_token; 119 | this.accessToken = proto.access_token; 120 | } 121 | else 122 | { 123 | //还未注册过 124 | return null; 125 | } 126 | } 127 | 128 | if (!string.IsNullOrEmpty(accessToken) && DateTime.Now.Subtract(lastUpdate).Days < 27) 129 | { 130 | return accessToken; 131 | } 132 | 133 | var url = "https://openapi.baidu.com/oauth/2.0/token?grant_type=refresh_token"; 134 | 135 | NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty); 136 | 137 | queryString.Add("refresh_token", this.refreshToken); 138 | queryString.Add("client_id", this.config.ClientID); 139 | queryString.Add("client_secret", this.config.ClientSecret); 140 | url = url + "&" + queryString.ToString(); 141 | 142 | var client = this.GetDefaultClient(); 143 | var jString = await client.GetStringAsync(url); 144 | var json = JObject.Parse(jString); 145 | if (json.SelectToken("error", false) != null) 146 | { 147 | Console.WriteLine("刷新token失败"); 148 | return null; 149 | } 150 | 151 | if (json.SelectToken("access_token") != null && json.SelectToken("refresh_token") != null) 152 | { 153 | var proto = new BaiduProto(); 154 | proto.id = this.config.ClientID; 155 | proto.refresh_token = (string)json.SelectToken("refresh_token"); 156 | refreshToken = proto.refresh_token; 157 | proto.access_token = (string)json["access_token"]; 158 | this.accessToken = proto.access_token; 159 | proto.last_update = DateTime.Now; 160 | lastUpdate = proto.last_update; 161 | this.baiduDB.InsertOrReplace(proto); 162 | Console.WriteLine($"Token 刷新成功 {this.accessToken}"); 163 | return this.accessToken; 164 | } 165 | 166 | return null; 167 | } 168 | 169 | private async Task GetSUrl(string shareUrl) 170 | { 171 | shareUrl = shareUrl.Trim(); 172 | var res = Regex.Match(shareUrl, @"https://pan\.baidu\.com/share/init\?surl=(.+?)$"); 173 | if (res.Success) 174 | { 175 | return res.Groups[1].Value; 176 | } 177 | 178 | // https://pan.baidu.com/s/1GQuNCeAzb92rl2KB68X3wA 179 | res = Regex.Match(shareUrl, @"https://pan\.baidu\.com/s/\S(.+?)$"); 180 | if (res.Success) 181 | { 182 | return res.Groups[1].Value; 183 | } 184 | 185 | throw new Exception($"通过 {shareUrl} 无法得到surl"); 186 | } 187 | 188 | private async Task GetSeKey(string surl, string pwd) 189 | { 190 | string url = 191 | $"https://pan.baidu.com/rest/2.0/xpan/share?method=verify&surl={surl}&access_token={await this.GetToken()}"; 192 | var client = this.GetDefaultClient(); 193 | var formContent = new FormUrlEncodedContent(new[] 194 | { 195 | new KeyValuePair("pwd", pwd), 196 | }); 197 | var resp = await client.PostAsync(url, formContent); 198 | var result = await resp.Content.ReadFromJsonAsync(); 199 | if (result.errno == 0) 200 | { 201 | var randsk = result.randsk; 202 | var sekey = HttpUtility.UrlDecode(randsk, Encoding.UTF8); 203 | return sekey; 204 | } 205 | else 206 | { 207 | var errormsg = result.errno switch 208 | { 209 | 105 => "链接地址错误", 210 | -12 => "非会员用户达到转存文件数目上限", 211 | -9 => "pwd错误", 212 | 2 => "参数错误,或者判断是否有referer", 213 | _ => $"GetSeKey时发生未知错误错误码:{result.errno}" 214 | }; 215 | throw new Exception($"通过{surl}和{pwd}发生{errormsg}无法得到sekey"); 216 | } 217 | 218 | return ""; 219 | } 220 | 221 | private async Task<(long shareid, long uk, List fsidList)?> GetTransFileInfo(string surl, string sekey) 222 | { 223 | var url = "https://pan.baidu.com/rest/2.0/xpan/share?method=list"; 224 | NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty); 225 | 226 | queryString.Add("shorturl", surl); 227 | queryString.Add("page", "1"); 228 | queryString.Add("num", "100"); 229 | queryString.Add("fid", "0"); 230 | queryString.Add("root", "1"); 231 | queryString.Add("sekey", sekey); 232 | queryString.Add("access_token", await this.GetToken()); 233 | url = url + "&" + queryString.ToString(); 234 | 235 | var client = GetDefaultClient(); 236 | var jstring = await client.GetStringAsync(url); 237 | var res = JsonNode.Parse(jstring); 238 | var errno = res["errno"].GetValue(); 239 | if (errno == 0) 240 | { 241 | long shareid = res["share_id"].GetValue(); 242 | long uk = res["uk"].GetValue(); 243 | List fsidList = new List(); 244 | foreach (var fs in res["list"].AsArray()) 245 | { 246 | fsidList.Add(fs["fs_id"].GetValue()); 247 | } 248 | 249 | return (shareid, uk, fsidList); 250 | } 251 | else 252 | { 253 | var error_msg = errno switch 254 | { 255 | 110 => "有其他转存任务在进行", 256 | 105 => "非会员用户达到转存文件数目上限", 257 | -7 => "达到高级会员转存上限", 258 | _ => $"未知错误{errno}", 259 | }; 260 | throw new Exception($"通过{surl}和{sekey}获取文件数据时发生{error_msg}无法得到sekey"); 261 | } 262 | } 263 | 264 | private async Task FileTransfer(long shareid, long uk, string sekey, List fsidlist, string path) 265 | { 266 | var url = "http://pan.baidu.com/rest/2.0/xpan/share?method=transfer"; 267 | NameValueCollection queryString = System.Web.HttpUtility.ParseQueryString(string.Empty); 268 | queryString.Add("shareid", shareid.ToString()); 269 | queryString.Add("from", uk.ToString()); 270 | queryString.Add("access_token", await this.GetToken()); 271 | url = url + "&" + queryString.ToString(); 272 | 273 | var formContent = new FormUrlEncodedContent(new[] 274 | { 275 | new KeyValuePair("sekey", sekey), 276 | new KeyValuePair("fsidlist", $"[{string.Join(",", fsidlist)}]"), 277 | new KeyValuePair("path", path), 278 | }); 279 | 280 | var client = GetDefaultClient(); 281 | var jresp = await client.PostAsync(url, formContent); 282 | var jString = await jresp.Content.ReadAsStringAsync(); 283 | var res = JsonNode.Parse(jString); 284 | 285 | int errno = res["errno"].GetValue(); 286 | if (errno == 0) 287 | { 288 | Console.WriteLine("文件转存成功"); 289 | } 290 | else 291 | { 292 | var error_msg = errno switch 293 | { 294 | 111 => "有其他转存任务在进行", 295 | 120 => "非会员用户达到转存文件数目上限", 296 | 130 => "达到高级会员转存上限", 297 | -33 => "达到转存文件数目上限", 298 | 12 => "批量操作失败,可能文件已经存在在该目录中了", 299 | -3 => "转存文件不存在", 300 | -9 => "密码错误", 301 | 5 => "分享文件夹等禁止文件", 302 | _ => "", 303 | }; 304 | throw new Exception(error_msg); 305 | } 306 | 307 | return errno; 308 | } 309 | 310 | public async Task TransFile(string url, string pwd) 311 | { 312 | var surl = await this.GetSUrl(url); 313 | var seKey = await this.GetSeKey(surl, pwd); 314 | var res = await this.GetTransFileInfo(surl, seKey); 315 | if (res.HasValue) 316 | { 317 | var v = res.Value; 318 | return await this.FileTransfer(v.shareid, v.uk, seKey, v.fsidList, this.config.DownloadPath); 319 | } 320 | 321 | return 999; 322 | } 323 | 324 | private HttpClient GetDefaultClient() 325 | { 326 | var client = new HttpClient(); 327 | foreach (var VARIABLE in headers) 328 | { 329 | client.DefaultRequestHeaders.Add(VARIABLE.Key, VARIABLE.Value); 330 | } 331 | 332 | return client; 333 | } 334 | } -------------------------------------------------------------------------------- /.idea/.idea.Bangumi/.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Anime/Anime.csproj 5 | Bangumi/Bangumi.csproj 6 | Bangumi/Bangumi.csproj 7 | Bangumi/Bangumi.csproj 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 107 | 108 | 114 | 115 | 116 | 121 | 123 | 124 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 276 | 277 | 279 | 280 | 282 | 283 | 284 | 285 | 288 | { 289 | "keyToString": { 290 | "RunOnceActivity.OpenProjectViewOnStart": "true", 291 | "RunOnceActivity.ShowReadmeOnStart": "true", 292 | "WebServerToolWindowFactoryState": "false", 293 | "XThreadsFramesViewSplitterKey": "0.49514562", 294 | "nodejs_package_manager_path": "npm", 295 | "settings.editor.selected.configurable": "database.other", 296 | "vue.rearranger.settings.migration": "true" 297 | }, 298 | "keyToStringList": { 299 | "DatabaseDriversLRU": [ 300 | "sqlite" 301 | ] 302 | } 303 | } 304 | 305 | 306 | 324 | 325 | 342 | 343 | 357 | 358 | 372 | 373 | 374 | 375 | 385 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 1655223326287 417 | 484 | 485 | 1668916982056 486 | 491 | 492 | 1668942175061 493 | 498 | 499 | 1668947425636 500 | 505 | 506 | 1668957417007 507 | 512 | 513 | 1669045296775 514 | 519 | 520 | 1669125390075 521 | 526 | 527 | 1669217067890 528 | 533 | 534 | 1669218382191 535 | 540 | 541 | 1669218473179 542 | 547 | 548 | 1669219676392 549 | 554 | 555 | 1669220685396 556 | 561 | 562 | 1669220961807 563 | 568 | 569 | 1669223288890 570 | 575 | 578 | 579 | 581 | 582 | 583 | 584 | 593 | 594 | 595 | 610 | 611 | 612 | 613 | 614 | file://$PROJECT_DIR$/Anime_Test/Catalyst_Test.cs 615 | 16 616 | 617 | 619 | 620 | file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1e493e93cd51dba47ff84e63a0e157dfb12eb1c1754abd7c8754aa2919f25b62/FileLoggerConfigurationExtensions.cs 621 | 206 622 | 623 | 624 | 626 | 627 | 629 | 630 | 632 | 633 | file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1e493e93cd51dba47ff84e63a0e157dfb12eb1c1754abd7c8754aa2919f25b62/FileLoggerConfigurationExtensions.cs 634 | 158 635 | 636 | 637 | 639 | 640 | 642 | 643 | 645 | 646 | file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1e493e93cd51dba47ff84e63a0e157dfb12eb1c1754abd7c8754aa2919f25b62/FileLoggerConfigurationExtensions.cs 647 | 111 648 | 649 | 650 | 652 | 653 | 655 | 656 | 658 | 659 | file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/1e493e93cd51dba47ff84e63a0e157dfb12eb1c1754abd7c8754aa2919f25b62/FileLoggerConfigurationExtensions.cs 660 | 72 661 | 662 | 663 | 665 | 666 | 668 | 669 | 671 | 672 | file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/3679d5494c9cbb4e5b626252aa8afa3b77d45941b72761e7deeef59df61b81d/FileSystemWatcher.cs 673 | 671 674 | 675 | 676 | 678 | 679 | 681 | 682 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 |