Store server icons

This commit is contained in:
chylex 2025-03-29 14:48:26 +01:00
parent 44b42657ef
commit 312be6609d
No known key found for this signature in database
13 changed files with 81 additions and 26 deletions

View File

@ -142,11 +142,19 @@ const STATE = (function() {
if (DISCORD.CHANNEL_TYPE.isPrivate(channelInfo.type)) {
server.id = channelInfo.id;
server.name = channel.name = getPrivateChannelName(channelInfo);
if (channelInfo.icon) {
server.icon = channelInfo.icon;
}
}
else if (serverInfo) {
server.id = serverInfo.id;
server.name = serverInfo.name;
channel.name = channelInfo.name;
if (serverInfo.icon) {
server.icon = serverInfo.icon;
}
}
else {
return;

View File

@ -2,6 +2,7 @@
* @name DiscordGuild
* @property {String} id
* @property {String} name
* @property {String|null|undefined} [icon]
*/
/**
@ -14,6 +15,7 @@
* @property {Number} [position]
* @property {String} [topic]
* @property {Boolean} [nsfw]
* @property {String|null|undefined} [icon]
* @property {DiscordUser[]} [rawRecipients]
*/

View File

@ -1,7 +1,12 @@
using DHT.Server.Download;
namespace DHT.Server.Data;
public readonly struct Server {
public ulong Id { get; init; }
public string Name { get; init; }
public ServerType? Type { get; init; }
public string? IconHash { get; init; }
internal FileUrl? IconUrl => Type == null || IconHash == null ? null : DownloadLinkExtractor.ServerIcon(Type.Value, Id, IconHash);
}

View File

@ -23,6 +23,9 @@ static class ViewerJson {
public sealed class JsonServer {
public required string Name { get; init; }
public required string Type { get; init; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? IconUrl { get; init; }
}
public sealed class JsonChannel {

View File

@ -108,6 +108,7 @@ static class ViewerJsonExport {
servers[server.Id] = new ViewerJson.JsonServer {
Name = server.Name,
Type = ServerTypes.ToJsonViewerString(server.Type),
IconUrl = server.IconUrl?.DownloadUrl,
};
}

View File

@ -394,16 +394,6 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
}
}
await using (var cmd = conn.Command("SELECT id, avatar_url FROM users WHERE avatar_url IS NOT NULL")) {
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken)) {
ulong id = reader.GetUint64(0);
string avatarHash = reader.GetString(1);
yield return DownloadLinkExtractor.UserAvatar(id, avatarHash);
}
}
await using (var cmd = conn.Command("SELECT DISTINCT emoji_id, emoji_flags FROM message_reactions WHERE emoji_id IS NOT NULL")) {
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
@ -413,5 +403,29 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
yield return DownloadLinkExtractor.Emoji(emojiId, emojiFlags);
}
}
await using (var cmd = conn.Command("SELECT id, type, icon_hash FROM servers WHERE icon_hash IS NOT NULL")) {
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken)) {
ulong id = reader.GetUint64(0);
ServerType? type = ServerTypes.FromString(reader.GetString(1));
string iconHash = reader.GetString(2);
if (DownloadLinkExtractor.ServerIcon(type, id, iconHash) is {} result) {
yield return result;
}
}
}
await using (var cmd = conn.Command("SELECT id, avatar_url FROM users WHERE avatar_url IS NOT NULL")) {
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken)) {
ulong id = reader.GetUint64(0);
string avatarHash = reader.GetString(1);
yield return DownloadLinkExtractor.UserAvatar(id, avatarHash);
}
}
}
}

View File

