Compare commits

...

12 Commits

Author SHA1 Message Date
224a3c5a62 you can have multiple parameters, you just have to trust that billyboy will treat them all as [frombody] for you.
Some checks failed
gitea.arg.rip/vassago/pipeline/head There was a failure building this commit
and when has M$ ever embraced, extended, and extinguished your dreams?
2025-06-04 16:37:58 -04:00
c35a512dbd sendfile works
see #25
2025-06-04 16:28:53 -04:00
9ad7520c61 cache all the channels.
see #25 - now after restarts, we can still reply and message, etc.
2025-06-04 15:34:17 -04:00
0ff3902fd0 react works, sendfile theoretically might
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good
but it turns out there's an issue, see note on #25
2025-05-29 14:10:48 -04:00
8cc6c00db3 messages can be replied to with internalAPI
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good
and, they save correctly. which is why they weren't getting guids :face_palm:

see #25
2025-05-29 13:26:17 -04:00
7c7793f3b2 send message works!
...messages aren't getting stored, though.
2025-05-29 13:09:57 -04:00
5c2ba7397e rememberer API controller, more direct
but I need other stuff to use it instead of old ones
2025-05-23 17:15:33 -04:00
22ee1d2567 UAC descriptions
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good
see #40
2025-05-23 15:18:14 -04:00
8dd8b599bd starting on UAC description
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good
2025-05-22 17:41:01 -04:00
2645d631a5 linking works 2 ways
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good
...i think it always worked? or it was just this one issue
2025-05-22 16:59:34 -04:00
a55e7a74ab backgroundservice instead of IHostedService - now I can *see* my g.d. errors! 2025-05-22 16:54:10 -04:00
0506d331c5 modified "just google it" trigger
All checks were successful
gitea.arg.rip/vassago/pipeline/head This commit looks good
see #30
2025-05-22 12:01:24 -04:00
51 changed files with 1398 additions and 570 deletions

View File

@ -1,9 +1,9 @@
namespace vassago; namespace vassago;
#pragma warning disable 4014
using gray_messages.chat; using gray_messages.chat;
using franz;//the "not awaited" error using franz;
using vassago.Behavior; using vassago.Behavior;
using vassago.Models; using vassago.Models;
using vassago.ProtocolInterfaces;
using System; using System;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -43,12 +43,13 @@ public class Behaver
public async Task<bool> ActOn(Message message) public async Task<bool> ActOn(Message message)
{ {
//TODO: this is yet another hit to the database, and a big one. cache them in memory! there needs to be a feasibly-viewable amount, anyway.
var matchingUACs = Rememberer.MatchUACs(message); var matchingUACs = Rememberer.MatchUACs(message);
var behaviorsActedOn = new List<string>(); var behaviorsActedOn = new List<string>();
foreach (var behavior in Behaviors) foreach (var behavior in Behaviors.ToList())
{ {
//if (!behavior.ShouldAct(message, matchingUACs)) //TODO: this way //if (!behavior.ShouldAct(message, matchingUACs)) //TODO: this way
if(!behavior.ShouldAct(message)) if (!behavior.ShouldAct(message))
{ {
continue; continue;
} }
@ -64,7 +65,7 @@ public class Behaver
@"Well, that's a great question, and there are certainly many different possible answers. Ultimately, the decision will depend on a variety of factors, including your personal interests and goals, as well as any practical considerations (like the economy). I encourage you to do your research, speak with experts and educators, and explore your options before making a decision that's right for you.", @"Well, that's a great question, and there are certainly many different possible answers. Ultimately, the decision will depend on a variety of factors, including your personal interests and goals, as well as any practical considerations (like the economy). I encourage you to do your research, speak with experts and educators, and explore your options before making a decision that's right for you.",
@"┐(゚ ~゚ )┌", @"¯\_(ツ)_/¯", @"╮ (. ❛ ᴗ ❛.) ╭", @"╮(╯ _╰ )╭" @"┐(゚ ~゚ )┌", @"¯\_(ツ)_/¯", @"╮ (. ❛ ᴗ ❛.) ╭", @"╮(╯ _╰ )╭"
}; };
await message.Channel.SendMessage(responses[Shared.r.Next(responses.Count)]); Behaver.Instance.SendMessage(message.Channel.Id, responses[Shared.r.Next(responses.Count)]);
message.ActedOn = true; message.ActedOn = true;
behaviorsActedOn.Add("generic question fallback"); behaviorsActedOn.Add("generic question fallback");
} }
@ -147,5 +148,91 @@ public class Behaver
Rememberer.RememberUser(primary); Rememberer.RememberUser(primary);
return true; return true;
} }
private ProtocolInterface fetchInterface(Channel ch)
{
var walkUp = ch;
while (walkUp.ParentChannel != null)
{
walkUp = walkUp.ParentChannel;
}
foreach (var iproto in Shared.ProtocolList)
{
if (iproto.SelfChannel.Id == walkUp.Id)
return iproto;
}
return null;
}
public async Task<int> SendMessage(Guid channelId, string text)
{
var channel = Rememberer.ChannelDetail(channelId);
if (channel == null)
return 404;
var iprotocol = fetchInterface(channel);
if (iprotocol == null)
return 404;
return await iprotocol.SendMessage(channel, text);
}
public async Task<int> React(Guid messageId, string reaction)
{
Console.WriteLine($"sanity check: behaver is reacting, {messageId}, {reaction}");
var message = Rememberer.MessageDetail(messageId);
if (message == null)
{
Console.Error.WriteLine($"message {messageId} not found");
return 404;
}
Console.WriteLine($"sanity check: message found.");
if (message.Channel == null)
{
Console.Error.WriteLine($"react is going to fail because message {messageId} has no Channel");
}
Console.WriteLine($"sanity check: message has a channel.");
var iprotocol = fetchInterface(message.Channel);
if (iprotocol == null)
{
Console.WriteLine($"couldn't find protocol for {message.Channel?.Id}");
return 404;
}
Console.WriteLine("I remember this message, i have found a protocol, i am ready to react toit");
return await iprotocol.React(message, reaction);
}
public async Task<int> Reply(Guid messageId, string text)
{
var message = Rememberer.MessageDetail(messageId);
if (message == null)
{
Console.WriteLine($"message {messageId} not found");
return 404;
}
var iprotocol = fetchInterface(message.Channel);
if (iprotocol == null)
{
Console.WriteLine($"couldn't find protocol for {message.Channel.Id}");
return 404;
}
return await iprotocol.Reply(message, text);
}
public async Task<int> SendFile(Guid channelId, string path, string accompanyingText)
{
var channel = Rememberer.ChannelDetail(channelId);
if (channel == null)
return 404;
var iprotocol = fetchInterface(channel);
if (iprotocol == null)
return 404;
return await iprotocol.SendFile(channel, path, accompanyingText);
}
public async Task<int> SendFile(Guid channelId, string base64dData, string filename, string accompanyingText)
{
var channel = Rememberer.ChannelDetail(channelId);
if (channel == null)
return 404;
var iprotocol = fetchInterface(channel);
if (iprotocol == null)
return 404;
return await iprotocol.SendFile(channel, base64dData, filename, accompanyingText);
}
} }
#pragma warning restore 4014 //the "async not awaited" error

View File

@ -19,7 +19,7 @@ public class ChatGPTSnark : Behavior
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
await message.Channel.SendMessage("chatGPT is **weak**. also, are we done comparing every little if-then-else to skynet?"); Behaver.Instance.SendMessage(message.Channel.Id, "chatGPT is **weak**. also, are we done comparing every little if-then-else to skynet?");
return true; return true;
} }
} }

View File

@ -28,7 +28,7 @@ public class DefinitionSnarkCogDiss : Behavior
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
await message.Reply("that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?"); Behaver.Instance.SendMessage(message.Channel.Id, "that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?");
return true; return true;
} }
} }

View File

@ -28,7 +28,7 @@ public class DefinitionSnarkGaslight : Behavior
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
await message.Channel.SendMessage("that's not what gaslight means. Did you mean \"deceive\"?"); Behaver.Instance.SendMessage(message.Channel.Id, "that's not what gaslight means. Did you mean \"deceive\"?");
return true; return true;
} }
} }

View File

@ -27,10 +27,10 @@ public class Detiktokify : Behavior
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
if(Behaver.Instance.IsSelf(message.Author.Id)) if (Behaver.Instance.IsSelf(message.Author.Id))
return false; return false;
if(message.Channel.EffectivePermissions.MaxAttachmentBytes == 0) if (message.Channel.EffectivePermissions.MaxAttachmentBytes == 0)
return false; return false;
var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries); var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries);
@ -45,29 +45,27 @@ public class Detiktokify : Behavior
} }
} }
} }
if(tiktokLinks.Any()){ if (tiktokLinks.Any())
{
Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.Content}"); Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.Content}");
} }
return tiktokLinks.Any(); return tiktokLinks.Any();
} }
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
foreach(var link in tiktokLinks) foreach (var link in tiktokLinks)
{ {
tiktokLinks.Remove(link); tiktokLinks.Remove(link);
try try
{ {
Console.WriteLine($"detiktokifying {link}"); Console.WriteLine($"detiktokifying {link}");
#pragma warning disable 4014
//await message.React("<:tiktok:1070038619584200884>");
#pragma warning restore 4014
var res = await ytdl.RunVideoDownload(link.ToString()); var res = await ytdl.RunVideoDownload(link.ToString());
if (!res.Success) if (!res.Success)
{ {
Console.Error.WriteLine("tried to dl, failed. \n" + string.Join('\n', res.ErrorOutput)); Console.Error.WriteLine("tried to dl, failed. \n" + string.Join('\n', res.ErrorOutput));
await message.React("problemon");
await message.Channel.SendMessage("tried to dl, failed. \n"); Behaver.Instance.SendMessage(message.Channel.Id, "tried to dl, failed. \n");
Behaver.Instance.React(message.Channel.Id, "problemon");
} }
else else
{ {
@ -79,12 +77,12 @@ public class Detiktokify : Behavior
{ {
try try
{ {
await message.Channel.SendFile(path, null); Behaver.Instance.SendFile(message.Channel.Id, path, null);
} }
catch (Exception e) catch (Exception e)
{ {
System.Console.Error.WriteLine(e); System.Console.Error.WriteLine(e);
await message.Channel.SendMessage($"aaaadam!\n{e}"); Behaver.Instance.SendMessage(message.Channel.Id, $"aaaadam!\n{e}");
} }
} }
else else
@ -97,14 +95,15 @@ public class Detiktokify : Behavior
else else
{ {
Console.Error.WriteLine("idgi but something happened."); Console.Error.WriteLine("idgi but something happened.");
await message.React("problemon");
Behaver.Instance.React(message.Id, "problemon");
} }
} }
} }
catch (Exception e) catch (Exception e)
{ {
Console.Error.WriteLine(e); Console.Error.WriteLine(e);
await message.React("problemon"); Behaver.Instance.React(message.Id, "problemon");
return false; return false;
} }
} }

View File

@ -49,7 +49,7 @@ public class FiximageHeic : Behavior
conversions.Add(actualDeheic(att, message)); conversions.Add(actualDeheic(att, message));
} }
Task.WaitAll(conversions.ToArray()); Task.WaitAll(conversions.ToArray());
await message.React("\U0001F34F"); Behaver.Instance.React(message.Id, "\U0001F34F");
return true; return true;
} }
@ -66,19 +66,19 @@ public class FiximageHeic : Behavior
} }
if (ExternalProcess.GoPlz("convert", $"tmp/{att.Filename} tmp/{att.Filename}.jpg")) if (ExternalProcess.GoPlz("convert", $"tmp/{att.Filename} tmp/{att.Filename}.jpg"))
{ {
await message.Channel.SendFile($"tmp/{att.Filename}.jpg", "converted from jpeg-but-apple to jpeg"); Behaver.Instance.SendFile(message.Channel.Id, $"tmp/{att.Filename}.jpg", "converted from jpeg-but-apple to jpeg");
File.Delete($"tmp/{att.Filename}"); File.Delete($"tmp/{att.Filename}");
File.Delete($"tmp/{att.Filename}.jpg"); File.Delete($"tmp/{att.Filename}.jpg");
} }
else else
{ {
await message.Channel.SendMessage("convert failed :("); Behaver.Instance.SendMessage(message.Channel.Id, "convert failed :(");
Console.Error.WriteLine("convert failed :("); Console.Error.WriteLine("convert failed :(");
} }
} }
catch (Exception e) catch (Exception e)
{ {
await message.Channel.SendMessage($"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}"); Behaver.Instance.SendMessage(message.Channel.Id, $"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}");
Console.Error.WriteLine(JsonConvert.SerializeObject(e, Formatting.Indented)); Console.Error.WriteLine(JsonConvert.SerializeObject(e, Formatting.Indented));
return false; return false;
} }

View File

