// Copyright (C) 2021-present sam (starshines.gay) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published // by the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . using Catalogger.Backend.Extensions; using NodaTime; using NodaTime.Extensions; using Remora.Discord.API; using Remora.Discord.API.Abstractions.Objects; using Remora.Discord.API.Abstractions.Rest; namespace Catalogger.Backend.Services; public class NewsService( Config config, ILogger logger, IDiscordRestChannelAPI channelApi, IClock clock ) { private static readonly Duration MaxNewsAge = Duration.FromDays(90); // 3 months private static readonly Duration ExpiresAfter = Duration.FromHours(1); private readonly ILogger _logger = logger.ForContext(); private List? _messages; private Instant _lastUpdated = Instant.MinValue; private readonly SemaphoreSlim _lock = new(1); private bool _isExpired => clock.GetCurrentInstant() > _lastUpdated + ExpiresAfter; public async Task> GetNewsAsync() { if (_messages != null && !_isExpired) return _messages.Select(FormatIMessage); var messages = await GetRawNewsAsync(); return messages.Select(FormatIMessage); } public void AddMessage(IMessage message) => _messages = _messages?.Take(4).Prepend(message).ToList(); private async Task> GetRawNewsAsync() { await _lock.WaitAsync(); try { if (config.Web.NewsChannel == null) return []; _logger.Information("Fetching news from channel {ChannelId}", config.Web.NewsChannel); var res = await channelApi.GetChannelMessagesAsync( DiscordSnowflake.New(config.Web.NewsChannel.Value), limit: 5 ); if (res.IsSuccess) return _messages = res .Entity.Where(m => m.ID.Timestamp.ToInstant() > clock.GetCurrentInstant() - MaxNewsAge ) .ToList(); return []; } finally { _lastUpdated = clock.GetCurrentInstant(); _lock.Release(); } } public record NewsMessage( string Author, string Content, IEnumerable AttachmentUrls, DateTimeOffset PostedAt, DateTimeOffset? EditedAt ); private NewsMessage FormatIMessage(IMessage msg) => new( msg.Author.Tag(), msg.Content, msg.Attachments.Select(a => a.Url), msg.ID.Timestamp, msg.EditedTimestamp ); }