forked from adam/discord-bot-shtik
Compare commits
3 Commits
c971add137
...
0d3a56c8db
Author | SHA1 | Date | |
---|---|---|---|
0d3a56c8db | |||
18e8f0f36e | |||
d006367ecc |
@ -8,6 +8,7 @@ using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using vassago.ProtocolInterfaces.DiscordInterface;
|
||||
|
||||
public class Behaver
|
||||
{
|
||||
|
@ -70,7 +70,7 @@ public class DiscordInterface
|
||||
protocolAsChannel.DisplayName = "discord (itself)";
|
||||
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot accept text"); };
|
||||
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"protocol isn't a real channel, cannot send file"); };
|
||||
protocolAsChannel= Rememberer.RememberChannel(protocolAsChannel);
|
||||
protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel);
|
||||
Console.WriteLine($"protocol as channel addeed; {protocolAsChannel}");
|
||||
}
|
||||
finally
|
||||
@ -115,7 +115,7 @@ public class DiscordInterface
|
||||
{
|
||||
await discordChannelSetup.WaitAsync();
|
||||
|
||||
try
|
||||
try
|
||||
{
|
||||
var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel);
|
||||
selfAccount.DisplayName = client.CurrentUser.Username;
|
||||
@ -129,7 +129,7 @@ public class DiscordInterface
|
||||
|
||||
private async Task MessageReceived(SocketMessage messageParam)
|
||||
{
|
||||
if(messageParam is not SocketUserMessage)
|
||||
if (messageParam is not SocketUserMessage)
|
||||
{
|
||||
Console.WriteLine($"{messageParam.Content}, but not a user message");
|
||||
return;
|
||||
@ -236,8 +236,8 @@ public class DiscordInterface
|
||||
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL);
|
||||
if (c == null)
|
||||
{
|
||||
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}");
|
||||
c = new Channel();
|
||||
Console.WriteLine($"adding channel {channel.Name}");
|
||||
}
|
||||
|
||||
c.DisplayName = channel.Name;
|
||||
@ -271,7 +271,7 @@ public class DiscordInterface
|
||||
if (channel is IGuildChannel)
|
||||
{
|
||||
parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL);
|
||||
if(parentChannel is null)
|
||||
if (parentChannel is null)
|
||||
{
|
||||
Console.Error.WriteLine("why am I still null?");
|
||||
}
|
||||
@ -290,7 +290,7 @@ public class DiscordInterface
|
||||
|
||||
c.SendMessage = (t) => { return channel.SendMessageAsync(t); };
|
||||
c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); };
|
||||
|
||||
|
||||
return Rememberer.RememberChannel(c);
|
||||
}
|
||||
internal Channel UpsertChannel(IGuild channel)
|
||||
@ -298,6 +298,7 @@ public class DiscordInterface
|
||||
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL);
|
||||
if (c == null)
|
||||
{
|
||||
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}");
|
||||
c = new Channel();
|
||||
}
|
||||
|
||||
@ -312,14 +313,19 @@ public class DiscordInterface
|
||||
|
||||
c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); };
|
||||
c.SendFile = (f, t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; send file"); };
|
||||
|
||||
|
||||
return Rememberer.RememberChannel(c);
|
||||
}
|
||||
internal static Account UpsertAccount(IUser user, Channel inChannel)
|
||||
{
|
||||
var acc = Rememberer.SearchAccount(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id);
|
||||
acc ??= new Account();
|
||||
|
||||
Console.WriteLine($"upserting account, retrieved {acc?.Id}.");
|
||||
if (acc != null)
|
||||
{
|
||||
Console.WriteLine($"acc's user: {acc.IsUser?.Id}");
|
||||
}
|
||||
acc ??= new Account() { IsUser = new User() };
|
||||
|
||||
acc.Username = user.Username;
|
||||
acc.ExternalId = user.Id.ToString();
|
||||
acc.IsBot = user.IsBot || user.IsWebhook;
|
||||
@ -327,9 +333,18 @@ public class DiscordInterface
|
||||
acc.SeenInChannel = inChannel;
|
||||
|
||||
acc.IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol));
|
||||
acc.IsUser ??= new User() { Accounts = [ acc ] };
|
||||
inChannel.Users ??= [];
|
||||
inChannel.Users.Add(acc);
|
||||
|
||||
Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}");
|
||||
if (acc.IsUser != null)
|
||||
{
|
||||
Console.WriteLine($"user has record of {acc.IsUser.Accounts?.Count ?? 0} accounts");
|
||||
}
|
||||
acc.IsUser ??= new User() { Accounts = [acc] };
|
||||
if (inChannel.Users?.Count > 0)
|
||||
{
|
||||
Console.WriteLine($"channel has {inChannel.Users.Count} accounts");
|
||||
}
|
||||
inChannel.Users ??= [acc];
|
||||
Rememberer.RememberAccount(acc);
|
||||
return acc;
|
||||
}
|
||||
|
10
README.md
10
README.md
@ -11,6 +11,16 @@ that's read messages/view channels, send messages, send messages in threads, and
|
||||
|
||||
## Data Types
|
||||
|
||||
database diagram. is a fancy term.
|
||||
|
||||
message 1:n attachment
|
||||
user 1:n account
|
||||
channel 1:n account
|
||||
channel 1:n message
|
||||
account 1:n message
|
||||
|
||||
featurepermission n:n ?
|
||||
|
||||
### Accounts
|
||||
|
||||
a `User` can have multiple `Account`s. e.g., @adam:greyn.club? that's an "account". I, however, am a `User`. An `Account` has references to the `Channels` its seen in - as in, leaf-level. If you're in a subchannel, you'll have an appropriate listing there - i.e., you will never have an account in "discord (itself)", you'll have one in the guild text-channels
|
||||
|
178
Rememberer.cs
178
Rememberer.cs
@ -2,13 +2,14 @@ namespace vassago;
|
||||
|
||||
using System.Linq.Expressions;
|
||||
using vassago.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public static class Rememberer
|
||||
{
|
||||
private static readonly SemaphoreSlim dbAccessSemaphore = new(1, 1);
|
||||
private static readonly ChattingContext db = new();
|
||||
public static Account SearchAccount(Expression<Func<Account, bool>> predicate)
|
||||
{
|
||||
return (new ChattingContext()).Accounts.FirstOrDefault(predicate);
|
||||
return (new ChattingContext()).Accounts.Include(a => a.IsUser).FirstOrDefault(predicate);
|
||||
}
|
||||
public static List<Account> SearchAccounts(Expression<Func<Account, bool>> predicate)
|
||||
{
|
||||
@ -28,160 +29,61 @@ public static class Rememberer
|
||||
}
|
||||
public static User SearchUser(Expression<Func<User, bool>> predicate)
|
||||
{
|
||||
return (new ChattingContext()).Users.FirstOrDefault(predicate);
|
||||
return (new ChattingContext()).Users.Include(u => u.Accounts).FirstOrDefault(predicate);
|
||||
}
|
||||
public static void RememberAccount(Account toRemember)
|
||||
{
|
||||
dbAccessSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
if (toRemember.Id == Guid.Empty)
|
||||
{
|
||||
var parentChannel = toRemember.SeenInChannel;
|
||||
var isUser = toRemember.IsUser;
|
||||
toRemember.SeenInChannel = null;
|
||||
toRemember.IsUser = null;
|
||||
db.Accounts.Add(toRemember);
|
||||
db.SaveChanges();
|
||||
|
||||
toRemember.SeenInChannel = parentChannel;
|
||||
toRemember.IsUser = isUser;
|
||||
db.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbAccessSemaphore.Release();
|
||||
}
|
||||
toRemember.IsUser ??= new User{ Accounts = [toRemember]};
|
||||
db.Update(toRemember.IsUser);
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void RememberAttachment(Attachment toRemember)
|
||||
{
|
||||
dbAccessSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
if (toRemember.Id == Guid.Empty)
|
||||
{
|
||||
var msg = toRemember.Message;
|
||||
toRemember.Message = null;
|
||||
db.Attachments.Add(toRemember);
|
||||
db.SaveChanges();
|
||||
toRemember.Message = msg;
|
||||
db.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbAccessSemaphore.Release();
|
||||
}
|
||||
toRemember.Message ??= new Message() { Attachments = [toRemember]};
|
||||
db.Update(toRemember.Message);
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static Channel RememberChannel(Channel toRemember)
|
||||
{
|
||||
dbAccessSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
if (toRemember.Id == Guid.Empty)
|
||||
{
|
||||
var parent = toRemember.ParentChannel;
|
||||
var subChannesl = toRemember.SubChannels;
|
||||
var msgs = toRemember.Messages;
|
||||
var accounts = toRemember.Users;
|
||||
toRemember.ParentChannel = null;
|
||||
toRemember.SubChannels = null;
|
||||
toRemember.Messages = null;
|
||||
toRemember.Users = null;
|
||||
db.Channels.Add(toRemember);
|
||||
db.SaveChanges();
|
||||
toRemember.ParentChannel = parent;
|
||||
toRemember.SubChannels = subChannesl;
|
||||
toRemember.Messages = msgs;
|
||||
toRemember.Users = accounts;
|
||||
db.SaveChanges();
|
||||
}
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbAccessSemaphore.Release();
|
||||
}
|
||||
db.Update(toRemember);
|
||||
db.SaveChanges();
|
||||
return toRemember;
|
||||
}
|
||||
public static void RememberMessage(Message toRemember)
|
||||
{
|
||||
dbAccessSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
if (toRemember.Id == Guid.Empty)
|
||||
{
|
||||
var author = toRemember.Author;
|
||||
var channel = toRemember.Channel;
|
||||
var attachments = toRemember.Attachments;
|
||||
toRemember.Author = null;
|
||||
toRemember.Channel = null;
|
||||
toRemember.Attachments = null;
|
||||
db.Messages.Add(toRemember);
|
||||
db.SaveChanges();
|
||||
toRemember.Author = author;
|
||||
toRemember.Channel = channel;
|
||||
toRemember.Attachments = attachments;
|
||||
db.SaveChanges();
|
||||
}
|
||||
db.SaveChanges();
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbAccessSemaphore.Release();
|
||||
}
|
||||
toRemember.Channel ??= new (){ Messages = [toRemember] };
|
||||
db.Update(toRemember.Channel);
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void RememberUser(User toRemember)
|
||||
{
|
||||
dbAccessSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
if (toRemember.Id == Guid.Empty)
|
||||
{
|
||||
var accs = toRemember.Accounts;
|
||||
toRemember.Accounts = null;
|
||||
db.Users.Add(toRemember);
|
||||
db.SaveChanges();
|
||||
toRemember.Accounts = accs;
|
||||
db.SaveChanges();
|
||||
}
|
||||
else
|
||||
{
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbAccessSemaphore.Release();
|
||||
}
|
||||
db.Users.Update(toRemember);
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static void ForgetUser(User toForget)
|
||||
{
|
||||
dbAccessSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
var db = new ChattingContext();
|
||||
db.Users.Remove(toForget);
|
||||
db.SaveChanges();
|
||||
}
|
||||
finally
|
||||
{
|
||||
dbAccessSemaphore.Release();
|
||||
}
|
||||
db.Users.Remove(toForget);
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
public static List<Account> AccountsOverview()
|
||||
{
|
||||
return db.Accounts.ToList();
|
||||
}
|
||||
public static List<Channel> ChannelsOverview()
|
||||
{
|
||||
return db.Channels.Include(u => u.SubChannels).Include(c => c.ParentChannel).ToList();
|
||||
}
|
||||
public static Channel ChannelDetail(Guid Id)
|
||||
{
|
||||
return db.Channels.Find(Id);
|
||||
// .Include(u => u.SubChannels)
|
||||
// .Include(u => u.Users)
|
||||
// .Include(u => u.ParentChannel);
|
||||
}
|
||||
public static List<User> UsersOverview()
|
||||
{
|
||||
return db.Users.ToList();
|
||||
}
|
||||
}
|
@ -8,30 +8,26 @@ using vassago.WebInterface.Models;
|
||||
|
||||
namespace vassago.WebInterface.Controllers;
|
||||
|
||||
public class ChannelsController(ChattingContext db) : Controller
|
||||
public class ChannelsController() : Controller
|
||||
{
|
||||
private ChattingContext Database => db;
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return Database.Channels != null ?
|
||||
View(Database.Channels.Include(u => u.ParentChannel).ToList().OrderBy(c => c.LineageSummary)) :
|
||||
var channels = Rememberer.ChannelsOverview();
|
||||
return channels != null ?
|
||||
View(channels.OrderBy(c => c.LineageSummary)) :
|
||||
Problem("Entity set '_db.Channels' is null.");
|
||||
}
|
||||
public async Task<IActionResult> Details(Guid id)
|
||||
{
|
||||
if(Database.Channels == null)
|
||||
var allChannels = Rememberer.ChannelsOverview();
|
||||
if(allChannels == null)
|
||||
return Problem("Entity set '_db.Channels' is null.");
|
||||
//"but adam", says the strawman, "why load *every* channel and walk your way up? surely there's a .Load command that works or something."
|
||||
//eh. I checked. Not really. You could make an SQL view that recurses its way up, meh idk how. You could just eagerly load *every* related object...
|
||||
//but that would take in all the messages.
|
||||
//realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel.
|
||||
var AllChannels = await Database.Channels
|
||||
.Include(u => u.SubChannels)
|
||||
.Include(u => u.Users)
|
||||
.Include(u => u.ParentChannel)
|
||||
.ToListAsync();
|
||||
var channel = AllChannels.First(u => u.Id == id);
|
||||
|
||||
var channel = allChannels.First(u => u.Id == id);
|
||||
var walker = channel;
|
||||
while(walker != null)
|
||||
{
|
||||
|
@ -12,24 +12,23 @@ namespace vassago.Controllers;
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
private readonly ChattingContext _db;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger, ChattingContext db)
|
||||
public HomeController(ILogger<HomeController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
var allAccounts = _db.Accounts.ToList();
|
||||
var allChannels = _db.Channels.Include(c => c.Users).ToList();
|
||||
var allAccounts = Rememberer.AccountsOverview();
|
||||
var allChannels = Rememberer.ChannelsOverview();
|
||||
Console.WriteLine($"accounts: {allAccounts?.Count ?? 0}, channels: {allChannels?.Count ?? 0}");
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("[");
|
||||
sb.Append('[');
|
||||
sb.Append("{text: \"channels\", nodes: [");
|
||||
|
||||
var first = true;
|
||||
var topLevelChannels = _db.Channels.Where(x => x.ParentChannel == null);
|
||||
var topLevelChannels = Rememberer.ChannelsOverview().Where(x => x.ParentChannel == null);
|
||||
foreach (var topLevelChannel in topLevelChannels)
|
||||
{
|
||||
if (first)
|
||||
@ -85,13 +84,13 @@ public class HomeController : Controller
|
||||
}
|
||||
sb.Append("]}");
|
||||
}
|
||||
var users = _db.Users.ToList();
|
||||
var users = Rememberer.UsersOverview();// _db.Users.ToList();
|
||||
if(users.Any())
|
||||
{
|
||||
sb.Append(",{text: \"users\", nodes: [");
|
||||
first=true;
|
||||
//refresh list; we'll be knocking them out again in serializeUser
|
||||
allAccounts = _db.Accounts.ToList();
|
||||
allAccounts = Rememberer.AccountsOverview();
|
||||
foreach(var user in users)
|
||||
{
|
||||
if (first)
|
||||
|
@ -2,6 +2,7 @@ using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using vassago.Models;
|
||||
using vassago.ProtocolInterfaces.DiscordInterface;
|
||||
|
||||
namespace vassago.Controllers.api;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user