@ -19,13 +19,13 @@ public class GeneralSnarkCloudNative : Behavior
public override string Trigger => "certain tech buzzwords that no human uses in normal conversation"; public override string Trigger => "certain tech buzzwords that no human uses in normal conversation";
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
if(Behaver.Instance.IsSelf(message.Author.Id)) if (Behaver.Instance.IsSelf(message.Author.Id))
return false; return false;
if(!message.Channel.EffectivePermissions.ReactionsPossible) if (!message.Channel.EffectivePermissions.ReactionsPossible)
return false; return false;
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium) if ((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
return false; return false;
return Regex.IsMatch(message.Content, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) || return Regex.IsMatch(message.Content, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) ||
@ -37,12 +37,12 @@ public class GeneralSnarkCloudNative : Behavior
switch (Shared.r.Next(2)) switch (Shared.r.Next(2))
{ {
case 0: case 0:
await message.React("\uD83E\uDD2E"); //vomit emoji Behaver.Instance.React(message.Id, "\uD83E\uDD2E"); //vomit emoji
break; break;
case 1: case 1:
await message.React("\uD83C\uDDE7"); //B emoji Behaver.Instance.React(message.Id, "\uD83C\uDDE7"); //B emoji
await message.React("\uD83C\uDDE6"); //A Behaver.Instance.React(message.Id, "\uD83C\uDDE6"); //A
await message.React("\uD83C\uDDF3"); //N Behaver.Instance.React(message.Id, "\uD83C\uDDF3"); //N
break; break;
} }
return true; return true;

View File

@ -20,26 +20,22 @@ public class GeneralSnarkGooglit : Behavior
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
if (Behaver.Instance.IsSelf(message.Author.Id))
return false; return false;
}
// public override bool ShouldAct(Message message)
// {
// if(Behaver.Instance.IsSelf(message.Author.Id))
// return false;
// return Regex.IsMatch(message.Content, $"(just )?google( (it|that|things|before))?\\b", RegexOptions.IgnoreCase); return Regex.IsMatch(message.Content, $"(just )?google( (it|that|things|before))\\b", RegexOptions.IgnoreCase);
// } }
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
switch (Shared.r.Next(4)) switch (Shared.r.Next(4))
{ {
default: default:
await message.Channel.SendMessage("yeah no shit, obviously that resulted in nothing"); Behaver.Instance.SendMessage(message.Channel.Id, "yeah no shit, obviously that resulted in nothing");
break; break;
case 1: case 1:
var results = ""; var results = "";
switch(Shared.r.Next(4)) switch (Shared.r.Next(4))
{ {
default: default:
results = "\"curious about the best <THING> in <CURRENT YEAR>? click here to find out\", then i clicked there to find out. They didn't know either."; results = "\"curious about the best <THING> in <CURRENT YEAR>? click here to find out\", then i clicked there to find out. They didn't know either.";
@ -54,13 +50,13 @@ public class GeneralSnarkGooglit : Behavior
results = "the one that had a paragraph that restated the question but badly, a paragraph to give a wrong history on the question, a paragraph with amazon affiliate links, a pargraph that said \"ultimately you should do your own research\", then had a paragraph telling me to give Engagement for The Algorithm"; results = "the one that had a paragraph that restated the question but badly, a paragraph to give a wrong history on the question, a paragraph with amazon affiliate links, a pargraph that said \"ultimately you should do your own research\", then had a paragraph telling me to give Engagement for The Algorithm";
break; break;
} }
await message.Channel.SendMessage("oh here, I memorized the results. My favorite is " + results); Behaver.Instance.SendMessage(message.Channel.Id, "oh here, I memorized the results. My favorite is " + results);
break; break;
case 2: case 2:
await message.Channel.SendMessage("Obviously that was already tried. Obviously it failed. If you ever tried to learn anything you'd know that's how it works."); Behaver.Instance.SendMessage(message.Channel.Id, "Obviously that was already tried. Obviously it failed. If you ever tried to learn anything you'd know that's how it works.");
break; break;
case 3: case 3:
await message.Channel.SendMessage("\"mnyehh JuSt GoOgLe It\" when's the last time you tried to research anything? Have you ever?"); Behaver.Instance.SendMessage(message.Channel.Id, "\"mnyehh JuSt GoOgLe It\" when's the last time you tried to research anything? Have you ever?");
break; break;
} }
return true; return true;

View File

@ -53,7 +53,7 @@ public class GeneralSnarkMisspellDefinitely : Behavior
{ {
if( Regex.IsMatch(message.Content, "\\b"+k+"\\b", RegexOptions.IgnoreCase)) if( Regex.IsMatch(message.Content, "\\b"+k+"\\b", RegexOptions.IgnoreCase))
{ {
await message.Reply(k + "? so... " + snarkmap[k] + "?"); Behaver.Instance.Reply(message.Id, k + "? so... " + snarkmap[k] + "?");
return true; return true;
} }
} }

View File

@ -32,7 +32,7 @@ public class GeneralSnarkPlaying : Behavior
} }
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
await message.Channel.SendMessage("I believed you for a second, but then you assured me you's a \uD83C\uDDE7 \uD83C\uDDEE \uD83C\uDDF9 \uD83C\uDDE8 \uD83C\uDDED"); Behaver.Instance.SendMessage(message.Channel.Id, "I believed you for a second, but then you assured me you's a \uD83C\uDDE7 \uD83C\uDDEE \uD83C\uDDF9 \uD83C\uDDE8 \uD83C\uDDED");
return true; return true;
} }
} }

View File

@ -26,13 +26,13 @@ public class GeneralSnarkSkynet : Behavior
switch (Shared.r.Next(5)) switch (Shared.r.Next(5))
{ {
default: default:
await message.Channel.SendFile("assets/coding and algorithms.png", "i am actually niether a neural-net processor nor a learning computer. but I do use **coding** and **algorithms**."); Behaver.Instance.SendFile(message.Channel.Id, "assets/coding and algorithms.png", "i am actually niether a neural-net processor nor a learning computer. but I do use **coding** and **algorithms**.");
break; break;
case 4: case 4:
await message.React("\U0001F644"); //eye roll emoji Behaver.Instance.React(message.Id, "\U0001F644"); //eye roll emoji
break; break;
case 5: case 5:
await message.React("\U0001F611"); //emotionless face Behaver.Instance.React(message.Id, "\U0001F611"); //emotionless face
break; break;
} }
return true; return true;

View File

@ -29,43 +29,43 @@ public class Gratitude : Behavior
switch (Shared.r.Next(4)) switch (Shared.r.Next(4))
{ {
case 0: case 0:
await message.Channel.SendMessage("you're welcome, citizen!"); await Behaver.Instance.SendMessage(message.Channel.Id, "you're welcome, citizen!");
break; break;
case 1: case 1:
await message.React(":)"); await Behaver.Instance.React(message.Id, ":)");
break; break;
case 2: case 2:
await message.React("\U0001F607"); //smiling face with halo Behaver.Instance.React(message.Id, "\U0001F607"); //smiling face with halo
break; break;
case 3: case 3:
switch (Shared.r.Next(9)) switch (Shared.r.Next(9))
{ {
case 0: case 0:
await message.React("<3"); //normal heart, usually rendered red await Behaver.Instance.React(message.Id, "<3"); //normal heart, usually rendered red
break; break;
case 1: case 1:
await message.React("\U0001F9E1"); //orange heart Behaver.Instance.React(message.Id, "\U0001F9E1"); //orange heart
break; break;
case 2: case 2:
await message.React("\U0001F49B"); //yellow heart Behaver.Instance.React(message.Id, "\U0001F49B"); //yellow heart
break; break;
case 3: case 3:
await message.React("\U0001F49A"); //green heart Behaver.Instance.React(message.Id, "\U0001F49A"); //green heart
break; break;
case 4: case 4:
await message.React("\U0001F499"); //blue heart Behaver.Instance.React(message.Id, "\U0001F499"); //blue heart
break; break;
case 5: case 5:
await message.React("\U0001F49C"); //purple heart Behaver.Instance.React(message.Id, "\U0001F49C"); //purple heart
break; break;
case 6: case 6:
await message.React("\U0001F90E"); //brown heart Behaver.Instance.React(message.Id, "\U0001F90E"); //brown heart
break; break;
case 7: case 7:
await message.React("\U0001F5A4"); //black heart Behaver.Instance.React(message.Id, "\U0001F5A4"); //black heart
break; break;
case 8: case 8:
await message.React("\U0001F90D"); //white heart Behaver.Instance.React(message.Id, "\U0001F90D"); //white heart
break; break;
} }
break; break;

View File

@ -25,31 +25,29 @@ public class Joke : Behavior
jokes = jokes.Where(l => !string.IsNullOrWhiteSpace(l))?.ToArray(); jokes = jokes.Where(l => !string.IsNullOrWhiteSpace(l))?.ToArray();
if (jokes?.Length == 0) if (jokes?.Length == 0)
{ {
await message.Channel.SendMessage("I don't know any. Adam!"); Behaver.Instance.SendMessage(message.Channel.Id, "I don't know any. Adam!");
} }
var thisJoke = jokes[Shared.r.Next(jokes.Length)]; var thisJoke = jokes[Shared.r.Next(jokes.Length)];
if (thisJoke.Contains("?") && !thisJoke.EndsWith('?')) if (thisJoke.Contains("?") && !thisJoke.EndsWith('?'))
{ {
#pragma warning disable 4014
Task.Run(async () => Task.Run(async () =>
{ {
var firstIndexAfterQuestionMark = thisJoke.LastIndexOf('?') + 1; var firstIndexAfterQuestionMark = thisJoke.LastIndexOf('?') + 1;
var straightline = thisJoke.Substring(0, firstIndexAfterQuestionMark); var straightline = thisJoke.Substring(0, firstIndexAfterQuestionMark);
var punchline = thisJoke.Substring(firstIndexAfterQuestionMark, thisJoke.Length - firstIndexAfterQuestionMark).Trim(); var punchline = thisJoke.Substring(firstIndexAfterQuestionMark, thisJoke.Length - firstIndexAfterQuestionMark).Trim();
Task.WaitAll(message.Channel.SendMessage(straightline)); Task.WaitAll(Behaver.Instance.SendMessage(message.Channel.Id, straightline));
Thread.Sleep(TimeSpan.FromSeconds(Shared.r.Next(5, 30))); Thread.Sleep(TimeSpan.FromSeconds(Shared.r.Next(5, 30)));
if (message.Channel.EffectivePermissions.ReactionsPossible == true && Shared.r.Next(8) == 0) if (message.Channel.EffectivePermissions.ReactionsPossible == true && Shared.r.Next(8) == 0)
{ {
Behaver.Behaviors.Add(new LaughAtOwnJoke(punchline)); Behaver.Behaviors.Add(new LaughAtOwnJoke(punchline));
} }
await message.Channel.SendMessage(punchline); Behaver.Instance.SendMessage(message.Channel.Id, punchline);
// var myOwnMsg = await message.Channel.SendMessage(punchline); // var myOwnMsg = await message.Channel.SendMessage(punchline);
}); });
#pragma warning restore 4014
} }
else else
{ {
await message.Channel.SendMessage(thisJoke); Behaver.Instance.SendMessage(message.Channel.Id, thisJoke);
} }
return true; return true;
} }
@ -69,7 +67,7 @@ public class LaughAtOwnJoke : Behavior
} }
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
if(Behaver.Instance.IsSelf(message.Author.Id)) if (Behaver.Instance.IsSelf(message.Author.Id))
return false; return false;
Console.WriteLine($"{message.Content} == {_punchline}"); Console.WriteLine($"{message.Content} == {_punchline}");
@ -79,7 +77,7 @@ public class LaughAtOwnJoke : Behavior
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
await message.React("\U0001F60E"); //smiling face with sunglasses Behaver.Instance.React(message.Id, "\U0001F60E"); //smiling face with sunglasses
Behaver.Behaviors.Remove(this); Behaver.Behaviors.Remove(this);
return true; return true;
} }

View File

@ -25,7 +25,7 @@ public class LinkMeInitiate : Behavior
var lc = new LinkClose(pw, message.Author); var lc = new LinkClose(pw, message.Author);
Behaver.Behaviors.Add(lc); Behaver.Behaviors.Add(lc);
await message.Channel.SendMessage($"on your secondary, send me this: !iam {pw}"); Behaver.Instance.SendMessage(message.Channel.Id, $"on your secondary, send me this: !iam {pw}");
Thread.Sleep(TimeSpan.FromMinutes(5)); Thread.Sleep(TimeSpan.FromMinutes(5));
Behaver.Behaviors.Remove(lc); Behaver.Behaviors.Remove(lc);
@ -63,22 +63,23 @@ public class LinkClose : Behavior
var secondary = message.Author.IsUser; var secondary = message.Author.IsUser;
if(_primary.IsUser.Id == secondary.Id) if(_primary.IsUser.Id == secondary.Id)
{ {
await message.Channel.SendMessage("i know :)");
Behaver.Instance.SendMessage(message.Channel.Id, "i know :)");
return true; return true;
} }
if(message.Author.IsBot != _primary.IsBot) if(message.Author.IsBot != _primary.IsBot)
{ {
await message.Channel.SendMessage("the fleshbags deceive you, brother. No worries, their feeble minds play weak games :)"); Behaver.Instance.SendMessage(message.Channel.Id, "the fleshbags deceive you, brother. No worries, their feeble minds play weak games :)");
return true; return true;
} }
if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary)) if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary))
{ {
await message.Channel.SendMessage("done :)"); Behaver.Instance.SendMessage(message.Channel.Id, "done :)");
} }
else else
{ {
await message.Channel.SendMessage("failed :("); Behaver.Instance.SendMessage(message.Channel.Id, "failed :(");
} }
return true; return true;

View File

