200 lines
8.2 KiB
C#
200 lines
8.2 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
|
|
namespace ytLive.Models;
|
|
|
|
public enum SocialService
|
|
{
|
|
YouTube, Twitch, X, Instagram, TikTok, Facebook, Discord, Kick,
|
|
Threads, Bluesky, GitHub, LinkedIn, Pinterest, Snapchat, Reddit,
|
|
WhatsApp, Telegram, Link, Website
|
|
}
|
|
|
|
public enum SocialBarPosition { Top, Bottom }
|
|
public enum SocialBarJustify { Left, Center, Right }
|
|
|
|
public sealed class SocialEntry : INotifyPropertyChanged
|
|
{
|
|
public SocialService Service { get; init; }
|
|
public string Handle { get; init; } = string.Empty;
|
|
public string ProfileUrl { get; init; } = string.Empty;
|
|
|
|
public string ServiceName => Service.ToString();
|
|
public string Initials => SocialServiceIcons.InitialsFor(Service);
|
|
public string AccentColor => SocialServiceIcons.ColorFor(Service);
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
private void Raise([CallerMemberName] string? name = null)
|
|
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
}
|
|
|
|
/// <summary>
|
|
/// The app-wide social bar config — a global resource (like the webcam) whose
|
|
/// per-scene presence is toggled by <see cref="Scene.HasSocialBar"/>. The creator
|
|
/// provides handles/URLs; the app validates each by looking up the social page
|
|
/// before adding it. Freemium: YT + 1 other. Premium: as many as fit one line.
|
|
/// </summary>
|
|
public sealed class SocialsConfig : INotifyPropertyChanged
|
|
{
|
|
public ObservableCollection<SocialEntry> Entries { get; } = new();
|
|
|
|
private SocialBarPosition _barPosition = SocialBarPosition.Bottom;
|
|
public SocialBarPosition BarPosition
|
|
{
|
|
get => _barPosition;
|
|
set => Set(ref _barPosition, value);
|
|
}
|
|
|
|
private SocialBarJustify _barJustify = SocialBarJustify.Center;
|
|
public SocialBarJustify BarJustify
|
|
{
|
|
get => _barJustify;
|
|
set => Set(ref _barJustify, value);
|
|
}
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
private bool Set<T>(ref T field, T value, [CallerMemberName] string? name = null)
|
|
{
|
|
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
|
|
field = value;
|
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public static class SocialServiceIcons
|
|
{
|
|
public static string InitialsFor(SocialService service) => service switch
|
|
{
|
|
SocialService.YouTube => "YT",
|
|
SocialService.Twitch => "TW",
|
|
SocialService.X => "X",
|
|
SocialService.Instagram => "IG",
|
|
SocialService.TikTok => "TT",
|
|
SocialService.Facebook => "FB",
|
|
SocialService.Discord => "DC",
|
|
SocialService.Kick => "K",
|
|
SocialService.Threads => "TH",
|
|
SocialService.Bluesky => "BS",
|
|
SocialService.GitHub => "GH",
|
|
SocialService.LinkedIn => "LI",
|
|
SocialService.Pinterest => "PN",
|
|
SocialService.Snapchat => "SC",
|
|
SocialService.Reddit => "RD",
|
|
SocialService.WhatsApp => "WA",
|
|
SocialService.Telegram => "TG",
|
|
SocialService.Link => "L",
|
|
_ => "★",
|
|
};
|
|
|
|
public static string ColorFor(SocialService service) => service switch
|
|
{
|
|
SocialService.YouTube => "#FF0000",
|
|
SocialService.Twitch => "#9146FF",
|
|
SocialService.X => "#000000",
|
|
SocialService.Instagram => "#E4405F",
|
|
SocialService.TikTok => "#010101",
|
|
SocialService.Facebook => "#1877F2",
|
|
SocialService.Discord => "#5865F2",
|
|
SocialService.Kick => "#53FC18",
|
|
SocialService.Threads => "#010101",
|
|
SocialService.Bluesky => "#0085FF",
|
|
SocialService.GitHub => "#181717",
|
|
SocialService.LinkedIn => "#0A66C2",
|
|
SocialService.Pinterest => "#BD081C",
|
|
SocialService.Snapchat => "#FFFC00",
|
|
SocialService.Reddit => "#FF4500",
|
|
SocialService.WhatsApp => "#25D366",
|
|
SocialService.Telegram => "#26A5E4",
|
|
_ => "#e94560",
|
|
};
|
|
|
|
public static string CanonicalUrlFor(SocialService service, string handle)
|
|
{
|
|
var h = handle.Trim().TrimStart('@');
|
|
return service switch
|
|
{
|
|
SocialService.YouTube => $"https://www.youtube.com/@{h}",
|
|
SocialService.Twitch => $"https://www.twitch.tv/{h}",
|
|
SocialService.X => $"https://x.com/{h}",
|
|
SocialService.Instagram => $"https://www.instagram.com/{h}",
|
|
SocialService.TikTok => $"https://www.tiktok.com/@{h}",
|
|
SocialService.Facebook => $"https://www.facebook.com/{h}",
|
|
SocialService.Discord => $"https://discord.gg/{h}",
|
|
SocialService.Kick => $"https://kick.com/{h}",
|
|
SocialService.Threads => $"https://www.threads.net/@{h}",
|
|
SocialService.Bluesky => $"https://bsky.app/profile/{h}",
|
|
SocialService.GitHub => $"https://github.com/{h}",
|
|
SocialService.LinkedIn => $"https://www.linkedin.com/in/{h}",
|
|
SocialService.Pinterest => $"https://www.pinterest.com/{h}",
|
|
SocialService.Snapchat => $"https://www.snapchat.com/add/{h}",
|
|
SocialService.Reddit => $"https://www.reddit.com/user/{h}",
|
|
SocialService.WhatsApp => $"https://wa.me/{h}",
|
|
SocialService.Telegram => $"https://t.me/{h}",
|
|
SocialService.Link or SocialService.Website => h.StartsWith("http") ? h : $"https://{h}",
|
|
_ => $"https://{h}",
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Auto-detects the social service from a URL or fediverse handle. Supports
|
|
/// full URLs (https://x.com/user), domain-only (x.com/user), and fediverse
|
|
/// handles (@user@instance.tube). Returns Link/Website for unknown domains
|
|
/// or bare handles — the caller can then ask which service it is.
|
|
/// </summary>
|
|
public static (SocialService service, string handle, string url) DetectService(string input)
|
|
{
|
|
var trimmed = input.Trim();
|
|
|
|
// Fediverse handle: @user@domain
|
|
if (trimmed.StartsWith("@") && trimmed.IndexOf('@', 1) > 0)
|
|
{
|
|
var parts = trimmed.TrimStart('@').Split('@', 2);
|
|
if (parts.Length == 2)
|
|
{
|
|
var (user, domain) = (parts[0], parts[1]);
|
|
return (SocialService.Link, user, $"https://{domain}/@{user}");
|
|
}
|
|
}
|
|
|
|
// Strip protocol for domain parsing
|
|
var url = trimmed;
|
|
if (!trimmed.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
url = $"https://{trimmed}";
|
|
|
|
try
|
|
{
|
|
var uri = new Uri(url);
|
|
var domain = uri.Host.ToLowerInvariant();
|
|
var path = uri.AbsolutePath.Trim('/');
|
|
|
|
return domain switch
|
|
{
|
|
"youtube.com" or "www.youtube.com" or "youtu.be" => (SocialService.YouTube, path, url),
|
|
"twitch.tv" or "www.twitch.tv" => (SocialService.Twitch, path, url),
|
|
"x.com" or "twitter.com" => (SocialService.X, path, url),
|
|
"instagram.com" or "www.instagram.com" => (SocialService.Instagram, path, url),
|
|
"tiktok.com" or "www.tiktok.com" => (SocialService.TikTok, path, url),
|
|
"facebook.com" or "www.facebook.com" or "fb.com" => (SocialService.Facebook, path, url),
|
|
"discord.gg" or "discord.com" => (SocialService.Discord, path, url),
|
|
"kick.com" => (SocialService.Kick, path, url),
|
|
"threads.net" or "www.threads.net" => (SocialService.Threads, path, url),
|
|
"bsky.app" => (SocialService.Bluesky, path, url),
|
|
"github.com" => (SocialService.GitHub, path, url),
|
|
"linkedin.com" or "www.linkedin.com" => (SocialService.LinkedIn, path, url),
|
|
"pinterest.com" or "www.pinterest.com" => (SocialService.Pinterest, path, url),
|
|
"snapchat.com" => (SocialService.Snapchat, path, url),
|
|
"reddit.com" or "www.reddit.com" => (SocialService.Reddit, path, url),
|
|
"wa.me" or "whatsapp.com" => (SocialService.WhatsApp, path, url),
|
|
"t.me" => (SocialService.Telegram, path, url),
|
|
_ => (SocialService.Website, path, url),
|
|
};
|
|
}
|
|
catch
|
|
{
|
|
return (SocialService.Link, trimmed.TrimStart('@'), SocialServiceIcons.CanonicalUrlFor(SocialService.Link, trimmed));
|
|
}
|
|
}
|
|
}
|