66 lines
6.2 KiB
Python
66 lines
6.2 KiB
Python
from pathlib import Path
|
|
|
|
root = Path(__file__).resolve().parents[1]
|
|
server = root / 'ikar_server/src/Ikar.Server'
|
|
def edit(path, before, after, count=1):
|
|
p = server / path
|
|
text = p.read_text(encoding='utf-8-sig')
|
|
assert text.count(before) == count, (path, before, text.count(before))
|
|
p.write_text(text.replace(before, after), encoding='utf-8')
|
|
|
|
edit('Data/Entities/Entities.cs', 'public sealed class Message\n{', 'public sealed class Message\n{\n public long? ScheduledForUnixMs { get; set; }')
|
|
edit('Infrastructure/Storage/DatabaseSchemaBootstrapper.cs', 'await EnsureColumnAsync("Messages", "EditedAt",', 'await EnsureColumnAsync("Messages", "ScheduledForUnixMs", "INTEGER NULL", cancellationToken);\n await EnsureColumnAsync("Messages", "EditedAt",')
|
|
edit('Program.cs', 'builder.Services.AddHostedService<PushDispatchBackgroundService>();', 'builder.Services.AddHostedService<PushDispatchBackgroundService>();\nbuilder.Services.AddHostedService<IkarScheduledMessageService>();')
|
|
path = 'Controllers/QlyraCompatibilityController.cs'
|
|
edit(path, ''' var visibleMessages = chat.Messages
|
|
.Where(message => message.DeletedAt is null && !hiddenMessageIds.Contains(message.Id));''', ''' var delayed = ReadString(payload, "itemType") == "DELAYED";
|
|
var visibleMessages = chat.Messages
|
|
.Where(message => message.DeletedAt is null && !hiddenMessageIds.Contains(message.Id))
|
|
.Where(message => delayed ? message.ScheduledForUnixMs != null && message.SenderId == user.Id : message.ScheduledForUnixMs == null);
|
|
if (delayed) anchor = DateTimeOffset.MaxValue;''')
|
|
edit(path, ''' chat.LastActivityAt = message.SentAt;
|
|
if (dbContext.Entry(message).State == EntityState.Detached) dbContext.Messages.Add(message);''', ''' var scheduled = ReadScheduledTime(payload, "message");
|
|
if (scheduled is not null && (scheduled <= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() || scheduled > DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeMilliseconds()))
|
|
return BadRequest(QlyraCompatibilityResponse.Error(opcode, "SCHEDULE_INVALID", "Укажите время отправки в пределах года."));
|
|
message.ScheduledForUnixMs = scheduled;
|
|
if (scheduled is null) chat.LastActivityAt = message.SentAt;
|
|
if (dbContext.Entry(message).State == EntityState.Detached) dbContext.Messages.Add(message);
|
|
if (scheduled is null) pushDispatchQueue?.StageMessages(dbContext, message);''')
|
|
edit(path, ''' chat.Members.Select(member => member.UserId),
|
|
128,
|
|
new { chatId = clientChatId, message = mappedMessage, unread = 0 },
|
|
cancellationToken);
|
|
return Ok(QlyraCompatibilityResponse.Ok(opcode, new { message = mappedMessage }));''', ''' message.ScheduledForUnixMs is not null ? [user.Id] : chat.Members.Select(member => member.UserId),
|
|
message.ScheduledForUnixMs is not null ? 154 : 128,
|
|
new { chatId = clientChatId, message = mappedMessage, unread = 0 },
|
|
cancellationToken);
|
|
if (message.ScheduledForUnixMs is null && pushDispatchQueue is not null) await pushDispatchQueue.EnqueueAsync(chat.Id, message.Id, cancellationToken);
|
|
return Ok(QlyraCompatibilityResponse.Ok(opcode, new { message = mappedMessage }));''', 2)
|
|
edit(path, ''' message.Text = text;
|
|
message.EditedAt = DateTimeOffset.UtcNow;''', ''' message.Text = text;
|
|
if (message.ScheduledForUnixMs is not null && ReadScheduledTime(payload) is long rescheduled)
|
|
{
|
|
if (rescheduled <= DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() || rescheduled > DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeMilliseconds())
|
|
return BadRequest(QlyraCompatibilityResponse.Error(opcode, "SCHEDULE_INVALID", "Некорректное время отправки."));
|
|
message.ScheduledForUnixMs = rescheduled;
|
|
}
|
|
message.EditedAt = DateTimeOffset.UtcNow;''')
|
|
edit(path, ''' if (ReadBool(payload, "forMe") == true)
|
|
{''', ''' if (ReadBool(payload, "forMe") == true && ReadString(payload, "itemType") != "DELAYED")
|
|
{''')
|
|
edit(path, ''' .SingleOrDefaultAsync(entity => entity.ChatId == chatId, cancellationToken);''', ''' .SingleOrDefaultAsync(entity => entity.ChatId == chatId && (entity.ScheduledForUnixMs == null || entity.SenderId == User.GetRequiredUserId()), cancellationToken);''')
|
|
edit(path, ''' status = message.DeletedAt is not null ? "REMOVED" : message.EditedAt is not null ? "EDITED" : "SENT",
|
|
attaches = mappedAttachments''', ''' status = message.DeletedAt is not null ? "REMOVED" : message.EditedAt is not null ? "EDITED" : "SENT",
|
|
delayedAttributes = message.ScheduledForUnixMs is null ? null : new { timeToFire = message.ScheduledForUnixMs.Value, notifySender = true },
|
|
attaches = mappedAttachments''')
|
|
# Ordinary history, summaries, search and media must never leak queued messages.
|
|
p = server / path
|
|
text = p.read_text(encoding='utf-8')
|
|
text = text.replace('message.DeletedAt is null && !hiddenMessageIds.Contains(message.Id))', 'message.DeletedAt is null && message.ScheduledForUnixMs == null && !hiddenMessageIds.Contains(message.Id))')
|
|
# Restore the dedicated regular/delayed branch above.
|
|
text = text.replace('var visibleMessages = chat.Messages\n .Where(message => message.DeletedAt is null && message.ScheduledForUnixMs == null && !hiddenMessageIds.Contains(message.Id))', 'var visibleMessages = chat.Messages\n .Where(message => message.DeletedAt is null && !hiddenMessageIds.Contains(message.Id))')
|
|
text = text.replace('message.DeletedAt == null && !hiddenMessageIds.Contains(message.Id)', 'message.DeletedAt == null && message.ScheduledForUnixMs == null && !hiddenMessageIds.Contains(message.Id)')
|
|
text = text.replace('message.DeletedAt is null && message.PinnedAt', 'message.DeletedAt is null && message.ScheduledForUnixMs == null && message.PinnedAt')
|
|
text = text.replace('message.DeletedAt is null &&\n message.SenderId', 'message.DeletedAt is null && message.ScheduledForUnixMs == null &&\n message.SenderId')
|
|
p.write_text(text, encoding='utf-8')
|