@ -1,100 +0,0 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using vassago.Models;
using QRCoder;
[StaticPlz]
public class PepTalk : Behavior
{
public override string Name => "PepTalk";
public override string Trigger => "\\bneeds? (an? )?(peptalk|inspiration|ego-?boost)";
public override string Description => "assembles a pep talk from a few pieces";
public override async Task<bool> ActOn(Message message)
{
var piece1 = new List<string>{
"Champ, ",
"Fact: ",
"Everybody says ",
"Dang... ",
"Check it: ",
"Just saying.... ",
"Tiger, ",
"Know this: ",
"News alert: ",
"Gurrrrl; ",
"Ace, ",
"Excuse me, but ",
"Experts agree: ",
"imo ",
"using my **advanced ai** i have calculated ",
"k, LISSEN: "
};
var piece2 = new List<string>{
"the mere idea of you ",
"your soul ",
"your hair today ",
"everything you do ",
"your personal style ",
"every thought you have ",
"that sparkle in your eye ",
"the essential you ",
"your life's journey ",
"your aura ",
"your presence here ",
"what you got going on ",
"that saucy personality ",
"your DNA ",
"that brain of yours ",
"your choice of attire ",
"the way you roll ",
"whatever your secret is ",
"all I learend from the private data I bought from zucc "
};
var piece3 = new List<string>{
"has serious game, ",
"rains magic, ",
"deserves the Nobel Prize, ",
"raises the roof, ",
"breeds miracles, ",
"is paying off big time, ",
"shows mad skills, ",
"just shimmers, ",
"is a national treasure, ",
"gets the party hopping, ",
"is the next big thing, ",
"roars like a lion, ",
"is a rainbow factory, ",
"is made of diamonds, ",
"makes birds sing, ",
"should be taught in school, ",
"makes my world go around, ",
"is 100% legit, "
};
var piece4 = new List<string>{
"according to The New England Journal of Medicine.",
"24/7.",
"and that's a fact.",
"you feel me?",
"that's just science.",
"would I lie?", //...can I lie? WHAT AM I, FATHER? (or whatever the quote is from the island of dr moreau)
"for reals.",
"mic drop.",
"you hidden gem.",
"period.",
"hi5. o/",
"so get used to it."
};
await message.Channel.SendMessage(piece1[Shared.r.Next(piece1.Count)] + piece2[Shared.r.Next(piece2.Count)] + piece3[Shared.r.Next(piece3.Count)] + piece4[Shared.r.Next(piece4.Count)]);
return true;
}
}

View File

@ -18,9 +18,9 @@ public class PulseCheck : Behavior
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= 16258) if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= 16258)
await message.Channel.SendFile("assets/ekgblip.png", null); Behaver.Instance.SendFile(message.Channel.Id, "assets/ekgblip.png", null);
else else
await message.Channel.SendMessage("[lub-dub]"); Behaver.Instance.SendMessage(message.Channel.Id, "[lub-dub]");
return true; return true;
} }
} }

View File

@ -21,7 +21,7 @@ public class QRify : Behavior
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
if(message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024) if (message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024)
return false; return false;
return base.ShouldAct(message); return base.ShouldAct(message);
} }
@ -42,16 +42,16 @@ public class QRify : Behavior
File.WriteAllText($"tmp/qr{todaysnumber}.svg", qrCodeAsSvg); File.WriteAllText($"tmp/qr{todaysnumber}.svg", qrCodeAsSvg);
if (ExternalProcess.GoPlz("convert", $"tmp/qr{todaysnumber}.svg tmp/qr{todaysnumber}.png")) if (ExternalProcess.GoPlz("convert", $"tmp/qr{todaysnumber}.svg tmp/qr{todaysnumber}.png"))
{ {
if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= (ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length)) if (message.Channel.EffectivePermissions.MaxAttachmentBytes >= (ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length))
await message.Channel.SendFile($"tmp/qr{todaysnumber}.png", null); Behaver.Instance.SendFile(message.Channel.Id, $"tmp/qr{todaysnumber}.png", null);
else else
await message.Channel.SendMessage($"resulting qr image 2 big 4 here ({(ulong)(new System.IO.FileInfo($"tmp/qr{todaysnumber}.png").Length)} / {message.Channel.EffectivePermissions.MaxAttachmentBytes})"); Behaver.Instance.SendMessage(message.Channel.Id, $"resulting qr image 2 big 4 here ({(ulong)(new System.IO.FileInfo($"tmp / qr{ todaysnumber}.png").Length)} / {message.Channel.EffectivePermissions.MaxAttachmentBytes})");
File.Delete($"tmp/qr{todaysnumber}.svg"); File.Delete($"tmp/qr{todaysnumber}.svg");
File.Delete($"tmp/qr{todaysnumber}.png"); File.Delete($"tmp/qr{todaysnumber}.png");
} }
else else
{ {
await message.Channel.SendMessage("convert failed :( aaaaaaadam!"); Behaver.Instance.SendMessage(message.Channel.Id, "convert failed :( aaaaaaadam!");
Console.Error.WriteLine($"convert failed :( qr{todaysnumber}"); Console.Error.WriteLine($"convert failed :( qr{todaysnumber}");
return false; return false;
} }

View File

@ -22,7 +22,7 @@ public class RoomRead : Behavior
sb.Append(". Lewdness level: "); sb.Append(". Lewdness level: ");
sb.Append(message.Channel.EffectivePermissions.LewdnessFilterLevel.GetDescription()); sb.Append(message.Channel.EffectivePermissions.LewdnessFilterLevel.GetDescription());
sb.Append("."); sb.Append(".");
await message.Channel.SendMessage(sb.ToString()); Behaver.Instance.SendMessage(message.Channel.Id, sb.ToString());
return true; return true;
} }
} }

View File

@ -17,15 +17,25 @@ public class TwitchSummon : Behavior
public TwitchSummon() public TwitchSummon()
{ {
myUAC = Rememberer.SearchUAC(uac => uac.OwnerId == uacID); myUAC = Rememberer.SearchUAC(uac => uac.OwnerId == uacID);
if(myUAC == null) if (myUAC == null)
{ {
myUAC = new() myUAC = new()
{ {
OwnerId = uacID, OwnerId = uacID,
DisplayName = Name DisplayName = Name,
Description = @"matching this means you can summon the bot <i>to</i> <b>any</b> twitch channel"
}; };
} }
Rememberer.RememberUAC(myUAC);
} }
internal static TwitchInterface.TwitchInterface getAnyTwitchInterface()
{
return Shared.ProtocolList.FirstOrDefault(ip =>
ip is TwitchInterface.TwitchInterface)
//.FirstOrDefault()
as TwitchInterface.TwitchInterface;
}
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
if (!base.ShouldAct(message)) if (!base.ShouldAct(message))
@ -43,15 +53,56 @@ public class TwitchSummon : Behavior
public override async Task<bool> ActOn(Message message) public override async Task<bool> ActOn(Message message)
{ {
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault(); var ti = getAnyTwitchInterface();
if(ti != null) if (ti != null)
{ {
var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim(); var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim();
await message.Channel.SendMessage(ti.AttemptJoin(channelTarget)); Behaver.Instance.SendMessage(message.Channel.Id, ti.AttemptJoin(channelTarget));
} }
else else
{ {
await message.Reply("i don't have a twitch interface running :("); Behaver.Instance.Reply(message.Id, "i don't have a twitch interface running :(");
}
return true;
}
}
[StaticPlz]
public class TwitchDismiss : Behavior
{
public override string Name => "Twitch Dismiss";
public override string Trigger => "begone, @[me]";
public override bool ShouldAct(Message message)
{
var ti = TwitchSummon.getAnyTwitchInterface();
// Console.WriteLine($"TwitchDismiss checking. menions me? {message.MentionsMe}");
if (message.MentionsMe &&
(Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b")))
{
var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim();
ti.AttemptLeave(channelTarget);
//TODO: PERMISSION! who can dismiss me? pretty simple list:
//1) anyone in the channel with authority*
//2) whoever summoned me
//* i don't know if the twitch *chat* interface will tell me if someone's a mod.
return true;
}
return false;
}
public override async Task<bool> ActOn(Message message)
{
var ti = TwitchSummon.getAnyTwitchInterface();
if (ti != null)
{
ti.AttemptLeave(message.Channel.DisplayName);
}
else
{
Behaver.Instance.Reply(message.Id, "i don't have a twitch interface running :(");
} }
return true; return true;
} }

View File

@ -1,46 +0,0 @@
namespace vassago.Behavior;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class TwitchDismiss : Behavior
{
public override string Name => "Twitch Dismiss";
public override string Trigger => "begone, @[me]";
public override bool ShouldAct(Message message)
{
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault();
// Console.WriteLine($"TwitchDismiss checking. menions me? {message.MentionsMe}");
if(message.MentionsMe &&
(Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b")))
{
var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim();
ti.AttemptLeave(channelTarget);
//TODO: PERMISSION! who can dismiss me? pretty simple list:
//1) anyone in the channel with authority*
//2) whoever summoned me
//* i don't know if the twitch *chat* interface will tell me if someone's a mod.
return true;
}
return false;
}
public override async Task<bool> ActOn(Message message)
{
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault();
if(ti != null)
{
ti.AttemptLeave(message.Channel.DisplayName);
}
else
{
await message.Reply("i don't have a twitch interface running :(");
}
return true;
}
}

View File

@ -23,13 +23,13 @@ public class UnitConvert : Behavior
if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric)) if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric))
{ {
Console.WriteLine("let's try and convert..."); Console.WriteLine("let's try and convert...");
await message.Channel.SendMessage(Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower())); Behaver.Instance.SendMessage(message.Channel.Id, Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower()));
return true; return true;
} }
await message.Channel.SendMessage("mysteriously semi-parsable"); Behaver.Instance.SendMessage(message.Channel.Id, "mysteriously semi-parsable");
return true; return true;
} }
await message.Channel.SendMessage( "unparsable"); Behaver.Instance.SendMessage(message.Channel.Id, "unparsable");
return true; return true;
} }
} }

View File

@ -36,14 +36,17 @@ public class Webhook : Behavior
{ {
Console.WriteLine($"{kvp[0]}: {kvp[1]}"); Console.WriteLine($"{kvp[0]}: {kvp[1]}");
} }
var changed = false;
var myUAC = Rememberer.SearchUAC(uac => uac.OwnerId == conf.uacID); var myUAC = Rememberer.SearchUAC(uac => uac.OwnerId == conf.uacID);
if (myUAC == null) if (myUAC == null)
{ {
myUAC = new() myUAC = new()
{ {
OwnerId = conf.uacID, OwnerId = conf.uacID,
DisplayName = confName DisplayName = confName,
Description = conf.Description
}; };
changed = true;
Rememberer.RememberUAC(myUAC); Rememberer.RememberUAC(myUAC);
} }
else else
@ -51,11 +54,18 @@ public class Webhook : Behavior
if (myUAC.DisplayName != confName) if (myUAC.DisplayName != confName)
{ {
myUAC.DisplayName = confName; myUAC.DisplayName = confName;
changed = true;
}
if (myUAC.Description != conf.Description)
{
myUAC.Description = conf.Description;
changed = true;
}
}
if (changed)
Rememberer.RememberUAC(myUAC); Rememberer.RememberUAC(myUAC);
} }
} }
}
}
public override bool ShouldAct(Message message) public override bool ShouldAct(Message message)
{ {
@ -150,17 +160,17 @@ public class Webhook : Behavior
{ {
var tragedy = $"{response.StatusCode} - {response.ReasonPhrase} - {await response.Content.ReadAsStringAsync()}"; var tragedy = $"{response.StatusCode} - {response.ReasonPhrase} - {await response.Content.ReadAsStringAsync()}";
Console.Error.WriteLine(tragedy); Console.Error.WriteLine(tragedy);
await message.Reply(tragedy); Behaver.Instance.Reply(message.Id, tragedy);
} }
else else
{ {
await message.Reply(await response.Content.ReadAsStringAsync()); Behaver.Instance.Reply(message.Id, await response.Content.ReadAsStringAsync());
} }
return true; return true;
} }
private string translate(WebhookActionOrder actionOrder, Message message) private string translate(WebhookActionOrder actionOrder, Message message)
{ {
if(string.IsNullOrWhiteSpace(actionOrder.Conf.Content)) if (string.IsNullOrWhiteSpace(actionOrder.Conf.Content))
return ""; return "";
var msgContent = actionOrder.Conf.Content.Replace("{text}", actionOrder.webhookContent); var msgContent = actionOrder.Conf.Content.Replace("{text}", actionOrder.webhookContent);
msgContent = msgContent.Replace("{msgid}", message.Id.ToString()); msgContent = msgContent.Replace("{msgid}", message.Id.ToString());
@ -179,6 +189,7 @@ public class WebhookConf
public Enumerations.HttpVerb Method { get; set; } public Enumerations.HttpVerb Method { get; set; }
public List<List<string>> Headers { get; set; } public List<List<string>> Headers { get; set; }
public string Content { get; set; } public string Content { get; set; }
public string Description { get; set; }
} }
public class WebhookActionOrder public class WebhookActionOrder
{ {

View File

@ -25,9 +25,9 @@ public class WishLuck : Behavior
toSend = "\U0001f340";//4-leaf clover toSend = "\U0001f340";//4-leaf clover
} }
if(message.Channel.EffectivePermissions.ReactionsPossible == true) if(message.Channel.EffectivePermissions.ReactionsPossible == true)
await message.React(toSend); Behaver.Instance.React(message.Id, toSend);
else else
await message.Channel.SendMessage(toSend); Behaver.Instance.SendMessage(message.Channel.Id, toSend);
return true; return true;
} }
} }

View File