@ -10,15 +10,9 @@ using Microsoft.Data.Sqlite;
namespace DHT.Server.Database.Sqlite.Repositories;
sealed class SqliteServerRepository : BaseSqliteRepository, IServerRepository {
sealed class SqliteServerRepository(SqliteConnectionPool pool, SqliteDownloadRepository downloads) : BaseSqliteRepository(Log), IServerRepository {
private static readonly Log Log = Log.ForType<SqliteServerRepository>();
private readonly SqliteConnectionPool pool;
public SqliteServerRepository(SqliteConnectionPool pool) : base(Log) {
this.pool = pool;
}
public async Task Add(IReadOnlyList<Data.Server> servers) {
await using (var conn = await pool.Take()) {
await conn.BeginTransactionAsync();
@ -27,13 +21,18 @@ sealed class SqliteServerRepository : BaseSqliteRepository, IServerRepository {
("id", SqliteType.Integer),
("name", SqliteType.Text),
("type", SqliteType.Text),
("icon_hash", SqliteType.Text),
]);
await using var downloadCollector = new SqliteDownloadRepository.NewDownloadCollector(downloads, conn);
foreach (Data.Server server in servers) {
cmd.Set(":id", server.Id);
cmd.Set(":name", server.Name);
cmd.Set(":type", ServerTypes.ToString(server.Type));
cmd.Set(":icon_hash", server.IconHash);
await cmd.ExecuteNonQueryAsync();
await downloadCollector.AddIfNotNull(server.IconUrl?.ToPendingDownload());
}
await conn.CommitTransactionAsync();
@ -50,7 +49,7 @@ sealed class SqliteServerRepository : BaseSqliteRepository, IServerRepository {
public async IAsyncEnumerable<Data.Server> Get([EnumeratorCancellation] CancellationToken cancellationToken) {
await using var conn = await pool.Take();
await using var cmd = conn.Command("SELECT id, name, type FROM servers");
await using var cmd = conn.Command("SELECT id, name, type, icon_hash FROM servers");
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken)) {
@ -58,6 +57,7 @@ sealed class SqliteServerRepository : BaseSqliteRepository, IServerRepository {
Id = reader.GetUint64(0),
Name = reader.GetString(1),
Type = ServerTypes.FromString(reader.GetString(2)),
IconHash = reader.IsDBNull(3) ? null : reader.GetString(3),
};
}
}

View File

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using DHT.Server.Database.Sqlite.Utils;
namespace DHT.Server.Database.Sqlite.Schema;
sealed class SqliteSchemaUpgradeTo11 : ISchemaUpgrade {
async Task ISchemaUpgrade.Run(ISqliteConnection conn, ISchemaUpgradeCallbacks.IProgressReporter reporter) {
await reporter.MainWork("Applying schema changes...", finishedItems: 0, totalItems: 1);
await conn.ExecuteAsync("ALTER TABLE servers ADD icon_hash TEXT");
}
}

View File

@ -66,16 +66,16 @@ public sealed class SqliteDatabaseFile : IDatabaseFile {
downloads = new SqliteDownloadRepository(pool);
settings = new SqliteSettingsRepository(pool);
users = new SqliteUserRepository(pool, downloads);
servers = new SqliteServerRepository(pool);
servers = new SqliteServerRepository(pool, downloads);
channels = new SqliteChannelRepository(pool);
messages = new SqliteMessageRepository(pool, downloads);
}
public async ValueTask DisposeAsync() {
users.Dispose();
servers.Dispose();
channels.Dispose();
messages.Dispose();
channels.Dispose();
servers.Dispose();
users.Dispose();
downloads.Dispose();
await pool.DisposeAsync();
}

View File

@ -13,7 +13,7 @@ using Microsoft.Data.Sqlite;
namespace DHT.Server.Database.Sqlite;
sealed class SqliteSchema(CustomSqliteConnection conn) {
internal const int Version = 10;
internal const int Version = 11;
private static readonly Log Log = Log.ForType<SqliteSchema>();
@ -92,9 +92,10 @@ sealed class SqliteSchema(CustomSqliteConnection conn) {
await conn.ExecuteAsync("""
CREATE TABLE servers (
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
type TEXT NOT NULL,
icon_hash TEXT
)
""");
@ -222,6 +223,7 @@ sealed class SqliteSchema(CustomSqliteConnection conn) {
{ 7, new SqliteSchemaUpgradeTo8() },
{ 8, new SqliteSchemaUpgradeTo9() },
{ 9, new SqliteSchemaUpgradeTo10() },
{ 10, new SqliteSchemaUpgradeTo11() },
};
Perf perf = Log.Start("from version " + dbVersion);

View File

@ -12,6 +12,14 @@ namespace DHT.Server.Download;
static class DownloadLinkExtractor {
private static readonly Log Log = Log.ForType(typeof(DownloadLinkExtractor));
public static FileUrl? ServerIcon(ServerType? type, ulong id, string iconHash) {
return type switch {
ServerType.Server => new FileUrl($"https://cdn.discordapp.com/icons/{id}/{iconHash}.webp", MediaTypeNames.Image.Webp),
ServerType.Group => new FileUrl($"https://cdn.discordapp.com/channel-icons/{id}/{iconHash}.webp", MediaTypeNames.Image.Webp),
_ => null,
};
}
public static FileUrl UserAvatar(ulong id, string avatarHash) {
return new FileUrl($"https://cdn.discordapp.com/avatars/{id}/{avatarHash}.webp", MediaTypeNames.Image.Webp);
}

View File

@ -24,6 +24,7 @@ sealed class TrackChannelEndpoint(IDatabaseFile db) : BaseEndpoint {
Id = json.RequireSnowflake("id", path),
Name = json.RequireString("name", path),
Type = ServerTypes.FromString(json.RequireString("type", path)) ?? throw new HttpException(HttpStatusCode.BadRequest, "Server type must be either 'SERVER', 'GROUP', or 'DM'."),
IconHash = json.HasKey("icon") ? json.RequireString("icon", path) : null,
};
}

Binary file not shown.