feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization

This commit is contained in:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace Paca.Browser.Content;
public sealed class MhtmlWebArchivePackager
{
public string Package(WebArchivePackageRequest request)
{
var boundary = "----=_NextPart_" + Guid.NewGuid().ToString("N");
var sb = new StringBuilder();
sb.AppendLine("From: <Saved by PACA Browser>");
sb.AppendLine($"Snapshot-Content-Location: {request.Url}");
sb.AppendLine($"Subject: {request.Title}");
sb.AppendLine($"Date: {DateTime.UtcNow:R}");
sb.AppendLine("MIME-Version: 1.0");
sb.AppendLine($"Content-Type: multipart/related; boundary=\"{boundary}\"; type=\"text/html\"");
sb.AppendLine();
// HTML Part
sb.AppendLine($"--{boundary}");
sb.AppendLine("Content-Type: text/html; charset=\"utf-8\"");
sb.AppendLine("Content-Transfer-Encoding: 8bit");
sb.AppendLine($"Content-Location: {request.Url}");
sb.AppendLine();
sb.AppendLine(request.HtmlContent);
sb.AppendLine();
// Resources
foreach (var res in request.Resources)
{
sb.AppendLine($"--{boundary}");
sb.AppendLine($"Content-Type: {res.MimeType}");
sb.AppendLine("Content-Transfer-Encoding: base64");
sb.AppendLine($"Content-Location: {res.Url}");
sb.AppendLine();
sb.AppendLine(Convert.ToBase64String(res.Data));
sb.AppendLine();
}
sb.AppendLine($"--{boundary}--");
return sb.ToString();
}
public WebArchiveUnpackResult Unpack(string mhtmlContent)
{
if (string.IsNullOrWhiteSpace(mhtmlContent))
{
return new WebArchiveUnpackResult("Untitled", string.Empty, string.Empty, Array.Empty<WebArchiveResource>());
}
var locMatch = Regex.Match(mhtmlContent, @"Snapshot-Content-Location:\s*([^\r\n]+)", RegexOptions.IgnoreCase);
var locationUrl = locMatch.Success ? locMatch.Groups[1].Value.Trim() : string.Empty;
var subjMatch = Regex.Match(mhtmlContent, @"Subject:\s*([^\r\n]+)", RegexOptions.IgnoreCase);
var title = subjMatch.Success ? subjMatch.Groups[1].Value.Trim() : "Untitled";
var boundMatch = Regex.Match(mhtmlContent, @"boundary=""?([^"";\r\n]+)""?", RegexOptions.IgnoreCase);
if (!boundMatch.Success)
{
return new WebArchiveUnpackResult(title, locationUrl, mhtmlContent, Array.Empty<WebArchiveResource>());
}
var boundary = boundMatch.Groups[1].Value;
var parts = mhtmlContent.Split(new[] { "--" + boundary }, StringSplitOptions.RemoveEmptyEntries);
string htmlContent = string.Empty;
var resources = new List<WebArchiveResource>();
foreach (var part in parts)
{
if (part.StartsWith("--") || string.IsNullOrWhiteSpace(part)) continue;
var doubleLine = part.IndexOf("\r\n\r\n", StringComparison.Ordinal);
if (doubleLine < 0) doubleLine = part.IndexOf("\n\n", StringComparison.Ordinal);
if (doubleLine < 0) continue;
var headers = part.Substring(0, doubleLine);
var body = part.Substring(doubleLine).Trim();
var typeMatch = Regex.Match(headers, @"Content-Type:\s*([^;\r\n]+)", RegexOptions.IgnoreCase);
var mimeType = typeMatch.Success ? typeMatch.Groups[1].Value.Trim() : "text/html";
if (mimeType.Contains("multipart/related", StringComparison.OrdinalIgnoreCase) ||
headers.Contains("boundary=", StringComparison.OrdinalIgnoreCase))
{
continue;
}
var urlMatch = Regex.Match(headers, @"Content-Location:\s*([^\r\n]+)", RegexOptions.IgnoreCase);
var resUrl = urlMatch.Success ? urlMatch.Groups[1].Value.Trim() : string.Empty;
if (mimeType.Contains("text/html", StringComparison.OrdinalIgnoreCase))
{
htmlContent = body;
}
else
{
byte[] data;
if (headers.Contains("base64", StringComparison.OrdinalIgnoreCase))
{
try
{
data = Convert.FromBase64String(body);
}
catch
{
data = Encoding.UTF8.GetBytes(body);
}
}
else
{
data = Encoding.UTF8.GetBytes(body);
}
resources.Add(new WebArchiveResource(resUrl, mimeType, data));
}
}
return new WebArchiveUnpackResult(title, locationUrl, htmlContent, resources.AsReadOnly());
}
}