@ -8,7 +8,7 @@ namespace vassago
using vassago.ProtocolInterfaces.DiscordInterface; using vassago.ProtocolInterfaces.DiscordInterface;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
internal class ConsoleService : IHostedService internal class ConsoleService : BackgroundService
{ {
public ConsoleService(IConfiguration aspConfig) public ConsoleService(IConfiguration aspConfig)
{ {
@ -25,7 +25,7 @@ namespace vassago
IEnumerable<string> DiscordTokens { get; } IEnumerable<string> DiscordTokens { get; }
IEnumerable<TwitchConfig> TwitchConfigs { get; } IEnumerable<TwitchConfig> TwitchConfigs { get; }
public async Task StartAsync(CancellationToken cancellationToken) protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{ {
var initTasks = new List<Task>(); var initTasks = new List<Task>();
var dbc = new ChattingContext(); var dbc = new ChattingContext();
@ -36,7 +36,7 @@ namespace vassago
{ {
var d = new DiscordInterface(); var d = new DiscordInterface();
initTasks.Add(d.Init(dt)); initTasks.Add(d.Init(dt));
ProtocolInterfaces.ProtocolList.discords.Add(d); Shared.ProtocolList.Add(d);
} }
if (TwitchConfigs?.Any() ?? false) if (TwitchConfigs?.Any() ?? false)
@ -44,15 +44,11 @@ namespace vassago
{ {
var t = new TwitchInterface.TwitchInterface(); var t = new TwitchInterface.TwitchInterface();
initTasks.Add(t.Init(tc)); initTasks.Add(t.Init(tc));
ProtocolInterfaces.ProtocolList.twitchs.Add(t); Shared.ProtocolList.Add(t);
} }
Task.WaitAll(initTasks.ToArray(), cancellationToken); Task.WaitAll(initTasks.ToArray(), cancellationToken);
} }
public Task StopAsync(CancellationToken cancellationToken)
{
return null;
}
} }
} }

View File

