C# Discord bot made using NetCord for keeping a TikTok-style streak
1using Microsoft.EntityFrameworkCore;
2using Microsoft.Extensions.Logging;
3using NetCord.Rest;
4using StreakBot.Data;
5using StreakBot.Data.Entities;
6using ChannelEntity = StreakBot.Data.Entities.Channel;
7using ChannelType = StreakBot.Data.Entities.ChannelType;
8
9namespace StreakBot.Services;
10
11public class ChannelService
12{
13 private readonly StreakDbContext _context;
14 private readonly RestClient _restClient;
15 private readonly ILogger<ChannelService> _logger;
16
17 public ChannelService(StreakDbContext context, RestClient restClient, ILogger<ChannelService> logger)
18 {
19 _context = context;
20 _restClient = restClient;
21 _logger = logger;
22 }
23
24 public async Task UpdateStreakChannelAsync(ulong serverId, int streakCount, bool endOfDay = false)
25 {
26 var channel = await _context.Channels
27 .FirstOrDefaultAsync(c => c.ServerId == serverId && c.ChannelType == ChannelType.StreakChannel);
28
29 var channelName = endOfDay ? $"🧊 {streakCount}" : $"🔥 {streakCount}";
30
31 if (channel == null)
32 {
33 var properties = new GuildChannelProperties(channelName, NetCord.ChannelType.VoiceGuildChannel);
34 var createdChannel = await _restClient.CreateGuildChannelAsync(serverId, properties);
35
36 channel = new ChannelEntity
37 {
38 ServerId = serverId,
39 ChannelId = createdChannel.Id,
40 ChannelType = ChannelType.StreakChannel
41 };
42
43 _context.Channels.Add(channel);
44 await _context.SaveChangesAsync();
45 }
46 else
47 {
48 await _restClient.ModifyGuildChannelAsync(channel.ChannelId, options => options.WithName(channelName));
49 }
50 }
51
52 public async Task UpdateTimeChannelAsync(ulong serverId, TimeSpan resetTime)
53 {
54 var channel = await _context.Channels
55 .FirstOrDefaultAsync(c => c.ServerId == serverId && c.ChannelType == ChannelType.TimeChannel);
56
57 var timeRemaining = CalculateTimeRemaining(resetTime);
58 var channelName = $"⏰ {timeRemaining:hh\\:mm}";
59
60 if (channel == null)
61 {
62 var properties = new GuildChannelProperties(channelName, NetCord.ChannelType.VoiceGuildChannel);
63 var createdChannel = await _restClient.CreateGuildChannelAsync(serverId, properties);
64
65 channel = new ChannelEntity
66 {
67 ServerId = serverId,
68 ChannelId = createdChannel.Id,
69 ChannelType = ChannelType.TimeChannel
70 };
71
72 _context.Channels.Add(channel);
73 await _context.SaveChangesAsync();
74 }
75 else
76 {
77 await _restClient.ModifyGuildChannelAsync(channel.ChannelId, options => options.WithName(channelName));
78 }
79 }
80
81 private static TimeSpan CalculateTimeRemaining(TimeSpan resetTime)
82 {
83 var now = DateTime.UtcNow.TimeOfDay;
84
85 if (now < resetTime)
86 {
87 return resetTime - now;
88 }
89 else
90 {
91 return TimeSpan.FromHours(24) - now + resetTime;
92 }
93 }
94
95 public async Task<ChannelEntity?> GetChannelAsync(ulong serverId, ChannelType channelType)
96 {
97 return await _context.Channels
98 .FirstOrDefaultAsync(c => c.ServerId == serverId && c.ChannelType == channelType);
99 }
100
101 public async Task<bool> DeleteChannelAsync(ulong channelId)
102 {
103 var test = await _restClient.DeleteChannelAsync(channelId);
104
105 return true;
106 }
107
108 public async Task SendStreakReminderAsync(ulong user1Id, ulong user2Id, bool user1NeedsReminder, bool user2NeedsReminder, ulong serverId)
109 {
110 if (user1NeedsReminder)
111 {
112 try
113 {
114 var dmChannel = await _restClient.GetDMChannelAsync(user1Id);
115 var message = new MessageProperties()
116 .WithContent($"Your streak with <@{user2Id}> will reset in less than an hour! Send a message in the server to keep it alive!");
117 await _restClient.SendMessageAsync(dmChannel.Id, message);
118 }
119 catch (RestException)
120 {
121 _logger.LogWarning("Failed to send streak reminder DM to user {UserId}", user1Id);
122 await SendReminderToTextChannelAsync(serverId, user1Id, user2Id);
123 }
124 }
125
126 if (user2NeedsReminder)
127 {
128 try
129 {
130 var dmChannel = await _restClient.GetDMChannelAsync(user2Id);
131 var message = new MessageProperties()
132 .WithContent($"Your streak with <@{user1Id}> will reset in less than an hour! Send a message in the server to keep it alive!");
133 await _restClient.SendMessageAsync(dmChannel.Id, message);
134 }
135 catch (RestException)
136 {
137 _logger.LogWarning("Failed to send streak reminder DM to user {UserId}", user2Id);
138 await SendReminderToTextChannelAsync(serverId, user2Id, user1Id);
139 }
140 }
141 }
142
143 private async Task SendReminderToTextChannelAsync(ulong serverId, ulong userToRemind, ulong streakPartnerUserId)
144 {
145 try
146 {
147 var guildChannels = await _restClient.GetGuildChannelsAsync(serverId);
148 var textChannel = guildChannels.FirstOrDefault(c => c is NetCord.TextChannel) as NetCord.TextChannel;
149
150 if (textChannel != null)
151 {
152 var message = new MessageProperties()
153 .WithContent($"<@{userToRemind}>, your streak with <@{streakPartnerUserId}> will reset in less than an hour! Send a message in the server to keep it alive! (DM failed to send)");
154 await textChannel.SendMessageAsync(message);
155 }
156 else
157 {
158 _logger.LogWarning("No text channel found in server {ServerId} to send fallback reminder", serverId);
159 }
160 }
161 catch (Exception ex)
162 {
163 _logger.LogError(ex, "Failed to send fallback reminder to text channel in server {ServerId}", serverId);
164 }
165 }
166
167 public async Task<ChannelEntity> CreateDmStreakMessageAsync(ulong channelId, int streakNumber, TimeSpan resetTime)
168 {
169 var timeRemaining = CalculateTimeRemaining(resetTime);
170 var content = $"🔥 {streakNumber}\n⏰ {timeRemaining:hh\\:mm}";
171
172 var messageProperties = new MessageProperties().WithContent(content);
173 var sentMessage = await _restClient.SendMessageAsync(channelId, messageProperties);
174
175 var channel = new ChannelEntity
176 {
177 ChannelId = channelId,
178 ChannelType = ChannelType.DmChannel,
179 MessageId = sentMessage.Id
180 };
181
182 _context.Channels.Add(channel);
183 await _context.SaveChangesAsync();
184
185 return channel;
186 }
187
188 public async Task UpdateDmStreakMessageAsync(ulong channelId, ulong messageId, int streakNumber, TimeSpan resetTime)
189 {
190 var timeRemaining = CalculateTimeRemaining(resetTime);
191 var content = $"🔥 {streakNumber}\n⏰ {timeRemaining:hh\\:mm}";
192
193 await _restClient.ModifyMessageAsync(channelId, messageId, message => message.WithContent(content));
194 }
195}