mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-09-15 10:32:09 +02:00
Compare commits
8 Commits
v42.0
...
1700f99bf7
Author | SHA1 | Date | |
---|---|---|---|
1700f99bf7
|
|||
84acf5f5d5
|
|||
ae56433836
|
|||
c3d4fa5532
|
|||
a6225b9721
|
|||
943163473a
|
|||
fa00df10d8
|
|||
f54465e5fe
|
@@ -249,11 +249,8 @@ sealed partial class MessageFilterPanelModel : ObservableObject, IDisposable {
|
|||||||
var checkBoxItems = new List<CheckBoxItem<ulong>>();
|
var checkBoxItems = new List<CheckBoxItem<ulong>>();
|
||||||
|
|
||||||
await foreach (var user in state.Db.Users.Get()) {
|
await foreach (var user in state.Db.Users.Get()) {
|
||||||
var name = user.Name;
|
|
||||||
var discriminator = user.Discriminator;
|
|
||||||
|
|
||||||
checkBoxItems.Add(new CheckBoxItem<ulong>(user.Id) {
|
checkBoxItems.Add(new CheckBoxItem<ulong>(user.Id) {
|
||||||
Title = discriminator == null ? name : name + " #" + discriminator,
|
Title = user.DisplayName == null ? user.Name : $"{user.DisplayName} ({user.Name})",
|
||||||
IsChecked = IncludedUsers == null || IncludedUsers.Contains(user.Id)
|
IsChecked = IncludedUsers == null || IncludedUsers.Contains(user.Id)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -73,6 +73,7 @@ sealed class DebugPageModel {
|
|||||||
var users = Enumerable.Range(0, userCount).Select(_ => new User {
|
var users = Enumerable.Range(0, userCount).Select(_ => new User {
|
||||||
Id = RandomId(rand),
|
Id = RandomId(rand),
|
||||||
Name = RandomName("u"),
|
Name = RandomName("u"),
|
||||||
|
DisplayName = RandomName("u"),
|
||||||
AvatarUrl = null,
|
AvatarUrl = null,
|
||||||
Discriminator = rand.Next(0, 9999).ToString(),
|
Discriminator = rand.Next(0, 9999).ToString(),
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
@@ -9,6 +9,8 @@ items:
|
|||||||
pattern: "^[0-9]+$"
|
pattern: "^[0-9]+$"
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
|
displayName:
|
||||||
|
type: string
|
||||||
avatar:
|
avatar:
|
||||||
type: string
|
type: string
|
||||||
discriminator:
|
discriminator:
|
||||||
|
@@ -69,6 +69,7 @@ const STATE = (function() {
|
|||||||
* @property {String} id
|
* @property {String} id
|
||||||
* @property {String} username
|
* @property {String} username
|
||||||
* @property {String} discriminator
|
* @property {String} discriminator
|
||||||
|
* @property {String} [globalName]
|
||||||
* @property {String} [avatar]
|
* @property {String} [avatar]
|
||||||
* @property {Boolean} [bot]
|
* @property {Boolean} [bot]
|
||||||
*/
|
*/
|
||||||
@@ -200,6 +201,10 @@ const STATE = (function() {
|
|||||||
name: user.username
|
name: user.username
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (user.globalName) {
|
||||||
|
obj.displayName = user.globalName;
|
||||||
|
}
|
||||||
|
|
||||||
if (user.avatar) {
|
if (user.avatar) {
|
||||||
obj.avatar = user.avatar;
|
obj.avatar = user.avatar;
|
||||||
}
|
}
|
||||||
|
@@ -25,6 +25,8 @@
|
|||||||
<div id="menu">
|
<div id="menu">
|
||||||
<button id="btn-settings">Settings</button>
|
<button id="btn-settings">Settings</button>
|
||||||
|
|
||||||
|
<div class="splitter"></div>
|
||||||
|
|
||||||
<div> <!-- needed to stop the select from messing up -->
|
<div> <!-- needed to stop the select from messing up -->
|
||||||
<select id="opt-messages-per-page">
|
<select id="opt-messages-per-page">
|
||||||
<option value="50">50 messages per page </option>
|
<option value="50">50 messages per page </option>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import discord from "./discord.mjs";
|
import discord from "./discord.mjs";
|
||||||
import gui from "./gui.mjs";
|
import gui from "./gui.mjs";
|
||||||
import state from "./state.mjs";
|
import state from "./state.mjs";
|
||||||
|
import "./polyfills.mjs";
|
||||||
|
|
||||||
window.DISCORD = discord;
|
window.DISCORD = discord;
|
||||||
|
|
||||||
|
@@ -80,7 +80,7 @@ export default (function() {
|
|||||||
processed = processed
|
processed = processed
|
||||||
.replace(regex.formatUrl, "<a href='$1' target='_blank' rel='noreferrer'>$1</a>")
|
.replace(regex.formatUrl, "<a href='$1' target='_blank' rel='noreferrer'>$1</a>")
|
||||||
.replace(regex.mentionChannel, (full, match) => "<span class='link mention-chat'>#" + state.getChannelName(match) + "</span>")
|
.replace(regex.mentionChannel, (full, match) => "<span class='link mention-chat'>#" + state.getChannelName(match) + "</span>")
|
||||||
.replace(regex.mentionUser, (full, match) => "<span class='link mention-user' title='#" + (state.getUserTag(match) || "????") + "'>@" + state.getUserName(match) + "</span>")
|
.replace(regex.mentionUser, (full, match) => "<span class='link mention-user' title='" + state.getUserName(match) + "'>@" + state.getUserDisplayName(match) + "</span>")
|
||||||
.replace(regex.customEmojiStatic, (full, m1, m2) => getEmoji(m1, m2, "webp"))
|
.replace(regex.customEmojiStatic, (full, m1, m2) => getEmoji(m1, m2, "webp"))
|
||||||
.replace(regex.customEmojiAnimated, (full, m1, m2) => getEmoji(m1, m2, animatedEmojiExtension));
|
.replace(regex.customEmojiAnimated, (full, m1, m2) => getEmoji(m1, m2, animatedEmojiExtension));
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ export default (function() {
|
|||||||
templateMessageNoAvatar = new template([
|
templateMessageNoAvatar = new template([
|
||||||
"<div>",
|
"<div>",
|
||||||
"<div class='reply-message'>{reply}</div>",
|
"<div class='reply-message'>{reply}</div>",
|
||||||
"<h2><strong class='username' title='#{user.tag}'>{user.name}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>",
|
"<h2><strong class='username' title='{user.name}'>{user.displayName}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>",
|
||||||
"<div class='message'>{contents}{embeds}{attachments}</div>",
|
"<div class='message'>{contents}{embeds}{attachments}</div>",
|
||||||
"{reactions}",
|
"{reactions}",
|
||||||
"</div>"
|
"</div>"
|
||||||
@@ -141,7 +141,7 @@ export default (function() {
|
|||||||
"<div class='avatar-wrapper'>",
|
"<div class='avatar-wrapper'>",
|
||||||
"<div class='avatar'>{avatar}</div>",
|
"<div class='avatar'>{avatar}</div>",
|
||||||
"<div>",
|
"<div>",
|
||||||
"<h2><strong class='username' title='#{user.tag}'>{user.name}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>",
|
"<h2><strong class='username' title='{user.name}'>{user.displayName}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>",
|
||||||
"<div class='message'>{contents}{embeds}{attachments}</div>",
|
"<div class='message'>{contents}{embeds}{attachments}</div>",
|
||||||
"{reactions}",
|
"{reactions}",
|
||||||
"</div>",
|
"</div>",
|
||||||
@@ -227,8 +227,8 @@ export default (function() {
|
|||||||
if (property === "avatar") {
|
if (property === "avatar") {
|
||||||
return value ? templateUserAvatar.apply(getAvatarUrlObject(value)) : "";
|
return value ? templateUserAvatar.apply(getAvatarUrlObject(value)) : "";
|
||||||
}
|
}
|
||||||
else if (property === "user.tag") {
|
else if (property === "user.displayName") {
|
||||||
return value ? value : "????";
|
return value ? value : message.user.name;
|
||||||
}
|
}
|
||||||
else if (property === "timestamp") {
|
else if (property === "timestamp") {
|
||||||
return dom.getHumanReadableTime(value);
|
return dom.getHumanReadableTime(value);
|
||||||
@@ -292,7 +292,7 @@ export default (function() {
|
|||||||
return value === null ? "<span class='reply-contents reply-missing'>(replies to an unknown message)</span>" : "";
|
return value === null ? "<span class='reply-contents reply-missing'>(replies to an unknown message)</span>" : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = "<span class='reply-username' title='#" + (value.user.tag ? value.user.tag : "????") + "'>" + value.user.name + "</span>";
|
const user = "<span class='reply-username' title='" + value.user.name + "'>" + (value.user.displayName ?? value.user.name) + "</span>";
|
||||||
const avatar = settings.enableUserAvatars && value.avatar ? "<span class='reply-avatar'>" + templateUserAvatar.apply(getAvatarUrlObject(value.avatar)) + "</span>" : "";
|
const avatar = settings.enableUserAvatars && value.avatar ? "<span class='reply-avatar'>" + templateUserAvatar.apply(getAvatarUrlObject(value.avatar)) + "</span>" : "";
|
||||||
const contents = value.contents ? "<span class='reply-contents'>" + processMessageContents(value.contents) + "</span>" : "";
|
const contents = value.contents ? "<span class='reply-contents'>" + processMessageContents(value.contents) + "</span>" : "";
|
||||||
|
|
||||||
|
@@ -243,10 +243,11 @@ export default (function() {
|
|||||||
|
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
for (const key of Object.keys(users)) {
|
for (const id of Object.keys(users)) {
|
||||||
|
const user = users[id];
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.value = key;
|
option.value = id;
|
||||||
option.text = users[key].name;
|
option.text = user.displayName ? `${user.displayName} (${user.name})` : user.name;
|
||||||
options.push(option);
|
options.push(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
app/Resources/Viewer/scripts/polyfills.mjs
Normal file
35
app/Resources/Viewer/scripts/polyfills.mjs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
// https://gist.github.com/MattiasBuelens/496fc1d37adb50a733edd43853f2f60e/088f061ab79b296f29225467ae9ba86ff990195d
|
||||||
|
|
||||||
|
ReadableStream.prototype.values ??= function({ preventCancel = false } = {}) {
|
||||||
|
const reader = this.getReader();
|
||||||
|
return {
|
||||||
|
async next() {
|
||||||
|
try {
|
||||||
|
const result = await reader.read();
|
||||||
|
if (result.done) {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
reader.releaseLock();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async return(value) {
|
||||||
|
if (!preventCancel) {
|
||||||
|
const cancelPromise = reader.cancel(value);
|
||||||
|
reader.releaseLock();
|
||||||
|
await cancelPromise;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reader.releaseLock();
|
||||||
|
}
|
||||||
|
return { done: true, value };
|
||||||
|
},
|
||||||
|
[Symbol.asyncIterator]() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
ReadableStream.prototype[Symbol.asyncIterator] ??= ReadableStream.prototype.values;
|
@@ -316,16 +316,16 @@ export default (function() {
|
|||||||
return (channelObj && channelObj.name) || channel;
|
return (channelObj && channelObj.name) || channel;
|
||||||
},
|
},
|
||||||
|
|
||||||
getUserTag(user) {
|
|
||||||
const userObj = loadedFileMeta.users[user];
|
|
||||||
return (userObj && userObj.tag) || "????";
|
|
||||||
},
|
|
||||||
|
|
||||||
getUserName(user) {
|
getUserName(user) {
|
||||||
const userObj = loadedFileMeta.users[user];
|
const userObj = loadedFileMeta.users[user];
|
||||||
return (userObj && userObj.name) || user;
|
return (userObj && userObj.name) || user;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUserDisplayName(user) {
|
||||||
|
const userObj = loadedFileMeta.users[user];
|
||||||
|
return (userObj && (userObj.displayName || userObj.name)) || user;
|
||||||
|
},
|
||||||
|
|
||||||
selectChannel(channel) {
|
selectChannel(channel) {
|
||||||
currentPage = 1;
|
currentPage = 1;
|
||||||
selectedChannel = channel;
|
selectedChannel = channel;
|
||||||
|
@@ -1,15 +1,16 @@
|
|||||||
#menu {
|
#menu {
|
||||||
width: 100%;
|
|
||||||
height: 48px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
background-color: #17181c;
|
background-color: #17181c;
|
||||||
border-bottom: 1px dotted #5d626b;
|
border-bottom: 1px dotted #5d626b;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .splitter {
|
#menu .splitter {
|
||||||
width: 1px;
|
flex: 0 0 1px;
|
||||||
margin: 9px 4px;
|
margin: 9px 1px;
|
||||||
background-color: #5d626b;
|
background-color: #5d626b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#menu button, #menu select, #menu input[type="text"] {
|
#menu button, #menu select, #menu input[type="text"] {
|
||||||
margin: 8px;
|
height: 31px;
|
||||||
|
padding: 0 10px;
|
||||||
background-color: #7289da;
|
background-color: #7289da;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75);
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75);
|
||||||
@@ -31,28 +33,25 @@
|
|||||||
|
|
||||||
#menu button {
|
#menu button {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
padding: 0 12px;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu select {
|
#menu select {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 6px;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu input[type="text"] {
|
#menu input[type="text"] {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 7px 12px;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav {
|
#menu .nav {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin: 0 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav > button {
|
#menu .nav > button {
|
||||||
@@ -66,7 +65,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#menu .nav > button, #menu .nav > p {
|
#menu .nav > button, #menu .nav > p {
|
||||||
margin: 8px 1px;
|
margin: 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#opt-filter-list > select, #opt-filter-list > input {
|
#opt-filter-list > select, #opt-filter-list > input {
|
||||||
@@ -76,3 +75,7 @@
|
|||||||
#opt-filter-list > .active {
|
#opt-filter-list > .active {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#btn-about {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
@@ -3,6 +3,7 @@ namespace DHT.Server.Data;
|
|||||||
public readonly struct User {
|
public readonly struct User {
|
||||||
public ulong Id { get; init; }
|
public ulong Id { get; init; }
|
||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
|
public string? DisplayName { get; init; }
|
||||||
public string? AvatarUrl { get; init; }
|
public string? AvatarUrl { get; init; }
|
||||||
public string? Discriminator { get; init; }
|
public string? Discriminator { get; init; }
|
||||||
}
|
}
|
||||||
|
@@ -14,10 +14,10 @@ static class ViewerJson {
|
|||||||
public required string Name { get; init; }
|
public required string Name { get; init; }
|
||||||
|
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public string? Avatar { get; init; }
|
public string? DisplayName { get; init; }
|
||||||
|
|
||||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||||
public string? Tag { get; init; }
|
public string? Avatar { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class JsonServer {
|
public sealed class JsonServer {
|
||||||
|
@@ -68,8 +68,8 @@ static class ViewerJsonExport {
|
|||||||
await foreach (var user in db.Users.Get(cancellationToken)) {
|
await foreach (var user in db.Users.Get(cancellationToken)) {
|
||||||
users[user.Id] = new ViewerJson.JsonUser {
|
users[user.Id] = new ViewerJson.JsonUser {
|
||||||
Name = user.Name,
|
Name = user.Name,
|
||||||
|
DisplayName = user.DisplayName,
|
||||||
Avatar = user.AvatarUrl,
|
Avatar = user.AvatarUrl,
|
||||||
Tag = user.Discriminator
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -20,7 +20,7 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
|
|||||||
|
|
||||||
internal sealed class NewDownloadCollector : IAsyncDisposable {
|
internal sealed class NewDownloadCollector : IAsyncDisposable {
|
||||||
private readonly SqliteDownloadRepository repository;
|
private readonly SqliteDownloadRepository repository;
|
||||||
private bool hasAdded = false;
|
private bool hasChanged = false;
|
||||||
|
|
||||||
private readonly SqliteCommand metadataCmd;
|
private readonly SqliteCommand metadataCmd;
|
||||||
|
|
||||||
@@ -31,7 +31,16 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
|
|||||||
"""
|
"""
|
||||||
INSERT INTO download_metadata (normalized_url, download_url, status, type, size)
|
INSERT INTO download_metadata (normalized_url, download_url, status, type, size)
|
||||||
VALUES (:normalized_url, :download_url, :status, :type, :size)
|
VALUES (:normalized_url, :download_url, :status, :type, :size)
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT (normalized_url)
|
||||||
|
DO UPDATE SET
|
||||||
|
download_url = excluded.download_url,
|
||||||
|
type = IFNULL(excluded.type, type),
|
||||||
|
size = IFNULL(excluded.size, size)
|
||||||
|
WHERE status != :success
|
||||||
|
AND (download_url != excluded.download_url
|
||||||
|
OR (excluded.type IS NOT NULL AND type IS NOT excluded.type)
|
||||||
|
OR (excluded.size IS NOT NULL AND size IS NOT excluded.size)
|
||||||
|
)
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
metadataCmd.Add(":normalized_url", SqliteType.Text);
|
metadataCmd.Add(":normalized_url", SqliteType.Text);
|
||||||
@@ -39,6 +48,7 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
|
|||||||
metadataCmd.Add(":status", SqliteType.Integer);
|
metadataCmd.Add(":status", SqliteType.Integer);
|
||||||
metadataCmd.Add(":type", SqliteType.Text);
|
metadataCmd.Add(":type", SqliteType.Text);
|
||||||
metadataCmd.Add(":size", SqliteType.Integer);
|
metadataCmd.Add(":size", SqliteType.Integer);
|
||||||
|
metadataCmd.AddAndSet(":success", SqliteType.Integer, (int) DownloadStatus.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Add(Data.Download download) {
|
public async Task Add(Data.Download download) {
|
||||||
@@ -47,11 +57,11 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
|
|||||||
metadataCmd.Set(":status", (int) download.Status);
|
metadataCmd.Set(":status", (int) download.Status);
|
||||||
metadataCmd.Set(":type", download.Type);
|
metadataCmd.Set(":type", download.Type);
|
||||||
metadataCmd.Set(":size", download.Size);
|
metadataCmd.Set(":size", download.Size);
|
||||||
hasAdded |= await metadataCmd.ExecuteNonQueryAsync() > 0;
|
hasChanged |= await metadataCmd.ExecuteNonQueryAsync() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnCommitted() {
|
public void OnCommitted() {
|
||||||
if (hasAdded) {
|
if (hasChanged) {
|
||||||
repository.UpdateTotalCount();
|
repository.UpdateTotalCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +100,8 @@ sealed class SqliteDownloadRepository(SqliteConnectionPool pool) : BaseSqliteRep
|
|||||||
"""
|
"""
|
||||||
INSERT INTO download_blobs (normalized_url, blob)
|
INSERT INTO download_blobs (normalized_url, blob)
|
||||||
VALUES (:normalized_url, ZEROBLOB(:blob_length))
|
VALUES (:normalized_url, ZEROBLOB(:blob_length))
|
||||||
ON CONFLICT (normalized_url) DO UPDATE SET blob = excluded.blob
|
ON CONFLICT (normalized_url)
|
||||||
|
DO UPDATE SET blob = excluded.blob
|
||||||
RETURNING rowid
|
RETURNING rowid
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
|
@@ -14,17 +14,9 @@ using Microsoft.Data.Sqlite;
|
|||||||
|
|
||||||
namespace DHT.Server.Database.Sqlite.Repositories;
|
namespace DHT.Server.Database.Sqlite.Repositories;
|
||||||
|
|
||||||
sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository {
|
sealed class SqliteMessageRepository(SqliteConnectionPool pool, SqliteDownloadRepository downloads) : BaseSqliteRepository(Log), IMessageRepository {
|
||||||
private static readonly Log Log = Log.ForType<SqliteMessageRepository>();
|
private static readonly Log Log = Log.ForType<SqliteMessageRepository>();
|
||||||
|
|
||||||
private readonly SqliteConnectionPool pool;
|
|
||||||
private readonly SqliteDownloadRepository downloads;
|
|
||||||
|
|
||||||
public SqliteMessageRepository(SqliteConnectionPool pool, SqliteDownloadRepository downloads) : base(Log) {
|
|
||||||
this.pool = pool;
|
|
||||||
this.downloads = downloads;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Add(IReadOnlyList<Message> messages) {
|
public async Task Add(IReadOnlyList<Message> messages) {
|
||||||
if (messages.Count == 0) {
|
if (messages.Count == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -50,25 +42,7 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
("timestamp", SqliteType.Integer)
|
("timestamp", SqliteType.Integer)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await using var deleteEditTimestampCmd = DeleteByMessageId(conn, "edit_timestamps");
|
await using var attachmentCmd = conn.Upsert("attachments", [
|
||||||
await using var deleteRepliedToCmd = DeleteByMessageId(conn, "replied_to");
|
|
||||||
|
|
||||||
await using var deleteAttachmentsCmd = DeleteByMessageId(conn, "attachments");
|
|
||||||
await using var deleteEmbedsCmd = DeleteByMessageId(conn, "embeds");
|
|
||||||
await using var deleteReactionsCmd = DeleteByMessageId(conn, "reactions");
|
|
||||||
|
|
||||||
await using var editTimestampCmd = conn.Insert("edit_timestamps", [
|
|
||||||
("message_id", SqliteType.Integer),
|
|
||||||
("edit_timestamp", SqliteType.Integer)
|
|
||||||
]);
|
|
||||||
|
|
||||||
await using var repliedToCmd = conn.Insert("replied_to", [
|
|
||||||
("message_id", SqliteType.Integer),
|
|
||||||
("replied_to_id", SqliteType.Integer)
|
|
||||||
]);
|
|
||||||
|
|
||||||
await using var attachmentCmd = conn.Insert("attachments", [
|
|
||||||
("message_id", SqliteType.Integer),
|
|
||||||
("attachment_id", SqliteType.Integer),
|
("attachment_id", SqliteType.Integer),
|
||||||
("name", SqliteType.Text),
|
("name", SqliteType.Text),
|
||||||
("type", SqliteType.Text),
|
("type", SqliteType.Text),
|
||||||
@@ -79,12 +53,34 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
("height", SqliteType.Integer)
|
("height", SqliteType.Integer)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await using var embedCmd = conn.Insert("embeds", [
|
await using var deleteMessageEditTimestampCmd = DeleteByMessageId(conn, "message_edit_timestamps");
|
||||||
|
await using var deleteMessageRepliedToCmd = DeleteByMessageId(conn, "message_replied_to");
|
||||||
|
|
||||||
|
await using var deleteMessageAttachmentsCmd = DeleteByMessageId(conn, "message_attachments");
|
||||||
|
await using var deleteMessageEmbedsCmd = DeleteByMessageId(conn, "message_embeds");
|
||||||
|
await using var deleteMessageReactionsCmd = DeleteByMessageId(conn, "message_reactions");
|
||||||
|
|
||||||
|
await using var messageEditTimestampCmd = conn.Insert("message_edit_timestamps", [
|
||||||
|
("message_id", SqliteType.Integer),
|
||||||
|
("edit_timestamp", SqliteType.Integer)
|
||||||
|
]);
|
||||||
|
|
||||||
|
await using var messageRepliedToCmd = conn.Insert("message_replied_to", [
|
||||||
|
("message_id", SqliteType.Integer),
|
||||||
|
("replied_to_id", SqliteType.Integer)
|
||||||
|
]);
|
||||||
|
|
||||||
|
await using var messageAttachmentCmd = conn.Insert("message_attachments", [
|
||||||
|
("message_id", SqliteType.Integer),
|
||||||
|
("attachment_id", SqliteType.Integer)
|
||||||
|
]);
|
||||||
|
|
||||||
|
await using var messageEmbedCmd = conn.Insert("message_embeds", [
|
||||||
("message_id", SqliteType.Integer),
|
("message_id", SqliteType.Integer),
|
||||||
("json", SqliteType.Text)
|
("json", SqliteType.Text)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await using var reactionCmd = conn.Insert("reactions", [
|
await using var messageReactionCmd = conn.Insert("message_reactions", [
|
||||||
("message_id", SqliteType.Integer),
|
("message_id", SqliteType.Integer),
|
||||||
("emoji_id", SqliteType.Integer),
|
("emoji_id", SqliteType.Integer),
|
||||||
("emoji_name", SqliteType.Text),
|
("emoji_name", SqliteType.Text),
|
||||||
@@ -104,29 +100,30 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
messageCmd.Set(":timestamp", message.Timestamp);
|
messageCmd.Set(":timestamp", message.Timestamp);
|
||||||
await messageCmd.ExecuteNonQueryAsync();
|
await messageCmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
await ExecuteDeleteByMessageId(deleteEditTimestampCmd, messageId);
|
await ExecuteDeleteByMessageId(deleteMessageEditTimestampCmd, messageId);
|
||||||
await ExecuteDeleteByMessageId(deleteRepliedToCmd, messageId);
|
await ExecuteDeleteByMessageId(deleteMessageRepliedToCmd, messageId);
|
||||||
|
|
||||||
await ExecuteDeleteByMessageId(deleteAttachmentsCmd, messageId);
|
await ExecuteDeleteByMessageId(deleteMessageAttachmentsCmd, messageId);
|
||||||
await ExecuteDeleteByMessageId(deleteEmbedsCmd, messageId);
|
await ExecuteDeleteByMessageId(deleteMessageEmbedsCmd, messageId);
|
||||||
await ExecuteDeleteByMessageId(deleteReactionsCmd, messageId);
|
await ExecuteDeleteByMessageId(deleteMessageReactionsCmd, messageId);
|
||||||
|
|
||||||
if (message.EditTimestamp is {} timestamp) {
|
if (message.EditTimestamp is {} timestamp) {
|
||||||
editTimestampCmd.Set(":message_id", messageId);
|
messageEditTimestampCmd.Set(":message_id", messageId);
|
||||||
editTimestampCmd.Set(":edit_timestamp", timestamp);
|
messageEditTimestampCmd.Set(":edit_timestamp", timestamp);
|
||||||
await editTimestampCmd.ExecuteNonQueryAsync();
|
await messageEditTimestampCmd.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.RepliedToId is {} repliedToId) {
|
if (message.RepliedToId is {} repliedToId) {
|
||||||
repliedToCmd.Set(":message_id", messageId);
|
messageRepliedToCmd.Set(":message_id", messageId);
|
||||||
repliedToCmd.Set(":replied_to_id", repliedToId);
|
messageRepliedToCmd.Set(":replied_to_id", repliedToId);
|
||||||
await repliedToCmd.ExecuteNonQueryAsync();
|
await messageRepliedToCmd.ExecuteNonQueryAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message.Attachments.IsEmpty) {
|
if (!message.Attachments.IsEmpty) {
|
||||||
foreach (var attachment in message.Attachments) {
|
foreach (var attachment in message.Attachments) {
|
||||||
attachmentCmd.Set(":message_id", messageId);
|
object attachmentId = attachment.Id;
|
||||||
attachmentCmd.Set(":attachment_id", attachment.Id);
|
|
||||||
|
attachmentCmd.Set(":attachment_id", attachmentId);
|
||||||
attachmentCmd.Set(":name", attachment.Name);
|
attachmentCmd.Set(":name", attachment.Name);
|
||||||
attachmentCmd.Set(":type", attachment.Type);
|
attachmentCmd.Set(":type", attachment.Type);
|
||||||
attachmentCmd.Set(":normalized_url", attachment.NormalizedUrl);
|
attachmentCmd.Set(":normalized_url", attachment.NormalizedUrl);
|
||||||
@@ -136,15 +133,19 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
attachmentCmd.Set(":height", attachment.Height);
|
attachmentCmd.Set(":height", attachment.Height);
|
||||||
await attachmentCmd.ExecuteNonQueryAsync();
|
await attachmentCmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
|
messageAttachmentCmd.Set(":message_id", messageId);
|
||||||
|
messageAttachmentCmd.Set(":attachment_id", attachmentId);
|
||||||
|
await messageAttachmentCmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
await downloadCollector.Add(DownloadLinkExtractor.FromAttachment(attachment));
|
await downloadCollector.Add(DownloadLinkExtractor.FromAttachment(attachment));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!message.Embeds.IsEmpty) {
|
if (!message.Embeds.IsEmpty) {
|
||||||
foreach (var embed in message.Embeds) {
|
foreach (var embed in message.Embeds) {
|
||||||
embedCmd.Set(":message_id", messageId);
|
messageEmbedCmd.Set(":message_id", messageId);
|
||||||
embedCmd.Set(":json", embed.Json);
|
messageEmbedCmd.Set(":json", embed.Json);
|
||||||
await embedCmd.ExecuteNonQueryAsync();
|
await messageEmbedCmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
if (DownloadLinkExtractor.TryFromEmbedJson(embed.Json) is {} download) {
|
if (DownloadLinkExtractor.TryFromEmbedJson(embed.Json) is {} download) {
|
||||||
await downloadCollector.Add(download);
|
await downloadCollector.Add(download);
|
||||||
@@ -154,12 +155,12 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
|
|
||||||
if (!message.Reactions.IsEmpty) {
|
if (!message.Reactions.IsEmpty) {
|
||||||
foreach (var reaction in message.Reactions) {
|
foreach (var reaction in message.Reactions) {
|
||||||
reactionCmd.Set(":message_id", messageId);
|
messageReactionCmd.Set(":message_id", messageId);
|
||||||
reactionCmd.Set(":emoji_id", reaction.EmojiId);
|
messageReactionCmd.Set(":emoji_id", reaction.EmojiId);
|
||||||
reactionCmd.Set(":emoji_name", reaction.EmojiName);
|
messageReactionCmd.Set(":emoji_name", reaction.EmojiName);
|
||||||
reactionCmd.Set(":emoji_flags", (int) reaction.EmojiFlags);
|
messageReactionCmd.Set(":emoji_flags", (int) reaction.EmojiFlags);
|
||||||
reactionCmd.Set(":count", reaction.Count);
|
messageReactionCmd.Set(":count", reaction.Count);
|
||||||
await reactionCmd.ExecuteNonQueryAsync();
|
await messageReactionCmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
if (reaction.EmojiId is {} emojiId) {
|
if (reaction.EmojiId is {} emojiId) {
|
||||||
await downloadCollector.Add(DownloadLinkExtractor.FromEmoji(emojiId, reaction.EmojiFlags));
|
await downloadCollector.Add(DownloadLinkExtractor.FromEmoji(emojiId, reaction.EmojiFlags));
|
||||||
@@ -221,7 +222,8 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
"""
|
"""
|
||||||
SELECT attachment_id, name, type, normalized_url, download_url, size, width, height
|
SELECT attachment_id, name, type, normalized_url, download_url, size, width, height
|
||||||
FROM attachments
|
FROM attachments
|
||||||
WHERE message_id = :message_id
|
JOIN message_attachments USING (attachment_id)
|
||||||
|
WHERE message_attachments.message_id = :message_id
|
||||||
""";
|
""";
|
||||||
|
|
||||||
await using var attachmentCmd = new MessageToManyCommand<Attachment>(conn, AttachmentSql, static reader => new Attachment {
|
await using var attachmentCmd = new MessageToManyCommand<Attachment>(conn, AttachmentSql, static reader => new Attachment {
|
||||||
@@ -238,7 +240,7 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
const string EmbedSql =
|
const string EmbedSql =
|
||||||
"""
|
"""
|
||||||
SELECT json
|
SELECT json
|
||||||
FROM embeds
|
FROM message_embeds
|
||||||
WHERE message_id = :message_id
|
WHERE message_id = :message_id
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@@ -249,7 +251,7 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
const string ReactionSql =
|
const string ReactionSql =
|
||||||
"""
|
"""
|
||||||
SELECT emoji_id, emoji_name, emoji_flags, count
|
SELECT emoji_id, emoji_name, emoji_flags, count
|
||||||
FROM reactions
|
FROM message_reactions
|
||||||
WHERE message_id = :message_id
|
WHERE message_id = :message_id
|
||||||
""";
|
""";
|
||||||
|
|
||||||
@@ -262,10 +264,10 @@ sealed class SqliteMessageRepository : BaseSqliteRepository, IMessageRepository
|
|||||||
|
|
||||||
await using var messageCmd = conn.Command(
|
await using var messageCmd = conn.Command(
|
||||||
$"""
|
$"""
|
||||||
SELECT m.message_id, m.sender_id, m.channel_id, m.text, m.timestamp, et.edit_timestamp, rt.replied_to_id
|
SELECT m.message_id, m.sender_id, m.channel_id, m.text, m.timestamp, met.edit_timestamp, mrt.replied_to_id
|
||||||
FROM messages m
|
FROM messages m
|
||||||
LEFT JOIN edit_timestamps et ON m.message_id = et.message_id
|
LEFT JOIN message_edit_timestamps met ON m.message_id = met.message_id
|
||||||
LEFT JOIN replied_to rt ON m.message_id = rt.message_id
|
LEFT JOIN message_replied_to mrt ON m.message_id = mrt.message_id
|
||||||
{filter.GenerateConditions("m").BuildWhereClause()}
|
{filter.GenerateConditions("m").BuildWhereClause()}
|
||||||
"""
|
"""
|
||||||
);
|
);
|
||||||
|
@@ -29,6 +29,7 @@ sealed class SqliteUserRepository : BaseSqliteRepository, IUserRepository {
|
|||||||
await using var cmd = conn.Upsert("users", [
|
await using var cmd = conn.Upsert("users", [
|
||||||
("id", SqliteType.Integer),
|
("id", SqliteType.Integer),
|
||||||
("name", SqliteType.Text),
|
("name", SqliteType.Text),
|
||||||
|
("display_name", SqliteType.Text),
|
||||||
("avatar_url", SqliteType.Text),
|
("avatar_url", SqliteType.Text),
|
||||||
("discriminator", SqliteType.Text)
|
("discriminator", SqliteType.Text)
|
||||||
]);
|
]);
|
||||||
@@ -38,6 +39,7 @@ sealed class SqliteUserRepository : BaseSqliteRepository, IUserRepository {
|
|||||||
foreach (var user in users) {
|
foreach (var user in users) {
|
||||||
cmd.Set(":id", user.Id);
|
cmd.Set(":id", user.Id);
|
||||||
cmd.Set(":name", user.Name);
|
cmd.Set(":name", user.Name);
|
||||||
|
cmd.Set(":display_name", user.DisplayName);
|
||||||
cmd.Set(":avatar_url", user.AvatarUrl);
|
cmd.Set(":avatar_url", user.AvatarUrl);
|
||||||
cmd.Set(":discriminator", user.Discriminator);
|
cmd.Set(":discriminator", user.Discriminator);
|
||||||
await cmd.ExecuteNonQueryAsync();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
@@ -62,15 +64,16 @@ sealed class SqliteUserRepository : BaseSqliteRepository, IUserRepository {
|
|||||||
public async IAsyncEnumerable<User> Get([EnumeratorCancellation] CancellationToken cancellationToken) {
|
public async IAsyncEnumerable<User> Get([EnumeratorCancellation] CancellationToken cancellationToken) {
|
||||||
await using var conn = await pool.Take();
|
await using var conn = await pool.Take();
|
||||||
|
|
||||||
await using var cmd = conn.Command("SELECT id, name, avatar_url, discriminator FROM users");
|
await using var cmd = conn.Command("SELECT id, name, display_name, avatar_url, discriminator FROM users");
|
||||||
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
|
await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
|
||||||
|
|
||||||
while (await reader.ReadAsync(cancellationToken)) {
|
while (await reader.ReadAsync(cancellationToken)) {
|
||||||
yield return new User {
|
yield return new User {
|
||||||
Id = reader.GetUint64(0),
|
Id = reader.GetUint64(0),
|
||||||
Name = reader.GetString(1),
|
Name = reader.GetString(1),
|
||||||
AvatarUrl = reader.IsDBNull(2) ? null : reader.GetString(2),
|
DisplayName = reader.IsDBNull(2) ? null : reader.GetString(2),
|
||||||
Discriminator = reader.IsDBNull(3) ? null : reader.GetString(3),
|
AvatarUrl = reader.IsDBNull(3) ? null : reader.GetString(3),
|
||||||
|
Discriminator = reader.IsDBNull(4) ? null : reader.GetString(4),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,10 @@ sealed class SqliteSchemaUpgradeTo3 : ISchemaUpgrade {
|
|||||||
await reporter.MainWork("Applying schema changes...", 0, 1);
|
await reporter.MainWork("Applying schema changes...", 0, 1);
|
||||||
|
|
||||||
await SqliteSchema.CreateMessageEditTimestampTable(conn);
|
await SqliteSchema.CreateMessageEditTimestampTable(conn);
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE message_edit_timestamps RENAME TO edit_timestamps");
|
||||||
|
|
||||||
await SqliteSchema.CreateMessageRepliedToTable(conn);
|
await SqliteSchema.CreateMessageRepliedToTable(conn);
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE message_replied_to RENAME TO replied_to");
|
||||||
|
|
||||||
await conn.ExecuteAsync("""
|
await conn.ExecuteAsync("""
|
||||||
INSERT INTO edit_timestamps (message_id, edit_timestamp)
|
INSERT INTO edit_timestamps (message_id, edit_timestamp)
|
||||||
|
11
app/Server/Database/Sqlite/Schema/SqliteSchemaUpgradeTo8.cs
Normal file
11
app/Server/Database/Sqlite/Schema/SqliteSchemaUpgradeTo8.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using DHT.Server.Database.Sqlite.Utils;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Sqlite.Schema;
|
||||||
|
|
||||||
|
sealed class SqliteSchemaUpgradeTo8 : ISchemaUpgrade {
|
||||||
|
async Task ISchemaUpgrade.Run(ISqliteConnection conn, ISchemaUpgradeCallbacks.IProgressReporter reporter) {
|
||||||
|
await reporter.MainWork("Applying schema changes...", 0, 1);
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE users ADD display_name TEXT");
|
||||||
|
}
|
||||||
|
}
|
23
app/Server/Database/Sqlite/Schema/SqliteSchemaUpgradeTo9.cs
Normal file
23
app/Server/Database/Sqlite/Schema/SqliteSchemaUpgradeTo9.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using DHT.Server.Database.Sqlite.Utils;
|
||||||
|
|
||||||
|
namespace DHT.Server.Database.Sqlite.Schema;
|
||||||
|
|
||||||
|
sealed class SqliteSchemaUpgradeTo9 : ISchemaUpgrade {
|
||||||
|
async Task ISchemaUpgrade.Run(ISqliteConnection conn, ISchemaUpgradeCallbacks.IProgressReporter reporter) {
|
||||||
|
await reporter.MainWork("Applying schema changes...", 0, 3);
|
||||||
|
await SqliteSchema.CreateMessageAttachmentsTable(conn);
|
||||||
|
|
||||||
|
await reporter.MainWork("Migrating message attachments...", 1, 3);
|
||||||
|
await conn.ExecuteAsync("INSERT INTO message_attachments (message_id, attachment_id) SELECT message_id, attachment_id FROM attachments");
|
||||||
|
|
||||||
|
await reporter.MainWork("Applying schema changes...", 2, 3);
|
||||||
|
await conn.ExecuteAsync("DROP INDEX attachments_message_ix");
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE attachments DROP COLUMN message_id");
|
||||||
|
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE embeds RENAME TO message_embeds");
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE edit_timestamps RENAME TO message_edit_timestamps");
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE replied_to RENAME TO message_replied_to");
|
||||||
|
await conn.ExecuteAsync("ALTER TABLE reactions RENAME TO message_reactions");
|
||||||
|
}
|
||||||
|
}
|
@@ -8,7 +8,7 @@ using DHT.Utils.Logging;
|
|||||||
namespace DHT.Server.Database.Sqlite;
|
namespace DHT.Server.Database.Sqlite;
|
||||||
|
|
||||||
sealed class SqliteSchema {
|
sealed class SqliteSchema {
|
||||||
internal const int Version = 7;
|
internal const int Version = 9;
|
||||||
|
|
||||||
private static readonly Log Log = Log.ForType<SqliteSchema>();
|
private static readonly Log Log = Log.ForType<SqliteSchema>();
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ sealed class SqliteSchema {
|
|||||||
CREATE TABLE users (
|
CREATE TABLE users (
|
||||||
id INTEGER PRIMARY KEY NOT NULL,
|
id INTEGER PRIMARY KEY NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
|
display_name TEXT,
|
||||||
avatar_url TEXT,
|
avatar_url TEXT,
|
||||||
discriminator TEXT
|
discriminator TEXT
|
||||||
)
|
)
|
||||||
@@ -85,7 +86,6 @@ sealed class SqliteSchema {
|
|||||||
|
|
||||||
await conn.ExecuteAsync("""
|
await conn.ExecuteAsync("""
|
||||||
CREATE TABLE attachments (
|
CREATE TABLE attachments (
|
||||||
message_id INTEGER NOT NULL,
|
|
||||||
attachment_id INTEGER NOT NULL PRIMARY KEY NOT NULL,
|
attachment_id INTEGER NOT NULL PRIMARY KEY NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
type TEXT,
|
type TEXT,
|
||||||
@@ -98,14 +98,14 @@ sealed class SqliteSchema {
|
|||||||
""");
|
""");
|
||||||
|
|
||||||
await conn.ExecuteAsync("""
|
await conn.ExecuteAsync("""
|
||||||
CREATE TABLE embeds (
|
CREATE TABLE message_embeds (
|
||||||
message_id INTEGER NOT NULL,
|
message_id INTEGER NOT NULL,
|
||||||
json TEXT NOT NULL
|
json TEXT NOT NULL
|
||||||
)
|
)
|
||||||
""");
|
""");
|
||||||
|
|
||||||
await conn.ExecuteAsync("""
|
await conn.ExecuteAsync("""
|
||||||
CREATE TABLE reactions (
|
CREATE TABLE message_reactions (
|
||||||
message_id INTEGER NOT NULL,
|
message_id INTEGER NOT NULL,
|
||||||
emoji_id INTEGER,
|
emoji_id INTEGER,
|
||||||
emoji_name TEXT,
|
emoji_name TEXT,
|
||||||
@@ -117,17 +117,17 @@ sealed class SqliteSchema {
|
|||||||
await CreateMessageEditTimestampTable(conn);
|
await CreateMessageEditTimestampTable(conn);
|
||||||
await CreateMessageRepliedToTable(conn);
|
await CreateMessageRepliedToTable(conn);
|
||||||
await CreateDownloadTables(conn);
|
await CreateDownloadTables(conn);
|
||||||
|
await CreateMessageAttachmentsTable(conn);
|
||||||
|
|
||||||
await conn.ExecuteAsync("CREATE INDEX attachments_message_ix ON attachments(message_id)");
|
await conn.ExecuteAsync("CREATE INDEX embeds_message_ix ON message_embeds(message_id)");
|
||||||
await conn.ExecuteAsync("CREATE INDEX embeds_message_ix ON embeds(message_id)");
|
await conn.ExecuteAsync("CREATE INDEX reactions_message_ix ON message_reactions(message_id)");
|
||||||
await conn.ExecuteAsync("CREATE INDEX reactions_message_ix ON reactions(message_id)");
|
|
||||||
|
|
||||||
await conn.ExecuteAsync("INSERT INTO metadata (key, value) VALUES ('version', " + Version + ")");
|
await conn.ExecuteAsync("INSERT INTO metadata (key, value) VALUES ('version', " + Version + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task CreateMessageEditTimestampTable(ISqliteConnection conn) {
|
internal static async Task CreateMessageEditTimestampTable(ISqliteConnection conn) {
|
||||||
await conn.ExecuteAsync("""
|
await conn.ExecuteAsync("""
|
||||||
CREATE TABLE edit_timestamps (
|
CREATE TABLE message_edit_timestamps (
|
||||||
message_id INTEGER PRIMARY KEY NOT NULL,
|
message_id INTEGER PRIMARY KEY NOT NULL,
|
||||||
edit_timestamp INTEGER NOT NULL
|
edit_timestamp INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
@@ -136,7 +136,7 @@ sealed class SqliteSchema {
|
|||||||
|
|
||||||
internal static async Task CreateMessageRepliedToTable(ISqliteConnection conn) {
|
internal static async Task CreateMessageRepliedToTable(ISqliteConnection conn) {
|
||||||
await conn.ExecuteAsync("""
|
await conn.ExecuteAsync("""
|
||||||
CREATE TABLE replied_to (
|
CREATE TABLE message_replied_to (
|
||||||
message_id INTEGER PRIMARY KEY NOT NULL,
|
message_id INTEGER PRIMARY KEY NOT NULL,
|
||||||
replied_to_id INTEGER NOT NULL
|
replied_to_id INTEGER NOT NULL
|
||||||
)
|
)
|
||||||
@@ -163,6 +163,18 @@ sealed class SqliteSchema {
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static async Task CreateMessageAttachmentsTable(ISqliteConnection conn) {
|
||||||
|
await conn.ExecuteAsync("""
|
||||||
|
CREATE TABLE message_attachments (
|
||||||
|
message_id INTEGER NOT NULL,
|
||||||
|
attachment_id INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (message_id, attachment_id),
|
||||||
|
FOREIGN KEY (message_id) REFERENCES messages (message_id) ON UPDATE CASCADE ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (attachment_id) REFERENCES attachments (attachment_id) ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
|
)
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
private async Task UpgradeSchemas(int dbVersion, ISchemaUpgradeCallbacks.IProgressReporter reporter) {
|
private async Task UpgradeSchemas(int dbVersion, ISchemaUpgradeCallbacks.IProgressReporter reporter) {
|
||||||
var upgrades = new Dictionary<int, ISchemaUpgrade> {
|
var upgrades = new Dictionary<int, ISchemaUpgrade> {
|
||||||
{ 1, new SqliteSchemaUpgradeTo2() },
|
{ 1, new SqliteSchemaUpgradeTo2() },
|
||||||
@@ -171,6 +183,8 @@ sealed class SqliteSchema {
|
|||||||
{ 4, new SqliteSchemaUpgradeTo5() },
|
{ 4, new SqliteSchemaUpgradeTo5() },
|
||||||
{ 5, new SqliteSchemaUpgradeTo6() },
|
{ 5, new SqliteSchemaUpgradeTo6() },
|
||||||
{ 6, new SqliteSchemaUpgradeTo7() },
|
{ 6, new SqliteSchemaUpgradeTo7() },
|
||||||
|
{ 7, new SqliteSchemaUpgradeTo8() },
|
||||||
|
{ 8, new SqliteSchemaUpgradeTo9() },
|
||||||
};
|
};
|
||||||
|
|
||||||
var perf = Log.Start("from version " + dbVersion);
|
var perf = Log.Start("from version " + dbVersion);
|
||||||
|
@@ -30,6 +30,7 @@ sealed class TrackUsersEndpoint(IDatabaseFile db) : BaseEndpoint(db) {
|
|||||||
private static User ReadUser(JsonElement json, string path) => new () {
|
private static User ReadUser(JsonElement json, string path) => new () {
|
||||||
Id = json.RequireSnowflake("id", path),
|
Id = json.RequireSnowflake("id", path),
|
||||||
Name = json.RequireString("name", path),
|
Name = json.RequireString("name", path),
|
||||||
|
DisplayName = json.HasKey("displayName") ? json.RequireString("displayName", path) : null,
|
||||||
AvatarUrl = json.HasKey("avatar") ? json.RequireString("avatar", path) : null,
|
AvatarUrl = json.HasKey("avatar") ? json.RequireString("avatar", path) : null,
|
||||||
Discriminator = json.HasKey("discriminator") ? json.RequireString("discriminator", path) : null
|
Discriminator = json.HasKey("discriminator") ? json.RequireString("discriminator", path) : null
|
||||||
};
|
};
|
||||||
|
@@ -8,5 +8,5 @@ using DHT.Utils;
|
|||||||
namespace DHT.Utils;
|
namespace DHT.Utils;
|
||||||
|
|
||||||
static class Version {
|
static class Version {
|
||||||
public const string Tag = "42.0.0.0";
|
public const string Tag = "42.1.0.0";
|
||||||
}
|
}
|
||||||
|
BIN
app/empty.dht
BIN
app/empty.dht
Binary file not shown.
Reference in New Issue
Block a user