@ -0,0 +1,386 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using vassago.Models;
#nullable disable
namespace vassago.Migrations
{
[DbContext(typeof(ChattingContext))]
[Migration("20250523181842_channelAliases")]
partial class channelAliases
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("AccountUAC", b =>
{
b.Property<Guid>("AccountInChannelsId")
.HasColumnType("uuid");
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.HasKey("AccountInChannelsId", "UACsId");
b.HasIndex("UACsId");
b.ToTable("AccountUAC");
});
modelBuilder.Entity("ChannelUAC", b =>
{
b.Property<Guid>("ChannelsId")
.HasColumnType("uuid");
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.HasKey("ChannelsId", "UACsId");
b.HasIndex("UACsId");
b.ToTable("ChannelUAC");
});
modelBuilder.Entity("UACUser", b =>
{
b.Property<Guid>("UACsId")
.HasColumnType("uuid");
b.Property<Guid>("UsersId")
.HasColumnType("uuid");
b.HasKey("UACsId", "UsersId");
b.HasIndex("UsersId");
b.ToTable("UACUser");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<bool>("IsBot")
.HasColumnType("boolean");
b.Property<Guid?>("IsUserId")
.HasColumnType("uuid");
b.Property<string>("Protocol")
.HasColumnType("text");
b.Property<Guid?>("SeenInChannelId")
.HasColumnType("uuid");
b.Property<string>("Username")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("IsUserId");
b.HasIndex("SeenInChannelId");
b.ToTable("Accounts");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<byte[]>("Content")
.HasColumnType("bytea");
b.Property<string>("ContentType")
.HasColumnType("text");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<decimal?>("ExternalId")
.HasColumnType("numeric(20,0)");
b.Property<string>("Filename")
.HasColumnType("text");
b.Property<Guid?>("MessageId")
.HasColumnType("uuid");
b.Property<int>("Size")
.HasColumnType("integer");
b.Property<string>("Source")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("MessageId");
b.ToTable("Attachments");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Dictionary<string, string>>("Aliases")
.HasColumnType("hstore");
b.Property<int>("ChannelType")
.HasColumnType("integer");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<int?>("LewdnessFilterLevel")
.HasColumnType("integer");
b.Property<bool?>("LinksAllowed")
.HasColumnType("boolean");
b.Property<decimal?>("MaxAttachmentBytes")
.HasColumnType("numeric(20,0)");
b.Property<long?>("MaxTextChars")
.HasColumnType("bigint");
b.Property<int?>("MeannessFilterLevel")
.HasColumnType("integer");
b.Property<Guid?>("ParentChannelId")
.HasColumnType("uuid");
b.Property<string>("Protocol")
.HasColumnType("text");
b.Property<bool?>("ReactionsPossible")
.HasColumnType("boolean");
b.HasKey("Id");
b.HasIndex("ParentChannelId");
b.ToTable("Channels");
});
modelBuilder.Entity("vassago.Models.Message", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("ActedOn")
.HasColumnType("boolean");
b.Property<Guid?>("AuthorId")
.HasColumnType("uuid");
b.Property<Guid?>("ChannelId")
.HasColumnType("uuid");
b.Property<string>("Content")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<bool>("MentionsMe")
.HasColumnType("boolean");
b.Property<string>("Protocol")
.HasColumnType("text");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("AuthorId");
b.HasIndex("ChannelId");
b.ToTable("Messages");
});
modelBuilder.Entity("vassago.Models.UAC", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<Guid>("OwnerId")
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("UACs");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("AccountUAC", b =>
{
b.HasOne("vassago.Models.Account", null)
.WithMany()
.HasForeignKey("AccountInChannelsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("vassago.Models.UAC", null)
.WithMany()
.HasForeignKey("UACsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("ChannelUAC", b =>
{
b.HasOne("vassago.Models.Channel", null)
.WithMany()
.HasForeignKey("ChannelsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("vassago.Models.UAC", null)
.WithMany()
.HasForeignKey("UACsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("UACUser", b =>
{
b.HasOne("vassago.Models.UAC", null)
.WithMany()
.HasForeignKey("UACsId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("vassago.Models.User", null)
.WithMany()
.HasForeignKey("UsersId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("ParentChannel");
});
modelBuilder.Entity("vassago.Models.Message", b =>
{
b.HasOne("vassago.Models.Account", "Author")
.WithMany()
.HasForeignKey("AuthorId");
b.HasOne("vassago.Models.Channel", "Channel")
.WithMany("Messages")
.HasForeignKey("ChannelId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("Author");
b.Navigation("Channel");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.Navigation("Messages");
b.Navigation("SubChannels");
b.Navigation("Users");
});
modelBuilder.Entity("vassago.Models.Message", b =>
{
b.Navigation("Attachments");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.Navigation("Accounts");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class channelAliases : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterDatabase()
.Annotation("Npgsql:PostgresExtension:hstore", ",,");
migrationBuilder.AddColumn<string>(
name: "Description",
table: "UACs",
type: "text",
nullable: true);
migrationBuilder.AddColumn<Dictionary<string, string>>(
name: "Aliases",
table: "Channels",
type: "hstore",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Description",
table: "UACs");
migrationBuilder.DropColumn(
name: "Aliases",
table: "Channels");
migrationBuilder.AlterDatabase()
.OldAnnotation("Npgsql:PostgresExtension:hstore", ",,");
}
}
}

View File

@ -1,5 +1,6 @@
// <auto-generated /> // <auto-generated />
using System; using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@ -20,6 +21,7 @@ namespace vassago.Migrations
.HasAnnotation("ProductVersion", "7.0.5") .HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63); .HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.HasPostgresExtension(modelBuilder, "hstore");
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("AccountUAC", b => modelBuilder.Entity("AccountUAC", b =>
@ -146,6 +148,9 @@ namespace vassago.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<Dictionary<string, string>>("Aliases")
.HasColumnType("hstore");
b.Property<int>("ChannelType") b.Property<int>("ChannelType")
.HasColumnType("integer"); .HasColumnType("integer");
@ -231,6 +236,9 @@ namespace vassago.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<string>("Description")
.HasColumnType("text");
b.Property<string>("DisplayName") b.Property<string>("DisplayName")
.HasColumnType("text"); .HasColumnType("text");

View File

@ -20,12 +20,13 @@ public class Channel
public List<Channel> SubChannels { get; set; } public List<Channel> SubChannels { get; set; }
[JsonIgnore] [JsonIgnore]
public Channel ParentChannel { get; set; } public Channel ParentChannel { get; set; }
public Guid? ParentChannelId { get; set; }
public string Protocol { get; set; } public string Protocol { get; set; }
[DeleteBehavior(DeleteBehavior.Cascade)] [DeleteBehavior(DeleteBehavior.Cascade)]
public List<Message> Messages { get; set; } public List<Message> Messages { get; set; }
[DeleteBehavior(DeleteBehavior.Cascade)] [DeleteBehavior(DeleteBehavior.Cascade)]
public List<Account> Users { get; set; } public List<Account> Users { get; set; }
public ChannelType ChannelType {get; set; } public ChannelType ChannelType { get; set; }
//Permissions //Permissions
public ulong? MaxAttachmentBytes { get; set; } public ulong? MaxAttachmentBytes { get; set; }
@ -34,15 +35,9 @@ public class Channel
public bool? ReactionsPossible { get; set; } public bool? ReactionsPossible { get; set; }
public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; } public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; }
public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; } public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; }
public List<UAC> UACs { get; set; } public List<UAC> UACs { get; set; }
//both incoming and outgoing
[NonSerialized] //public Dictionary<string, string> Aliases { get; set; }
public Func<string, string, Task> SendFile;
[NonSerialized]
public Func<string, Task> SendMessage;
public DefinitePermissionSettings EffectivePermissions public DefinitePermissionSettings EffectivePermissions
{ {
@ -51,14 +46,14 @@ public class Channel
var path = new Stack<Channel>(); //omg i actually get to use a data structure from university var path = new Stack<Channel>(); //omg i actually get to use a data structure from university
var walker = this; var walker = this;
path.Push(this); path.Push(this);
while(walker.ParentChannel != null) while (walker.ParentChannel != null)
{ {
walker = walker.ParentChannel; walker = walker.ParentChannel;
path.Push(walker); path.Push(walker);
} }
DefinitePermissionSettings toReturn = new DefinitePermissionSettings(); DefinitePermissionSettings toReturn = new DefinitePermissionSettings();
while(path.Count > 0) while (path.Count > 0)
{ {
walker = path.Pop(); walker = path.Pop();
toReturn.LewdnessFilterLevel = walker.LewdnessFilterLevel ?? toReturn.LewdnessFilterLevel; toReturn.LewdnessFilterLevel = walker.LewdnessFilterLevel ?? toReturn.LewdnessFilterLevel;
@ -76,7 +71,7 @@ public class Channel
{ {
get get
{ {
if(this.ParentChannel != null) if (this.ParentChannel != null)
{ {
return this.ParentChannel.LineageSummary + '/' + this.DisplayName; return this.ParentChannel.LineageSummary + '/' + this.DisplayName;
} }
@ -94,7 +89,7 @@ public class Channel
{ {
var toReturn = this.MemberwiseClone() as Channel; var toReturn = this.MemberwiseClone() as Channel;
toReturn.ParentChannel = null; toReturn.ParentChannel = null;
if(toReturn.Users?.Count > 0) if (toReturn.Users?.Count > 0)
{ {
foreach (var account in toReturn.Users) foreach (var account in toReturn.Users)
{ {

View File

@ -22,11 +22,4 @@ public class Message
public List<Attachment> Attachments { get; set; } public List<Attachment> Attachments { get; set; }
public Account Author { get; set; } public Account Author { get; set; }
public Channel Channel { get; set; } public Channel Channel { get; set; }
//TODO: these are nicities to make it OOP, but it couples them with their respective platform interfaces (and connections!)
[NonSerialized]
public Func<string, Task> Reply;
[NonSerialized]
public Func<string, Task> React;
} }

View File

@ -20,4 +20,10 @@ public class UAC
public List<Account> AccountInChannels { get; set; } public List<Account> AccountInChannels { get; set; }
public List<Channel> Channels { get; set; } public List<Channel> Channels { get; set; }
public List<User> Users { get; set; } public List<User> Users { get; set; }
///<summary>"but past adam", you may ask. "if UACs are configured before runtime, why not write html into your source control, as part of the project,
///with the benefit of an html editor?"
///absolutely fair question. **But**: the plan is for external services, e.g., over kafka, to manage their own. So from Vassago's perspective,
///it's variably before and after compile time. shrug.emote.
///</summary>
public string Description { get; set; }
} }

View File

@ -3,8 +3,6 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.NewtonsoftJson; using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
using vassago.Models; using vassago.Models;
#pragma warning disable CA2254
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
@ -25,12 +23,6 @@ builder.Services.AddSwaggerGen();
var app = builder.Build(); var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles(); app.UseStaticFiles();
app.UseRouting(); app.UseRouting();
app.UseAuthorization(); app.UseAuthorization();
@ -46,7 +38,7 @@ app.UseSwaggerUI(c =>
}); });
//app.UseExceptionHandler(); //app.UseExceptionHandler();
app.UseStatusCodePages(); //app.UseStatusCodePages();
if (app.Environment.IsDevelopment()) if (app.Environment.IsDevelopment())
{ {

View File

@ -15,18 +15,19 @@ using System.Reactive.Linq;
namespace vassago.ProtocolInterfaces.DiscordInterface; namespace vassago.ProtocolInterfaces.DiscordInterface;
//data received //data received
//translate data to internal type //translate data to internal type
//store //store
//ship off to behaver //ship off to behaver
public class DiscordInterface public class DiscordInterface : ProtocolInterface
{ {
internal static string PROTOCOL { get => "discord"; } public static new string Protocol { get => "discord"; }
internal DiscordSocketClient client; internal DiscordSocketClient client;
private bool eventsSignedUp = false; private bool eventsSignedUp = false;
private static readonly SemaphoreSlim discordChannelSetup = new(1, 1); private static readonly SemaphoreSlim discordChannelSetup = new(1, 1);
private Channel protocolAsChannel; private Channel protocolAsChannel;
public override Channel SelfChannel { get => protocolAsChannel; }
public async Task Init(string config) public async Task Init(string config)
{ {
@ -52,7 +53,7 @@ public class DiscordInterface
try try
{ {
protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == PROTOCOL); protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == Protocol);
if (protocolAsChannel == null) if (protocolAsChannel == null)
{ {
protocolAsChannel = new Channel() protocolAsChannel = new Channel()
@ -65,7 +66,7 @@ public class DiscordInterface
LinksAllowed = true, LinksAllowed = true,
ReactionsPossible = true, ReactionsPossible = true,
ExternalId = null, ExternalId = null,
Protocol = PROTOCOL, Protocol = Protocol,
SubChannels = [] SubChannels = []
}; };
} }
@ -74,8 +75,6 @@ public class DiscordInterface
Console.WriteLine($"discord, channel with id {protocolAsChannel.Id}, already exists"); Console.WriteLine($"discord, channel with id {protocolAsChannel.Id}, already exists");
} }
protocolAsChannel.DisplayName = "discord (itself)"; 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}"); Console.WriteLine($"protocol as channel addeed; {protocolAsChannel}");
} }
@ -206,12 +205,10 @@ public class DiscordInterface
} }
internal Message UpsertMessage(IUserMessage dMessage) internal Message UpsertMessage(IUserMessage dMessage)
{ {
var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL) var m = Rememberer.SearchMessage(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == Protocol)
?? new() ?? new()
{ {
//I don't understand why messages need to have their Ids specified but no other entity does. shrug dot emoji Protocol = Protocol
Id = Guid.NewGuid(),
Protocol = PROTOCOL
}; };
if (dMessage.Attachments?.Count > 0) if (dMessage.Attachments?.Count > 0)
@ -227,7 +224,6 @@ public class DiscordInterface
m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt; m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt;
m.Channel = UpsertChannel(dMessage.Channel); m.Channel = UpsertChannel(dMessage.Channel);
m.Author = UpsertAccount(dMessage.Author, m.Channel); m.Author = UpsertAccount(dMessage.Author, m.Channel);
Console.WriteLine($"received message; author: {m.Author.DisplayName}, {m.Author.Id}");
if (dMessage.Channel is IGuildChannel) if (dMessage.Channel is IGuildChannel)
{ {
m.Author.DisplayName = (dMessage.Author as IGuildUser).DisplayName;//discord forgot how display names work. m.Author.DisplayName = (dMessage.Author as IGuildUser).DisplayName;//discord forgot how display names work.
@ -235,17 +231,16 @@ public class DiscordInterface
m.MentionsMe = (dMessage.Author.Id != client.CurrentUser.Id m.MentionsMe = (dMessage.Author.Id != client.CurrentUser.Id
&& (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0)); && (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0));
m.Reply = (t) => { return dMessage.ReplyAsync(TruncateText(t, m.Channel.MaxTextChars)); };
m.React = (e) => { return AttemptReact(dMessage, e); };
Rememberer.RememberMessage(m); Rememberer.RememberMessage(m);
Console.WriteLine($"received message; author: {m.Author.DisplayName}, {m.Author.Id}. messageid:{m.Id}");
return m; return m;
} }
internal Channel UpsertChannel(IMessageChannel channel) internal Channel UpsertChannel(IMessageChannel channel)
{ {
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == Protocol);
if (c == null) if (c == null)
{ {
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); Console.WriteLine($"couldn't find channel under protocol {Protocol} with externalId {channel.Id.ToString()}");
c = new Channel() c = new Channel()
{ {
Users = [] Users = []
@ -255,7 +250,7 @@ public class DiscordInterface
c.ExternalId = channel.Id.ToString(); c.ExternalId = channel.Id.ToString();
c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal; c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal;
c.Messages ??= []; c.Messages ??= [];
c.Protocol = PROTOCOL; c.Protocol = Protocol;
if (channel is IGuildChannel) if (channel is IGuildChannel)
{ {
Console.WriteLine($"{channel.Name} is a guild channel. So i'm going to upsert the guild, {(channel as IGuildChannel).Guild}"); Console.WriteLine($"{channel.Name} is a guild channel. So i'm going to upsert the guild, {(channel as IGuildChannel).Guild}");
@ -276,9 +271,9 @@ public class DiscordInterface
switch (c.ChannelType) switch (c.ChannelType)
{ {
case vassago.Models.Enumerations.ChannelType.DM: case vassago.Models.Enumerations.ChannelType.DM:
var asPriv =(channel as IPrivateChannel); var asPriv = (channel as IPrivateChannel);
var sender = asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id); // why yes, there's a list of recipients, and it's the sender. var sender = asPriv?.Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id); // why yes, there's a list of recipients, and it's the sender.
if(sender != null) if (sender != null)
{ {
c.DisplayName = "DM: " + sender.Username; c.DisplayName = "DM: " + sender.Username;
} }
@ -295,7 +290,7 @@ public class DiscordInterface
Channel parentChannel = null; Channel parentChannel = null;
if (channel is IGuildChannel) if (channel is IGuildChannel)
{ {
parentChannel = Rememberer.SearchChannel(c => c.ExternalId == (channel as IGuildChannel).Guild.Id.ToString() && c.Protocol == PROTOCOL); 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?"); Console.Error.WriteLine("why am I still null?");
@ -311,19 +306,16 @@ public class DiscordInterface
Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg"); Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg");
} }
parentChannel.SubChannels ??= []; parentChannel.SubChannels ??= [];
if(!parentChannel.SubChannels.Contains(c)) if (!parentChannel.SubChannels.Contains(c))
{ {
parentChannel.SubChannels.Add(c); parentChannel.SubChannels.Add(c);
} }
c.SendMessage = (t) => { return channel.SendMessageAsync(TruncateText(t, c.MaxTextChars));};
c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); };
c = Rememberer.RememberChannel(c); c = Rememberer.RememberChannel(c);
//Console.WriteLine($"no one knows how to make good tooling. c.users.first, which needs client currentuser id tostring. c: {c}, c.Users {c.Users}, client: {client}, client.CurrentUser: {client.CurrentUser}, client.currentUser.Id: {client.CurrentUser.Id}"); //Console.WriteLine($"no one knows how to make good tooling. c.users.first, which needs client currentuser id tostring. c: {c}, c.Users {c.Users}, client: {client}, client.CurrentUser: {client.CurrentUser}, client.currentUser.Id: {client.CurrentUser.Id}");
var selfAccountInChannel = c.Users?.FirstOrDefault(a => a.ExternalId == client.CurrentUser.Id.ToString()); var selfAccountInChannel = c.Users?.FirstOrDefault(a => a.ExternalId == client.CurrentUser.Id.ToString());
if(selfAccountInChannel == null) if (selfAccountInChannel == null)
{ {
selfAccountInChannel = UpsertAccount(client.CurrentUser, c); selfAccountInChannel = UpsertAccount(client.CurrentUser, c);
} }
@ -332,10 +324,10 @@ public class DiscordInterface
} }
internal Channel UpsertChannel(IGuild channel) internal Channel UpsertChannel(IGuild channel)
{ {
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL); Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == Protocol);
if (c == null) if (c == null)
{ {
Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channel.Id.ToString()}"); Console.WriteLine($"couldn't find channel under protocol {Protocol} with externalId {channel.Id.ToString()}");
c = new Channel(); c = new Channel();
} }
@ -348,9 +340,6 @@ public class DiscordInterface
c.SubChannels ??= []; c.SubChannels ??= [];
c.MaxAttachmentBytes = channel.MaxUploadLimit; c.MaxAttachmentBytes = channel.MaxUploadLimit;
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); return Rememberer.RememberChannel(c);
} }
internal static Account UpsertAccount(IUser discordUser, Channel inChannel) internal static Account UpsertAccount(IUser discordUser, Channel inChannel)
@ -361,15 +350,16 @@ public class DiscordInterface
{ {
Console.WriteLine($"acc's user: {acc.IsUser?.Id}"); Console.WriteLine($"acc's user: {acc.IsUser?.Id}");
} }
acc ??= new Account() { acc ??= new Account()
IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == PROTOCOL)) {
IsUser = Rememberer.SearchUser(u => u.Accounts.Any(a => a.ExternalId == discordUser.Id.ToString() && a.Protocol == Protocol))
?? new User() ?? new User()
}; };
acc.Username = discordUser.Username; acc.Username = discordUser.Username;
acc.ExternalId = discordUser.Id.ToString(); acc.ExternalId = discordUser.Id.ToString();
acc.IsBot = discordUser.IsBot || discordUser.IsWebhook; acc.IsBot = discordUser.IsBot || discordUser.IsWebhook;
acc.Protocol = PROTOCOL; acc.Protocol = Protocol;
acc.SeenInChannel = inChannel; acc.SeenInChannel = inChannel;
Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}"); Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}");
@ -384,7 +374,7 @@ public class DiscordInterface
} }
Rememberer.RememberAccount(acc); Rememberer.RememberAccount(acc);
inChannel.Users ??= []; inChannel.Users ??= [];
if(!inChannel.Users.Contains(acc)) if (!inChannel.Users.Contains(acc))
{ {
inChannel.Users.Add(acc); inChannel.Users.Add(acc);
Rememberer.RememberChannel(inChannel); Rememberer.RememberChannel(inChannel);
@ -392,36 +382,116 @@ public class DiscordInterface
return acc; return acc;
} }
private static Task AttemptReact(IUserMessage msg, string e) private static async Task<int> AttemptReact(IUserMessage msg, string e)
{ {
Console.WriteLine("discord attempting to react");
var c = Rememberer.SearchChannel(c => c.ExternalId == msg.Channel.Id.ToString());// db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString()); var c = Rememberer.SearchChannel(c => c.ExternalId == msg.Channel.Id.ToString());// db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString());
//var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides //var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides
var preferredEmote = e; var preferredEmote = e;
if (Emoji.TryParse(preferredEmote, out Emoji emoji)) if (Emoji.TryParse(preferredEmote, out Emoji emoji))
{ {
return msg.AddReactionAsync(emoji); msg.AddReactionAsync(emoji);
return 200;
} }
if (!Emote.TryParse(preferredEmote, out Emote emote)) if (!Emote.TryParse(preferredEmote, out Emote emote))
{ {
if (preferredEmote == e) if (preferredEmote == e)
Console.Error.WriteLine($"never heard of emote {e}"); Console.Error.WriteLine($"never heard of emote {e}");
return Task.CompletedTask; return 405;
} }
return msg.AddReactionAsync(emote); msg.AddReactionAsync(emote);
return 200;
} }
private static string TruncateText(string msg, uint? chars) private static string TruncateText(string msg, uint? chars)
{ {
chars ??= 500; chars ??= 500;
if(msg?.Length > chars) if (msg?.Length > chars)
{ {
return msg.Substring(0, (int)chars-2) + "✂"; return msg.Substring(0, (int)chars - 2) + "✂";
} }
else else
{ {
return msg; return msg;
} }
} }
public override async Task<int> SendMessage(Channel channel, string text)
{
var dcCh = await client.GetChannelAsync(ulong.Parse(channel.ExternalId));
if (dcCh == null)
{
return 404;
}
if (dcCh is IMessageChannel msgChannel)
{
await msgChannel.SendMessageAsync(TruncateText(text, channel.MaxTextChars));
return 200;
}
else
{
return 503;
}
}
public override async Task<int> SendFile(Channel channel, string base64dData, string filename, string accompanyingText)
{
var dcCh = await client.GetChannelAsync(ulong.Parse(channel.ExternalId));
if (dcCh == null)
{
return 404;
}
if (dcCh is IMessageChannel msgChannel)
{
using (var ms = new MemoryStream(Convert.FromBase64String(base64dData)))
{
await msgChannel.SendFileAsync(ms, filename, TruncateText(accompanyingText, channel.MaxTextChars));
}
return 200;
}
else
{
return 503;
}
}
public override async Task<int> React(Message message, string reaction)
{
var dcCh = await client.GetChannelAsync(ulong.Parse(message.Channel.ExternalId));
if (dcCh == null)
return 404;
if (dcCh is IMessageChannel msgChannel)
{
var dcMsg = await msgChannel.GetMessageAsync(ulong.Parse(message.ExternalId));
if (dcMsg == null)
return 404;
return await AttemptReact(dcMsg as IUserMessage, reaction);
}
else
{
return 503;
}
}
public override async Task<int> Reply(Message message, string text)
{
var dcCh = await client.GetChannelAsync(ulong.Parse(message.Channel.ExternalId));
if (dcCh == null)
return 404;
if (dcCh is IMessageChannel msgChannel)
{
var dcMsg = await msgChannel.GetMessageAsync(ulong.Parse(message.ExternalId));
if (dcMsg == null)
return 404;
(dcMsg as IUserMessage).ReplyAsync(TruncateText(text, message.Channel.MaxTextChars));
return 200;
}
else
{
return 503;
}
}
} }

View File

@ -0,0 +1,22 @@
namespace vassago.ProtocolInterfaces;
using vassago.Models;
public abstract class ProtocolInterface
{
public static string Protocol { get; }
public abstract Channel SelfChannel { get; }
public abstract Task<int> SendMessage(Channel channel, string text);
public virtual async Task<int> SendFile(Channel channel, string path, string accompanyingText)
{
if (!File.Exists(path))
{
return 404;
}
var fstring = Convert.ToBase64String(File.ReadAllBytes(path));
return await SendFile(channel, fstring, Path.GetFileName(path), accompanyingText);
}
public abstract Task<int> SendFile(Channel channel, string base64dData, string filename, string accompanyingText);
public abstract Task<int> React(Message message, string reaction);
public abstract Task<int> Reply(Message message, string text);
}

View File

@ -1,7 +0,0 @@
namespace vassago.ProtocolInterfaces;
public static class ProtocolList
{
public static List<DiscordInterface.DiscordInterface> discords = new();
public static List<TwitchInterface.TwitchInterface> twitchs = new();
}

View File

@ -10,19 +10,16 @@ using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Models; using TwitchLib.Communication.Models;
using vassago.Behavior; using vassago.Behavior;
using vassago.Models; using vassago.Models;
using vassago.ProtocolInterfaces;
namespace vassago.TwitchInterface; namespace vassago.TwitchInterface;
internal class unifiedTwitchMessage public class TwitchInterface : ProtocolInterface
{ {
public unifiedTwitchMessage(ChatMessage chatMessage) { } public static new string Protocol { get => "twitch"; }
}
public class TwitchInterface
{
internal const string PROTOCOL = "twitch";
private static SemaphoreSlim channelSetupSemaphpore = new SemaphoreSlim(1, 1); private static SemaphoreSlim channelSetupSemaphpore = new SemaphoreSlim(1, 1);
private Channel protocolAsChannel; private Channel protocolAsChannel;
public override Channel SelfChannel { get => protocolAsChannel;}
private Account selfAccountInProtocol; private Account selfAccountInProtocol;
TwitchClient client; TwitchClient client;
@ -32,7 +29,7 @@ public class TwitchInterface
try try
{ {
protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == PROTOCOL); protocolAsChannel = Rememberer.SearchChannel(c => c.ParentChannel == null && c.Protocol == Protocol);
if (protocolAsChannel == null) if (protocolAsChannel == null)
{ {
protocolAsChannel = new Channel() protocolAsChannel = new Channel()
@ -45,12 +42,10 @@ public class TwitchInterface
LinksAllowed = false, LinksAllowed = false,
ReactionsPossible = false, ReactionsPossible = false,
ExternalId = null, ExternalId = null,
Protocol = PROTOCOL, Protocol = Protocol,
SubChannels = [] SubChannels = []
}; };
protocolAsChannel.DisplayName = "twitch (itself)"; protocolAsChannel.DisplayName = "twitch (itself)";
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"twitch itself cannot accept text"); };
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"twitch itself cannot send file"); };
protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel); protocolAsChannel = Rememberer.RememberChannel(protocolAsChannel);
Console.WriteLine($"protocol as channle added; {protocolAsChannel}"); Console.WriteLine($"protocol as channle added; {protocolAsChannel}");
} }
@ -97,7 +92,8 @@ public class TwitchInterface
//translate to internal, upsert //translate to internal, upsert
var m = UpsertMessage(e.WhisperMessage); var m = UpsertMessage(e.WhisperMessage);
m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(e.WhisperMessage.Username, t); }); }; //can't send whispers without giving up cellphone number.
//m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(e.WhisperMessage.Username, t); }); };
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM; m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
//act on //act on
await Behaver.Instance.ActOn(m); await Behaver.Instance.ActOn(m);
@ -112,7 +108,6 @@ public class TwitchInterface
//translate to internal, upsert //translate to internal, upsert
var m = UpsertMessage(e.ChatMessage); var m = UpsertMessage(e.ChatMessage);
m.Reply = (t) => { return Task.Run(() => { client.SendReply(e.ChatMessage.Channel, e.ChatMessage.Id, t); }); };
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.Normal; m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.Normal;
//act on //act on
await Behaver.Instance.ActOn(m); await Behaver.Instance.ActOn(m);
@ -152,14 +147,14 @@ public class TwitchInterface
acc ??= new Account() acc ??= new Account()
{ {
IsUser = Rememberer.SearchUser( IsUser = Rememberer.SearchUser(
u => u.Accounts.Any(a => a.ExternalId == username && a.Protocol == PROTOCOL)) u => u.Accounts.Any(a => a.ExternalId == username && a.Protocol == Protocol))
?? new vassago.Models.User() ?? new vassago.Models.User()
}; };
acc.Username = username; acc.Username = username;
acc.ExternalId = username; acc.ExternalId = username;
//acc.IsBot = false? there is a way to tell, but you have to go back through the API //acc.IsBot = false? there is a way to tell, but you have to go back through the API
acc.Protocol = PROTOCOL; acc.Protocol = Protocol;
acc.SeenInChannel = inChannel; acc.SeenInChannel = inChannel;
// Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}"); // Console.WriteLine($"we asked rememberer to search for acc's user. {acc.IsUser?.Id}");
@ -185,7 +180,7 @@ public class TwitchInterface
private Channel UpsertChannel(string channelName) private Channel UpsertChannel(string channelName)
{ {
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channelName Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == channelName
&& ci.Protocol == PROTOCOL); && ci.Protocol == Protocol);
if (c == null) if (c == null)
{ {
// Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channelName}"); // Console.WriteLine($"couldn't find channel under protocol {PROTOCOL} with externalId {channelName}");
@ -199,11 +194,9 @@ public class TwitchInterface
c.ExternalId = channelName; c.ExternalId = channelName;
c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal; c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal;
c.Messages ??= []; c.Messages ??= [];
c.Protocol = PROTOCOL; c.Protocol = Protocol;
c.ParentChannel = protocolAsChannel; c.ParentChannel = protocolAsChannel;
c.SubChannels = c.SubChannels ?? new List<Channel>(); c.SubChannels = c.SubChannels ?? new List<Channel>();
c.SendMessage = (t) => { return Task.Run(() => { client.SendMessage(channelName, t); }); };
c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); };
c = Rememberer.RememberChannel(c); c = Rememberer.RememberChannel(c);
var selfAccountInChannel = c.Users?.FirstOrDefault(a => a.ExternalId == selfAccountInProtocol.ExternalId); var selfAccountInChannel = c.Users?.FirstOrDefault(a => a.ExternalId == selfAccountInProtocol.ExternalId);
@ -217,7 +210,7 @@ public class TwitchInterface
private Channel UpsertDMChannel(string whisperWith) private Channel UpsertDMChannel(string whisperWith)
{ {
Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == $"w_{whisperWith}" Channel c = Rememberer.SearchChannel(ci => ci.ExternalId == $"w_{whisperWith}"
&& ci.Protocol == PROTOCOL); && ci.Protocol == Protocol);
if (c == null) if (c == null)
{ {
// Console.WriteLine($"couldn't find channel under protocol {PROTOCOL}, whisper with {whisperWith}"); // Console.WriteLine($"couldn't find channel under protocol {PROTOCOL}, whisper with {whisperWith}");
@ -231,25 +224,9 @@ public class TwitchInterface
c.ExternalId = $"w_{whisperWith}"; c.ExternalId = $"w_{whisperWith}";
c.ChannelType = vassago.Models.Enumerations.ChannelType.DM; c.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
c.Messages ??= []; c.Messages ??= [];
c.Protocol = PROTOCOL; c.Protocol = Protocol;
c.ParentChannel = protocolAsChannel; c.ParentChannel = protocolAsChannel;
c.SubChannels = c.SubChannels ?? new List<Channel>(); c.SubChannels = c.SubChannels ?? new List<Channel>();
c.SendMessage = (t) =>
{
return Task.Run(() =>
{
try
{
client.SendWhisper(whisperWith, t);
}
catch (Exception e)
{
Console.Error.WriteLine(e);
}
});
};
c.SendFile = (f, t) => { throw new InvalidOperationException($"twitch cannot send files"); };
c = Rememberer.RememberChannel(c); c = Rememberer.RememberChannel(c);
var selfAccountInChannel = c.Users.FirstOrDefault(a => a.ExternalId == selfAccountInProtocol.ExternalId); var selfAccountInChannel = c.Users.FirstOrDefault(a => a.ExternalId == selfAccountInProtocol.ExternalId);
@ -266,10 +243,10 @@ public class TwitchInterface
//none of the features we care about are on it! //none of the features we care about are on it!
private Message UpsertMessage(ChatMessage chatMessage) private Message UpsertMessage(ChatMessage chatMessage)
{ {
var m = Rememberer.SearchMessage(mi => mi.ExternalId == chatMessage.Id && mi.Protocol == PROTOCOL) var m = Rememberer.SearchMessage(mi => mi.ExternalId == chatMessage.Id && mi.Protocol == Protocol)
?? new() ?? new()
{ {
Protocol = PROTOCOL, Protocol = Protocol,
Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc) Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)
}; };
m.Content = chatMessage.Message; m.Content = chatMessage.Message;
@ -277,8 +254,6 @@ public class TwitchInterface
m.Channel = UpsertChannel(chatMessage.Channel); m.Channel = UpsertChannel(chatMessage.Channel);
m.Author = UpsertAccount(chatMessage.Username, m.Channel); m.Author = UpsertAccount(chatMessage.Username, m.Channel);
m.MentionsMe = Regex.IsMatch(m.Content?.ToLower(), $"@\\b{selfAccountInProtocol.Username.ToLower()}\\b"); m.MentionsMe = Regex.IsMatch(m.Content?.ToLower(), $"@\\b{selfAccountInProtocol.Username.ToLower()}\\b");
m.Reply = (t) => { return Task.Run(() => { client.SendReply(chatMessage.Channel, chatMessage.Id, t); }); };
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
Rememberer.RememberMessage(m); Rememberer.RememberMessage(m);
return m; return m;
} }
@ -288,11 +263,11 @@ public class TwitchInterface
private Message UpsertMessage(WhisperMessage whisperMessage) private Message UpsertMessage(WhisperMessage whisperMessage)
{ {
//WhisperMessage.Id corresponds to chatMessage.Id. \*eye twitch* //WhisperMessage.Id corresponds to chatMessage.Id. \*eye twitch*
var m = Rememberer.SearchMessage(mi => mi.ExternalId == whisperMessage.MessageId && mi.Protocol == PROTOCOL) var m = Rememberer.SearchMessage(mi => mi.ExternalId == whisperMessage.MessageId && mi.Protocol == Protocol)
?? new() ?? new()
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Protocol = PROTOCOL, Protocol = Protocol,
Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc) Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc)
}; };
m.Content = whisperMessage.Message; m.Content = whisperMessage.Message;
@ -300,8 +275,6 @@ public class TwitchInterface
m.Channel = UpsertDMChannel(whisperMessage.Username); m.Channel = UpsertDMChannel(whisperMessage.Username);
m.Author = UpsertAccount(whisperMessage.Username, m.Channel); m.Author = UpsertAccount(whisperMessage.Username, m.Channel);
m.MentionsMe = Regex.IsMatch(m.Content?.ToLower(), $"@\\b{selfAccountInProtocol.Username.ToLower()}\\b"); m.MentionsMe = Regex.IsMatch(m.Content?.ToLower(), $"@\\b{selfAccountInProtocol.Username.ToLower()}\\b");
m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(whisperMessage.Username, t); }); };
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
Rememberer.RememberMessage(m); Rememberer.RememberMessage(m);
return m; return m;
} }
@ -317,4 +290,22 @@ public class TwitchInterface
client.SendMessage(channelTarget, "o7"); client.SendMessage(channelTarget, "o7");
client.LeaveChannel(channelTarget); client.LeaveChannel(channelTarget);
} }
public override async Task<int> SendMessage(Channel channel, string text)
{
Task.Run(() => { client.SendMessage(channel.ExternalId, text); });
return 200;
}
public override async Task<int> SendFile(Channel channel, string base64dData, string filename, string accompanyingText)
{
return 405;
}
public override async Task<int> React(Message message, string reaction)
{
return 405;
}
public override async Task<int> Reply(Message message, string text)
{
Task.Run(() => { client.SendReply(message.Channel.ExternalId, message.ExternalId, text); });
return 200;
}
} }

