[Quicker] 蓝奏云API - 源码归档
动作:蓝奏云API
封装蓝奏云解析逻辑,支持解析文件直链、名称和大小。
更新时间:2025-12-30 23:02
原始文件:LanzouApi.cs
核心代码
//css_ref System.Windows.Forms.dll
//css_ref System.Drawing.dll
//css_ref Microsoft.Web.WebView2.Core.dll
//css_ref System.Web.dll
//css_ref System.Net.Http.dll
using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;
using Microsoft.Web.WebView2.Core;
using Newtonsoft.Json.Linq;
using Quicker.Public;
public static void Exec(IStepContext context)
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
{
throw new Exception("请将模块执行线程设置为 [后台线程 (STA独立线程)]");
}
StringBuilder log = new StringBuilder();
string filePath = context.GetVarValue("file_path")?.ToString();
string link = context.GetVarValue("link")?.ToString();
string pwd = context.GetVarValue("pwd")?.ToString();
string cookie = context.GetVarValue("cookie")?.ToString();
log.AppendLine($"[*] 任务启动: {DateTime.Now:HH:mm:ss}");
// --- 逻辑分支:上传 vs 下载 ---
if (!string.IsNullOrWhiteSpace(filePath))
{
log.AppendLine("[*] 检测到文件路径,进入上传模式...");
if (string.IsNullOrWhiteSpace(cookie)) throw new Exception("上传模式需要提供 Cookie (phpdisk_info)。");
try {
string shareLink = TryHttpUpload(filePath, cookie, context, log);
context.SetVarValue("share_link", shareLink);
context.SetVarValue("text", shareLink);
log.AppendLine($"[SUCCESS] 上传并获取链接成功: {shareLink}");
} catch (Exception ex) {
log.AppendLine($"[ERROR] 上传失败: {ex.Message}");
throw;
}
}
else if (!string.IsNullOrWhiteSpace(link))
{
log.AppendLine("[*] 检测到链接,进入解析下载模式...");
if (!link.StartsWith("http")) link = "https://" + link;
bool httpSuccess = false;
try {
log.AppendLine("[*] 正在尝试 HTTP 引擎解析 (Fast Path)...");
httpSuccess = TryHttpParse(link, pwd, context, log);
} catch (Exception ex) {
log.AppendLine($"[!] HTTP 引擎解析受阻: {ex.Message}");
}
if (!httpSuccess) {
log.AppendLine("[*] 自动切换至 WebView2 浏览器引擎 (Robust Path)...");
try {
var result = RunWebViewMachine(link, pwd, log, context);
if (result != null) {
context.SetVarValue("text", result.DirectUrl);
context.SetVarValue("file_name", result.FileName);
log.AppendLine("[SUCCESS] WebView2 引擎成功补位。");
}
} catch (Exception ex) {
log.AppendLine($"[CRITICAL] 所有路径均不可达: {ex.Message}");
context.SetVarValue("debug_log", log.ToString());
throw;
}
}
}
else
{
throw new Exception("错误:未提供链接或待上传文件路径。");
}
context.SetVarValue("debug_log", log.ToString());
}
#region 引擎三:上传模式 (Upload Engine)
private static string TryHttpUpload(string filePath, string cookie, IStepContext context, StringBuilder log)
{
if (!File.Exists(filePath)) throw new FileNotFoundException("找不到待上传的文件。");
// 规范化 Cookie
if (!cookie.Contains("phpdisk_info=")) cookie = "phpdisk_info=" + cookie;
string folderId = context.GetVarValue("folder_id")?.ToString() ?? "-1";
string ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
var handler = new HttpClientHandler { UseCookies = false };
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add("User-Agent", ua);
client.DefaultRequestHeaders.Add("Cookie", cookie);
client.DefaultRequestHeaders.Add("Referer", "https://pc.woozooo.com/mydisk.php");
client.DefaultRequestHeaders.Add("Origin", "https://pc.woozooo.com");
log.AppendLine($"[*] 开始模拟 HTML5 上传: {Path.GetFileName(filePath)}");
using (var content = new MultipartFormDataContent())
{
content.Add(new StringContent("1"), "task");
content.Add(new StringContent("2"), "ve");
content.Add(new StringContent("2"), "vie");
content.Add(new StringContent(folderId), "folder_id");
content.Add(new StringContent("WU_FILE_0"), "id");
content.Add(new StringContent(Path.GetFileName(filePath)), "name");
var fileContent = new ByteArrayContent(File.ReadAllBytes(filePath));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
content.Add(fileContent, "upload_file", Path.GetFileName(filePath));
// 1. 发送上传请求 (使用抓包确认的域名和接口)
var response = client.PostAsync("https://pc.woozooo.com/html5up.php", content).Result;
string resJson = response.Content.ReadAsStringAsync().Result;
if (!resJson.Trim().StartsWith("{")) {
string snippet = resJson.Length > 500 ? resJson.Substring(0, 500) : resJson;
log.AppendLine($"[DEBUG] 响应内容: {snippet}");
throw new Exception("上传接口未返回 JSON。请确认 Cookie 是否包含 phpdisk_info、ylogin 和 uag。");
}
var obj = JObject.Parse(resJson);
if (obj["zt"]?.ToString() != "1")
{
throw new Exception($"上传业务报错: {obj["info"] ?? obj["text"]}");
}
string fileId = obj["text"]?[0]?["id"]?.ToString();
if (string.IsNullOrEmpty(fileId)) {
// 有时候 id 在 info 里或 text 是字符串
fileId = obj["text"]?.ToString();
}
if (string.IsNullOrEmpty(fileId)) throw new Exception("上传成功但未提取到文件ID。");
log.AppendLine($"[+] 上传成功,文件ID: {fileId}。正在获取分享链接...");
// 2. 获取分享链接 (task=18)
var postData = new FormUrlEncodedContent(new Dictionary<string, string> {
{ "task", "18" },
{ "file_id", fileId }
});
var shareResp = client.PostAsync("https://pc.woozooo.com/doupload.php", postData).Result;
string shareJson = shareResp.Content.ReadAsStringAsync().Result;
if (!shareJson.Trim().StartsWith("{")) {
log.AppendLine($"[DEBUG] 获取分享链接响应异常: {shareJson.Substring(0, Math.Min(shareJson.Length, 100))}");
return $"https://www.lanzouy.com/{fileId}"; // 兜底返回基础链接
}
var shareObj = JObject.Parse(shareJson);
if (shareObj["zt"]?.ToString() == "1")
{
return shareObj["info"]?["url"]?.ToString();
}
else
{
log.AppendLine($"[!] 获取分享链接失败: {shareObj["info"]}");
// 某些情况下虽然 task=18 失败,但文件已经在那了,可以构造基础链接
return $"https://www.lanzouy.com/{fileId}";
}
}
}
}
#endregion
#region 引擎一:HTTP 解析模式
private static bool TryHttpParse(string link, string pwd, IStepContext context, StringBuilder log)
{
const string UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
var cookieContainer = new CookieContainer();
var handler = new HttpClientHandler {
CookieContainer = cookieContainer,
AllowAutoRedirect = false,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
using (var client = new HttpClient(handler))
{
client.DefaultRequestHeaders.Add("User-Agent", UA);
client.DefaultRequestHeaders.Add("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
string html = GetHtmlWithRetry(client, link, log, cookieContainer);
string pageTitle = Regex.Match(html, @"<title>(.*?)</title>").Groups[1].Value.Replace(" - 蓝奏云", "").Trim();
var iframeMatch = Regex.Match(html, @"iframe.*?src=""(/fn\?[^""]+)""");
string currentUrl = link;
if (iframeMatch.Success)
{
string iframeUrl = $"https://{new Uri(link).Host}{iframeMatch.Groups[1].Value}";
var req = new HttpRequestMessage(HttpMethod.Get, iframeUrl);
req.Headers.Add("Referer", link);
var fnResp = client.SendAsync(req).Result;
html = fnResp.Content.ReadAsStringAsync().Result;
currentUrl = iframeUrl;
}
string cleanHtml = Regex.Replace(html, @"//.*|/\*[\s\S]*?\*/|<!--[\s\S]*?-->", "");
string wp_sign = Regex.Match(cleanHtml, @"var\s+(?:wp_sign|skdklds|wsk_sign|aihidcms|ciucjdsdc)\s*=\s*'([a-zA-Z0-9+/=_ -]{30,})';").Groups[1].Value;
if (string.IsNullOrEmpty(wp_sign))
wp_sign = Regex.Match(cleanHtml, @"var\s+\w+\s*=\s*'([a-zA-Z0-9+/=_ -]{40,})';").Groups[1].Value;
string ajaxdata = Regex.Match(cleanHtml, @"var\s+ajaxdata\s*=\s*'([^']+)';").Groups[1].Value;
string fid = Regex.Match(cleanHtml, @"/ajaxm\.php\?file=(\d+)").Groups[1].Value;
if (string.IsNullOrEmpty(fid))
fid = Regex.Match(cleanHtml, @"var\s+f_id\s*=\s*'([^']+)';|var\s+fid\s*=\s*'([^']+)';").Groups.Cast<Group>().LastOrDefault(g => g.Success)?.Value;
if (string.IsNullOrEmpty(wp_sign) || string.IsNullOrEmpty(fid)) return false;
var uri = new Uri(currentUrl);
string ajaxUrl = $"{uri.Scheme}://{uri.Host}/ajaxm.php?file={fid}";
var postData = new Dictionary<string, string> {
{ "action", "downprocess" }, { "signs", ajaxdata }, { "websignkey", ajaxdata },
{ "websign", "" }, { "sign", wp_sign }, { "kd", "1" }, { "ves", "1" }, { "p", pwd }
};
var ajaxReq = new HttpRequestMessage(HttpMethod.Post, ajaxUrl);
ajaxReq.Content = new FormUrlEncodedContent(postData);
ajaxReq.Headers.Add("Referer", currentUrl);
ajaxReq.Headers.Add("X-Requested-With", "XMLHttpRequest");
var ajaxResp = client.SendAsync(ajaxReq).Result;
string json = ajaxResp.Content.ReadAsStringAsync().Result;
var obj = JObject.Parse(json);
if (obj["zt"]?.ToString() == "1")
{
string directLink = obj["dom"]?.ToString() + "/file/" + obj["url"]?.ToString();
string fileName = obj["inf"]?.ToString();
if (string.IsNullOrEmpty(fileName) || fileName == "0") fileName = pageTitle;
var traceReq = new HttpRequestMessage(HttpMethod.Get, directLink);
traceReq.Headers.Add("Referer", currentUrl);
traceReq.Headers.Add("Cookie", "down_ip=1");
var traceResp = client.SendAsync(traceReq).Result;
string finalUrl = (traceResp.StatusCode == HttpStatusCode.Redirect || traceResp.StatusCode == HttpStatusCode.Found || traceResp.StatusCode == HttpStatusCode.MovedPermanently)
? traceResp.Headers.Location.ToString() : directLink;
context.SetVarValue("text", finalUrl);
context.SetVarValue("file_name", fileName);
log.AppendLine($"[HTTP-SUCCESS] 解析成功: {fileName}");
return true;
}
return false;
}
}
private static string GetHtmlWithRetry(HttpClient client, string url, StringBuilder log, CookieContainer cc, int depth = 0)
{
if (depth > 3) throw new Exception("WAF 安全验证死循环。");
var response = client.GetAsync(url).Result;
string html = response.Content.ReadAsStringAsync().Result;
if (html.Contains("acw_sc__v2"))
{
var arg1Match = Regex.Match(html, @"arg1='([0-9A-Z]+)'");
if (arg1Match.Success)
{
string acw = CalcAcwScV2(arg1Match.Groups[1].Value);
cc.Add(new Uri(url), new Cookie("acw_sc__v2", acw));
return GetHtmlWithRetry(client, url, log, cc, depth + 1);
}
}
return html;
}
private static string CalcAcwScV2(string arg1)
{
int[] v1 = { 15, 35, 29, 24, 33, 16, 1, 38, 10, 9, 19, 31, 40, 27, 22, 23, 25, 13, 6, 11, 39, 18, 20, 8, 14, 21, 32, 26, 2, 30, 7, 4, 17, 5, 3, 28, 34, 37, 12, 36 };
char[] v2 = new char[v1.Length];
for (int i = 0; i < arg1.Length && i < v1.Length; i++) {
for (int j = 0; j < v1.Length; j++) { if (v1[j] == i + 1) { v2[j] = arg1[i]; break; } }
}
string hexArg = new string(v2); string xorKey = "3000176000856006061501533003690027800375";
StringBuilder sb = new StringBuilder();
for (int i = 0; i < hexArg.Length && i < xorKey.Length; i += 2) {
int b1 = Convert.ToInt32(hexArg.Substring(i, 2), 16);
int b2 = Convert.ToInt32(xorKey.Substring(i, 2), 16);
sb.Append((b1 ^ b2).ToString("x2"));
}
return sb.ToString();
}
#endregion
#region 引擎二:WebView2 模式
public static ParsingResult RunWebViewMachine(string url, string pwd, StringBuilder log, IStepContext context)
{
ParsingResult result = null;
using (var form = new Form())
{
form.Text = "Lanzou Parser Fallback";
form.ShowInTaskbar = false;
form.Size = new Size(100, 100);
form.Location = new Point(-32000, -32000);
var panel = new Panel { Dock = DockStyle.Fill };
form.Controls.Add(panel);
CoreWebView2Controller controller = null;
CoreWebView2 webview = null;
bool isDone = false;
bool isNavFiles = false;
Action<string, string> finalize = (raw, name) => {
if (isDone) return; isDone = true;
string final = raw;
try {
using (var h = new HttpClientHandler { AllowAutoRedirect = false })
using (var c = new HttpClient(h)) {
c.DefaultRequestHeaders.Add("Referer", webview.Source);
c.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
var r = c.GetAsync(raw).GetAwaiter().GetResult();
if (r.Headers.Location != null) final = r.Headers.Location.ToString();
}
} catch {}
result = new ParsingResult { DirectUrl = final, FileName = name };
context.SetVarValue("text", final);
context.SetVarValue("file_name", name);
form.Invoke(new Action(() => form.Close()));
};
form.Shown += async (s, e) => {
try {
var env = await CoreWebView2Environment.CreateAsync(null, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Quicker_Lanzou_WV2"));
controller = await env.CreateCoreWebView2ControllerAsync(panel.Handle);
webview = controller.CoreWebView2;
webview.WebResourceResponseReceived += async (ss, ee) => {
if (isDone || isNavFiles) return;
if (ee.Request.Uri.Contains("ajaxm.php")) {
try {
var stream = await ee.Response.GetContentAsync();
using (var sr = new StreamReader(stream)) {
var data = JObject.Parse(await sr.ReadToEndAsync());
if (data["zt"]?.ToString() == "1") {
isNavFiles = true;
webview.Navigate(data["dom"]?.ToString() + "/file/" + data["url"]?.ToString());
}
}
} catch {}
}
};
webview.DownloadStarting += (ss, ee) => {
ee.Cancel = true;
finalize(ee.DownloadOperation.Uri, webview.DocumentTitle.Replace(" - 蓝奏云", "").Trim());
};
webview.Navigate(url);
await Task.Run(async () => {
int sc = 0;
while(!isDone && sc++ < 45) {
await Task.Delay(1600);
if(isDone) break;
form.Invoke(new Action(async () => {
if(isDone || webview == null) return;
try {
string js = $@"
(function() {{
let ifr = document.querySelector('iframe');
if(!window._eb && ifr && ifr.src.includes('fn?')) {{ window._eb=true; location.href=ifr.src; return 'Jump'; }}
let doc = ifr?.contentDocument || document;
let b = Array.from(doc.querySelectorAll('a,button,span')).find(el => el.innerText.match(/电信下载|联通下载|普通下载/));
if(b) {{
const e = (n) => b.dispatchEvent(new MouseEvent(n, {{ bubbles:true, cancelable:true, view:window }}));
e('mousedown'); setTimeout(() => {{ e('mouseup'); e('click'); }}, 100);
return 'Click:' + b.innerText;
}}
return 'Scan';
}})()";
await webview.ExecuteScriptAsync(js);
} catch {}
}));
}
});
} catch (Exception) { form.Close(); }
};
Application.Run(form);
return result;
}
}
#endregion
public class ParsingResult { public string DirectUrl { get; set; } public string FileName { get; set; } }
动作配置 (JSON)
{
"ActionId": "46d5af77-9a62-45a2-91ee-ee1117c5455c",
"Title": "蓝奏云API",
"Description": "封装蓝奏云解析逻辑,支持解析文件直链、名称和大小。",
"Icon": "fa:Solid_CloudDownloadAlt:#0080FF",
"References": [
"Microsoft.Web.WebView2.Core.dll",
"System.Windows.Forms.dll",
"System.Drawing.dll",
"System.Web.dll",
"System.Runtime.dll"
],
"Variables": [
{
"Key": "link",
"IsLocked": false,
"Type": 0,
"Desc": "蓝奏云链接",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "pwd",
"IsLocked": false,
"Type": 0,
"Desc": "提取码",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "text",
"IsLocked": false,
"Type": 0,
"Desc": "直链",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "file_name",
"IsLocked": false,
"Type": 0,
"Desc": "文件名",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "file_size",
"IsLocked": false,
"Type": 0,
"Desc": "文件大小",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "debug_log",
"IsLocked": false,
"Type": 0,
"Desc": "调试日志",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "file_path",
"IsLocked": false,
"Type": 0,
"Desc": "待上传文件路径",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "cookie",
"IsLocked": false,
"Type": 0,
"Desc": "登录 Cookie (phpdisk_info)",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
},
{
"Key": "folder_id",
"IsLocked": false,
"Type": 0,
"Desc": "文件夹ID (默认-1)",
"DefaultValue": "-1",
"SaveState": false,
"ParamName": null
},
{
"Key": "share_link",
"IsLocked": false,
"Type": 0,
"Desc": "上传后的分享链接",
"DefaultValue": null,
"SaveState": false,
"ParamName": null
}
]
}
使用方法
- 确保您已安装 动作构建 (Action Builder) 动作。
- 将本文提供的
.cs和.json文件下载到同一目录。 - 选中
.json文件,运行「动作构建」即可生成并安装动作。
浙公网安备 33010602011771号