windowcapture
исходный код / Detection/MediaParser.cs

MediaParser.cs

880 строк · 36,835 байт · модуль Detection
  1using System;
  2using System.Collections.Generic;
  3using System.Diagnostics;
  4using System.Drawing;
  5using System.IO;
  6using System.Net;
  7using System.Runtime.InteropServices;
  8using System.Text;
  9using System.Text.RegularExpressions;
 10using System.Windows.Forms;
 11using WindowCapture.Native;
 12
 13namespace WindowCapture.Detection
 14{
 15    /// <summary>
 16    /// Media item found on a web page
 17    /// </summary>
 18    public class MediaItem
 19    {
 20        public string Url;
 21        public string Type; // "image", "video", "audio"
 22        public string FileName;
 23        public string Description;
 24        public string TagName;
 25        public long Size;
 26
 27        public MediaItem(string url, string type)
 28        {
 29            Url = CleanUrl(url);
 30            Type = type;
 31            FileName = GetFileNameFromUrl(Url);
 32            Size = -1;
 33        }
 34
 35        private static string CleanUrl(string url)
 36        {
 37            if (string.IsNullOrEmpty(url)) return url;
 38            url = url.Trim();
 39            if (url.StartsWith("//")) url = "https:" + url;
 40            return url;
 41        }
 42
 43        private string GetFileNameFromUrl(string url)
 44        {
 45            try
 46            {
 47                if (string.IsNullOrEmpty(url)) return "media_" + DateTime.Now.Ticks;
 48
 49                // Remove query and fragment
 50                int q = url.IndexOf('?');
 51                if (q > 0) url = url.Substring(0, q);
 52                int h = url.IndexOf('#');
 53                if (h > 0) url = url.Substring(0, h);
 54
 55                var uri = new Uri(url);
 56                var name = Path.GetFileName(uri.LocalPath);
 57
 58                if (string.IsNullOrEmpty(name) || name.Length > 100)
 59                    name = "media_" + DateTime.Now.Ticks;
 60
 61                if (!Path.HasExtension(name))
 62                {
 63                    if (Type == "video") name += ".mp4";
 64                    else if (Type == "audio") name += ".mp3";
 65                    else name += ".jpg";
 66                }
 67
 68                return name;
 69            }
 70            catch
 71            {
 72                return "media_" + DateTime.Now.Ticks + ".bin";
 73            }
 74        }
 75    }
 76
 77    /// <summary>
 78    /// Deep media parser - extracts media using multiple strategies without requiring Chrome debug flags
 79    /// </summary>
 80    public static class MediaParser
 81    {
 82        /// <summary>
 83        /// WebClient with a connection/response timeout so a slow or unresponsive page never
 84        /// hangs indefinitely (which would freeze the UI thread if the parse runs there).
 85        /// </summary>
 86        private class TimedWebClient : WebClient
 87        {
 88            public int TimeoutMs = 15000;
 89            protected override WebRequest GetWebRequest(Uri address)
 90            {
 91                var r = base.GetWebRequest(address);
 92                if (r != null)
 93                {
 94                    r.Timeout = TimeoutMs;
 95                    var hr = r as HttpWebRequest;
 96                    if (hr != null) hr.ReadWriteTimeout = TimeoutMs * 2;
 97                }
 98                return r;
 99            }
100        }
101
102        /// <summary>
103        /// Parse media from browser at screen point
104        /// </summary>
105        public static List<MediaItem> ParseMediaAt(Point screenPt, IntPtr excludeWindow)
106        {
107            var results = new List<MediaItem>();
108            var seenUrls = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
109
110            // Find the window under cursor
111            WinApi.POINT pt;
112            pt.X = screenPt.X;
113            pt.Y = screenPt.Y;
114            IntPtr hwnd = WinApi.FindAppWindowAtPointExcluding(pt, excludeWindow);
115
116            // Strategy 1: Get URL from clipboard (user might have right-clicked "Copy image address")
117            string clipUrl = GetClipboardUrl();
118            if (!string.IsNullOrEmpty(clipUrl))
119            {
120                AddUniqueMedia(results, seenUrls, clipUrl, "Clipboard", null);
121            }
122
123            // Strategy 2: Deep UI Automation scan of the element under cursor
124            var uiaMedia = GetMediaFromUIAutomation(screenPt);
125            foreach (var url in uiaMedia)
126            {
127                AddUniqueMedia(results, seenUrls, url, "UIAutomation", null);
128            }
129
130            // Strategy 3: Get browser URL from address bar and parse HTML
131            string browserUrl = GetBrowserUrlFromAddressBar(hwnd);
132            if (!string.IsNullOrEmpty(browserUrl))
133            {
134                var pageMedia = ParsePageHtml(browserUrl);
135                foreach (var url in pageMedia)
136                {
137                    AddUniqueMedia(results, seenUrls, url, "HTML", null);
138                }
139            }
140
141            // Strategy 4: Legacy IAccessible scan
142            if (hwnd != IntPtr.Zero)
143            {
144                var accMedia = GetMediaFromAccessibility(hwnd, screenPt);
145                foreach (var url in accMedia)
146                {
147                    AddUniqueMedia(results, seenUrls, url, "Accessibility", null);
148                }
149            }
150
151            // Strategy 5: Scan visible text for URLs
152            var textUrls = ScanWindowTextForUrls(hwnd);
153            foreach (var url in textUrls)
154            {
155                AddUniqueMedia(results, seenUrls, url, "WindowText", null);
156            }
157
158            return results;
159        }
160
161        /// <summary>
162        /// Get media URLs using UI Automation (deeper access than IAccessible)
163        /// </summary>
164        private static List<string> GetMediaFromUIAutomation(Point screenPt)
165        {
166            var results = new List<string>();
167
168            try
169            {
170                // Create UI Automation instance
171                IUIAutomation uiAutomation = (IUIAutomation)new CUIAutomation();
172                if (uiAutomation == null) return results;
173
174                // Get element at point
175                IUIAutomationElement element = uiAutomation.ElementFromPoint(new tagPOINT { x = screenPt.X, y = screenPt.Y });
176                if (element == null) return results;
177
178                // Extract URLs from this element and its ancestors/descendants
179                ExtractUrlsFromUIAElement(uiAutomation, element, results, 0, true);
180
181                // Also check parent elements (might contain image URL)
182                var walker = uiAutomation.CreateTreeWalker(uiAutomation.RawViewCondition);
183                IUIAutomationElement parent = element;
184                for (int i = 0; i < 5 && parent != null; i++)
185                {
186                    try
187                    {
188                        parent = walker.GetParentElement(parent);
189                        if (parent != null)
190                            ExtractUrlsFromUIAElement(uiAutomation, parent, results, 0, false);
191                    }
192                    catch { break; }
193                }
194            }
195            catch { }
196
197            return results;
198        }
199
200        private static void ExtractUrlsFromUIAElement(IUIAutomation uiAutomation, IUIAutomationElement element, List<string> results, int depth, bool scanChildren)
201        {
202            if (depth > 10 || results.Count > 100 || element == null) return;
203
204            try
205            {
206                // Get various properties that might contain URLs
207                string[] propertiesToCheck = new string[10];
208
209                try { propertiesToCheck[0] = element.CurrentName; } catch { }
210                try { propertiesToCheck[1] = element.CurrentHelpText; } catch { }
211                try { propertiesToCheck[2] = element.CurrentItemStatus; } catch { }
212                try { propertiesToCheck[3] = element.CurrentAutomationId; } catch { }
213                try { propertiesToCheck[4] = element.CurrentClassName; } catch { }
214
215                // Try to get Value pattern
216                try
217                {
218                    object patternObj;
219                    element.GetCurrentPattern(10002, out patternObj); // UIA_ValuePatternId
220                    var valuePattern = patternObj as IUIAutomationValuePattern;
221                    if (valuePattern != null)
222                    {
223                        propertiesToCheck[5] = valuePattern.CurrentValue;
224                    }
225                }
226                catch { }
227
228                // Try to get LegacyIAccessible pattern for more data
229                try
230                {
231                    object patternObj;
232                    element.GetCurrentPattern(10018, out patternObj); // UIA_LegacyIAccessiblePatternId
233                    var legacyPattern = patternObj as IUIAutomationLegacyIAccessiblePattern;
234                    if (legacyPattern != null)
235                    {
236                        try { propertiesToCheck[6] = legacyPattern.CurrentValue; } catch { }
237                        try { propertiesToCheck[7] = legacyPattern.CurrentDescription; } catch { }
238                        try { propertiesToCheck[8] = legacyPattern.CurrentName; } catch { }
239                        try { propertiesToCheck[9] = legacyPattern.CurrentDefaultAction; } catch { }
240                    }
241                }
242                catch { }
243
244                // Extract URLs from all properties
245                foreach (var prop in propertiesToCheck)
246                {
247                    if (string.IsNullOrEmpty(prop)) continue;
248                    ExtractUrlsFromText(prop, results);
249                }
250
251                // Scan children if requested
252                if (scanChildren && depth < 5)
253                {
254                    try
255                    {
256                        var children = element.FindAll(TreeScope.TreeScope_Children, uiAutomation.CreateTrueCondition());
257                        if (children != null)
258                        {
259                            int count = children.Length;
260                            for (int i = 0; i < count && i < 50 && results.Count < 100; i++)
261                            {
262                                try
263                                {
264                                    var child = children.GetElement(i);
265                                    ExtractUrlsFromUIAElement(uiAutomation, child, results, depth + 1, true);
266                                }
267                                catch { }
268                            }
269                        }
270                    }
271                    catch { }
272                }
273            }
274            catch { }
275        }
276
277        private static void ExtractUrlsFromText(string text, List<string> results)
278        {
279            if (string.IsNullOrEmpty(text)) return;
280
281            // Find HTTP/HTTPS URLs
282            var urlMatches = Regex.Matches(text, @"https?://[^\s\""'<>\]\)]+", RegexOptions.IgnoreCase);
283            foreach (Match m in urlMatches)
284            {
285                string url = m.Value.TrimEnd('.', ',', ';', ':', '!', '?');
286                if (IsMediaUrl(url) && results.Count < 100)
287                    results.Add(url);
288            }
289
290            // Find data: URLs for images (base64)
291            if (text.StartsWith("data:image/"))
292            {
293                // Skip data URLs for now - they're embedded
294            }
295        }
296
297        /// <summary>
298        /// Get browser URL from address bar using UI Automation
299        /// </summary>
300        private static string GetBrowserUrlFromAddressBar(IntPtr hwnd)
301        {
302            if (hwnd == IntPtr.Zero) return null;
303
304            try
305            {
306                IUIAutomation uiAutomation = (IUIAutomation)new CUIAutomation();
307                var rootElement = uiAutomation.ElementFromHandle(hwnd);
308                if (rootElement == null) return null;
309
310                // Look for address bar - it's usually an Edit control with specific automation ID
311                // Chrome: "addressTextField" or class "OmniboxViewViews"
312                // Firefox: "urlbar-input"
313                // Edge: Similar to Chrome
314
315                string[] addressBarIds = { "addressTextField", "urlbar-input", "addressEditBox", "URL bar" };
316
317                foreach (var id in addressBarIds)
318                {
319                    try
320                    {
321                        var condition = uiAutomation.CreatePropertyCondition(30011, id); // AutomationIdProperty
322                        var addressBar = rootElement.FindFirst(TreeScope.TreeScope_Descendants, condition);
323                        if (addressBar != null)
324                        {
325                            object patternObj;
326                            addressBar.GetCurrentPattern(10002, out patternObj);
327                            var valuePattern = patternObj as IUIAutomationValuePattern;
328                            if (valuePattern != null)
329                            {
330                                string url = valuePattern.CurrentValue;
331                                if (!string.IsNullOrEmpty(url))
332                                {
333                                    if (!url.StartsWith("http")) url = "https://" + url;
334                                    return url;
335                                }
336                            }
337                        }
338                    }
339                    catch { }
340                }
341
342                // Fallback: look for Edit controls with URL-like content
343                var editCondition = uiAutomation.CreatePropertyCondition(30003, 50004); // ControlTypeProperty = Edit
344                var edits = rootElement.FindAll(TreeScope.TreeScope_Descendants, editCondition);
345                if (edits != null)
346                {
347                    int count = edits.Length;
348                    for (int i = 0; i < count && i < 20; i++)
349                    {
350                        try
351                        {
352                            var edit = edits.GetElement(i);
353                            object patternObj;
354                            edit.GetCurrentPattern(10002, out patternObj);
355                            var valuePattern = patternObj as IUIAutomationValuePattern;
356                            if (valuePattern != null)
357                            {
358                                string value = valuePattern.CurrentValue;
359                                if (!string.IsNullOrEmpty(value) &&
360                                    (value.StartsWith("http://") || value.StartsWith("https://") ||
361                                     value.Contains(".com") || value.Contains(".org") || value.Contains(".net")))
362                                {
363                                    if (!value.StartsWith("http")) value = "https://" + value;
364                                    return value;
365                                }
366                            }
367                        }
368                        catch { }
369                    }
370                }
371            }
372            catch { }
373
374            return null;
375        }
376
377        /// <summary>
378        /// Parse HTML page for media URLs
379        /// </summary>
380        private static List<string> ParsePageHtml(string pageUrl)
381        {
382            var results = new List<string>();
383
384            try
385            {
386                using (var client = new TimedWebClient())
387                {
388                    client.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
389                    client.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
390                    client.Encoding = Encoding.UTF8;
391
392                    string html = client.DownloadString(pageUrl);
393                    Uri baseUri = new Uri(pageUrl);
394
395                    // Find all src, href, data-src, poster, srcset attributes
396                    var srcMatches = Regex.Matches(html, @"(?:src|href|data-src|data-original|poster|srcset)\s*=\s*[""']([^""']+)[""']", RegexOptions.IgnoreCase);
397                    foreach (Match m in srcMatches)
398                    {
399                        string val = m.Groups[1].Value;
400                        // Handle srcset (multiple URLs)
401                        if (val.Contains(","))
402                        {
403                            foreach (var part in val.Split(','))
404                            {
405                                string url = part.Trim().Split(' ')[0];
406                                url = ResolveUrl(url, baseUri);
407                                if (IsMediaUrl(url)) results.Add(url);
408                            }
409                        }
410                        else
411                        {
412                            string url = ResolveUrl(val, baseUri);
413                            if (IsMediaUrl(url)) results.Add(url);
414                        }
415                    }
416
417                    // Find url() in CSS
418                    var cssMatches = Regex.Matches(html, @"url\s*\(\s*[""']?([^""'\)]+)[""']?\s*\)", RegexOptions.IgnoreCase);
419                    foreach (Match m in cssMatches)
420                    {
421                        string url = ResolveUrl(m.Groups[1].Value, baseUri);
422                        if (IsMediaUrl(url)) results.Add(url);
423                    }
424
425                    // Find og:image, og:video, twitter:image meta tags
426                    var metaMatches = Regex.Matches(html, @"<meta[^>]+(?:property|name)\s*=\s*[""'](?:og:image|og:video|og:video:url|twitter:image|twitter:player)[""'][^>]+content\s*=\s*[""']([^""']+)[""']", RegexOptions.IgnoreCase);
427                    foreach (Match m in metaMatches)
428                    {
429                        string url = ResolveUrl(m.Groups[1].Value, baseUri);
430                        if (!string.IsNullOrEmpty(url)) results.Add(url);
431                    }
432
433                    // Also check content before property (different order)
434                    var metaMatches2 = Regex.Matches(html, @"<meta[^>]+content\s*=\s*[""']([^""']+)[""'][^>]+(?:property|name)\s*=\s*[""'](?:og:image|og:video|og:video:url|twitter:image)[""']", RegexOptions.IgnoreCase);
435                    foreach (Match m in metaMatches2)
436                    {
437                        string url = ResolveUrl(m.Groups[1].Value, baseUri);
438                        if (!string.IsNullOrEmpty(url)) results.Add(url);
439                    }
440
441                    // Find video sources in JavaScript (common patterns)
442                    var jsVideoMatches = Regex.Matches(html, @"[""'](https?://[^""']+\.(?:mp4|webm|m3u8|mpd)(?:\?[^""']*)?)[""']", RegexOptions.IgnoreCase);
443                    foreach (Match m in jsVideoMatches)
444                    {
445                        results.Add(m.Groups[1].Value);
446                    }
447
448                    // Find image URLs in JSON-LD
449                    var jsonLdMatches = Regex.Matches(html, @"""(?:image|thumbnailUrl|contentUrl|url)""\s*:\s*""(https?://[^""]+)""", RegexOptions.IgnoreCase);
450                    foreach (Match m in jsonLdMatches)
451                    {
452                        string url = m.Groups[1].Value;
453                        if (IsMediaUrl(url)) results.Add(url);
454                    }
455                }
456            }
457            catch { }
458
459            return results;
460        }
461
462        /// <summary>
463        /// Scan window text for URLs
464        /// </summary>
465        private static List<string> ScanWindowTextForUrls(IntPtr hwnd)
466        {
467            var results = new List<string>();
468            if (hwnd == IntPtr.Zero) return results;
469
470            try
471            {
472                // Get window text
473                int length = GetWindowTextLength(hwnd);
474                if (length > 0 && length < 10000)
475                {
476                    var sb = new StringBuilder(length + 1);
477                    GetWindowText(hwnd, sb, sb.Capacity);
478                    ExtractUrlsFromText(sb.ToString(), results);
479                }
480
481                // Enumerate child windows
482                EnumChildWindows(hwnd, (childHwnd, lParam) =>
483                {
484                    try
485                    {
486                        int len = GetWindowTextLength(childHwnd);
487                        if (len > 0 && len < 5000)
488                        {
489                            var sb = new StringBuilder(len + 1);
490                            GetWindowText(childHwnd, sb, sb.Capacity);
491                            ExtractUrlsFromText(sb.ToString(), results);
492                        }
493                    }
494                    catch { }
495                    return results.Count < 100;
496                }, IntPtr.Zero);
497            }
498            catch { }
499
500            return results;
501        }
502
503        /// <summary>
504        /// Get media URLs from accessibility tree (legacy IAccessible)
505        /// </summary>
506        private static List<string> GetMediaFromAccessibility(IntPtr hwnd, Point screenPt)
507        {
508            var results = new List<string>();
509
510            try
511            {
512                object accObj;
513                object childId;
514                int hr = AccessibleObjectFromPoint(screenPt, out accObj, out childId);
515
516                if (hr == 0 && accObj != null)
517                {
518                    var acc = accObj as IAccessible;
519                    if (acc != null)
520                    {
521                        ExtractUrlsFromAccessible(acc, childId, results, 0);
522                        Marshal.ReleaseComObject(acc);
523                    }
524                }
525            }
526            catch { }
527
528            return results;
529        }
530
531        private static void ExtractUrlsFromAccessible(IAccessible acc, object childId, List<string> results, int depth)
532        {
533            if (depth > 15 || results.Count > 100) return;
534
535            try
536            {
537                string[] props = new string[4];
538                try { props[0] = acc.get_accName(childId); } catch { }
539                try { props[1] = acc.get_accValue(childId); } catch { }
540                try { props[2] = acc.get_accDescription(childId); } catch { }
541                try { props[3] = acc.get_accHelp(childId); } catch { }
542
543                foreach (var prop in props)
544                {
545                    if (!string.IsNullOrEmpty(prop))
546                        ExtractUrlsFromText(prop, results);
547                }
548
549                // Check children
550                int childCount = 0;
551                try { childCount = acc.accChildCount; } catch { }
552
553                if (childCount > 0 && childCount < 100)
554                {
555                    object[] children = new object[childCount];
556                    int obtained = 0;
557                    AccessibleChildren(acc, 0, childCount, children, out obtained);
558
559                    for (int i = 0; i < obtained && results.Count < 100; i++)
560                    {
561                        var childAcc = children[i] as IAccessible;
562                        if (childAcc != null)
563                        {
564                            ExtractUrlsFromAccessible(childAcc, 0, results, depth + 1);
565                            Marshal.ReleaseComObject(childAcc);
566                        }
567                    }
568                }
569            }
570            catch { }
571        }
572
573        private static string GetClipboardUrl()
574        {
575            try
576            {
577                if (Clipboard.ContainsText())
578                {
579                    string text = Clipboard.GetText().Trim();
580                    if ((text.StartsWith("http://") || text.StartsWith("https://")) &&
581                        !text.Contains(" ") && !text.Contains("\n"))
582                        return text;
583                }
584            }
585            catch { }
586            return null;
587        }
588
589        private static void AddUniqueMedia(List<MediaItem> results, HashSet<string> seen, string url, string source, string tag)
590        {
591            if (string.IsNullOrEmpty(url)) return;
592
593            url = url.Trim();
594            if (url.StartsWith("//")) url = "https:" + url;
595            if (!url.StartsWith("http")) return;
596            if (seen.Contains(url)) return;
597
598            // Skip tiny icons and tracking pixels
599            if (url.Contains("favicon") || url.Contains("1x1") || url.Contains("pixel") ||
600                url.Contains("tracking") || url.Contains("beacon") || url.Contains("analytics"))
601                return;
602
603            // Skip common non-media URLs
604            if (url.Contains(".js") || url.Contains(".css") || url.Contains(".woff") ||
605                url.Contains(".ttf") || url.Contains(".eot"))
606                return;
607
608            seen.Add(url);
609
610            string type = "image";
611            string lower = url.ToLower();
612            if (lower.Contains(".mp4") || lower.Contains(".webm") || lower.Contains(".avi") ||
613                lower.Contains(".mkv") || lower.Contains(".mov") || lower.Contains(".m3u8") ||
614                lower.Contains(".mpd") || lower.Contains("video") ||
615                lower.Contains("youtube.com/embed") || lower.Contains("player.vimeo"))
616                type = "video";
617            else if (lower.Contains(".mp3") || lower.Contains(".wav") || lower.Contains(".ogg") ||
618                     lower.Contains(".m4a") || lower.Contains(".flac") || lower.Contains(".aac") ||
619                     lower.Contains("audio"))
620                type = "audio";
621
622            var item = new MediaItem(url, type);
623            item.Description = source + (tag != null ? ": <" + tag + ">" : "");
624            item.TagName = tag;
625            results.Add(item);
626        }
627
628        private static string ResolveUrl(string url, Uri baseUri)
629        {
630            if (string.IsNullOrEmpty(url)) return null;
631            url = url.Trim();
632            if (url.StartsWith("data:") || url.StartsWith("#") || url.StartsWith("javascript:") || url.StartsWith("blob:"))
633                return null;
634
635            try
636            {
637                if (url.StartsWith("//")) return baseUri.Scheme + ":" + url;
638                if (url.StartsWith("/")) return baseUri.Scheme + "://" + baseUri.Host + url;
639                if (!url.StartsWith("http")) return new Uri(baseUri, url).ToString();
640                return url;
641            }
642            catch { return null; }
643        }
644
645        private static bool IsMediaUrl(string url)
646        {
647            if (string.IsNullOrEmpty(url)) return false;
648            string lower = url.ToLower();
649
650            // Skip non-media
651            if (lower.EndsWith(".js") || lower.EndsWith(".css") || lower.EndsWith(".html") || lower.EndsWith(".htm"))
652                return false;
653
654            string[] exts = { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp", ".svg", ".ico", ".avif", ".tiff",
655                              ".mp4", ".webm", ".avi", ".mkv", ".mov", ".flv", ".m4v", ".m3u8", ".mpd", ".ts",
656                              ".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac", ".opus" };
657            foreach (var ext in exts)
658            {
659                if (lower.Contains(ext + "?") || lower.EndsWith(ext))
660                    return true;
661            }
662
663            // URL path hints
664            if (lower.Contains("/image") || lower.Contains("/img/") || lower.Contains("/images/") ||
665                lower.Contains("/video") || lower.Contains("/videos/") || lower.Contains("/audio") ||
666                lower.Contains("/media/") || lower.Contains("/thumb") || lower.Contains("/photo") ||
667                lower.Contains("/picture") || lower.Contains("/uploads/") || lower.Contains("/content/"))
668                return true;
669
670            // CDN hints
671            if (lower.Contains("cdn.") || lower.Contains("static.") || lower.Contains("img.") ||
672                lower.Contains("images.") || lower.Contains("media.") || lower.Contains("i.imgur") ||
673                lower.Contains("pbs.twimg") || lower.Contains("scontent"))
674                return true;
675
676            return false;
677        }
678
679        public static bool DownloadMedia(MediaItem item, string savePath)
680        {
681            try
682            {
683                using (var client = new TimedWebClient())
684                {
685                    client.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
686                    client.Headers.Add("Accept", "*/*");
687                    client.Headers.Add("Referer", new Uri(item.Url).GetLeftPart(UriPartial.Authority) + "/");
688                    client.DownloadFile(item.Url, savePath);
689                    return File.Exists(savePath) && new FileInfo(savePath).Length > 0;
690                }
691            }
692            catch { return false; }
693        }
694
695        // P/Invoke for IAccessible
696        [DllImport("oleacc.dll")]
697        private static extern int AccessibleObjectFromPoint(Point pt,
698            [MarshalAs(UnmanagedType.Interface)] out object ppacc, out object pvarChild);
699
700        [DllImport("oleacc.dll")]
701        private static extern int AccessibleChildren(IAccessible paccContainer,
702            int iChildStart, int cChildren, [Out] object[] rgvarChildren, out int pcObtained);
703
704        // P/Invoke for window text
705        [DllImport("user32.dll", CharSet = CharSet.Auto)]
706        private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
707
708        [DllImport("user32.dll")]
709        private static extern int GetWindowTextLength(IntPtr hWnd);
710
711        private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
712
713        [DllImport("user32.dll")]
714        private static extern bool EnumChildWindows(IntPtr hwndParent, EnumWindowsProc lpEnumFunc, IntPtr lParam);
715    }
716
717    // IAccessible interface
718    [ComImport, Guid("618736E0-3C3D-11CF-810C-00AA00389B71"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
719    public interface IAccessible
720    {
721        [DispId(-5000)] object get_accParent();
722        [DispId(-5001)] int accChildCount { get; }
723        [DispId(-5002)] object get_accChild(object varChild);
724        [DispId(-5003)] string get_accName(object varChild);
725        [DispId(-5004)] string get_accValue(object varChild);
726        [DispId(-5005)] string get_accDescription(object varChild);
727        [DispId(-5006)] object get_accRole(object varChild);
728        [DispId(-5007)] object get_accState(object varChild);
729        [DispId(-5008)] string get_accHelp(object varChild);
730        [DispId(-5009)] int get_accHelpTopic(out string pszHelpFile, object varChild);
731        [DispId(-5010)] string get_accKeyboardShortcut(object varChild);
732        [DispId(-5011)] object get_accFocus();
733        [DispId(-5012)] object get_accSelection();
734        [DispId(-5013)] string get_accDefaultAction(object varChild);
735        [DispId(-5014)] void accSelect(int flagsSelect, object varChild);
736        [DispId(-5015)] void accLocation(out int pxLeft, out int pyTop, out int pcxWidth, out int pcyHeight, object varChild);
737        [DispId(-5016)] object accNavigate(int navDir, object varStart);
738        [DispId(-5017)] object accHitTest(int xLeft, int yTop);
739        [DispId(-5018)] void accDoDefaultAction(object varChild);
740        [DispId(-5003)] void set_accName(object varChild, string szName);
741        [DispId(-5004)] void set_accValue(object varChild, string szValue);
742    }
743
744    // UI Automation COM interfaces
745    [ComImport, Guid("30CBE57D-D9D0-452A-AB13-7AC5AC4825EE"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
746    public interface IUIAutomation
747    {
748        int CompareElements(IUIAutomationElement el1, IUIAutomationElement el2);
749        int CompareRuntimeIds(int[] runtimeId1, int[] runtimeId2);
750        IUIAutomationElement GetRootElement();
751        IUIAutomationElement ElementFromHandle(IntPtr hwnd);
752        IUIAutomationElement ElementFromPoint(tagPOINT pt);
753        IUIAutomationElement GetFocusedElement();
754        IUIAutomationTreeWalker CreateTreeWalker(IUIAutomationCondition pCondition);
755        IUIAutomationTreeWalker ControlViewWalker { get; }
756        IUIAutomationTreeWalker ContentViewWalker { get; }
757        IUIAutomationTreeWalker RawViewWalker { get; }
758        IUIAutomationCondition RawViewCondition { get; }
759        IUIAutomationCondition ControlViewCondition { get; }
760        IUIAutomationCondition ContentViewCondition { get; }
761        IUIAutomationCacheRequest CreateCacheRequest();
762        IUIAutomationCondition CreateTrueCondition();
763        IUIAutomationCondition CreateFalseCondition();
764        IUIAutomationCondition CreatePropertyCondition(int propertyId, object value);
765    }
766
767    [ComImport, Guid("D22108AA-8AC5-49A5-837B-37BBB3D7591E"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
768    public interface IUIAutomationElement
769    {
770        void SetFocus();
771        int[] GetRuntimeId();
772        IUIAutomationElement FindFirst(TreeScope scope, IUIAutomationCondition condition);
773        IUIAutomationElementArray FindAll(TreeScope scope, IUIAutomationCondition condition);
774        void FindFirstBuildCache();
775        void FindAllBuildCache();
776        void BuildUpdatedCache();
777        void GetCurrentPropertyValue(int propertyId, out object retVal);
778        void MethodPlaceholder1();
779        void MethodPlaceholder2();
780        void MethodPlaceholder3();
781        void MethodPlaceholder4();
782        void GetCurrentPattern(int patternId, out object patternObject);
783        void MethodPlaceholder5();
784        void MethodPlaceholder6();
785        void MethodPlaceholder7();
786        void MethodPlaceholder8();
787        int CurrentProcessId { get; }
788        int CurrentControlType { get; }
789        string CurrentLocalizedControlType { get; }
790        string CurrentName { get; }
791        string CurrentAcceleratorKey { get; }
792        string CurrentAccessKey { get; }
793        int CurrentHasKeyboardFocus { get; }
794        int CurrentIsKeyboardFocusable { get; }
795        int CurrentIsEnabled { get; }
796        string CurrentAutomationId { get; }
797        string CurrentClassName { get; }
798        string CurrentHelpText { get; }
799        int CurrentCulture { get; }
800        int CurrentIsControlElement { get; }
801        int CurrentIsContentElement { get; }
802        int CurrentIsPassword { get; }
803        IntPtr CurrentNativeWindowHandle { get; }
804        string CurrentItemType { get; }
805        int CurrentIsOffscreen { get; }
806        int CurrentOrientation { get; }
807        string CurrentFrameworkId { get; }
808        int CurrentIsRequiredForForm { get; }
809        string CurrentItemStatus { get; }
810    }
811
812    [ComImport, Guid("14314595-B4BC-4055-95F2-58F2E42C9855"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
813    public interface IUIAutomationElementArray
814    {
815        int Length { get; }
816        IUIAutomationElement GetElement(int index);
817    }
818
819    [ComImport, Guid("352FFBA8-0973-437C-A61F-F64CAFD81DF9"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
820    public interface IUIAutomationCondition { }
821
822    [ComImport, Guid("4042C624-389C-4AFC-A630-9DF854A541FC"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
823    public interface IUIAutomationTreeWalker
824    {
825        IUIAutomationElement GetParentElement(IUIAutomationElement element);
826        IUIAutomationElement GetFirstChildElement(IUIAutomationElement element);
827        IUIAutomationElement GetLastChildElement(IUIAutomationElement element);
828        IUIAutomationElement GetNextSiblingElement(IUIAutomationElement element);
829        IUIAutomationElement GetPreviousSiblingElement(IUIAutomationElement element);
830    }
831
832    [ComImport, Guid("B17D6187-0907-464B-A168-0EF17A1572B1"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
833    public interface IUIAutomationCacheRequest { }
834
835    [ComImport, Guid("A94CD8B1-0844-4CD6-9D2D-640537AB39E9"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
836    public interface IUIAutomationValuePattern
837    {
838        void SetValue(string val);
839        string CurrentValue { get; }
840        int CurrentIsReadOnly { get; }
841    }
842
843    [ComImport, Guid("828055AD-355B-4435-86D5-3B51C14A9B1B"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
844    public interface IUIAutomationLegacyIAccessiblePattern
845    {
846        void Select(int flagsSelect);
847        void DoDefaultAction();
848        void SetValue(string szValue);
849        int CurrentChildId { get; }
850        string CurrentName { get; }
851        string CurrentValue { get; }
852        string CurrentDescription { get; }
853        int CurrentRole { get; }
854        int CurrentState { get; }
855        string CurrentHelp { get; }
856        string CurrentKeyboardShortcut { get; }
857        IUIAutomationElementArray GetCurrentSelection();
858        string CurrentDefaultAction { get; }
859    }
860
861    [StructLayout(LayoutKind.Sequential)]
862    public struct tagPOINT
863    {
864        public int x;
865        public int y;
866    }
867
868    public enum TreeScope
869    {
870        TreeScope_Element = 1,
871        TreeScope_Children = 2,
872        TreeScope_Descendants = 4,
873        TreeScope_Parent = 8,
874        TreeScope_Ancestors = 16,
875        TreeScope_Subtree = 7
876    }
877
878    [ComImport, Guid("FF48DBA4-60EF-4201-AA87-54103EEF594E"), ClassInterface(ClassInterfaceType.None)]
879    public class CUIAutomation { }
880}