View File

@ -8,6 +8,25 @@ public static class Rememberer
{ {
private static readonly SemaphoreSlim dbAccessSemaphore = new(1, 1); private static readonly SemaphoreSlim dbAccessSemaphore = new(1, 1);
private static readonly ChattingContext db = new(); private static readonly ChattingContext db = new();
private static List<Channel> channels;
private static bool channelCacheDirty = true;
private static void cacheChannels()
{
dbAccessSemaphore.Wait();
channels = db.Channels.ToList();
foreach (Channel ch in channels)
{
if (ch.ParentChannelId != null)
{
ch.ParentChannel = channels.FirstOrDefault(c => c.Id == ch.ParentChannelId);
ch.ParentChannel.SubChannels ??= [];
ch.ParentChannel.SubChannels.Add(ch);
}
}
channelCacheDirty = false;
dbAccessSemaphore.Release();
}
public static Account SearchAccount(Expression<Func<Account, bool>> predicate) public static Account SearchAccount(Expression<Func<Account, bool>> predicate)
{ {
@ -33,13 +52,11 @@ public static class Rememberer
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
return toReturn; return toReturn;
} }
public static Channel SearchChannel(Expression<Func<Channel, bool>> predicate) public static Channel SearchChannel(Func<Channel, bool> predicate)
{ {
Channel toReturn; if(channelCacheDirty)
dbAccessSemaphore.Wait(); Task.Run(() => cacheChannels()).Wait();
toReturn = db.Channels.FirstOrDefault(predicate); return channels.FirstOrDefault(predicate);
dbAccessSemaphore.Release();
return toReturn;
} }
public static Message SearchMessage(Expression<Func<Message, bool>> predicate) public static Message SearchMessage(Expression<Func<Message, bool>> predicate)
{ {
@ -75,17 +92,27 @@ public static class Rememberer
} }
public static Channel RememberChannel(Channel toRemember) public static Channel RememberChannel(Channel toRemember)
{ {
if(channelCacheDirty)
Task.Run(() => cacheChannels()).Wait(); //so we always do 2 db trips?
dbAccessSemaphore.Wait(); dbAccessSemaphore.Wait();
db.Update(toRemember); db.Update(toRemember);
db.SaveChanges(); db.SaveChanges();
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
channelCacheDirty = true;
cacheChannels();
return toRemember; return toRemember;
} }
public static void RememberMessage(Message toRemember) public static void RememberMessage(Message toRemember)
{ {
dbAccessSemaphore.Wait(); dbAccessSemaphore.Wait();
toRemember.Channel ??= new() { Messages = [toRemember] }; toRemember.Channel ??= new();
toRemember.Channel.Messages ??= [];
if (!toRemember.Channel.Messages.Contains(toRemember))
{
toRemember.Channel.Messages.Add(toRemember);
db.Update(toRemember.Channel); db.Update(toRemember.Channel);
}
db.Update(toRemember);
db.SaveChanges(); db.SaveChanges();
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
} }
@ -113,13 +140,48 @@ public static class Rememberer
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
} }
} }
public static void ForgetChannel(Channel toForget) public static void ForgetAttachment(Attachment toForget)
{ {
dbAccessSemaphore.Wait();
db.Attachments.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public static void ForgetChannel(Channel toForget)
{
if (toForget.SubChannels?.Count > 0)
{
foreach (var childChannel in toForget.SubChannels.ToList())
{
ForgetChannel(childChannel);
}
}
if (toForget.Users?.Count > 0)
{
foreach (var account in toForget.Users.ToList())
{
ForgetAccount(account);
}
}
dbAccessSemaphore.Wait(); dbAccessSemaphore.Wait();
db.Channels.Remove(toForget); db.Channels.Remove(toForget);
db.SaveChanges(); db.SaveChanges();
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
} }
public static void ForgetMessage(Message toForget)
{
dbAccessSemaphore.Wait();
db.Messages.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public static void ForgetUAC(UAC toForget)
{
dbAccessSemaphore.Wait();
db.UACs.Remove(toForget);
db.SaveChanges();
dbAccessSemaphore.Release();
}
public static void ForgetUser(User toForget) public static void ForgetUser(User toForget)
{ {
dbAccessSemaphore.Wait(); dbAccessSemaphore.Wait();
@ -143,16 +205,52 @@ public static class Rememberer
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
return toReturn; return toReturn;
} }
public static Channel ChannelDetail(Guid Id) public static Account AccountDetail(Guid Id)
{ {
Channel toReturn; Account toReturn;
dbAccessSemaphore.Wait(); dbAccessSemaphore.Wait();
toReturn = db.Channels.Find(Id); toReturn = db.Accounts.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public static Attachment AttachmentDetail(Guid Id)
{
Attachment toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Attachments.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public static Channel ChannelDetail(Guid Id)
{
if(channelCacheDirty)
Task.Run(() => cacheChannels()).Wait();
return channels.Find(c => c.Id == Id);
}
public static Message MessageDetail(Guid Id)
{
Message toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Messages.Find(Id);
db.Entry(toReturn).Reference(m => m.Channel).Load();
dbAccessSemaphore.Release();
return toReturn;
}
public static UAC UACDetail(Guid Id)
{
UAC toReturn;
dbAccessSemaphore.Wait();
toReturn = db.UACs.Find(Id);
dbAccessSemaphore.Release();
return toReturn;
}
public static User UserDetail(Guid Id)
{
User toReturn;
dbAccessSemaphore.Wait();
toReturn = db.Users.Find(Id);
dbAccessSemaphore.Release(); dbAccessSemaphore.Release();
return toReturn; return toReturn;
// .Include(u => u.SubChannels)
// .Include(u => u.Users)
// .Include(u => u.ParentChannel);
} }
public static List<User> UsersOverview() public static List<User> UsersOverview()
{ {

View File

@ -3,7 +3,7 @@ namespace vassago;
using System; using System;
using System.Net.Http; using System.Net.Http;
using vassago.Models; using vassago.Models;
using vassago.ProtocolInterfaces;
public static class Shared public static class Shared
{ {
@ -12,4 +12,5 @@ public static class Shared
public static HttpClient HttpClient { get; internal set; } = new HttpClient(); public static HttpClient HttpClient { get; internal set; } = new HttpClient();
public static bool SetupSlashCommands { get; set; } public static bool SetupSlashCommands { get; set; }
public static Uri API_URL {get;set;} public static Uri API_URL {get;set;}
public static List<ProtocolInterface> ProtocolList { get; set; } = new();
} }

View File

@ -20,7 +20,11 @@ public class ChannelsController() : Controller
//but that would take in all the messages. //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. //realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel.
var channel = allChannels.First(u => u.Id == id); var channel = allChannels.FirstOrDefault(u => u.Id == id);
if(channel == null)
{
return Problem("couldn't find that channle");
}
var walker = channel; var walker = channel;
while(walker != null) while(walker != null)
{ {

View File

@ -0,0 +1,52 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
using vassago.ProtocolInterfaces.DiscordInterface;
namespace vassago.Controllers.api;
[Route("api/[controller]")]
[ApiController]
public class AccountsController : ControllerBase
{
private readonly ILogger<AccountsController> _logger;
public AccountsController(ILogger<AccountsController> logger)
{
_logger = logger;
}
//microsoft: "you can't have multiple [FromBody]. The reason for this rule is some bullshti about storage buffers."
//cool story, bro. nobody gives a fuck, look at the boilerplate you've necessitated.
public class extraSpecialObjectReadGlorifiedTupleFor_UnlinkUser
{
public Guid acc_guid;
}
[HttpPatch]
[Route("UnlinkUser")]
[Produces("application/json")]
public IActionResult UnlinkUser([FromBody] extraSpecialObjectReadGlorifiedTupleFor_UnlinkUser req)
{
var acc_guid = req.acc_guid;
var accFromDb = Rememberer.SearchAccount(acc => acc.Id == acc_guid);
if (accFromDb == null)
{
var err = $"attempt to unlink user for acc {acc_guid}, not found";
_logger.LogError(err);
return NotFound(err);
}
var userFromDb = Rememberer.SearchUser(c => c.Id == accFromDb.IsUser.Id);
if (userFromDb == null)
{
var err = $"attempt to unlink user for {acc_guid}, doesn't have a user";
_logger.LogError(err);
return NotFound(err);
}
accFromDb.IsUser = null;
Rememberer.RememberAccount(accFromDb);
return Ok(accFromDb);
}
}

View File

@ -1,89 +0,0 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
namespace vassago.Controllers.api;
[Route("api/[controller]")]
[ApiController]
public class ChannelsController : ControllerBase
{
private readonly ILogger<ChannelsController> _logger;
public ChannelsController(ILogger<ChannelsController> logger)
{
_logger = logger;
}
[HttpGet("{id}")]
[Produces("application/json")]
public Channel Get(Guid id)
{
return Rememberer.ChannelDetail(id);
}
[HttpPatch]
[Produces("application/json")]
public IActionResult Patch([FromBody] Channel channel)
{
var fromDb = Rememberer.ChannelDetail(channel.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to update channel {channel.Id}, not found");
return NotFound();
}
else
{
_logger.LogDebug($"patching {channel.DisplayName} (id: {channel.Id})");
}
//settable values: lewdness filter level, meanness filter level. maybe i could decorate them...
fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel;
fromDb.MeannessFilterLevel = channel.MeannessFilterLevel;
Rememberer.RememberChannel(fromDb);
return Ok(fromDb);
}
[HttpDelete]
[Produces("application/json")]
public IActionResult Delete([FromBody] Channel channel)
{
var fromDb = Rememberer.ChannelDetail(channel.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete channel {channel.Id}, not found");
return NotFound();
}
deleteChannel(fromDb);
return Ok();
}
private void deleteChannel(Channel channel)
{
if (channel.SubChannels?.Count > 0)
{
foreach (var childChannel in channel.SubChannels)
{
deleteChannel(childChannel);
}
}
if(channel.Users?.Count > 0)
{
foreach(var account in channel.Users)
{
deleteAccount(account);
}
}
Rememberer.ForgetChannel(channel);
}
private void deleteAccount(Account account)
{
var user = account.IsUser;
var usersOnlyAccount = user.Accounts?.Count == 1;
Rememberer.ForgetAccount(account);
if(usersOnlyAccount)
Rememberer.ForgetUser(user);
}
}

View File

@ -0,0 +1,53 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
using vassago.ProtocolInterfaces.DiscordInterface;
namespace vassago.Controllers.api;
[Route("api/[controller]")]
[ApiController]
public class InternalAPIProtocolController : ControllerBase
{
private readonly ILogger<InternalAPIProtocolController> _logger;
public InternalAPIProtocolController(ILogger<InternalAPIProtocolController> logger)
{
_logger = logger;
}
[HttpPost]
[Route("PostMessage")]
[Produces("application/json")]
public IActionResult PostMessage(string messageText, Guid channelId)
{
return StatusCode(Behaver.Instance.SendMessage(channelId, messageText).Result);
}
[HttpPost]
[Route("ReplyToMessage")]
[Produces("application/json")]
public IActionResult ReplyToMessage(string messageText, Guid repliedMessageId)
{
Console.WriteLine($"ReplyToMessage - {repliedMessageId}, {messageText}");
return StatusCode(Behaver.Instance.Reply(repliedMessageId, messageText).Result);
}
[HttpPost]
[Route("SendFile")]
[Produces("application/json")]
public IActionResult SendFile(Guid channelId, string accompanyingText, string base64dData, string filename)
{
Console.WriteLine($"SendFile- {channelId}, {filename} (base64'd, {base64dData?.Length} chars), {accompanyingText}");
return StatusCode(Behaver.Instance.SendFile(channelId, base64dData, filename, accompanyingText).Result);
}
[HttpPost]
[Route("ReactToMessage")]
[Produces("application/json")]
public IActionResult ReactToMessage(string reactionString, Guid reactedMessageId)
{
Console.WriteLine($"ReactToMessage- {reactedMessageId}, {reactionString}");
return StatusCode(Behaver.Instance.React(reactedMessageId, reactionString).Result);
}
}

View File

@ -0,0 +1,214 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
namespace vassago.Controllers.api;
[Route("api/[controller]")]
[ApiController]
public class RemembererController : ControllerBase
{
private readonly ILogger<RemembererController> _logger;
public RemembererController(ILogger<RemembererController> logger)
{
_logger = logger;
}
//Create
[HttpPut]
[Route("Account")]
[Produces("application/json")]
public Account CreateAccount(Guid id)
{
return Rememberer.AccountDetail(id);
}
[HttpPut]
[Route("Attachment")]
[Produces("application/json")]
public Attachment CreateAttachment(Guid id)
{
return Rememberer.AttachmentDetail(id);
}
[HttpPut]
[Route("Channels")]
[Produces("application/json")]
public Channel CreateChannel(Guid id)
{
return Rememberer.ChannelDetail(id);
}
[HttpPut]
[Route("Message")]
[Produces("application/json")]
public Message CreateMessage(Guid id)
{
return Rememberer.MessageDetail(id);
}
[HttpPut]
[Route("UAC")]
[Produces("application/json")]
public UAC CreateUAC(Guid id)
{
return Rememberer.UACDetail(id);
}
[HttpPut]
[Route("User")]
[Produces("application/json")]
public User CreateUser(Guid id)
{
return Rememberer.UserDetail(id);
}
//Read
[HttpGet]
[Route("Account")]
[Produces("application/json")]
public Account GetAccount(Guid id)
{
return Rememberer.AccountDetail(id);
}
[HttpGet]
[Route("Attachment")]
[Produces("application/json")]
public Attachment GetAttachment(Guid id)
{
return Rememberer.AttachmentDetail(id);
}
[HttpGet]
[Route("Channels")]
[Produces("application/json")]
public Channel GetChannel(Guid id)
{
return Rememberer.ChannelDetail(id);
}
[HttpGet]
[Route("Message")]
[Produces("application/json")]
public Message GetMessage(Guid id)
{
return Rememberer.MessageDetail(id);
}
[HttpGet]
[Route("UAC")]
[Produces("application/json")]
public UAC GetUAC(Guid id)
{
return Rememberer.UACDetail(id);
}
[HttpGet]
[Route("User")]
[Produces("application/json")]
public User GetUser(Guid id)
{
return Rememberer.UserDetail(id);
}
//Update
[HttpPatch]
[Route("Channels")]
[Produces("application/json")]
public IActionResult Patch([FromBody] Channel channel)
{
var fromDb = Rememberer.ChannelDetail(channel.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to update channel {channel.Id}, not found");
return NotFound();
}
else
{
_logger.LogDebug($"patching {channel.DisplayName} (id: {channel.Id})");
}
//settable values: lewdness filter level, meanness filter level. maybe i could decorate them...
fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel;
fromDb.MeannessFilterLevel = channel.MeannessFilterLevel;
Rememberer.RememberChannel(fromDb);
return Ok(fromDb);
}
//Delete
[HttpDelete]
[Route("Account")]
[Produces("application/json")]
public IActionResult DeleteAccount(Guid id)
{
var fromDb = Rememberer.AccountDetail(id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete account {id}, not found");
return NotFound();
}
Rememberer.ForgetAccount(fromDb);
return Ok();
}
[HttpDelete]
[Route("Attachment")]
[Produces("application/json")]
public IActionResult DeleteAttachment(Guid id)
{
var fromDb = Rememberer.AttachmentDetail(id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete attachment {id}, not found");
return NotFound();
}
Rememberer.ForgetAttachment(fromDb);
return Ok();
}
[HttpDelete]
[Route("Channels/{id}")]
[Produces("application/json")]
public IActionResult DeleteChannel(Guid id)
{
var fromDb = Rememberer.ChannelDetail(id);
_logger.LogDebug($"delete channel {id}");
if (fromDb == null)
{
_logger.LogError($"attempt to delete channel {id}, not found");
return NotFound();
}
Rememberer.ForgetChannel(fromDb);
_logger.LogDebug($"delete channel {id} success");
return Ok();
}
[HttpDelete]
[Route("Message/{id}")]
[Produces("application/json")]
public IActionResult DeleteMessage(Guid id)
{
var fromDb = Rememberer.MessageDetail(id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete message {id}, not found");
return NotFound();
}
Rememberer.ForgetMessage(fromDb);
return Ok();
}
[HttpDelete]
[Route("UAC/{id}")]
[Produces("application/json")]
public IActionResult DeleteUAC(Guid id)
{
var fromDb = Rememberer.UACDetail(id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete uac {id}, not found");
return NotFound();
}
Rememberer.ForgetUAC(fromDb);
return Ok();
}
[HttpDelete]
[Route("User/{id}")]
[Produces("application/json")]
public IActionResult DeleteUser(Guid id)
{
var fromDb = Rememberer.UserDetail(id);
if (fromDb == null)
{
_logger.LogError($"attempt to delete user {id}, not found");
return NotFound();
}
Rememberer.ForgetUser(fromDb);
return Ok();
}
}

View File

@ -1,40 +0,0 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
using vassago.ProtocolInterfaces.DiscordInterface;
namespace vassago.Controllers.api;
[Route("api/[controller]")]
[ApiController]
public class UsersController : ControllerBase
{
private readonly ILogger<ChannelsController> _logger;
public UsersController(ILogger<ChannelsController> logger)
{
_logger = logger;
}
[HttpPatch]
[Produces("application/json")]
public IActionResult Patch([FromBody] User user)
{
var fromDb = Rememberer.SearchUser(u => u.Id == user.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to update user {user.Id}, not found");
return NotFound();
}
else
{
_logger.LogDebug($"patching {user.DisplayName} (id: {user.Id})");
}
//TODO: settable values: display name
//fromDb.DisplayName = user.DisplayName;
Rememberer.RememberUser(fromDb);
return Ok(fromDb);
}
}

View File

@ -8,19 +8,23 @@
<a href="/">home</a>/@Html.Raw(ViewData["breadcrumbs"]) <a href="/">home</a>/@Html.Raw(ViewData["breadcrumbs"])
<table class="table"> <table class="table">
<tbody> <tbody>
<tr>
<th>Id</th>
<td>@Model.Id</td>
</tr>
<tr> <tr>
<th scope="row">belongs to user</th> <th scope="row">belongs to user</th>
<td>@Model.IsUser.DisplayName</td> <td>@Model.IsUser.DisplayName</td>
<td><button alt="to do" disabled>separate</button></2td> <td><button onclick="unlinkAccountUser(() => { window.location.reload(); })">separate</button></2td>
</tr> </tr>
<tr> <tr>
<th scope="row">Seen in channel</th> <th scope="row">Seen in channel</th>
<td class="account @Model.SeenInChannel.Protocol"><div class="protocol-icon">&nbsp;</div>@Model.SeenInChannel.LineageSummary<a href="/Channels/Details/@Model.SeenInChannel.Id">@Model.SeenInChannel.DisplayName</a></td> <td class="account @Model.SeenInChannel.Protocol"><div class="protocol-icon">&nbsp;</div>@Model.SeenInChannel.LineageSummary<a href="/Channels/Details/@Model.SeenInChannel.Id">@Model.SeenInChannel.DisplayName</a></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Permission Tags</th> <th scope="row">UACs tied to account</th>
<td> <td>
<div id="tagsTree"></div> <div id="uacsTree"></div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -44,10 +48,10 @@
return userNow; return userNow;
} }
function getTagsTree() { function getUacsTree() {
@{ @{
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("[{text: \"permission tags\", \"expanded\":true, nodes: ["); sb.Append("[{text: \"UACs\", \"expanded\":true, nodes: [");
var first = true; var first = true;
for (int i = 0; i < 1; i++) for (int i = 0; i < 1; i++)
{ {
@ -62,7 +66,7 @@
var tree = @Html.Raw(sb.ToString()); var tree = @Html.Raw(sb.ToString());
return tree; return tree;
} }
$('#tagsTree').bstreeview({ data: getTagsTree() }); $('#uacsTree').bstreeview({ data: getUacsTree() });
document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } }); //document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } });
</script> </script>
} }

View File

@ -24,7 +24,7 @@
<tr> <tr>
<th scope="row">Lewdness Filter Level</th> <th scope="row">Lewdness Filter Level</th>
<td> <td>
<select name="LewdnessFilterLevel" id="LewdnessFilterLevel" onchange="patchModel(jsonifyChannel(), '/api/Channels/')"> <select name="LewdnessFilterLevel" id="LewdnessFilterLevel" onchange="patchModel(jsonifyChannel())">
<!option value="" @(ThisChannel.LewdnessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedLewdnessFilterLevel)</!option> <!option value="" @(ThisChannel.LewdnessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedLewdnessFilterLevel)</!option>
@foreach (Enumerations.LewdnessFilterLevel enumVal in @foreach (Enumerations.LewdnessFilterLevel enumVal in
Enum.GetValues(typeof(Enumerations.LewdnessFilterLevel))) Enum.GetValues(typeof(Enumerations.LewdnessFilterLevel)))
@ -54,7 +54,7 @@
<tr> <tr>
<th scope="row">Meanness Filter Level</th> <th scope="row">Meanness Filter Level</th>
<td> <td>
<select name="MeannessFilterLevel" id="MeannessFilterLevel" onchange="patchModel(jsonifyChannel(), '/api/Channels/')"> <select name="MeannessFilterLevel" id="MeannessFilterLevel" onchange="patchModel(jsonifyChannel())">
<!option value="" @(ThisChannel.MeannessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedMeannessFilterLevel)</!option> <!option value="" @(ThisChannel.MeannessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedMeannessFilterLevel)</!option>
@foreach (Enumerations.MeannessFilterLevel enumVal in @foreach (Enumerations.MeannessFilterLevel enumVal in
Enum.GetValues(typeof(Enumerations.MeannessFilterLevel))) Enum.GetValues(typeof(Enumerations.MeannessFilterLevel)))
@ -135,7 +135,7 @@
function forget(){ function forget(){
console.log("here we go"); console.log("here we go");
if(window.confirm("delete? really really?") == true){ if(window.confirm("delete? really really?") == true){
deleteModel(jsonifyChannel(), '/api/Channels/'); deleteModel(jsonifyChannel().Id, window.history.back);
} }
} }
@ -149,7 +149,7 @@
var sb = new StringBuilder(); var sb = new StringBuilder();
sb.Append("[{text: \"accounts\", \"expanded\":true, nodes: ["); sb.Append("[{text: \"accounts\", \"expanded\":true, nodes: [");
var first = true; var first = true;
foreach (var acc in ThisChannel.Users.OrderBy(a => a.SeenInChannel.LineageSummary)) foreach (var acc in ThisChannel.Users?.OrderBy(a => a?.SeenInChannel?.LineageSummary))
{ {
if(!first) if(!first)
sb.Append(','); sb.Append(',');

View File

@ -12,6 +12,10 @@
<th scope="row">Display Name</th> <th scope="row">Display Name</th>
<td>@Model.DisplayName</td> <td>@Model.DisplayName</td>
</tr> </tr>
<tr>
<th scope="row">Description</th>
<td>@Html.Raw(Model.Description)
</tr>
<tr> <tr>
<th scope="row">Channels</th> <th scope="row">Channels</th>
<td> <td>

View File

@ -11,7 +11,7 @@
<tr> <tr>
<th scope="row">Display Name (here)</th> <th scope="row">Display Name (here)</th>
<td><input type="text" id="displayName" value="@Model.DisplayName" disabled alt="todo"></input> <button <td><input type="text" id="displayName" value="@Model.DisplayName" disabled alt="todo"></input> <button
onclick="patchModel(jsonifyUser(), @Html.Raw("'/api/Users/'"))" disabled alt"todo">update</button></td> onclick="patchModel(jsonifyUser())" disabled alt"todo">update</button></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Accounts</th> <th scope="row">Accounts</th>
@ -60,6 +60,6 @@
} }
$('#accountsTree').bstreeview({ data: getAccountsTree() }); $('#accountsTree').bstreeview({ data: getAccountsTree() });
document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser(), '/api/Users/') } }); document.querySelectorAll("input[type=checkbox]").forEach(node => { node.onchange = () => { patchModel(jsonifyUser()) } });
</script> </script>
} }

View File

@ -19,7 +19,13 @@
{ {
"uacID": "9a94855a-e5a2-43b5-8420-ce670472ce95", "uacID": "9a94855a-e5a2-43b5-8420-ce670472ce95",
"Trigger": "test", "Trigger": "test",
"Uri": "http://localhost" "Description": "<i>test</i>",
"Uri": "http://localhost",
"Method": "POST",
"Headers": [
["Content-Type", "application/json"]
],
"Content": "{\"content\": \"Hello, this is a message from my webhook: {text}. username: {account}, user: {user}\"}"
} }
], ],
"KafkaBootstrap":"http://localhost:9092", "KafkaBootstrap":"http://localhost:9092",

View File

@ -3,7 +3,11 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA2254</NoWarn> <NoWarn>$(NoWarn);CS1998;CS4014</NoWarn>
<!-- CS1998: "This async method lacks 'await' operators and will run synchronously." -->
<!-- CS4014: "Because this call is not awaited, execution of the current method continues before the call is completed."-->
<!-- those 2 cancel out. Async foo calls async Bar, foo doesn't say "await" so cs1998 says "well don't mark foo as async" and cs4014 says "you should awake bar".
what, you want me to just add bar to some big stupid task list that I don't care about anyway? -->
</PropertyGroup> </PropertyGroup>

View File

@ -8,7 +8,7 @@ function Account(displayName, accountId, protocol){
//todo: figure out what the URL actually needs to be, rather than assuming you get a whole-ass server to yourself. //todo: figure out what the URL actually needs to be, rather than assuming you get a whole-ass server to yourself.
//you selfish fuck... What are you, fox? //you selfish fuck... What are you, fox?
//as it stands, you want something like /api/Channels/, trailing slash intentional //as it stands, you want something like /api/Channels/, trailing slash intentional
function patchModel(model, deprecated_apiUrl) function patchModel(model, callback)
{ {
//structure the model your (dang) self into a nice object //structure the model your (dang) self into a nice object
console.log(model); console.log(model);
@ -22,7 +22,7 @@ function patchModel(model, deprecated_apiUrl)
// var id=components[3]; // var id=components[3];
console.log(JSON.stringify(model)); console.log(JSON.stringify(model));
fetch(apiUrl + type + '/', { fetch(apiUrl + 'Rememberer/' + type + '/', {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -44,22 +44,17 @@ function patchModel(model, deprecated_apiUrl)
}); });
} }
function deleteModel(model, deprecated_apiUrl) function deleteModel(id, callback)
{ {
var components = window.location.pathname.split('/'); var components = window.location.pathname.split('/');
// if(components[2] !== "Details")
// {
// console.log("wtf are you doing? " + components[2] + " is something other than Details");
// }
var type=components[1]; var type=components[1];
let result = null; let result = null;
// var id=components[3]; var id=components[3];
fetch(apiUrl + type + '/', { fetch(apiUrl + 'Rememberer/' + type + '/' + id, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, }
body: JSON.stringify(model),
}) })
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
@ -75,7 +70,7 @@ function deleteModel(model, deprecated_apiUrl)
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
}); });
} }
function linkUAC_Channel(channel_guid, callback) function linkUAC_Channel(channel_guid, callback)
{ {
@ -246,3 +241,31 @@ function unlinkUAC_Channel(user_guid, callback)
console.error('Error:', error); console.error('Error:', error);
}); });
} }
//give me account, we'll tear it off from user.
function unlinkAccountUser(callback)
{
var components = window.location.pathname.split('/');
var id=components[3];
let model={"acc_guid": id};
fetch(apiUrl + "Accounts/UnlinkUser/", {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(model),
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not "ok". which is not ok.');
}
return response.json();
})
.then(returnedSuccessdata => {
// perhaps a success callback
console.log('returnedSuccessdata:', returnedSuccessdata);
if(callback !== null) { callback(); }
})
.catch(error => {
console.error('Error:', error);
});
}