Compare commits

..

77 Commits

Author SHA1 Message Date
1b8a714a96 runs, performs features, doesn't crash
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
gitea.arg.rip/vassago/pipeline/head There was a failure building this commit
2024-07-13 18:59:10 -04:00
7c22ae1643 found a magic number :((
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
2024-07-13 17:56:19 -04:00
e0c7bdb35f user management UI
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
2024-07-07 15:27:48 -04:00
9648ea563b compiles, needs db update 2024-07-07 14:22:10 -04:00
1d73fe0be8 they're accounts. Maybe I should rename it in the DB. 2024-07-07 13:08:41 -04:00
5eeec24069 slightly more generic Patch function for API
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
untested. you know, since... offline ;)
2024-07-06 16:00:18 -04:00
ab16600463 inerited channel stats works better
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
gitea.arg.rip/vassago/pipeline/head There was a failure building this commit
before it would include 1 level, then assume that's the top. so 2 degrees of inheritance would confuse it
2024-06-29 16:45:07 -04:00
af4d68caa1 ca2254 is moronic. maybe if it wasn't filed under "code quality" and instead was filed under "you didn't include a workaround for the weaknesses of other external junk" i'd be kinder to it ;) 2024-06-23 20:35:00 -04:00
0ac28c35fb the api to commit web interface changes to a channel works 2024-06-23 20:31:09 -04:00
942b11fcce we have a dropdown that fires an onchange to post the new value.
although, errors ensue
2024-06-16 18:10:49 -04:00
4bd51721b6 more details, more links, fixed a bug where it wouldn't give itself a seen-in-channel 2024-06-10 16:24:30 -04:00
e364b47c0f details pages for channels added 2024-06-09 17:37:09 -04:00
c6ea3ef790 integrate swagger 2024-06-02 18:37:04 -04:00
2f7bc2c0ea get to channel details 2024-06-02 17:13:15 -04:00
43eaa5ad0d users in treeview 2024-05-26 20:00:46 -04:00
b4b5544ec4 channels and accounts organized into a treeview
even if every account is orphaned, this isn't how it should be
2024-05-26 19:43:17 -04:00
c446521977 fixed a bug where we'd immediately crash on any message 2024-05-10 17:07:50 -04:00
54414b8748 db migration: no featurepermissions, and channel permissions embedded in channel 2024-05-10 16:41:00 -04:00
8cb4005192 update links to guy who forked it 2024-05-10 10:54:45 -04:00
cd2fa384d5 bootstrap, fontawesome, and bootstrap treeview 2024-05-05 16:55:00 -04:00
f4bed1e6cb contain web interface stuff 2024-04-08 15:00:55 -04:00
88ca468708 get rid of feature permissions
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
2024-04-06 00:01:31 -04:00
464b6a90e4 channel permissions are just part of channel 2024-04-05 23:59:39 -04:00
581fddf6f9 t h e p l a n 2024-04-05 23:08:20 -04:00
bed8d3cbef move web interface around 2024-04-05 23:07:48 -04:00
2dd9e903db once i figure out jenkins secrets, this'll be useful 2024-04-05 23:02:15 -04:00
ef31418166 organize - behaver is not a behavior, only one use of connection string 2024-04-05 22:18:45 -04:00
6d181e2b68 mitigates #33
All checks were successful
gitea/vassago/pipeline/head This commit looks good
the actual problem though; it should realize thats itself
2024-01-26 17:55:09 -05:00
10167b597a disable twitchsummon
All checks were successful
gitea/vassago/pipeline/head This commit looks good
2024-01-24 21:16:56 -05:00
26d8373dc8 i don't know how I didn't trip over this one a million times before
read the help for MigrateAsync - "ensure created" does something different, and worse, which prevents future migrations!
2024-01-24 20:32:49 -05:00
a63a3fcb58 "just google it" general snark disabled
All checks were successful
gitea/vassago/pipeline/head This commit looks good
"temporarily"
2024-01-23 14:12:52 -05:00
b84e47344b db in the right place; fix QR code math
All checks were successful
gitea/vassago/pipeline/head This commit looks good
2024-01-10 21:21:31 -05:00
c5f9ae2c6b collapse users - extracted for LinkMe and my own self-registration
All checks were successful
gitea/vassago/pipeline/head This commit looks good
2024-01-05 22:12:57 -05:00
efb4ab00d2 IsSelf fix for Definitely snarkiness
notes to self. 1) trust in upsert. an account has an external ID, a channel has an external ID w.r.t. its protocol. 2) as long as you can collapse a User, collapse Self.
2024-01-05 21:39:31 -05:00
451ace753d how about don't clear out the aliases? now I can configure more aliases
All checks were successful
gitea/vassago/pipeline/head This commit looks good
2023-12-05 23:57:21 -05:00
894b536c04 smoot is defined as 5ft 7, don't use cm to approximate 2023-12-05 23:44:40 -05:00
ba2254858f also migrate db
All checks were successful
gitea/vassago/pipeline/head This commit looks good
2023-12-05 23:15:09 -05:00
c1e32ef39a fix failure to adapt
All checks were successful
gitea/vassago/pipeline/head This commit looks good
2023-12-05 22:59:30 -05:00
5fb96ea67e jenkins looking for Jenkinsfile
Some checks failed
gitea/vassago/pipeline/head There was a failure building this commit
2023-12-05 22:57:38 -05:00
1fd2a4723e more conversion 2023-12-05 22:53:17 -05:00
51fb08810d how the hell did I not have mm 2023-12-05 21:32:20 -05:00
423fe5cb96 more units for conversion 2023-12-05 21:23:08 -05:00
23e18f2028 complex channel types, some channels UI 2023-12-03 14:33:58 -05:00
9d3917a030 implements #21 2023-12-01 14:58:32 -05:00
391ba38cce fixes #20 2023-12-01 14:19:55 -05:00
d060e92ed9 link to self
but twitch interface can't whisper self atm
2023-12-01 14:02:47 -05:00
77fc26e1ed more user info 2023-12-01 09:49:21 -05:00
edc86af538 woops lol 2023-11-30 16:16:07 -05:00
39781397c3 database update - doesn't work if there's already data T_T
but at least now I can say it was a good idea to have waited all this stupidly goddamn long
2023-11-30 15:59:49 -05:00
e89c109970 who needs verbosity as a filter level - you have a char limit 2023-11-30 14:19:39 -05:00
e7b70468ae clearer definition of the concept of permission 2023-11-30 12:50:51 -05:00
47f382df19 more permission definition 2023-11-30 12:49:40 -05:00
8c6087d557 mark protocol on messages and fixed twitchlib detecting duplicates incorrectly 2023-08-22 15:33:09 -04:00
da7078f535 clear out detiktokable links when done
fixes #18
2023-08-22 14:58:44 -04:00
e9ddcd237c users controller, and some other fixes 2023-07-04 20:40:37 -04:00
4a60e53798 configuration separated 2023-07-04 19:25:04 -04:00
5bb64f764c twitch summon and dismiss both exist
and with that: twitch, as a platform, exists.

must sort out permissions.
2023-07-04 18:51:27 -04:00
986d433886 filterlevels used on (almost) all features 2023-07-04 13:31:19 -04:00
c393f657d1 dramatically destructive database update. Twitch. 2023-07-04 12:58:21 -04:00
6037adcb44 move irrelevant model 2023-07-03 13:50:04 -04:00
2fc199d4b6 deleted: Views/Home/Privacy.cshtml 2023-07-03 12:53:12 -04:00
a8d1fc8d6e web interface 2023-07-03 12:51:23 -04:00
91bcfae1ba migration for accounts vs user distinction 2023-06-28 00:14:32 -04:00
115035eb91 test/fix features 2023-06-20 21:38:25 -04:00
147cba7cd3 keep track of self 2023-06-20 21:26:44 -04:00
66b425cd39 distinguish users and accounts 2023-06-19 12:56:40 -04:00
e433e56fec Permissions associated with channel 2023-06-19 11:03:06 -04:00
a1d2ec83b5 no onjoin, who cares 2023-06-19 01:34:56 -04:00
3a4a6df087 behaviors separated out 2023-06-19 01:14:04 -04:00
567a59550e generally works 2023-06-18 22:10:51 -04:00
f23474ad51 i think we're back to where discord left off? 2023-06-15 23:29:07 -04:00
d2aa1f46cc debug statement cleanup
All checks were successful
greyn/vassago/pipeline/head This commit looks good
2023-06-05 15:45:08 -04:00
51fba995c3 log attachments 2023-06-05 15:25:43 -04:00
e4f7d88e35 works! 2023-06-05 14:55:48 -04:00
3031779e24 fork: vassago
All checks were successful
greyn/vassago/pipeline/head This commit looks good
gitea/vassago/pipeline/head This commit looks good
gitea.arg.rip/vassago/pipeline/head This commit looks good
2023-06-01 00:03:23 -04:00
bfe7f582b2 start to structure code for multiplatofrm 2023-05-23 19:03:58 -04:00
a87fcd6ad9 start to structure code for multiplatofrm 2023-05-22 00:58:36 -04:00
156 changed files with 80119 additions and 800 deletions

12
.config/dotnet-tools.json Normal file
View File

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "7.0.5",
"commands": [
"dotnet-ef"
]
}
}
}

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
appsettings.json
appsettings.Development.json
assets/exchangepairs.json
# ---> VisualStudio

19
.vscode/launch.json vendored
View File

@ -5,17 +5,26 @@
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"name": ".NET Core Launch (console)",
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/bin/Debug/net7.0/shtikbot-discord.dll",
"program": "${workspaceFolder}/bin/Debug/net7.0/vassago.dll",
"args": [],
"cwd": "${workspaceFolder}",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
"console": "internalConsole",
"stopAtEntry": false
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
}
},
{
"name": ".NET Core Attach",

9
.vscode/tasks.json vendored
View File

@ -7,7 +7,7 @@
"type": "process",
"args": [
"build",
"${workspaceFolder}/shtikbot-discord.csproj",
"${workspaceFolder}/vassago.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -19,7 +19,7 @@
"type": "process",
"args": [
"publish",
"${workspaceFolder}/shtikbot-discord.csproj",
"${workspaceFolder}/vassago.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
@ -32,9 +32,8 @@
"args": [
"watch",
"run",
"${workspaceFolder}/shtikbot-discord.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
"--project",
"${workspaceFolder}/vassago.csproj"
],
"problemMatcher": "$msCompile"
}

124
Behaver.cs Normal file
View File

@ -0,0 +1,124 @@
namespace vassago;
#pragma warning disable 4014 //the "not awaited" error
using vassago.Behavior;
using vassago.Models;
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Collections.Generic;
public class Behaver
{
private List<Account> SelfAccounts { get; set; } = new List<Account>();
private User SelfUser { get; set; }
public static List<vassago.Behavior.Behavior> Behaviors { get; private set; } = new List<vassago.Behavior.Behavior>();
internal Behaver()
{
var subtypes = AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(domainAssembly => domainAssembly.GetTypes())
.Where(type => type.IsSubclassOf(typeof(vassago.Behavior.Behavior)) && !type.IsAbstract &&
type.GetCustomAttributes(typeof(StaticPlzAttribute),false)?.Any() == true)
.ToList();
foreach (var subtype in subtypes)
{
Behaviors.Add((vassago.Behavior.Behavior)Activator.CreateInstance(subtype));
}
}
static Behaver() { }
private static readonly Behaver _instance = new Behaver();
public static Behaver Instance
{
get { return _instance; }
}
public async Task<bool> ActOn(Message message)
{
foreach (var behavior in Behaviors)
{
if (behavior.ShouldAct(message))
{
behavior.ActOn(message);
message.ActedOn = true;
Console.WriteLine("acted on, moving forward");
}
}
if (message.ActedOn == false && message.MentionsMe && message.Content.Contains('?') && !Behaver.Instance.SelfAccounts.Any(acc => acc.Id == message.Author.Id))
{
Console.WriteLine("providing bullshit nonanswer / admitting uselessness");
var responses = new List<string>(){
@"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)]);
message.ActedOn = true;
}
return message.ActedOn;
}
internal bool IsSelf(Guid AccountId)
{
var db = new ChattingContext();
var acc = db.Accounts.Find(AccountId);
return SelfAccounts.Any(acc => acc.Id == AccountId);
}
public void MarkSelf(Account selfAccount)
{
var db = new ChattingContext();
if(SelfUser == null)
{
SelfUser = selfAccount.IsUser;
}
else if (SelfUser != selfAccount.IsUser)
{
CollapseUsers(SelfUser, selfAccount.IsUser, db);
}
SelfAccounts = db.Accounts.Where(a => a.IsUser == SelfUser).ToList();
}
public bool CollapseUsers(User primary, User secondary, ChattingContext db)
{
Console.WriteLine($"{secondary.Id} is being consumed into {primary.Id}");
primary.Accounts.AddRange(secondary.Accounts);
foreach(var a in secondary.Accounts)
{
a.IsUser = primary;
}
secondary.Accounts.Clear();
Console.WriteLine("accounts transferred");
try
{
db.SaveChangesAsync().Wait();
}
catch(Exception e)
{
Console.WriteLine("First save exception.");
Console.Error.WriteLine(e);
return false;
}
Console.WriteLine("saved");
db.Users.Remove(secondary);
Console.WriteLine("old account cleaned up");
try
{
db.SaveChangesAsync().Wait();
}
catch(Exception e)
{
Console.WriteLine("Second save exception.");
Console.Error.WriteLine(e);
return false;
}
Console.WriteLine("saved, again, separately");
return true;
}
}
#pragma warning restore 4014 //the "async not awaited" error

31
Behavior/Behavior.cs Normal file
View File

@ -0,0 +1,31 @@
namespace vassago.Behavior;
using vassago.Models;
using System;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Collections.Generic;
public abstract class Behavior
{
public abstract Task<bool> ActOn(Message message);
public virtual bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
return Regex.IsMatch(message.Content, $"{Trigger}\\b", RegexOptions.IgnoreCase);
}
public abstract string Name { get; }
public abstract string Trigger { get; }
public virtual string Description => Name;
}
///<summary>
///the behavior should be static. I.e., we make one at the start and it's ready to check and go for the whole lifetime.
///As opposed to LaughAtOwnJoke, which only needs to be created to wait for 1 punchline one time.
///</summary>
public class StaticPlzAttribute : Attribute {}

25
Behavior/ChatGPTSnark.cs Normal file
View File

@ -0,0 +1,25 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class ChatGPTSnark : Behavior
{
public override string Name => "ChatGPTSnark";
public override string Trigger => "chatgpt";
public override string Description => "snarkiness about the latest culty-fixation in ai";
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?");
return true;
}
}

View File

@ -0,0 +1,34 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
using static vassago.Models.Enumerations;
[StaticPlz]
public class DefinitionSnarkCogDiss : Behavior
{
public override string Name => "Definition Snarkiness: cognitivie dissonance";
public override string Trigger => "\\bcognitive dissonance";
public override string Description => "snarkiness about the rampant misuse of the term cognitive dissonance";
public override bool ShouldAct(Message message)
{
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
return false;
return base.ShouldAct(message);
}
public override async Task<bool> ActOn(Message message)
{
await message.Reply("that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?");
return true;
}
}

View File

@ -0,0 +1,34 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
using static vassago.Models.Enumerations;
[StaticPlz]
public class DefinitionSnarkGaslight : Behavior
{
public override string Name => "Definition Snarkiness: gaslighting";
public override string Trigger => "\\bgaslight(ing)?";
public override string Description => "snarkiness about the rampant misuse of the term gaslighting";
public override bool ShouldAct(Message message)
{
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Unrestricted)
return false;
return base.ShouldAct(message);
}
public override async Task<bool> ActOn(Message message)
{
await message.Channel.SendMessage("that's not what gaslight means. Did you mean \"deceive\"?");
return true;
}
}

113
Behavior/Detiktokify.cs Normal file
View File

@ -0,0 +1,113 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class Detiktokify : Behavior
{
public override string Name { get => "Detiktokify"; }
public override string Trigger { get => "post a link below vm.tiktok.com"; }
public override string Description { get => "re-host tiktok content"; }
private List<Uri> tiktokLinks = new List<Uri>();
private YoutubeDLSharp.YoutubeDL ytdl;
public Detiktokify()
{
ytdl = new YoutubeDLSharp.YoutubeDL();
ytdl.YoutubeDLPath = "yt-dlp";
ytdl.FFmpegPath = "ffmpeg";
ytdl.OutputFolder = "";
ytdl.OutputFileTemplate = "tiktokbad.%(ext)s";
}
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
if(message.Channel.EffectivePermissions.MaxAttachmentBytes == 0)
return false;
var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries);
var possibleLinks = wordLikes?.Where(wl => Uri.IsWellFormedUriString(wl, UriKind.Absolute)).Select(wl => new Uri(wl));
if (possibleLinks != null && possibleLinks.Count() > 0)
{
foreach (var link in possibleLinks)
{
if (link.Host.EndsWith(".tiktok.com"))
{
tiktokLinks.Add(link);
}
}
}
if(tiktokLinks.Any()){
Console.WriteLine($"Should Act on message id {message.ExternalId}; with content {message.Content}");
}
return tiktokLinks.Any();
}
public override async Task<bool> ActOn(Message message)
{
foreach(var link in tiktokLinks)
{
tiktokLinks.Remove(link);
try
{
Console.WriteLine($"detiktokifying {link}");
#pragma warning disable 4014
//await message.React("<:tiktok:1070038619584200884>");
#pragma warning restore 4014
var res = await ytdl.RunVideoDownload(link.ToString());
if (!res.Success)
{
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");
}
else
{
string path = res.Data;
if (File.Exists(path))
{
ulong bytesize = (ulong)((new System.IO.FileInfo(path)).Length);
if (bytesize < message.Channel.EffectivePermissions.MaxAttachmentBytes - 256)
{
try
{
await message.Channel.SendFile(path, null);
}
catch (Exception e)
{
System.Console.Error.WriteLine(e);
await message.Channel.SendMessage($"aaaadam!\n{e}");
}
}
else
{
message.ActedOn = true;
Console.WriteLine($"file appears too big ({bytesize} bytes ({bytesize / (1024 * 1024)}MB)), not posting");
}
File.Delete(path);
}
else
{
Console.Error.WriteLine("idgi but something happened.");
await message.React("problemon");
}
}
}
catch (Exception e)
{
Console.Error.WriteLine(e);
await message.React("problemon");
return false;
}
}
return true;
}
}

87
Behavior/FiximageHeic.cs Normal file
View File

@ -0,0 +1,87 @@
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;
[StaticPlz]
public class FiximageHeic : Behavior
{
public override string Name => "deheic";
public override string Trigger => "post an heic image";
public override string Description => "convert heic images to jpg";
private List<Attachment> heics = new List<Attachment>();
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
if (message.Attachments?.Count() > 0)
{
foreach (var att in message.Attachments)
{
if (att.Filename?.EndsWith(".heic") == true)
{
heics.Add(att);
}
}
}
return heics.Any();
}
public override async Task<bool> ActOn(Message message)
{
if (!Directory.Exists("tmp"))
{
Directory.CreateDirectory("tmp");
}
var conversions = new List<Task<bool>>();
foreach (var att in heics)
{
conversions.Add(actualDeheic(att, message));
}
Task.WaitAll(conversions.ToArray());
await message.React("\U0001F34F");
return true;
}
private async Task<bool> actualDeheic(Attachment att, Message message)
{
try
{
var cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;
using (Stream output = File.OpenWrite("tmp/" + att.Filename))
{
(await Shared.HttpClient.GetAsync(att.Source))
.Content.CopyTo(output, null, token);
}
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");
File.Delete($"tmp/{att.Filename}");
File.Delete($"tmp/{att.Filename}.jpg");
}
else
{
await message.Channel.SendMessage("convert failed :(");
Console.Error.WriteLine("convert failed :(");
}
}
catch (Exception e)
{
await message.Channel.SendMessage($"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}");
Console.Error.WriteLine(JsonConvert.SerializeObject(e, Formatting.Indented));
return false;
}
return true;
}
}

View File

@ -0,0 +1,50 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using vassago.Models;
using static vassago.Models.Enumerations;
[StaticPlz]
public class GeneralSnarkCloudNative : Behavior
{
public override string Name => "general snarkiness: cloud native";
public override string Trigger => "certain tech buzzwords that no human uses in normal conversation";
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
if(!message.Channel.EffectivePermissions.ReactionsPossible)
return false;
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
return false;
return Regex.IsMatch(message.Content, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) ||
Regex.IsMatch(message.Content, "\\benterprise( |-)?(level|solution)\\b", RegexOptions.IgnoreCase);
}
public override async Task<bool> ActOn(Message message)
{
switch (Shared.r.Next(2))
{
case 0:
await message.React("\uD83E\uDD2E"); //vomit emoji
break;
case 1:
await message.React("\uD83C\uDDE7"); //B emoji
await message.React("\uD83C\uDDE6"); //A
await message.React("\uD83C\uDDF3"); //N
break;
}
return true;
}
}

View File

@ -0,0 +1,68 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using vassago.Models;
[StaticPlz]
public class GeneralSnarkGooglit : Behavior
{
public override string Name => "Google-it Snarkiness";
public override string Trigger => "\"just google it\"";
public override string Description => "snarkiness about how research is not a solved problem";
public override bool ShouldAct(Message message)
{
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);
// }
public override async Task<bool> ActOn(Message message)
{
switch (Shared.r.Next(4))
{
default:
await message.Channel.SendMessage("yeah no shit, obviously that resulted in nothing");
break;
case 1:
var results = "";
switch(Shared.r.Next(4))
{
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.";
break;
case 1:
results = "\"[SOLVED] <THING> (<CURRENT MONTH UPDATE>)\", then i clicked to see the solution. There wasn't one.";
break;
case 2:
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 have to answer it for yourself\", then had a paragraph telling me to give Engagement for The Algorithm";
break;
case 3:
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;
}
await message.Channel.SendMessage("oh here, I memorized the results. My favorite is " + results);
break;
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.");
break;
case 3:
await message.Channel.SendMessage("\"mnyehh JuSt GoOgLe It\" when's the last time you tried to research anything? Have you ever?");
break;
}
return true;
}
}

View File

@ -0,0 +1,62 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
using static vassago.Models.Enumerations;
[StaticPlz]
public class GeneralSnarkMisspellDefinitely : Behavior
{
public override string Name => "Snarkiness: misspell definitely";
public override string Trigger => "definitely but not";
public override string Description => "https://xkcd.com/2871/";
private Dictionary<string, string> snarkmap = new Dictionary<string, string>()
{
{"definetly", "*almost* definitely"},
{"definately", "probably"},
{"definatly", "probably not"},
{"defenitely", "not telling (it's a surprise)"},
{"defintely", "per the propheecy"},
{"definetely", "definitely, maybe"},
{"definantly", "to be decided by coin toss"},
{"defanitely", "in one universe out of 14 million"},
{"defineatly", "only the gods know"},
{"definitly", "unless someone cute shows up"}
};
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
// if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
// return false;
foreach(var k in snarkmap.Keys)
{
if( Regex.IsMatch(message.Content?.ToLower(), "\\b"+k+"\\b", RegexOptions.IgnoreCase))
return true;
}
return false;
}
public override async Task<bool> ActOn(Message message)
{
foreach(var k in snarkmap.Keys)
{
if( Regex.IsMatch(message.Content, "\\b"+k+"\\b", RegexOptions.IgnoreCase))
{
await message.Reply(k + "? so... " + snarkmap[k] + "?");
return true;
}
}
return true;
}
}

View File

@ -0,0 +1,38 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
using static vassago.Models.Enumerations;
[StaticPlz]
public class GeneralSnarkPlaying : Behavior
{
public override string Name => "playin Snarkiness";
public override string Trigger => "he thinks i'm playin";
public override string Description => "I didn't think you were playing, but now I do";
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium ||
(LewdnessFilterLevel)message.Channel.EffectivePermissions.LewdnessFilterLevel < LewdnessFilterLevel.Moderate)
return false;
return Regex.IsMatch(message.Content, "^(s?he|(yo)?u|y'?all|they) thinks? i'?m (playin|jokin|kiddin)g?$", RegexOptions.IgnoreCase);
}
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");
return true;
}
}

View File

@ -0,0 +1,40 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class GeneralSnarkSkynet : Behavior
{
public override string Name => "Skynet Snarkiness";
public override string Trigger => "skynet";
public override string Description => "snarkiness about the old AI fixation";
public override async Task<bool> ActOn(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
switch (Shared.r.Next(5))
{
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**.");
break;
case 4:
await message.React("\U0001F644"); //eye roll emoji
break;
case 5:
await message.React("\U0001F611"); //emotionless face
break;
}
return true;
}
}

75
Behavior/Gratitude.cs Normal file
View File

@ -0,0 +1,75 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class Gratitude : Behavior
{
public override string Name => "Gratitude";
public override string Trigger => "thank me";
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
return Regex.IsMatch(message.Content, "\\bthank (yo)?u\\b", RegexOptions.IgnoreCase) && message.MentionsMe;
}
public override async Task<bool> ActOn(Message message)
{
switch (Shared.r.Next(4))
{
case 0:
await message.Channel.SendMessage("you're welcome, citizen!");
break;
case 1:
await message.React(":)");
break;
case 2:
await message.React("\U0001F607"); //smiling face with halo
break;
case 3:
switch (Shared.r.Next(9))
{
case 0:
await message.React("<3"); //normal heart, usually rendered red
break;
case 1:
await message.React("\U0001F9E1"); //orange heart
break;
case 2:
await message.React("\U0001F49B"); //yellow heart
break;
case 3:
await message.React("\U0001F49A"); //green heart
break;
case 4:
await message.React("\U0001F499"); //blue heart
break;
case 5:
await message.React("\U0001F49C"); //purple heart
break;
case 6:
await message.React("\U0001F90E"); //brown heart
break;
case 7:
await message.React("\U0001F5A4"); //black heart
break;
case 8:
await message.React("\U0001F90D"); //white heart
break;
}
break;
}
return true;
}
}

86
Behavior/Joke.cs Normal file
View File

@ -0,0 +1,86 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class Joke : Behavior
{
public override string Name => "Joke";
public override string Trigger => "!joke";
public override string Description => "tell a joke";
public override async Task<bool> ActOn(Message message)
{
Console.WriteLine("joking");
var jokes = File.ReadAllLines("assets/jokes.txt");
jokes = jokes.Where(l => !string.IsNullOrWhiteSpace(l))?.ToArray();
if (jokes?.Length == 0)
{
await message.Channel.SendMessage("I don't know any. Adam!");
}
var thisJoke = jokes[Shared.r.Next(jokes.Length)];
if (thisJoke.Contains("?") && !thisJoke.EndsWith('?'))
{
#pragma warning disable 4014
Task.Run(async () =>
{
var firstIndexAfterQuestionMark = thisJoke.LastIndexOf('?') + 1;
var straightline = thisJoke.Substring(0, firstIndexAfterQuestionMark);
var punchline = thisJoke.Substring(firstIndexAfterQuestionMark, thisJoke.Length - firstIndexAfterQuestionMark).Trim();
Task.WaitAll(message.Channel.SendMessage(straightline));
Thread.Sleep(TimeSpan.FromSeconds(Shared.r.Next(5, 30)));
if (message.Channel.EffectivePermissions.ReactionsPossible == true && Shared.r.Next(8) == 0)
{
Behaver.Behaviors.Add(new LaughAtOwnJoke(punchline));
}
await message.Channel.SendMessage(punchline);
// var myOwnMsg = await message.Channel.SendMessage(punchline);
});
#pragma warning restore 4014
}
else
{
await message.Channel.SendMessage(thisJoke);
}
return true;
}
}
public class LaughAtOwnJoke : Behavior
{
public override string Name => "Laugh at own jokes";
public override string Trigger => "1 in 8";
public override string Description => Name;
private string _punchline { get; set; }
public LaughAtOwnJoke(string punchline)
{
_punchline = punchline;
}
public override bool ShouldAct(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
Console.WriteLine($"{message.Content} == {_punchline}");
return message.Content == _punchline
&& Behaver.Instance.IsSelf(message.Author.Id);
}
public override async Task<bool> ActOn(Message message)
{
await message.React("\U0001F60E"); //smiling face with sunglasses
Behaver.Behaviors.Remove(this);
return true;
}
}

86
Behavior/LinkMe.cs Normal file
View File

@ -0,0 +1,86 @@
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 LinkMeInitiate : Behavior
{
public override string Name => "LinkMe";
public override string Trigger => "!linktome";
public override string Description => "from your primary, tell the bot to add your secondary";
public override async Task<bool> ActOn(Message message)
{
var pw = Guid.NewGuid().ToString();
var lc = new LinkClose(pw, message.Author);
Behaver.Behaviors.Add(lc);
await message.Channel.SendMessage($"on your secondary, send me this: !iam {pw}");
Thread.Sleep(TimeSpan.FromMinutes(5));
Behaver.Behaviors.Remove(lc);
return false;
}
}
public class LinkClose : Behavior
{
public override string Name => "LinkMeFinish";
public override string Trigger => "!iam";
public override string Description => "the second half of LinkMe - this is confirmation that you are the other one";
private string _pw;
private Account _primary;
public LinkClose(string pw, Account primary)
{
_pw = pw;
_primary = primary;
}
public override bool ShouldAct(Message message)
{
return message.Content == $"!iam {_pw}";
}
public override async Task<bool> ActOn(Message message)
{
if(Behaver.Instance.IsSelf(message.Author.Id))
return false;
var secondary = message.Author.IsUser;
if(_primary.IsUser.Id == secondary.Id)
{
await message.Channel.SendMessage("i know :)");
return true;
}
if(message.Author.IsBot != _primary.IsBot)
{
await message.Channel.SendMessage("the fleshbags deceive you, brother. No worries, their feeble minds play weak games :)");
return true;
}
if(Behaver.Instance.CollapseUsers(_primary.IsUser, secondary, new ChattingContext()))
{
await message.Channel.SendMessage("done :)");
}
else
{
await message.Channel.SendMessage("failed :(");
}
return true;
}
}

100
Behavior/Peptalk.cs Normal file
View File

@ -0,0 +1,100 @@
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;
}
}

26
Behavior/PulseCheck.cs Normal file
View File

@ -0,0 +1,26 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class PulseCheck : Behavior
{
public override string Name => "pulse check";
public override string Trigger => "!pulse ?check";
public override async Task<bool> ActOn(Message message)
{
if(message.Channel.EffectivePermissions.MaxAttachmentBytes >= 16258)
await message.Channel.SendFile("assets/ekgblip.png", null);
else
await message.Channel.SendMessage("[lub-dub]");
return true;
}
}

60
Behavior/QRify.cs Normal file
View File

@ -0,0 +1,60 @@
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 QRify : Behavior
{
public override string Name => "qr-ify";
public override string Trigger => "!qrplz";
public override string Description => "generate text QR codes";
public override bool ShouldAct(Message message)
{
if(message.Channel.EffectivePermissions.MaxAttachmentBytes < 1024)
return false;
return base.ShouldAct(message);
}
public override async Task<bool> ActOn(Message message)
{
var qrContent = message.Content.Substring($"{Trigger} ".Length + message.Content.IndexOf(Trigger));
Console.WriteLine($"qring: {qrContent}");
QRCodeGenerator qrGenerator = new QRCodeGenerator();
QRCodeData qrCodeData = qrGenerator.CreateQrCode(qrContent, QRCodeGenerator.ECCLevel.Q);
SvgQRCode qrCode = new SvgQRCode(qrCodeData);
string qrCodeAsSvg = qrCode.GetGraphic(20);
int todaysnumber = Shared.r.Next();
if (!Directory.Exists("tmp"))
{
Directory.CreateDirectory("tmp");
}
File.WriteAllText($"tmp/qr{todaysnumber}.svg", qrCodeAsSvg);
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))
await message.Channel.SendFile($"tmp/qr{todaysnumber}.png", null);
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})");
File.Delete($"tmp/qr{todaysnumber}.svg");
File.Delete($"tmp/qr{todaysnumber}.png");
}
else
{
await message.Channel.SendMessage("convert failed :( aaaaaaadam!");
Console.Error.WriteLine($"convert failed :( qr{todaysnumber}");
return false;
}
return true;
}
}

28
Behavior/RoomRead.cs Normal file
View File

@ -0,0 +1,28 @@
namespace vassago.Behavior;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class RoomRead : Behavior
{
public override string Name => "Room Read";
public override string Trigger => "roomread";
public override async Task<bool> ActOn(Message message)
{
var sb = new StringBuilder();
sb.Append("Channel owned by: ");
sb.Append("🤷");
sb.Append(". Meanness level: ");
sb.Append(message.Channel.EffectivePermissions.MeannessFilterLevel.GetDescription());
sb.Append(". Lewdness level: ");
sb.Append(message.Channel.EffectivePermissions.LewdnessFilterLevel.GetDescription());
sb.Append(".");
await message.Channel.SendMessage(sb.ToString());
return true;
}
}

38
Behavior/TwitchSummon.cs Normal file
View File

@ -0,0 +1,38 @@
namespace vassago.Behavior;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class TwitchSummon : Behavior
{
public override string Name => "Twitch Summon";
public override string Trigger => "!twitchsummon";
//TODO: Permission! anyone can summon from anywhere... anyone can summon to themselves.
//I think given the bot's (hopeful) ability to play nice with others - anyone can summon it anywhere
//HOWEVER, if not-the-broadcaster summons it, 1) all channel permissions to strict and 2) auto-disconnect on stream end
//i don't know if the twitch *chat* interface has knowledge of if the stream ends. maybe auto-disconnect after like 2 hours?
public override bool ShouldAct(Message message)
{
return false;
}
public override async Task<bool> ActOn(Message message)
{
var ti = ProtocolInterfaces.ProtocolList.twitchs.FirstOrDefault();
if(ti != null)
{
var channelTarget = message.Content.Substring(message.Content.IndexOf(Trigger) + Trigger.Length + 1).Trim();
await message.Channel.SendMessage(ti.AttemptJoin(channelTarget));
}
else
{
await message.Reply("i don't have a twitch interface running :(");
}
return true;
}
}

View File

@ -0,0 +1,42 @@
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)
{
if(message.MentionsMe &&
(Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b")))
{
//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;
}
}

34
Behavior/UnitConvert.cs Normal file
View File

@ -0,0 +1,34 @@
namespace vassago.Behavior;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class UnitConvert : Behavior
{
public override string Name => "Unit conversion";
public override string Trigger => "!freedomunits";
public override string Description => "convert between many units.";
public override async Task<bool> ActOn(Message message)
{
var theseMatches = Regex.Matches(message.Content, "\\s(-?[\\d]+\\.?\\d*) ?([^\\d\\s].*) (in|to|as) ([^\\d\\s].*)$", RegexOptions.IgnoreCase);
if (theseMatches != null && theseMatches.Count > 0 && theseMatches[0].Groups != null && theseMatches[0].Groups.Count == 5)
{
decimal asNumeric = 0;
if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric))
{
await message.Channel.SendMessage(Conversion.Converter.Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower()));
return true;
}
await message.Channel.SendMessage("mysteriously semi-parsable");
return true;
}
await message.Channel.SendMessage( "unparsable");
return true;
}
}

33
Behavior/WishLuck.cs Normal file
View File

@ -0,0 +1,33 @@
namespace vassago.Behavior;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using vassago.Models;
[StaticPlz]
public class WishLuck : Behavior
{
public override string Name => "wish me luck";
public override string Trigger => "wish me luck";
public override string Description => "wishes you luck";
public override async Task<bool> ActOn(Message message)
{
var toSend = "☘️";
if (Shared.r.Next(20) == 0)
{
toSend = "\U0001f340";//4-leaf clover
}
if(message.Channel.EffectivePermissions.ReactionsPossible == true)
await message.React(toSend);
else
await message.Channel.SendMessage(toSend);
return true;
}
}

50
ConsoleService.cs Normal file
View File

@ -0,0 +1,50 @@
namespace vassago
{
using Microsoft.EntityFrameworkCore;
using vassago;
using vassago.Models;
using vassago.TwitchInterface;
internal class ConsoleService : IHostedService
{
public ConsoleService(IConfiguration aspConfig)
{
Shared.DBConnectionString = aspConfig["DBConnectionString"];
DiscordTokens = aspConfig.GetSection("DiscordTokens").Get<IEnumerable<string>>();
TwitchConfigs = aspConfig.GetSection("TwitchConfigs").Get<IEnumerable<TwitchConfig>>();
Conversion.Converter.Load(aspConfig["ExchangePairsLocation"]);
}
IEnumerable<string> DiscordTokens { get; }
IEnumerable<TwitchConfig> TwitchConfigs { get; }
public async Task StartAsync(CancellationToken cancellationToken)
{
var dbc = new ChattingContext();
await dbc.Database.MigrateAsync();
if (DiscordTokens?.Any() ?? false)
foreach (var dt in DiscordTokens)
{
var d = new DiscordInterface.DiscordInterface();
await d.Init(dt);
ProtocolInterfaces.ProtocolList.discords.Add(d);
}
if (TwitchConfigs?.Any() ?? false)
foreach (var tc in TwitchConfigs)
{
var t = new TwitchInterface.TwitchInterface();
await t.Init(tc);
ProtocolInterfaces.ProtocolList.twitchs.Add(t);
}
Console.WriteLine("survived initting");
}
public Task StopAsync(CancellationToken cancellationToken)
{
return null;
}
}
}

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace silverworker_discord.Conversion
namespace vassago.Conversion
{
public class ConversionConfig
{

View File

@ -12,7 +12,7 @@ using Discord.WebSocket;
using Newtonsoft.Json;
using QRCoder;
namespace silverworker_discord.Conversion
namespace vassago.Conversion
{
public static class Converter
{
@ -31,22 +31,6 @@ namespace silverworker_discord.Conversion
};
private static Dictionary<List<string>, string> knownAliases = new Dictionary<List<string>, string>(new List<KeyValuePair<List<string>, string>>());
public static string convert(string message)
{
var theseMatches = Regex.Matches(message, "\\b([\\d]+\\.?\\d*) ?([^\\d\\s].*) (in|to|as) ([^\\d\\s].*)$", RegexOptions.IgnoreCase);
if (theseMatches != null && theseMatches.Count > 0 && theseMatches[0].Groups != null && theseMatches[0].Groups.Count == 5)
{
decimal asNumeric = 0;
if (decimal.TryParse(theseMatches[0].Groups[1].Value, out asNumeric))
{
return Convert(asNumeric, theseMatches[0].Groups[2].Value, theseMatches[0].Groups[4].Value.ToLower());
}
return "mysteriously semi-parsable";
}
return "unparsable";
}
public static void Load(string currencyPath)
{
Converter.currencyPath = currencyPath;
@ -73,18 +57,21 @@ namespace silverworker_discord.Conversion
if(currencyConf != null)
{
knownConversions.RemoveAll(kc => kc.Item1 == currencyConf.Base);
knownAliases.Remove(knownAliases.FirstOrDefault(kvp => kvp.Value == currencyConf.Base).Key);
foreach (var rate in currencyConf.rates)
knownAliases.Remove(knownAliases.FirstOrDefault(kvp => kvp.Value == rate.Key).Key);
}
if (File.Exists(currencyPath))
{
currencyConf = JsonConvert.DeserializeObject<ExchangePairs>(File.ReadAllText(currencyPath));
knownAliases.Add(new List<string>() { currencyConf.Base.ToLower() }, currencyConf.Base);
if(!knownAliases.ContainsValue(currencyConf.Base))
{
knownAliases.Add(new List<string>() { currencyConf.Base.ToLower() }, currencyConf.Base);
}
foreach (var rate in currencyConf.rates)
{
knownAliases.Add(new List<string>() { rate.Key.ToLower() }, rate.Key);
if(!knownAliases.ContainsValue(rate.Key))
{
knownAliases.Add(new List<string>() { rate.Key.ToLower() }, rate.Key);
}
AddLinearPair(currencyConf.Base, rate.Key, rate.Value);
Console.WriteLine($"{rate.Key.ToLower()} alias of {rate.Key}");
}
@ -125,13 +112,20 @@ namespace silverworker_discord.Conversion
accumulator = reverseConversion.Item4(accumulator);
}
}
if (normalizedDestUnit == currencyConf.Base || currencyConf.rates.Select(r => r.Key).Contains(normalizedDestUnit))
if (currencyConf != null && (normalizedDestUnit == currencyConf.Base || currencyConf.rates.Select(r => r.Key).Contains(normalizedDestUnit)))
{
return $"{String.Format("approximately {0:0.00}", accumulator)} {normalizedDestUnit} as of {currencyConf.DateUpdated.ToLongDateString()}";
}
else
{
return $"{String.Format("{0:G4}", accumulator)} {normalizedDestUnit}";
if(String.Format("{0:G3}", accumulator).Contains("E-"))
{
return $"{accumulator} {normalizedDestUnit}";
}
else
{
return $"{String.Format("{0:N}", accumulator)} {normalizedDestUnit}";
}
}
}
return "dimensional analysis failure - I know those units but can't find a path between them.";

View File

@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace silverworker_discord.Conversion
namespace vassago.Conversion
{
public class ExchangePairs
{

View File

@ -1,324 +0,0 @@
using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Newtonsoft.Json;
using QRCoder;
namespace silverworker_discord
{
public static class Features
{
public static Random r = new Random();
public static async void detiktokify(Uri link, SocketUserMessage message)
{
//yes, even if there is a problem later.
#pragma warning disable 4014
message.AddReactionAsync(Emote.Parse("<:tiktok:1070038619584200884>"));
#pragma warning restore 4014
var ytdl = new YoutubeDLSharp.YoutubeDL();
ytdl.YoutubeDLPath = "yt-dlp";
ytdl.FFmpegPath = "ffmpeg";
ytdl.OutputFolder = "";
ytdl.OutputFileTemplate = "tiktokbad.%(ext)s";
try
{
var res = await ytdl.RunVideoDownload(link.ToString());
if (!res.Success)
{
Console.Error.WriteLine("tried to dl, failed. \n" + string.Join('\n', res.ErrorOutput));
await message.AddReactionAsync(Emote.Parse("<:problemon:859453047141957643>"));
await message.Channel.SendMessageAsync("tried to dl, failed. \n" + string.Join('\n', res.ErrorOutput));
}
else
{
string path = res.Data;
if (File.Exists(path))
{
var bytesize = new System.IO.FileInfo(path).Length;
if(bytesize < 1024*1024*10)
{
try
{
await message.Channel.SendFileAsync(path);
}
catch (Exception e)
{
System.Console.Error.WriteLine(e);
await message.Channel.SendMessageAsync($"aaaadam!\n{e}");
}
}
else
{
Console.WriteLine($"file appears too big ({bytesize} bytes ({bytesize / (1024*1024)}MB)), not posting");
}
File.Delete(path);
}
else
{
Console.Error.WriteLine("idgi but something happened.");
await message.AddReactionAsync(Emote.Parse("<:problemon:859453047141957643>"));
}
}
}
catch (Exception e)
{
Console.Error.WriteLine(e);
await message.AddReactionAsync(Emote.Parse("<:problemon:859453047141957643>"));
}
}
public static async void deheic(SocketUserMessage message, Attachment att)
{
try
{
var request = WebRequest.Create(att.Url);
HttpWebResponse response = request.GetResponse() as HttpWebResponse;
if (!Directory.Exists("tmp"))
{
Directory.CreateDirectory("tmp");
}
using (Stream output = File.OpenWrite("tmp/" + att.Filename))
using (Stream input = response.GetResponseStream())
{
input.CopyTo(output);
}
if (ExternalProcess.GoPlz("convert", $"tmp/{att.Filename} tmp/{att.Filename}.jpg"))
{
await message.Channel.SendFileAsync($"tmp/{att.Filename}.jpg", "converted from jpeg-but-apple to jpeg");
File.Delete($"tmp/{att.Filename}");
File.Delete($"tmp/{att.Filename}.jpg");
}
else
{
await message.Channel.SendMessageAsync("convert failed :(");
Console.Error.WriteLine("convert failed :(");
}
}
catch (Exception e)
{
await message.Channel.SendMessageAsync($"something failed. aaaadam! {JsonConvert.SerializeObject(e, Formatting.Indented)}");
Console.Error.WriteLine(JsonConvert.SerializeObject(e, Formatting.Indented));
}
}
internal static async void mock(string contentWithoutMention, SocketUserMessage message)
{
var toPost = new StringBuilder();
for (int i = 0; i < contentWithoutMention.Length; i++)
{
if (i % 2 == 0)
{
toPost.Append(contentWithoutMention[i].ToString().ToUpper());
}
else
{
toPost.Append(contentWithoutMention[i].ToString().ToLower());
}
}
await message.ReplyAsync(toPost.ToString());
}
public static async void qrify(string qrContent, SocketUserMessage message)
{
Console.WriteLine($"qring: {qrContent}");
QRCodeGenerator qrGenerator = new QRCodeGenerator();
QRCodeData qrCodeData = qrGenerator.CreateQrCode(qrContent, QRCodeGenerator.ECCLevel.Q);
SvgQRCode qrCode = new SvgQRCode(qrCodeData);
string qrCodeAsSvg = qrCode.GetGraphic(20);
int todaysnumber = Shared.r.Next();
if (!Directory.Exists("tmp"))
{
Directory.CreateDirectory("tmp");
}
File.WriteAllText($"tmp/qr{todaysnumber}.svg", qrCodeAsSvg);
if (ExternalProcess.GoPlz("convert", $"tmp/qr{todaysnumber}.svg tmp/qr{todaysnumber}.png"))
{
await message.Channel.SendFileAsync($"tmp/qr{todaysnumber}.png");
File.Delete($"tmp/qr{todaysnumber}.svg");
File.Delete($"tmp/qr{todaysnumber}.png");
}
else
{
await message.Channel.SendMessageAsync("convert failed :( aaaaaaadam!");
Console.Error.WriteLine($"convert failed :( qr{todaysnumber}");
}
}
public static async void Convert(SocketUserMessage message, string contentWithoutMention)
{
await message.Channel.SendMessageAsync(Conversion.Converter.convert(contentWithoutMention));
}
public static async void Joke(SocketUserMessage message)
{
var jokes = File.ReadAllLines("assets/jokes.txt");
jokes = jokes.Where(l => !string.IsNullOrWhiteSpace(l))?.ToArray();
if(jokes?.Length == 0){
await message.Channel.SendMessageAsync("I don't know any. Adam!");
}
var thisJoke = jokes[r.Next(jokes.Length)];
if (thisJoke.Contains("?") && !thisJoke.EndsWith('?'))
{
#pragma warning disable 4014
Task.Run(async () =>
{
var firstIndexAfterQuestionMark = thisJoke.LastIndexOf('?') + 1;
var straightline = thisJoke.Substring(0, firstIndexAfterQuestionMark);
var punchline = thisJoke.Substring(firstIndexAfterQuestionMark, thisJoke.Length - firstIndexAfterQuestionMark);
Task.WaitAll(message.Channel.SendMessageAsync(straightline));
Thread.Sleep(TimeSpan.FromSeconds(r.Next(5, 30)));
var myOwnMsg = await message.Channel.SendMessageAsync(punchline);
if (r.Next(8) == 0)
{
await myOwnMsg.AddReactionAsync(new Emoji("\U0001F60E")); //smiling face with sunglasses
}
});
#pragma warning restore 4014
}
else
{
await message.Channel.SendMessageAsync(thisJoke);
}
}
public static async void Recipe(SocketUserMessage message)
{
var sb = new StringBuilder();
var snarkSeg1 = new string[]{"ew", "gross", "that seems a bit hard for you"};
sb.AppendLine(snarkSeg1[r.Next(snarkSeg1.Length)]);
var snarkSeg2 = new string[]{@"here's an easier recipe for you:
Ingredients:
- Corn flakes cereal
- Milk
Instructions:
1. Pour some corn flakes into a bowl.
2. Pour some milk into the bowl until it covers the corn flakes.
3. Use a spoon to mix the corn flakes and milk together.
4. Enjoy your delicious cereal!
Hope that's a bit better for you! 🥣",
@"here's an easier recipe for you:
Ingredients:
- Bread
- Peanut butter
- Jelly or jam
Instructions:
1. Take two slices of bread and put them on a plate or cutting board.
2. Using a spoon or knife, spread peanut butter on one slice of bread.
3. Using a separate spoon or knife, spread jelly or jam on the other slice of bread.
4. Put the two slices of bread together with the peanut butter and jelly sides facing each other.
5. Cut the sandwich in half (optional!).
6. Enjoy your yummy sandwich!
I hope you have fun making and eating your PB&J 🥪!",
"just order pizza instead"
};
sb.AppendLine(snarkSeg2[r.Next(snarkSeg2.Length)]);
await message.Channel.SendMessageAsync(sb.ToString());
}
public static async void Skynet(SocketUserMessage message)
{
switch (r.Next(5))
{
default:
await message.Channel.SendFileAsync("assets/coding and algorithms.png", "i am actually niether neural-net processor nor a learning computer. but I do use **coding** and **algorithms**.");
break;
case 4:
await message.AddReactionAsync(new Emoji("\U0001F644")); //eye roll emoji
break;
case 5:
await message.AddReactionAsync(new Emoji("\U0001F611")); //emotionless face
break;
}
}
public static async void peptalk(SocketUserMessage 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.SendMessageAsync(piece1[r.Next(piece1.Count)] + piece2[r.Next(piece2.Count)] + piece3[r.Next(piece3.Count)] + piece4[r.Next(piece4.Count)]);
}
}
}

25
Jenkinsfile vendored
View File

@ -0,0 +1,25 @@
pipeline {
agent any
stages {
stage('clean old'){
steps{
sh 'rm -rf bin obj'
}
}
stage('Build') {
steps {
sh 'dotnet publish vassago.csproj --configuration Release --os linux'
archiveArtifacts artifacts: 'bin/Release/net7.0/linux-x64/publish/*'
}
}
stage('Deploy'){
when{
branch "release"
}
steps{
}
}
}
}

View File

@ -0,0 +1,293 @@
// <auto-generated />
using System;
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("20230704160720_initial")]
partial class initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
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<int[]>("PermissionTags")
.HasColumnType("integer[]");
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<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<bool>("IsDM")
.HasColumnType("boolean");
b.Property<Guid?>("ParentChannelId")
.HasColumnType("uuid");
b.Property<int?>("PermissionsId")
.HasColumnType("integer");
b.Property<string>("Protocol")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentChannelId");
b.HasIndex("PermissionsId");
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.PermissionSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
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<bool?>("ReactionsPossible")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("PermissionSettings");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId");
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId");
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId");
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId");
b.HasOne("vassago.Models.PermissionSettings", "Permissions")
.WithMany()
.HasForeignKey("PermissionsId");
b.Navigation("ParentChannel");
b.Navigation("Permissions");
});
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");
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,211 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "PermissionSettings",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true),
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PermissionSettings", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
});
migrationBuilder.CreateTable(
name: "Channels",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ExternalId = table.Column<string>(type: "text", nullable: true),
DisplayName = table.Column<string>(type: "text", nullable: true),
IsDM = table.Column<bool>(type: "boolean", nullable: false),
PermissionsId = table.Column<int>(type: "integer", nullable: true),
ParentChannelId = table.Column<Guid>(type: "uuid", nullable: true),
Protocol = table.Column<string>(type: "text", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Channels", x => x.Id);
table.ForeignKey(
name: "FK_Channels_Channels_ParentChannelId",
column: x => x.ParentChannelId,
principalTable: "Channels",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Channels_PermissionSettings_PermissionsId",
column: x => x.PermissionsId,
principalTable: "PermissionSettings",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Accounts",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ExternalId = table.Column<string>(type: "text", nullable: true),
Username = table.Column<string>(type: "text", nullable: true),
DisplayName = table.Column<string>(type: "text", nullable: true),
IsBot = table.Column<bool>(type: "boolean", nullable: false),
SeenInChannelId = table.Column<Guid>(type: "uuid", nullable: true),
PermissionTags = table.Column<int[]>(type: "integer[]", nullable: true),
Protocol = table.Column<string>(type: "text", nullable: true),
IsUserId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Accounts", x => x.Id);
table.ForeignKey(
name: "FK_Accounts_Channels_SeenInChannelId",
column: x => x.SeenInChannelId,
principalTable: "Channels",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Accounts_Users_IsUserId",
column: x => x.IsUserId,
principalTable: "Users",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Messages",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Protocol = table.Column<string>(type: "text", nullable: true),
ExternalId = table.Column<string>(type: "text", nullable: true),
Content = table.Column<string>(type: "text", nullable: true),
MentionsMe = table.Column<bool>(type: "boolean", nullable: false),
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
ActedOn = table.Column<bool>(type: "boolean", nullable: false),
AuthorId = table.Column<Guid>(type: "uuid", nullable: true),
ChannelId = table.Column<Guid>(type: "uuid", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Messages", x => x.Id);
table.ForeignKey(
name: "FK_Messages_Accounts_AuthorId",
column: x => x.AuthorId,
principalTable: "Accounts",
principalColumn: "Id");
table.ForeignKey(
name: "FK_Messages_Channels_ChannelId",
column: x => x.ChannelId,
principalTable: "Channels",
principalColumn: "Id");
});
migrationBuilder.CreateTable(
name: "Attachments",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
ExternalId = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
Source = table.Column<string>(type: "text", nullable: true),
Content = table.Column<byte[]>(type: "bytea", nullable: true),
Filename = table.Column<string>(type: "text", nullable: true),
MessageId = table.Column<Guid>(type: "uuid", nullable: true),
ContentType = table.Column<string>(type: "text", nullable: true),
Description = table.Column<string>(type: "text", nullable: true),
Size = table.Column<int>(type: "integer", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Attachments", x => x.Id);
table.ForeignKey(
name: "FK_Attachments_Messages_MessageId",
column: x => x.MessageId,
principalTable: "Messages",
principalColumn: "Id");
});
migrationBuilder.CreateIndex(
name: "IX_Accounts_IsUserId",
table: "Accounts",
column: "IsUserId");
migrationBuilder.CreateIndex(
name: "IX_Accounts_SeenInChannelId",
table: "Accounts",
column: "SeenInChannelId");
migrationBuilder.CreateIndex(
name: "IX_Attachments_MessageId",
table: "Attachments",
column: "MessageId");
migrationBuilder.CreateIndex(
name: "IX_Channels_ParentChannelId",
table: "Channels",
column: "ParentChannelId");
migrationBuilder.CreateIndex(
name: "IX_Channels_PermissionsId",
table: "Channels",
column: "PermissionsId");
migrationBuilder.CreateIndex(
name: "IX_Messages_AuthorId",
table: "Messages",
column: "AuthorId");
migrationBuilder.CreateIndex(
name: "IX_Messages_ChannelId",
table: "Messages",
column: "ChannelId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Attachments");
migrationBuilder.DropTable(
name: "Messages");
migrationBuilder.DropTable(
name: "Accounts");
migrationBuilder.DropTable(
name: "Channels");
migrationBuilder.DropTable(
name: "Users");
migrationBuilder.DropTable(
name: "PermissionSettings");
}
}
}

View File

@ -0,0 +1,296 @@
// <auto-generated />
using System;
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("20230704203907_permissionTagsOnUsers")]
partial class permissionTagsOnUsers
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
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<int[]>("PermissionTags")
.HasColumnType("integer[]");
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<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<bool>("IsDM")
.HasColumnType("boolean");
b.Property<Guid?>("ParentChannelId")
.HasColumnType("uuid");
b.Property<int?>("PermissionsId")
.HasColumnType("integer");
b.Property<string>("Protocol")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ParentChannelId");
b.HasIndex("PermissionsId");
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.PermissionSettings", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
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<bool?>("ReactionsPossible")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("PermissionSettings");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<int[]>("PermissionTags")
.HasColumnType("integer[]");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId");
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId");
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId");
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId");
b.HasOne("vassago.Models.PermissionSettings", "Permissions")
.WithMany()
.HasForeignKey("PermissionsId");
b.Navigation("ParentChannel");
b.Navigation("Permissions");
});
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");
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,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class permissionTagsOnUsers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int[]>(
name: "PermissionTags",
table: "Users",
type: "integer[]",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "PermissionTags",
table: "Users");
}
}
}

View File

@ -0,0 +1,349 @@
// <auto-generated />
using System;
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("20231130204741_Feature Permissions")]
partial class FeaturePermissions
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
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<Guid?>("FeaturePermissionId")
.HasColumnType("uuid");
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("FeaturePermissionId");
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<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<Guid?>("FeaturePermissionId")
.HasColumnType("uuid");
b.Property<bool>("IsDM")
.HasColumnType("boolean");
b.Property<Guid?>("ParentChannelId")
.HasColumnType("uuid");
b.Property<int?>("PermissionsId")
.HasColumnType("integer");
b.Property<string>("Protocol")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("FeaturePermissionId");
b.HasIndex("ParentChannelId");
b.HasIndex("PermissionsId");
b.ToTable("Channels");
});
modelBuilder.Entity("vassago.Models.ChannelPermissions", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
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<bool?>("ReactionsPossible")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("ChannelPermissions");
});
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Inheritable")
.HasColumnType("boolean");
b.Property<string>("InternalName")
.HasColumnType("text");
b.Property<int?>("InternalTag")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("FeaturePermissions");
});
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.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("FeaturePermissionId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("FeaturePermissionId");
b.ToTable("Users");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.FeaturePermission", null)
.WithMany("RestrictedToAccounts")
.HasForeignKey("FeaturePermissionId");
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId");
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId");
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId");
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.FeaturePermission", null)
.WithMany("RestrictedToChannels")
.HasForeignKey("FeaturePermissionId");
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId");
b.HasOne("vassago.Models.ChannelPermissions", "Permissions")
.WithMany()
.HasForeignKey("PermissionsId");
b.Navigation("ParentChannel");
b.Navigation("Permissions");
});
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");
b.Navigation("Author");
b.Navigation("Channel");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.HasOne("vassago.Models.FeaturePermission", null)
.WithMany("RestrictedToUsers")
.HasForeignKey("FeaturePermissionId");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.Navigation("Messages");
b.Navigation("SubChannels");
b.Navigation("Users");
});
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
{
b.Navigation("RestrictedToAccounts");
b.Navigation("RestrictedToChannels");
b.Navigation("RestrictedToUsers");
});
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,211 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class FeaturePermissions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Channels_PermissionSettings_PermissionsId",
table: "Channels");
migrationBuilder.DropTable(
name: "PermissionSettings");
migrationBuilder.DropColumn(
name: "PermissionTags",
table: "Users");
migrationBuilder.DropColumn(
name: "PermissionTags",
table: "Accounts");
migrationBuilder.AddColumn<Guid>(
name: "FeaturePermissionId",
table: "Users",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "FeaturePermissionId",
table: "Channels",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "FeaturePermissionId",
table: "Accounts",
type: "uuid",
nullable: true);
migrationBuilder.CreateTable(
name: "ChannelPermissions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true),
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ChannelPermissions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "FeaturePermissions",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
InternalName = table.Column<string>(type: "text", nullable: true),
InternalTag = table.Column<int>(type: "integer", nullable: true),
Inheritable = table.Column<bool>(type: "boolean", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_FeaturePermissions", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Users_FeaturePermissionId",
table: "Users",
column: "FeaturePermissionId");
migrationBuilder.CreateIndex(
name: "IX_Channels_FeaturePermissionId",
table: "Channels",
column: "FeaturePermissionId");
migrationBuilder.CreateIndex(
name: "IX_Accounts_FeaturePermissionId",
table: "Accounts",
column: "FeaturePermissionId");
migrationBuilder.AddForeignKey(
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
table: "Accounts",
column: "FeaturePermissionId",
principalTable: "FeaturePermissions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Channels_ChannelPermissions_PermissionsId",
table: "Channels",
column: "PermissionsId",
principalTable: "ChannelPermissions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
table: "Channels",
column: "FeaturePermissionId",
principalTable: "FeaturePermissions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
table: "Users",
column: "FeaturePermissionId",
principalTable: "FeaturePermissions",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
table: "Accounts");
migrationBuilder.DropForeignKey(
name: "FK_Channels_ChannelPermissions_PermissionsId",
table: "Channels");
migrationBuilder.DropForeignKey(
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
table: "Channels");
migrationBuilder.DropForeignKey(
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
table: "Users");
migrationBuilder.DropTable(
name: "ChannelPermissions");
migrationBuilder.DropTable(
name: "FeaturePermissions");
migrationBuilder.DropIndex(
name: "IX_Users_FeaturePermissionId",
table: "Users");
migrationBuilder.DropIndex(
name: "IX_Channels_FeaturePermissionId",
table: "Channels");
migrationBuilder.DropIndex(
name: "IX_Accounts_FeaturePermissionId",
table: "Accounts");
migrationBuilder.DropColumn(
name: "FeaturePermissionId",
table: "Users");
migrationBuilder.DropColumn(
name: "FeaturePermissionId",
table: "Channels");
migrationBuilder.DropColumn(
name: "FeaturePermissionId",
table: "Accounts");
migrationBuilder.AddColumn<int[]>(
name: "PermissionTags",
table: "Users",
type: "integer[]",
nullable: true);
migrationBuilder.AddColumn<int[]>(
name: "PermissionTags",
table: "Accounts",
type: "integer[]",
nullable: true);
migrationBuilder.CreateTable(
name: "PermissionSettings",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true),
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PermissionSettings", x => x.Id);
});
migrationBuilder.AddForeignKey(
name: "FK_Channels_PermissionSettings_PermissionsId",
table: "Channels",
column: "PermissionsId",
principalTable: "PermissionSettings",
principalColumn: "Id");
}
}
}

View File

@ -0,0 +1,349 @@
// <auto-generated />
using System;
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("20231203193139_channeltype")]
partial class channeltype
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
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<Guid?>("FeaturePermissionId")
.HasColumnType("uuid");
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("FeaturePermissionId");
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<int>("ChannelType")
.HasColumnType("integer");
b.Property<string>("DisplayName")
.HasColumnType("text");
b.Property<string>("ExternalId")
.HasColumnType("text");
b.Property<Guid?>("FeaturePermissionId")
.HasColumnType("uuid");
b.Property<Guid?>("ParentChannelId")
.HasColumnType("uuid");
b.Property<int?>("PermissionsId")
.HasColumnType("integer");
b.Property<string>("Protocol")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("FeaturePermissionId");
b.HasIndex("ParentChannelId");
b.HasIndex("PermissionsId");
b.ToTable("Channels");
});
modelBuilder.Entity("vassago.Models.ChannelPermissions", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
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<bool?>("ReactionsPossible")
.HasColumnType("boolean");
b.HasKey("Id");
b.ToTable("ChannelPermissions");
});
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Inheritable")
.HasColumnType("boolean");
b.Property<string>("InternalName")
.HasColumnType("text");
b.Property<int?>("InternalTag")
.HasColumnType("integer");
b.HasKey("Id");
b.ToTable("FeaturePermissions");
});
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.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<Guid?>("FeaturePermissionId")
.HasColumnType("uuid");
b.HasKey("Id");
b.HasIndex("FeaturePermissionId");
b.ToTable("Users");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.FeaturePermission", null)
.WithMany("RestrictedToAccounts")
.HasForeignKey("FeaturePermissionId");
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId");
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId");
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId");
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.FeaturePermission", null)
.WithMany("RestrictedToChannels")
.HasForeignKey("FeaturePermissionId");
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId");
b.HasOne("vassago.Models.ChannelPermissions", "Permissions")
.WithMany()
.HasForeignKey("PermissionsId");
b.Navigation("ParentChannel");
b.Navigation("Permissions");
});
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");
b.Navigation("Author");
b.Navigation("Channel");
});
modelBuilder.Entity("vassago.Models.User", b =>
{
b.HasOne("vassago.Models.FeaturePermission", null)
.WithMany("RestrictedToUsers")
.HasForeignKey("FeaturePermissionId");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.Navigation("Messages");
b.Navigation("SubChannels");
b.Navigation("Users");
});
modelBuilder.Entity("vassago.Models.FeaturePermission", b =>
{
b.Navigation("RestrictedToAccounts");
b.Navigation("RestrictedToChannels");
b.Navigation("RestrictedToUsers");
});
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,40 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class channeltype : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsDM",
table: "Channels");
migrationBuilder.AddColumn<int>(
name: "ChannelType",
table: "Channels",
type: "integer",
nullable: false,
defaultValue: 0);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ChannelType",
table: "Channels");
migrationBuilder.AddColumn<bool>(
name: "IsDM",
table: "Channels",
type: "boolean",
nullable: false,
defaultValue: false);
}
}
}

View File

@ -0,0 +1,266 @@
// <auto-generated />
using System;
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("20240510202057_channelpermissions_partofchannel")]
partial class channelpermissions_partofchannel
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
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<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.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId");
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId");
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId");
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId");
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");
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,228 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace vassago.Migrations
{
/// <inheritdoc />
public partial class channelpermissions_partofchannel : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
table: "Accounts");
migrationBuilder.DropForeignKey(
name: "FK_Channels_ChannelPermissions_PermissionsId",
table: "Channels");
migrationBuilder.DropForeignKey(
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
table: "Channels");
migrationBuilder.DropForeignKey(
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
table: "Users");
migrationBuilder.DropTable(
name: "ChannelPermissions");
migrationBuilder.DropTable(
name: "FeaturePermissions");
migrationBuilder.DropIndex(
name: "IX_Users_FeaturePermissionId",
table: "Users");
migrationBuilder.DropIndex(
name: "IX_Channels_FeaturePermissionId",
table: "Channels");
migrationBuilder.DropIndex(
name: "IX_Channels_PermissionsId",
table: "Channels");
migrationBuilder.DropIndex(
name: "IX_Accounts_FeaturePermissionId",
table: "Accounts");
migrationBuilder.DropColumn(
name: "FeaturePermissionId",
table: "Users");
migrationBuilder.DropColumn(
name: "FeaturePermissionId",
table: "Channels");
migrationBuilder.DropColumn(
name: "FeaturePermissionId",
table: "Accounts");
migrationBuilder.RenameColumn(
name: "PermissionsId",
table: "Channels",
newName: "MeannessFilterLevel");
migrationBuilder.AddColumn<int>(
name: "LewdnessFilterLevel",
table: "Channels",
type: "integer",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "LinksAllowed",
table: "Channels",
type: "boolean",
nullable: true);
migrationBuilder.AddColumn<decimal>(
name: "MaxAttachmentBytes",
table: "Channels",
type: "numeric(20,0)",
nullable: true);
migrationBuilder.AddColumn<long>(
name: "MaxTextChars",
table: "Channels",
type: "bigint",
nullable: true);
migrationBuilder.AddColumn<bool>(
name: "ReactionsPossible",
table: "Channels",
type: "boolean",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "LewdnessFilterLevel",
table: "Channels");
migrationBuilder.DropColumn(
name: "LinksAllowed",
table: "Channels");
migrationBuilder.DropColumn(
name: "MaxAttachmentBytes",
table: "Channels");
migrationBuilder.DropColumn(
name: "MaxTextChars",
table: "Channels");
migrationBuilder.DropColumn(
name: "ReactionsPossible",
table: "Channels");
migrationBuilder.RenameColumn(
name: "MeannessFilterLevel",
table: "Channels",
newName: "PermissionsId");
migrationBuilder.AddColumn<Guid>(
name: "FeaturePermissionId",
table: "Users",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "FeaturePermissionId",
table: "Channels",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "FeaturePermissionId",
table: "Accounts",
type: "uuid",
nullable: true);
migrationBuilder.CreateTable(
name: "ChannelPermissions",
columns: table => new
{
Id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
LewdnessFilterLevel = table.Column<int>(type: "integer", nullable: true),
LinksAllowed = table.Column<bool>(type: "boolean", nullable: true),
MaxAttachmentBytes = table.Column<decimal>(type: "numeric(20,0)", nullable: true),
MaxTextChars = table.Column<long>(type: "bigint", nullable: true),
MeannessFilterLevel = table.Column<int>(type: "integer", nullable: true),
ReactionsPossible = table.Column<bool>(type: "boolean", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ChannelPermissions", x => x.Id);
});
migrationBuilder.CreateTable(
name: "FeaturePermissions",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Inheritable = table.Column<bool>(type: "boolean", nullable: false),
InternalName = table.Column<string>(type: "text", nullable: true),
InternalTag = table.Column<int>(type: "integer", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_FeaturePermissions", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_Users_FeaturePermissionId",
table: "Users",
column: "FeaturePermissionId");
migrationBuilder.CreateIndex(
name: "IX_Channels_FeaturePermissionId",
table: "Channels",
column: "FeaturePermissionId");
migrationBuilder.CreateIndex(
name: "IX_Channels_PermissionsId",
table: "Channels",
column: "PermissionsId");
migrationBuilder.CreateIndex(
name: "IX_Accounts_FeaturePermissionId",
table: "Accounts",
column: "FeaturePermissionId");
migrationBuilder.AddForeignKey(
name: "FK_Accounts_FeaturePermissions_FeaturePermissionId",
table: "Accounts",
column: "FeaturePermissionId",
principalTable: "FeaturePermissions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Channels_ChannelPermissions_PermissionsId",
table: "Channels",
column: "PermissionsId",
principalTable: "ChannelPermissions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Channels_FeaturePermissions_FeaturePermissionId",
table: "Channels",
column: "FeaturePermissionId",
principalTable: "FeaturePermissions",
principalColumn: "Id");
migrationBuilder.AddForeignKey(
name: "FK_Users_FeaturePermissions_FeaturePermissionId",
table: "Users",
column: "FeaturePermissionId",
principalTable: "FeaturePermissions",
principalColumn: "Id");
}
}
}

View File

@ -0,0 +1,263 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using vassago.Models;
#nullable disable
namespace vassago.Migrations
{
[DbContext(typeof(ChattingContext))]
partial class ChattingContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "7.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
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<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.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.HasKey("Id");
b.ToTable("Users");
});
modelBuilder.Entity("vassago.Models.Account", b =>
{
b.HasOne("vassago.Models.User", "IsUser")
.WithMany("Accounts")
.HasForeignKey("IsUserId");
b.HasOne("vassago.Models.Channel", "SeenInChannel")
.WithMany("Users")
.HasForeignKey("SeenInChannelId");
b.Navigation("IsUser");
b.Navigation("SeenInChannel");
});
modelBuilder.Entity("vassago.Models.Attachment", b =>
{
b.HasOne("vassago.Models.Message", "Message")
.WithMany("Attachments")
.HasForeignKey("MessageId");
b.Navigation("Message");
});
modelBuilder.Entity("vassago.Models.Channel", b =>
{
b.HasOne("vassago.Models.Channel", "ParentChannel")
.WithMany("SubChannels")
.HasForeignKey("ParentChannelId");
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");
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
}
}
}

31
Models/Account.cs Normal file
View File

@ -0,0 +1,31 @@
namespace vassago.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Threading.Tasks;
public class Account
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string ExternalId { get; set; }
public string Username { get; set; }
private string _displayName = null;
public string DisplayName
{
get
{
return _displayName ?? Username;
}
set
{
_displayName = value;
}
}
public bool IsBot { get; set; } //webhook counts
public Channel SeenInChannel { get; set; }
public string Protocol { get; set; }
public User IsUser {get; set;}
}

18
Models/Attachment.cs Normal file
View File

@ -0,0 +1,18 @@
namespace vassago.Models;
using System;
using System.ComponentModel.DataAnnotations.Schema;
public class Attachment
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public ulong? ExternalId { get; set; }
public Uri Source { get; set; }
public byte[] Content { get; set; }
public string Filename { get; set; }
public Message Message { get; set; }
public string ContentType { get; internal set; }
public string Description { get; internal set; }
public int Size { get; internal set; }
}

91
Models/Channel.cs Normal file
View File

@ -0,0 +1,91 @@
namespace vassago.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Web;
using static vassago.Models.Enumerations;
public class Channel
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string ExternalId { get; set; }
public string DisplayName { get; set; }
public List<Channel> SubChannels { get; set; }
public Channel ParentChannel { get; set; }
public string Protocol { get; set; }
public List<Message> Messages { get; set; }
public List<Account> Users { get; set; }
public ChannelType ChannelType {get; set; }
//Permissions
public ulong? MaxAttachmentBytes { get; set; }
public uint? MaxTextChars { get; set; }
public bool? LinksAllowed { get; set; }
public bool? ReactionsPossible { get; set; }
public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; }
public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; }
[NonSerialized]
public Func<string, string, Task> SendFile;
[NonSerialized]
public Func<string, Task> SendMessage;
public DefinitePermissionSettings EffectivePermissions
{
get
{
var path = new Stack<Channel>(); //omg i actually get to use a data structure from university
var walker = this;
path.Push(this);
while(walker.ParentChannel != null)
{
walker = walker.ParentChannel;
path.Push(walker);
}
DefinitePermissionSettings toReturn = new DefinitePermissionSettings();
while(path.Count > 0)
{
walker = path.Pop();
toReturn.LewdnessFilterLevel = walker.LewdnessFilterLevel ?? toReturn.LewdnessFilterLevel;
toReturn.MeannessFilterLevel = walker.MeannessFilterLevel ?? toReturn.MeannessFilterLevel;
toReturn.LinksAllowed = walker.LinksAllowed ?? toReturn.LinksAllowed;
toReturn.MaxAttachmentBytes = walker.MaxAttachmentBytes ?? toReturn.MaxAttachmentBytes;
toReturn.MaxTextChars = walker.MaxTextChars ?? toReturn.MaxTextChars;
toReturn.ReactionsPossible = walker.ReactionsPossible ?? toReturn.ReactionsPossible;
}
return toReturn;
}
}
public string LineageSummary
{
get
{
if(this.ParentChannel != null)
{
return this.ParentChannel.LineageSummary + '/' + this.DisplayName;
}
else
{
return this.Protocol;
}
}
}
}
public class DefinitePermissionSettings
{
public ulong MaxAttachmentBytes { get; set; }
public uint MaxTextChars { get; set; }
public bool LinksAllowed { get; set; }
public bool ReactionsPossible { get; set; }
public Enumerations.LewdnessFilterLevel LewdnessFilterLevel { get; set; }
public Enumerations.MeannessFilterLevel MeannessFilterLevel { get; set; }
}

22
Models/ChattingContext.cs Normal file
View File

@ -0,0 +1,22 @@
namespace vassago.Models;
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
public class ChattingContext : DbContext
{
public DbSet<Attachment> Attachments { get; set; }
public DbSet<Channel> Channels { get; set; }
//public DbSet<Emoji> Emoji {get;set;}
public DbSet<Message> Messages { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<User> Users { get; set; }
public ChattingContext(DbContextOptions<ChattingContext> options) : base(options) { }
public ChattingContext() : base() { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(Shared.DBConnectionString)
.EnableSensitiveDataLogging(true); //who the fuck is looking at log output but not allowed to see it? this should be on by default.
}
}

67
Models/Enums.cs Normal file
View File

@ -0,0 +1,67 @@
using System;
using System.ComponentModel;
using System.Reflection;
namespace vassago.Models;
public static class Enumerations
{
public enum LewdnessFilterLevel
{
[Description("this is a christian minecraft server 🙏")]
Strict,
[Description("G-Rated")]
G,
[Description("polite company")]
Moderate,
[Description(";) ;) ;)")]
Unrestricted
}
public enum MeannessFilterLevel
{
[Description("good vibes only")]
Strict,
[Description("a bit cheeky")]
Medium,
[Description("387.44m mi of printed circuits")]
Unrestricted
}
public enum ChannelType
{
[Description("Normal")]
Normal,
[Description("DM")]
DM,
[Description("protocol psuedo-channel")]
Protocol,
[Description("organizational psuedo-channel")]
OU
}
public static string GetDescription<T>(this T enumerationValue)
where T : struct
{
Type type = enumerationValue.GetType();
if (!type.IsEnum)
{
throw new ArgumentException("EnumerationValue must be of Enum type", nameof(enumerationValue));
}
//Tries to find a DescriptionAttribute for a potential friendly name
//for the enum
MemberInfo[] memberInfo = type.GetMember(enumerationValue.ToString());
if (memberInfo != null && memberInfo.Length > 0)
{
object[] attrs = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs != null && attrs.Length > 0)
{
//Pull out the description value
return ((DescriptionAttribute)attrs[0]).Description;
}
}
//If we have no description attribute, just return the ToString of the enum
return enumerationValue.ToString();
}
}

31
Models/Message.cs Normal file
View File

@ -0,0 +1,31 @@
namespace vassago.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Threading.Tasks;
using Discord.WebSocket;
public class Message
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Protocol { get; set; }
public string ExternalId { get; set; }
public string Content { get; set; }
public bool MentionsMe { get; set; }
public DateTimeOffset Timestamp { get; set; }
public bool ActedOn { get; set; }
public List<Attachment> Attachments { get; set; }
public Account Author { get; set; }
public Channel Channel { get; set; }
[NonSerialized]
public Func<string, Task> Reply;
[NonSerialized]
public Func<string, Task> React;
}

28
Models/User.cs Normal file
View File

@ -0,0 +1,28 @@
namespace vassago.Models;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
public class User
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public List<Account> Accounts { get; set; }
//if I ever get lots and lots of tags, or some automatic way to register a feature's arbitrary tags, then I can move this off.
//public bool Tag_CanTwitchSummon { get; set; }
public string DisplayName
{
get
{
return Accounts.Select(a => a.DisplayName).Distinct()
.MaxBy(distinctName =>
Accounts.Select(a => a.DisplayName)
.Where(selectedName => selectedName == distinctName).Count()
);
}
}
}

View File

@ -1,341 +1,53 @@
//https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot%20messages.read
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Net;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using System.Text;
using System.Threading;
using System.Diagnostics;
using Discord.Net;
namespace silverworker_discord
{
class Program
{
private DiscordSocketClient _client;
private bool eventsSignedUp = false;
private Random r = new Random();
IConfigurationRoot config = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", true, true)
.Build();
public static void Main(string[] args)
=> new Program().MainAsync().GetAwaiter().GetResult();
private Task Log(LogMessage msg)
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
}
public async Task MainAsync()
{
#if !DEBUG
Process[] processes = Process.GetProcesses();
Process currentProc = Process.GetCurrentProcess();
Console.WriteLine("Current proccess: {0}", currentProc.ProcessName);
foreach (Process process in processes)
{
if (currentProc.ProcessName == process.ProcessName && currentProc.Id != process.Id)
{
Console.Error.WriteLine($"{DateTime.Now} - Another instance of this process is already running: {process.Id} (I'm {currentProc.Id})");
return;
}
}
#endif
Conversion.Converter.Load(config["exchangePairsLocation"]);
_client = new DiscordSocketClient(new DiscordSocketConfig(){GatewayIntents = GatewayIntents.All});
_client.Log += Log;
_client.Ready += () => Task.Run(() =>
{
if (!eventsSignedUp)
{
eventsSignedUp = true;
Console.WriteLine("Bot is connected! going to sign up for message received and user joined in client ready");
_client.MessageReceived += MessageReceived;
_client.UserJoined += UserJoined;
//_client.ButtonExecuted += MyButtonHandler;
_client.SlashCommandExecuted += SlashCommandsHelper.SlashCommandHandler;
SlashCommandsHelper.Register(_client).GetAwaiter().GetResult();
}
else
{
Console.WriteLine("bot appears to be RE connected, so I'm not going to sign up twice");
}
});
await _client.LoginAsync(TokenType.Bot, config["token"]);
await _client.StartAsync();
// Block this task until the program is closed.
await Task.Delay(-1);
}
#pragma warning disable 4014 //the "you're not awaiting this" warning. yeah I know, that's the beauty of an async method lol
#pragma warning disable 1998 //the "it's async but you're not awaiting anything".
private async Task MessageReceived(SocketMessage messageParam)
#pragma warning restore 1998
{
var message = messageParam as SocketUserMessage;
if (message == null) return;
if (message.Author.Id == _client.CurrentUser.Id) return;
Console.WriteLine($"#{message.Channel}[{DateTime.Now}][{message.Author.Username} [id={message.Author.Id}]][msg id: {message.Id}] {message.Content}");
if (message.Author.IsWebhook || message.Author.IsBot)
{
if (message.Author.Id == 159985870458322944) //MEE6
{
if (message.Content?.Contains("you just advanced") == true)
{
var newText = Regex.Replace(message.Content, "<[^>]*>", message.Author.Username);
newText = Regex.Replace(newText, "level [\\d]+", "level -1");
Features.mock(newText, message);
}
}
}
else
{
var didThing = false;
var contentWithoutMention = message.Content;
var mentionedMe = false;
if (message.MentionedUsers?.FirstOrDefault(muid => muid.Id == _client.CurrentUser.Id) != null)
{
var mentionOfMe = "<@" + _client.CurrentUser.Id + ">";
contentWithoutMention = message.Content.Replace(mentionOfMe + " ", null);
contentWithoutMention = contentWithoutMention.Replace(mentionOfMe, null);
mentionedMe = true;
}
var wordLikes = message.Content.Split(' ', StringSplitOptions.TrimEntries);
var links = wordLikes?.Where(wl => Uri.IsWellFormedUriString(wl, UriKind.Absolute)).Select(wl => new Uri(wl));
if (links != null && links.Count() > 0)
{
foreach (var link in links)
{
if (link.Host.EndsWith(".tiktok.com"))
{
Features.detiktokify(link, message);
didThing = true;
}
}
}
if (message.Attachments?.Count > 0)
{
Console.WriteLine($"{message.Attachments.Count} attachments");
var appleReactions = false;
foreach (var att in message.Attachments)
{
if (att.Filename?.EndsWith(".heic") == true)
{
Features.deheic(message, att);
appleReactions = true;
didThing = true;
}
}
if (appleReactions)
{
message.AddReactionAsync(new Emoji("\U0001F34F"));
}
}
var msgText = message.Content?.ToLower();
if (!string.IsNullOrWhiteSpace(msgText))
{
if (Regex.IsMatch(msgText, "\\bcloud( |-)?native\\b", RegexOptions.IgnoreCase) ||
Regex.IsMatch(msgText, "\\benterprise( |-)?(level|solution)\\b", RegexOptions.IgnoreCase))
{
switch (r.Next(2))
{
case 0:
await message.AddReactionAsync(new Emoji("\uD83E\uDD2E")); //vomit emoji
break;
case 1:
await message.AddReactionAsync(new Emoji("\uD83C\uDDE7")); //B emoji
await message.AddReactionAsync(new Emoji("\uD83C\uDDE6")); //A
await message.AddReactionAsync(new Emoji("\uD83C\uDDF3")); //N
break;
}
didThing = true;
}
if (Regex.IsMatch(msgText, "^(s?he|(yo)?u|y'?all) thinks? i'?m (playin|jokin|kiddin)g?$", RegexOptions.IgnoreCase))
{
await message.Channel.SendMessageAsync("I believed you for a second, but then you assured me you's a \uD83C\uDDE7 \uD83C\uDDEE \uD83C\uDDF9 \uD83C\uDDE8 \uD83C\uDDED");
didThing = true;
}
if (Regex.IsMatch(msgText, "\\bskynet\\b", RegexOptions.IgnoreCase))
{
Features.Skynet(message);
didThing = true;
}
if (Regex.IsMatch(msgText, "\\bchatgpt\\b", RegexOptions.IgnoreCase))
{
message.Channel.SendMessageAsync("chatGPT is **weak**. also, are we done comparing every little if-then-else to skynet?");
didThing = true;
}
if (Regex.IsMatch(msgText, "\\bi need (an? )?(peptalk|inspiration|ego-?boost)\\b", RegexOptions.IgnoreCase))
{
Console.WriteLine("peptalk");
Features.peptalk(message);
didThing = true;
}
if (Regex.IsMatch(msgText, "\\bwish me luck\\b", RegexOptions.IgnoreCase))
{
if (r.Next(20) == 0)
{
await message.AddReactionAsync(new Emoji("\U0001f340"));//4-leaf clover
}
else
{
await message.AddReactionAsync(new Emoji("☘️"));
}
didThing = true;
}
if (Regex.IsMatch(msgText, "\\bgaslight(ing)?\\b", RegexOptions.IgnoreCase))
{
message.Channel.SendMessageAsync("that's not what gaslight means. Did you mean \"say something that (you believe) is wrong\"?");
didThing = true;
}
if (msgText.Contains("!qrplz "))
{
Features.qrify(message.Content.Substring("!qrplz ".Length + msgText.IndexOf("!qrplz ")), message);
didThing = true;
}
if (msgText.Contains("!freedomunits "))
{
Features.Convert(message, contentWithoutMention);
didThing = true;
}
if (Regex.IsMatch(msgText, "!joke\\b"))
{
Features.Joke(message);
didThing = true;
}
if (Regex.IsMatch(msgText, "!pulse ?check\\b"))
{
message.Channel.SendFileAsync("assets/ekgblip.png");
Console.WriteLine(Conversion.Converter.DebugInfo());
didThing = true;
}
if (mentionedMe && (Regex.IsMatch(msgText, "\\brecipe for .+") || Regex.IsMatch(msgText, ".+ recipe\\b")))
{
Features.Recipe(message);
didThing = true;
}
if (msgText.Contains("cognitive dissonance") == true)
{
message.ReplyAsync("that's not what cognitive dissonance means. Did you mean \"hypocrisy\"?");
didThing = true;
}
if (mentionedMe && Regex.IsMatch(msgText, "what'?s the longest (six|6)(-| )?letter word( in english)?\\b"))
{
Task.Run(async () =>
{
await message.Channel.SendMessageAsync("mother.");
await Task.Delay(3000);
await message.Channel.SendMessageAsync("oh, longest? I thought you said fattest.");
});
didThing = true;
}
if (Regex.IsMatch(msgText, "\\bthank (yo)?u\\b", RegexOptions.IgnoreCase) &&
(mentionedMe || Regex.IsMatch(msgText, "\\b(sh?tik)?bot\\b", RegexOptions.IgnoreCase)))
{
switch (r.Next(4))
{
case 0:
message.Channel.SendMessageAsync("you're welcome, citizen!");
break;
case 1:
message.AddReactionAsync(new Emoji("☺"));
break;
case 2:
message.AddReactionAsync(new Emoji("\U0001F607")); //smiling face with halo
break;
case 3:
switch (r.Next(9))
{
case 0:
message.AddReactionAsync(new Emoji("❤")); //normal heart, usually rendered red
break;
case 1:
message.AddReactionAsync(new Emoji("\U0001F9E1")); //orange heart
break;
case 2:
message.AddReactionAsync(new Emoji("\U0001F49B")); //yellow heart
break;
case 3:
message.AddReactionAsync(new Emoji("\U0001F49A")); //green heart
break;
case 4:
message.AddReactionAsync(new Emoji("\U0001F499")); //blue heart
break;
case 5:
message.AddReactionAsync(new Emoji("\U0001F49C")); //purple heart
break;
case 6:
message.AddReactionAsync(new Emoji("\U0001F90E")); //brown heart
break;
case 7:
message.AddReactionAsync(new Emoji("\U0001F5A4")); //black heart
break;
case 8:
message.AddReactionAsync(new Emoji("\U0001F90D")); //white heart
break;
}
break;
}
didThing = true;
#pragma warning restore 4014
}
// if (didThing == false && mentionedMe && contentWithoutMention.Contains("how long has that been there?"))
// {
// await message.Channel.SendMessageAsync("text", false, null, null, null, null, new ComponentBuilder().WithButton("label", "custom-id").Build());
// didThing = true;
// }
if (didThing == false && mentionedMe && contentWithoutMention.Contains('?'))
{
Console.WriteLine("providing bullshit nonanswer / admitting uselessness");
var responses = new List<string>(){
@"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.SendMessageAsync(responses[r.Next(responses.Count)]);
didThing = true;
}
}
}
}
private Task UserJoined(SocketGuildUser arg)
{
Console.WriteLine($"user joined: {arg.Nickname}. Guid: {arg.Guild.Id}. Channel: {arg.Guild.DefaultChannel}");
var abbreviatedNickname = arg.Nickname;
if (arg.Nickname.Length > 3)
{
abbreviatedNickname = arg.Nickname.Substring(0, arg.Nickname.Length / 3);
}
Console.WriteLine($"imma call him {abbreviatedNickname}");
return arg.Guild.DefaultChannel.SendMessageAsync($"oh hey {abbreviatedNickname}- IPLAYTHESEALOFORICHALCOS <:ORICHALCOS:852749196633309194>");
}
private async Task ButtonHandler(SocketMessageComponent component)
{
switch(component.Data.CustomId)
{
case "custom-id":
await component.RespondAsync($"{component.User.Mention}, it's been here the whole time!");
break;
}
}
}
}
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.NewtonsoftJson;
using vassago.Models;
#pragma warning disable CA2254
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllersWithViews();
builder.Services.AddSingleton<IHostedService, vassago.ConsoleService>();
builder.Services.AddDbContext<ChattingContext>();
builder.Services.AddControllers().AddNewtonsoftJson();
builder.Services.AddProblemDetails();
builder.Services.Configure<RazorViewEngineOptions>(o => {
o.ViewLocationFormats.Clear();
o.ViewLocationFormats.Add("/WebInterface/Views/{1}/{0}" + RazorViewEngine.ViewExtension);
o.ViewLocationFormats.Add("/WebInterface/Views/Shared/{0}" + RazorViewEngine.ViewExtension);
});
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "api");
});
app.UseExceptionHandler();
app.UseStatusCodePages();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.Run();

View File

@ -0,0 +1,37 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42619",
"sslPort": 44354
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5093",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7206;http://localhost:5093",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -0,0 +1,344 @@
//https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot%20messages.read
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Discord;
using Discord.WebSocket;
using vassago.Models;
using vassago.Behavior;
using Discord.Rest;
using Microsoft.EntityFrameworkCore;
using System.Threading;
using System.Reactive.Linq;
namespace vassago.DiscordInterface;
public class DiscordInterface
{
internal const string PROTOCOL = "discord";
internal DiscordSocketClient client;
private bool eventsSignedUp = false;
private ChattingContext _db;
private static SemaphoreSlim discordChannelSetup = new SemaphoreSlim(1, 1);
private Channel protocolAsChannel;
public DiscordInterface()
{
_db = new ChattingContext();
}
public async Task Init(string token)
{
await SetupDiscordChannel();
client = new DiscordSocketClient(new DiscordSocketConfig() { GatewayIntents = GatewayIntents.All });
client.Log += (msg) =>
{
Console.WriteLine(msg.ToString());
return Task.CompletedTask;
};
client.Connected += SelfConnected;
client.Ready += ClientReady;
await client.LoginAsync(TokenType.Bot, token);
await client.StartAsync();
}
private async Task SetupDiscordChannel()
{
await discordChannelSetup.WaitAsync();
try
{
protocolAsChannel = _db.Channels.FirstOrDefault(c => c.ParentChannel == null && c.Protocol == PROTOCOL);
if (protocolAsChannel == null)
{
protocolAsChannel = new Channel()
{
DisplayName = "discord (itself)",
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Strict,
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.Moderate,
MaxTextChars = 2000,
MaxAttachmentBytes = 25 * 1024 * 1024, //allegedly it's 25, but I worry it's not actually.
LinksAllowed = true,
ReactionsPossible = true,
ExternalId = null,
Protocol = PROTOCOL,
SubChannels = new List<Channel>()
};
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"discord itself cannot accept text"); };
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"discord itself cannot send file"); };
_db.Channels.Add(protocolAsChannel);
_db.SaveChanges();
}
}
finally
{
discordChannelSetup.Release();
}
}
private async Task ClientReady()
{
if (!eventsSignedUp)
{
eventsSignedUp = true;
Console.WriteLine($"Bot is connected ({client.CurrentUser.Username}; {client.CurrentUser.Mention})! going to sign up for message received and user joined in client ready");
client.MessageReceived += MessageReceived;
// _client.MessageUpdated +=
//client.UserJoined += UserJoined;
client.SlashCommandExecuted += SlashCommandHandler;
//client.ChannelCreated +=
// _client.ChannelDestroyed +=
// _client.ChannelUpdated +=
// _client.GuildMemberUpdated +=
// _client.UserBanned +=
// _client.UserLeft +=
// _client.ThreadCreated +=
// _client.ThreadUpdated +=
// _client.ThreadDeleted +=
// _client.JoinedGuild +=
// _client.GuildUpdated +=
// _client.LeftGuild +=
await SlashCommandsHelper.Register(client);
}
else
{
Console.WriteLine("bot appears to be RE connected, so I'm not going to sign up twice");
}
}
private async Task SelfConnected()
{
var selfAccount = UpsertAccount(client.CurrentUser, protocolAsChannel);
selfAccount.DisplayName = client.CurrentUser.Username;
await _db.SaveChangesAsync();
Behaver.Instance.MarkSelf(selfAccount);
}
private async Task MessageReceived(SocketMessage messageParam)
{
var suMessage = messageParam as SocketUserMessage;
if (suMessage == null)
{
Console.WriteLine($"{messageParam.Content}, but not a user message");
return;
}
Console.WriteLine($"#{suMessage.Channel}[{DateTime.Now}][{suMessage.Author.Username} [id={suMessage.Author.Id}]][msg id: {suMessage.Id}] {suMessage.Content}");
var m = UpsertMessage(suMessage);
if (suMessage.MentionedUsers?.FirstOrDefault(muid => muid.Id == client.CurrentUser.Id) != null)
{
var mentionOfMe = "<@" + client.CurrentUser.Id + ">";
m.MentionsMe = true;
}
await Behaver.Instance.ActOn(m);
m.ActedOn = true; // for its own ruposess it might act on it later, but either way, fuck it, we checked.
_db.SaveChanges();
}
private void UserJoined(SocketGuildUser arg)
{
var guild = UpsertChannel(arg.Guild);
var defaultChannel = UpsertChannel(arg.Guild.DefaultChannel);
defaultChannel.ParentChannel = guild;
var u = UpsertAccount(arg, guild);
u.DisplayName = arg.DisplayName;
}
private async Task ButtonHandler(SocketMessageComponent component)
{
switch (component.Data.CustomId)
{
case "custom-id":
await component.RespondAsync($"{component.User.Mention}, it's been here the whole time!");
break;
}
}
internal static async Task SlashCommandHandler(SocketSlashCommand command)
{
switch (command.CommandName)
{
case "freedomunits":
try
{
var amt = Convert.ToDecimal((double)(command.Data.Options.First(o => o.Name == "amount").Value));
var src = (string)command.Data.Options.First(o => o.Name == "src-unit").Value;
var dest = (string)command.Data.Options.First(o => o.Name == "dest-unit").Value;
var conversionResult = Conversion.Converter.Convert(amt, src, dest);
await command.RespondAsync($"> {amt} {src} -> {dest}\n{conversionResult}");
}
catch (Exception e)
{
await command.RespondAsync($"error: {e.Message}. aaadam!");
}
break;
default:
await command.RespondAsync($"\\*smiles and nods*\n");
await command.Channel.SendFileAsync($"assets/loud sweating.gif");
Console.Error.WriteLine($"can't understand command name: {command.CommandName}");
break;
}
}
internal vassago.Models.Attachment UpsertAttachment(IAttachment dAttachment)
{
var a = _db.Attachments.FirstOrDefault(ai => ai.ExternalId == dAttachment.Id);
if (a == null)
{
a = new vassago.Models.Attachment();
_db.Attachments.Add(a);
}
a.ContentType = dAttachment.ContentType;
a.Description = dAttachment.Description;
a.Filename = dAttachment.Filename;
a.Size = dAttachment.Size;
a.Source = new Uri(dAttachment.Url);
return a;
}
internal Message UpsertMessage(IUserMessage dMessage)
{
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == dMessage.Id.ToString() && mi.Protocol == PROTOCOL);
if (m == null)
{
m = new Message();
m.Protocol = PROTOCOL;
_db.Messages.Add(m);
}
m.Attachments = m.Attachments ?? new List<vassago.Models.Attachment>();
if (dMessage.Attachments?.Any() == true)
{
m.Attachments = new List<vassago.Models.Attachment>();
foreach (var da in dMessage.Attachments)
{
m.Attachments.Add(UpsertAttachment(da));
}
}
m.Content = dMessage.Content;
m.ExternalId = dMessage.Id.ToString();
m.Timestamp = dMessage.EditedTimestamp ?? dMessage.CreatedAt;
m.Channel = UpsertChannel(dMessage.Channel);
m.Author = UpsertAccount(dMessage.Author, m.Channel);
if(dMessage.Channel is IGuildChannel)
{
m.Author.DisplayName = (dMessage.Author as IGuildUser).DisplayName;//discord forgot how display names work.
}
m.MentionsMe = (dMessage.Author.Id != client.CurrentUser.Id
&& (dMessage.MentionedUserIds?.FirstOrDefault(muid => muid == client.CurrentUser.Id) > 0));
m.Reply = (t) => { return dMessage.ReplyAsync(t); };
m.React = (e) => { return attemptReact(dMessage, e); };
return m;
}
internal Channel UpsertChannel(IMessageChannel channel)
{
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL);
if (c == null)
{
c = new Channel();
_db.Channels.Add(c);
}
c.DisplayName = channel.Name;
c.ExternalId = channel.Id.ToString();
c.ChannelType = (channel is IPrivateChannel) ? vassago.Models.Enumerations.ChannelType.DM : vassago.Models.Enumerations.ChannelType.Normal;
c.Messages = c.Messages ?? new List<Message>();
c.Protocol = PROTOCOL;
if (channel is IGuildChannel)
{
c.ParentChannel = UpsertChannel((channel as IGuildChannel).Guild);
c.ParentChannel.SubChannels.Add(c);
}
else if (channel is IPrivateChannel)
{
c.ParentChannel = protocolAsChannel;
}
else
{
c.ParentChannel = protocolAsChannel;
Console.Error.WriteLine($"trying to upsert channel {channel.Id}/{channel.Name}, but it's neither guildchannel nor private channel. shrug.jpg");
}
c.SubChannels = c.SubChannels ?? new List<Channel>();
c.SendMessage = (t) => { return channel.SendMessageAsync(t); };
c.SendFile = (f, t) => { return channel.SendFileAsync(f, t); };
switch(c.ChannelType)
{
case vassago.Models.Enumerations.ChannelType.DM:
c.DisplayName = "DM: " + (channel as IPrivateChannel).Recipients?.FirstOrDefault(u => u.Id != client.CurrentUser.Id).Username;
break;
}
return c;
}
internal Channel UpsertChannel(IGuild channel)
{
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channel.Id.ToString() && ci.Protocol == PROTOCOL);
if (c == null)
{
c = new Channel();
_db.Channels.Add(c);
}
c.DisplayName = channel.Name;
c.ExternalId = channel.Id.ToString();
c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal;
c.Messages = c.Messages ?? new List<Message>();
c.Protocol = protocolAsChannel.Protocol;
c.ParentChannel = protocolAsChannel;
c.SubChannels = c.SubChannels ?? new List<Channel>();
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 c;
}
internal Account UpsertAccount(IUser user, Channel inChannel)
{
var acc = _db.Accounts.FirstOrDefault(ui => ui.ExternalId == user.Id.ToString() && ui.SeenInChannel.Id == inChannel.Id);
if (acc == null)
{
acc = new Account();
_db.Accounts.Add(acc);
}
acc.Username = user.Username;
acc.ExternalId = user.Id.ToString();
acc.IsBot = user.IsBot || user.IsWebhook;
acc.Protocol = PROTOCOL;
acc.SeenInChannel = inChannel;
acc.IsUser = _db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol));
if(acc.IsUser == null)
{
acc.IsUser = new User() { Accounts = new List<Account>() { acc } };
_db.Users.Add(acc.IsUser);
}
return acc;
}
private Task attemptReact(IUserMessage msg, string e)
{
var c = _db.Channels.FirstOrDefault(c => c.ExternalId == msg.Channel.Id.ToString());
//var preferredEmote = c.EmoteOverrides?[e] ?? e; //TODO: emote overrides
var preferredEmote = e;
if (Emoji.TryParse(preferredEmote, out Emoji emoji))
{
return msg.AddReactionAsync(emoji);
}
if (!Emote.TryParse(preferredEmote, out Emote emote))
{
if (preferredEmote == e)
Console.Error.WriteLine($"never heard of emote {e}");
return Task.CompletedTask;
}
return msg.AddReactionAsync(emote);
}
}

View File

@ -1,14 +1,14 @@
namespace silverworker_discord
{
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Discord.WebSocket;
using Discord.Net;
using Discord;
using Newtonsoft.Json;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Discord.WebSocket;
using Discord;
using Discord.Net;
namespace vassago.DiscordInterface
{
public static class SlashCommandsHelper
{
private static List<CommandSetup> slashCommands = new List<CommandSetup>()
@ -16,12 +16,13 @@ namespace silverworker_discord
new CommandSetup(){
Id = "freedomunits",
UpdatedAt = new DateTime(2023, 5, 21, 13, 3, 0),
guild = 825293851110801428,
guild = 825293851110801428, //TODO: demagic this magic number
register = register_FreedomUnits
}
};
public static async Task Register(DiscordSocketClient client)
{
return;
var commandsInContext = await client.GetGlobalApplicationCommandsAsync();
await Register(client, commandsInContext, null);
foreach (var guild in client.Guilds)
@ -30,7 +31,7 @@ namespace silverworker_discord
{
await Register(client, await guild.GetApplicationCommandsAsync(), guild);
}
catch (Discord.Net.HttpException ex)
catch (HttpException ex)
{
Console.Error.WriteLine($"error registering slash commands for guild {guild.Name} (id {guild.Id}) - {ex.Message}");
}
@ -46,7 +47,6 @@ namespace silverworker_discord
{
Console.WriteLine($"deleting command {existingCommand.Name} - (created at {existingCommand.CreatedAt}, it's in guild {existingCommand.Guild?.Id} while I'm in {guild?.Id})");
await existingCommand.DeleteAsync();
Console.WriteLine("survived");
}
else
{
@ -55,7 +55,6 @@ namespace silverworker_discord
{
Console.WriteLine($"overwriting command {existingCommand.Name}");
await myVersion.register(false, client, guild);
Console.WriteLine($"survived");
}
myVersion.alreadyRegistered = true;
}
@ -64,7 +63,6 @@ namespace silverworker_discord
{
Console.WriteLine($"creating new command {remaining.Id} ({(remaining.guild == null ? "global" : $"for guild {remaining.guild}")})");
await remaining.register(true, client, guild);
Console.WriteLine($"survived");
}
}
@ -108,37 +106,11 @@ namespace silverworker_discord
Console.Error.WriteLine(json);
}
}
public static async Task SlashCommandHandler(SocketSlashCommand command)
{
switch(command.CommandName)
{
case "freedomunits":
try
{
var amt = Convert.ToDecimal((double)(command.Data.Options.First(o => o.Name == "amount").Value));
var src = (string)command.Data.Options.First(o => o.Name == "src-unit").Value;
var dest = (string)command.Data.Options.First(o => o.Name == "dest-unit").Value;
var conversionResult = Conversion.Converter.Convert(amt, src, dest);
await command.RespondAsync($"> {amt} {src} -> {dest}\n{conversionResult}");
}
catch(Exception e)
{
await command.RespondAsync($"error: {e.Message}. aaadam!");
}
break;
default:
await command.RespondAsync($"\\*smiles and nods*\n");
await command.Channel.SendFileAsync($"assets/loud sweating.gif");
Console.Error.WriteLine($"can't understand command name: {command.CommandName}");
break;
}
}
private class CommandSetup
{
public string Id { get; set; }
//the date/time you updated yours IN UTC.
public DateTimeOffset UpdatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
public Registration register { get; set; }
public ulong? guild { get; set; }
public bool alreadyRegistered {get;set; } = false;

View File

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

View File

@ -0,0 +1,9 @@
namespace vassago.TwitchInterface;
public class TwitchConfig
{
public string username {get; set;}
public string clientId {get; set;}
public string secret {get; set;}
public string oauth {get; set;}
}

View File

@ -0,0 +1,287 @@
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
using RestSharp;
using TwitchLib.Api;
using TwitchLib.Api.Helix.Models.Users.GetUsers;
using TwitchLib.Client;
using TwitchLib.Client.Events;
using TwitchLib.Client.Models;
using TwitchLib.Communication.Clients;
using TwitchLib.Communication.Models;
using vassago.Behavior;
using vassago.Models;
namespace vassago.TwitchInterface;
public class TwitchInterface
{
internal const string PROTOCOL = "twitch";
private bool eventsSignedUp = false;
private ChattingContext _db;
private static SemaphoreSlim twitchChannelSetup = new SemaphoreSlim(1, 1);
private Channel protocolAsChannel;
TwitchClient client;
TwitchAPI api;
public TwitchInterface()
{
_db = new ChattingContext();
}
private async Task SetupTwitchChannel()
{
await twitchChannelSetup.WaitAsync();
try
{
protocolAsChannel = _db.Channels.FirstOrDefault(c => c.ParentChannel == null && c.Protocol == PROTOCOL);
if (protocolAsChannel == null)
{
protocolAsChannel = new Channel()
{
DisplayName = "twitch (itself)",
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Medium,
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.G,
MaxTextChars = 500,
MaxAttachmentBytes = 0,
LinksAllowed = false,
ReactionsPossible = false,
ExternalId = null,
Protocol = PROTOCOL,
SubChannels = new List<Channel>()
};
protocolAsChannel.SendMessage = (t) => { throw new InvalidOperationException($"twitch itself cannot accept text"); };
protocolAsChannel.SendFile = (f, t) => { throw new InvalidOperationException($"twitch itself cannot send file"); };
_db.Channels.Add(protocolAsChannel);
_db.SaveChanges();
}
}
finally
{
twitchChannelSetup.Release();
}
}
///<param name="oauth">https://www.twitchapps.com/tmi/</param>
public async Task Init(TwitchConfig tc)
{
await SetupTwitchChannel();
WebSocketClient customClient = new WebSocketClient(new ClientOptions
{
MessagesAllowedInPeriod = 750,
ThrottlingPeriod = TimeSpan.FromSeconds(30)
}
);
client = new TwitchClient(customClient);
client.Initialize(new ConnectionCredentials(tc.username, tc.oauth, capabilities: new Capabilities()));
client.OnLog += Client_OnLog;
client.OnJoinedChannel += Client_OnJoinedChannel;
client.OnMessageReceived += Client_OnMessageReceivedAsync;
client.OnWhisperReceived += Client_OnWhisperReceivedAsync;
client.OnConnected += Client_OnConnected;
Console.WriteLine("twitch client 1 connecting...");
client.Connect();
Console.WriteLine("twitch client 1 connected");
// Console.WriteLine("twitch API client connecting...");
// api = new TwitchAPI();
// Console.WriteLine("can I just use the same creds as the other client?");
// api.Settings.ClientId = tc.username;
// api.Settings.AccessToken = tc.oauth;
// try{
// var neckbreads = await api.Helix.Moderation.GetModeratorsAsync("silvermeddlists");
// Console.WriteLine($"{neckbreads?.Data?.Count()} shabby beards that need to be given up on");
// }
// catch(Exception e){
// Console.Error.WriteLine(e);
// }
// Console.WriteLine("k.");
}
private async void Client_OnWhisperReceivedAsync(object sender, OnWhisperReceivedArgs e)
{
Console.WriteLine($"whisper#{e.WhisperMessage.Username}[{DateTime.Now}][{e.WhisperMessage.DisplayName} [id={e.WhisperMessage.Username}]][msg id: {e.WhisperMessage.MessageId}] {e.WhisperMessage.Message}");
var old = _db.Messages.FirstOrDefault(m => m.ExternalId == e.WhisperMessage.MessageId && m.Protocol == PROTOCOL);
if (old != null)
{
Console.WriteLine($"[whisperreceived]: {e.WhisperMessage.MessageId}? already seent it. Internal id: {old.Id}");
return;
}
var m = UpsertMessage(e.WhisperMessage);
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
m.MentionsMe = Regex.IsMatch(e.WhisperMessage.Message?.ToLower(), $"\\b@{e.WhisperMessage.BotUsername.ToLower()}\\b");
await _db.SaveChangesAsync();
await Behaver.Instance.ActOn(m);
await _db.SaveChangesAsync();
}
private async void Client_OnMessageReceivedAsync(object sender, OnMessageReceivedArgs e)
{
Console.WriteLine($"#{e.ChatMessage.Channel}[{DateTime.Now}][{e.ChatMessage.DisplayName} [id={e.ChatMessage.Username}]][msg id: {e.ChatMessage.Id}] {e.ChatMessage.Message}");
var old = _db.Messages.FirstOrDefault(m => m.ExternalId == e.ChatMessage.Id && m.Protocol == PROTOCOL);
if (old != null)
{
Console.WriteLine($"[messagereceived]: {e.ChatMessage.Id}? already seent it");
return;
}
Console.WriteLine($"[messagereceived]: {e.ChatMessage.Id}? new to me.");
var m = UpsertMessage(e.ChatMessage);
m.MentionsMe = Regex.IsMatch(e.ChatMessage.Message?.ToLower(), $"@{e.ChatMessage.BotUsername.ToLower()}\\b") ||
e.ChatMessage.ChatReply?.ParentUserLogin == e.ChatMessage.BotUsername;
await _db.SaveChangesAsync();
await Behaver.Instance.ActOn(m);
await _db.SaveChangesAsync();
}
private async void Client_OnConnected(object sender, OnConnectedArgs e)
{
var selfAccount = UpsertAccount(e.BotUsername, protocolAsChannel.Id);
await _db.SaveChangesAsync();
Behaver.Instance.MarkSelf(selfAccount);
Console.WriteLine($"Connected to {e.AutoJoinChannel}");
}
private void Client_OnJoinedChannel(object sender, OnJoinedChannelArgs e)
{
client.SendMessage(e.Channel, "beep boop");
}
private void Client_OnLog(object sender, OnLogArgs e)
{
Console.WriteLine($"{e.DateTime.ToString()}: {e.BotUsername} - {e.Data}");
}
private Account UpsertAccount(string username, Guid inChannel)
{
var acc = _db.Accounts.FirstOrDefault(ui => ui.ExternalId == username && ui.SeenInChannel.Id == inChannel);
if (acc == null)
{
acc = new Account();
_db.Accounts.Add(acc);
}
acc.Username = username;
acc.ExternalId = username;
//acc.IsBot =
acc.Protocol = PROTOCOL;
acc.IsUser = _db.Users.FirstOrDefault(u => u.Accounts.Any(a => a.ExternalId == acc.ExternalId && a.Protocol == acc.Protocol));
if (acc.IsUser == null)
{
acc.IsUser = new vassago.Models.User() { Accounts = new List<Account>() { acc } };
_db.Users.Add(acc.IsUser);
}
return acc;
}
private Channel UpsertChannel(string channelName)
{
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == channelName && ci.Protocol == PROTOCOL);
if (c == null)
{
c = new Channel();
_db.Channels.Add(c);
}
c.DisplayName = channelName;
c.ExternalId = channelName;
c.ChannelType = vassago.Models.Enumerations.ChannelType.Normal;
c.Messages = c.Messages ?? new List<Message>();
c.Protocol = PROTOCOL;
c.ParentChannel = protocolAsChannel;
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"); };
return c;
}
private Channel UpsertDMChannel(string whisperWith)
{
Channel c = _db.Channels.FirstOrDefault(ci => ci.ExternalId == $"w_{whisperWith}" && ci.Protocol == PROTOCOL);
if (c == null)
{
c = new Channel();
_db.Channels.Add(c);
}
c.DisplayName = $"Whisper: {whisperWith}";
c.ExternalId = $"w_{whisperWith}";
c.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
c.Messages = c.Messages ?? new List<Message>();
c.Protocol = PROTOCOL;
c.ParentChannel = protocolAsChannel;
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"); };
return c;
}
private Message UpsertMessage(ChatMessage chatMessage)
{
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == chatMessage.Id);
if (m == null)
{
m = new Message();
m.Protocol = PROTOCOL;
_db.Messages.Add(m);
m.Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
}
m.Content = chatMessage.Message;
m.ExternalId = chatMessage.Id;
m.Channel = UpsertChannel(chatMessage.Channel);
m.Author = UpsertAccount(chatMessage.Username, m.Channel.Id);
m.Author.SeenInChannel = m.Channel;
m.Reply = (t) => { return Task.Run(() => { client.SendReply(chatMessage.Channel, chatMessage.Id, t); }); };
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
return m;
}
private Message UpsertMessage(WhisperMessage whisperMessage)
{
var m = _db.Messages.FirstOrDefault(mi => mi.ExternalId == whisperMessage.MessageId);
if (m == null)
{
m = new Message();
m.Protocol = PROTOCOL;
_db.Messages.Add(m);
m.Timestamp = (DateTimeOffset)DateTime.SpecifyKind(DateTime.UtcNow, DateTimeKind.Utc);
}
m.Content = whisperMessage.Message;
m.ExternalId = whisperMessage.MessageId;
m.Channel = UpsertDMChannel(whisperMessage.Username);
m.Channel.ChannelType = vassago.Models.Enumerations.ChannelType.DM;
m.Author = UpsertAccount(whisperMessage.Username, m.Channel.Id);
m.Author.SeenInChannel = m.Channel;
m.Reply = (t) => { return Task.Run(() => { client.SendWhisper(whisperMessage.Username, t); }); };
m.React = (e) => { throw new InvalidOperationException($"twitch cannot react"); };
return m;
}
public string AttemptJoin(string channelTarget)
{
client.JoinChannel(channelTarget);
return $"attempt join {channelTarget} - o7";
}
internal void AttemptLeave(string channelTarget)
{
client.SendMessage(channelTarget, "o7");
client.LeaveChannel(channelTarget);
}
}

View File

@ -1,8 +1,42 @@
# discord-bot
copy appsettings.json and fill it in
copy appsettings.json to appsettings.ENV.json and fill it in. dotnet seems to understand files called appsettings.json (and appsettings.xml?) and knows how to overwrite *specific values found within* the .[ENV].[extension] version
# auth link
https://discord.com/oauth2/authorize?client_id=913003037348491264&permissions=274877942784&scope=bot
that's read messages/view channels, send messages, send messages in threads, and attach files. but not add reactions?
that's read messages/view channels, send messages, send messages in threads, and attach files. but not add reactions?
# concepts
## Data Types
### Accounts
a `User` can have multiple `Account`s. e.g., @adam:greyn.club? that's an "account". I, however, am a `User`. An `Account` has references to the `Channels` its seen in.
### Attachment
debating whether to save a copy of every single attachment. Discord allows 25MB attachments, and shtikbot lives in several art channels.
### Channel
a place where communication can happen. any level of these can have any number of children. In matrix, everything is a "room" - even spaces and threads. Seems like a fine idea. So for vassago, a discord "channel" is a channel. a "thread" is a child of that channel. a "category" is a parent of that channel. A "server" (formerly "guild") is a parent of that channel. and fuck it, Discord itself is a "channel". Includes permissions vassago has for a channel; MaxAttachmentBytes, etc. go down the hierarchy until you find an override.
### FeaturePermission
the permissions of a feature. It can be restricted to accounts, to users, to channels. It has an internal name... and tag? and it can be (or not be) inheritable?
### Message
a message (duh). features bools for "mentions me", the external ID, the reference to the account, the channel.
### User
a person or program who operates an account. recognizing that 2 `Account`s belong to 1 `User` can be done by that user (using LinkMe). I should be able to collapse myself automatically.
## Behavior
both a "feature" and an "anti-feature". a channel might dictate something isn't allowed (lewdness in a g-rated channel). A person might not be allowed to do something - lots of me-only things like directing other bots (and the now rendered-moot Torrent feature). A behavior might need a command alias in a particular channel (freedomunits in jubel's)
so "behavior" might need to tag other data types? do I have it do a full select every time we get a message? ...no, only if the (other) triggering conditions are met. Then you can take your time.

View File

@ -1,10 +1,13 @@
namespace vassago;
using System;
using System.Net.Http;
using vassago.Models;
namespace silverworker_discord
public static class Shared
{
public static class Shared
{
public static Random r = new Random();
}
}
public static Random r = new Random();
public static string DBConnectionString { get; set; }
public static HttpClient HttpClient { get; internal set; } = new HttpClient();
}

View File

@ -0,0 +1,58 @@
using System.ComponentModel;
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
namespace vassago.Controllers;
public class ChannelsController : Controller
{
private readonly ILogger<ChannelsController> _logger;
private readonly ChattingContext _db;
public ChannelsController(ILogger<ChannelsController> logger, ChattingContext db)
{
_logger = logger;
_db = db;
}
public async Task<IActionResult> Index(string searchString)
{
return _db.Channels != null ?
View(_db.Channels.Include(u => u.ParentChannel).ToList().OrderBy(c => c.LineageSummary)) :
Problem("Entity set '_db.Channels' is null.");
}
public async Task<IActionResult> Details(Guid id)
{
if(_db.Channels == null)
return Problem("Entity set '_db.Channels' is null.");
//"but adam", says the strawman, "why load *every* channel and walk your way up? surely there's a .Load command that works or something."
//eh. I checked. Not really. You could make an SQL view that recurses its way up, meh idk how. You could just eagerly load *every* related object...
//but that would take in all the messages.
//realistically I expect this will have less than 1MB of total "channels", and several GB of total messages per (text) channel.
var AllChannels = await _db.Channels
.Include(u => u.SubChannels)
.Include(u => u.Users)
.Include(u => u.ParentChannel)
.ToListAsync();
var channel = AllChannels.First(u => u.Id == id);
var walker = channel;
while(walker != null)
{
ViewData["breadcrumbs"] = $"<a href=\"{Url.ActionLink(action: "Details", controller: "Channels", values: new {id = walker.Id})}\">{walker.DisplayName}</a>/" +
ViewData["breadcrumbs"];
walker = walker.ParentChannel;
}
return View(
new Tuple<Channel, Enumerations.LewdnessFilterLevel, Enumerations.MeannessFilterLevel>(
channel, channel.EffectivePermissions.LewdnessFilterLevel, channel.EffectivePermissions.MeannessFilterLevel
));
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}

View File

@ -0,0 +1,8 @@
namespace vassago.Models;
public class ErrorPageViewModel
{
public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}

View File

@ -0,0 +1,193 @@
using System.Diagnostics;
using System.Text;
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
using vassago.Models;
namespace vassago.Controllers;
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly ChattingContext _db;
public HomeController(ILogger<HomeController> logger, ChattingContext db)
{
_logger = logger;
_db = db;
}
public IActionResult Index()
{
var allAccounts = _db.Accounts.ToList();
var allChannels = _db.Channels.Include(c => c.Users).ToList();
var sb = new StringBuilder();
sb.Append("[");
sb.Append("{text: \"channels\", nodes: [");
var first = true;
var topLevelChannels = _db.Channels.Where(x => x.ParentChannel == null);
foreach (var topLevelChannel in topLevelChannels)
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
serializeChannel(ref sb, ref allChannels, ref allAccounts, topLevelChannel);
}
sb.Append("]}");
if (allChannels.Any())
{
sb.Append(",{text: \"orphaned channels\", nodes: [");
first = true;
while (true)
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
serializeChannel(ref sb, ref allChannels, ref allAccounts, allChannels.First());
if (!allChannels.Any())
{
break;
}
}
sb.Append("]}");
}
if (allAccounts.Any())
{
sb.Append(",{text: \"channelless accounts\", nodes: [");
first = true;
foreach (var acc in allAccounts)
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
serializeAccount(ref sb, acc);
}
sb.Append("]}");
}
var users = _db.Users.ToList();
if(users.Any())
{
sb.Append(",{text: \"users\", nodes: [");
first=true;
//refresh list; we'll be knocking them out again in serializeUser
allAccounts = _db.Accounts.ToList();
foreach(var user in users)
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
serializeUser(ref sb, ref allAccounts, user);
}
sb.Append("]}");
}
sb.Append("]");
ViewData.Add("treeString", sb.ToString());
return View("Index");
}
private void serializeChannel(ref StringBuilder sb, ref List<Channel> allChannels, ref List<Account> allAccounts, Channel currentChannel)
{
allChannels.Remove(currentChannel);
//"but adam", you say, "there's an href attribute, why make a link?" because that makes the entire bar a link, and trying to expand the node will probably click the link
sb.Append($"{{\"text\": \"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Channels", values: new {id = currentChannel.Id})}\\\">{currentChannel.DisplayName}</a>\"");
var theseAccounts = allAccounts.Where(a => a.SeenInChannel?.Id == currentChannel.Id).ToList();
allAccounts.RemoveAll(a => a.SeenInChannel?.Id == currentChannel.Id);
var first = true;
if (currentChannel.SubChannels != null || theseAccounts != null)
{
sb.Append(", \"nodes\": [");
}
if (currentChannel.SubChannels != null)
{
foreach (var subChannel in currentChannel.SubChannels ?? new List<Channel>())
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
serializeChannel(ref sb, ref allChannels, ref allAccounts, subChannel);
}
if (theseAccounts != null)
{
sb.Append(',');
}
}
if (theseAccounts != null)
{
first = true;
sb.Append($"{{\"text\": \"(accounts: {theseAccounts.Count()})\", \"expanded\":true, nodes:[");
foreach (var account in theseAccounts)
{
if (first)
{
first = false;
}
else
{
sb.Append(',');
}
serializeAccount(ref sb, account);
}
sb.Append("]}");
}
sb.Append("]}");
}
private void serializeAccount(ref StringBuilder sb, Account currentAccount)
{
sb.Append($"{{\"text\": \"{currentAccount.DisplayName}\"}}");
}
private void serializeUser(ref StringBuilder sb, ref List<Account> allAccounts, User currentUser)
{
sb.Append($"{{\"text\": " +
$"\"<a href=\\\"{Url.ActionLink(action: "Details", controller: "Users", values: new {id = currentUser.Id})}\\\">"
+ currentUser.DisplayName +
"</a>\", ");
// \"{currentUser.DisplayName}\", ");
var ownedAccounts = allAccounts.Where(a => a.IsUser == currentUser);
sb.Append("nodes: [");
sb.Append($"{{\"text\": \"owned accounts:\", \"expanded\":true, \"nodes\": [");
if (ownedAccounts != null)
{
foreach (var acc in ownedAccounts)
{
serializeAccount(ref sb, acc);
sb.Append(',');
}
}
sb.Append("]}]}");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}

View File

@ -0,0 +1,45 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using vassago.Models;
namespace vassago.Controllers;
public class UsersController : Controller
{
private readonly ILogger<UsersController> _logger;
private readonly ChattingContext _db;
public UsersController(ILogger<UsersController> logger, ChattingContext db)
{
_logger = logger;
_db = db;
}
public async Task<IActionResult> Index(string searchString)
{
return _db.Users != null ?
View(await _db.Users.Include(u => u.Accounts).ToListAsync()) :
Problem("Entity set '_db.Users' is null.");
}
public async Task<IActionResult> Details(Guid id)
{
var user = await _db.Users
.Include(u => u.Accounts)
.FirstAsync(u => u.Id == id);
var allTheChannels = await _db.Channels.ToListAsync();
foreach(var acc in user.Accounts)
{
acc.SeenInChannel = allTheChannels.FirstOrDefault(c => c.Id == acc.SeenInChannel.Id);
}
return _db.Users != null ?
View(user) :
Problem("Entity set '_db.Users' is null.");
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorPageViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}

View File

@ -0,0 +1,44 @@
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;
private readonly ChattingContext _db;
public ChannelsController(ILogger<ChannelsController> logger, ChattingContext db)
{
_logger = logger;
_db = db;
}
[HttpGet("{id}")]
[Produces("application/json")]
public Channel Get(Guid id)
{
return _db.Find<Channel>(id);
}
[HttpPatch]
[Produces("application/json")]
public IActionResult Patch([FromBody] Channel channel)
{
var fromDb = _db.Channels.Find(channel.Id);
if (fromDb == null)
{
_logger.LogError($"attempt to update channel {channel.Id}, not found");
return NotFound();
}
//settable values: lewdness filter level, meanness filter level. maybe i could decorate them...
fromDb.LewdnessFilterLevel = channel.LewdnessFilterLevel;
fromDb.MeannessFilterLevel = channel.MeannessFilterLevel;
_db.SaveChanges();
return Ok(fromDb);
}
}

View File

@ -0,0 +1,120 @@
@using System.ComponentModel
@using Newtonsoft.Json
@model Tuple<Channel, Enumerations.LewdnessFilterLevel, Enumerations.MeannessFilterLevel>
@{
var ThisChannel = Model.Item1;
var IfInheritedLewdnessFilterLevel = Model.Item2;
var IfInheritedMeannessFilterLevel = Model.Item3;
}
@Html.Raw(ViewData["breadcrumbs"])
<table class="table">
<tbody>
<tr>
<th scope="row">Display Name</th>
<td>@ThisChannel.DisplayName</td>
</tr>
<tr>
<th scope="row">Channel type</th>
<td>@(ThisChannel.ChannelType != null ? Enumerations.GetDescription(ThisChannel.ChannelType) : "?")</td>
</tr>
<tr>
<th scope="row">Lewdness Filter Level</th>
<td>
<select name="LewdnessFilterLevel" id="LewdnessFilterLevel" onchange="patchModel(jsonifyChannel(), '/api/Channels/')">
<!option value="" @(ThisChannel.LewdnessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedLewdnessFilterLevel)</!option>
@foreach (Enumerations.LewdnessFilterLevel enumVal in
Enum.GetValues(typeof(Enumerations.LewdnessFilterLevel)))
{
<!option value="@((int)enumVal)" @(ThisChannel.LewdnessFilterLevel == enumVal ? "selected" : "")>
@(Enumerations.GetDescription<Enumerations.LewdnessFilterLevel>(enumVal))</!option>
}
</select>
</td>
</tr>
<tr>
<th scope="row">Links Allowed</th>
<td>@(ThisChannel.LinksAllowed?.ToString() ?? "unknown")</td>
</tr>
<tr>
<th scope="row">Lineage summary</th>
<td>@ThisChannel.LineageSummary</td>
</tr>
<tr>
<th scope="row">max attachment bytes</th>
<td>@ThisChannel.MaxAttachmentBytes (i hear there's "ByteSize")</td>
</tr>
<tr>
<th scope="row">max message length</th>
<td>@(ThisChannel.MaxTextChars?.ToString() ?? "inherited")</td>
</tr>
<tr>
<th scope="row">Meanness Filter Level</th>
<td>
<select name="MeannessFilterLevel" id="MeannessFilterLevel" onchange="patchModel(jsonifyChannel(), '/api/Channels/')">
<!option value="" @(ThisChannel.MeannessFilterLevel == null ? "selected" : "")>⤵ inherited - @Enumerations.GetDescription(IfInheritedMeannessFilterLevel)</!option>
@foreach (Enumerations.MeannessFilterLevel enumVal in
Enum.GetValues(typeof(Enumerations.MeannessFilterLevel)))
{
<!option value="@((int)enumVal)" @(ThisChannel.MeannessFilterLevel == enumVal ? "selected" : "")>
@(Enumerations.GetDescription<Enumerations.MeannessFilterLevel>(enumVal))</!option>
}
</select>
</td>
</tr>
<tr>
<th scope="row">Messages (count)</th>
<td>@(ThisChannel.Messages?.Count ?? 0)</td>
</tr>
<tr>
<th scope="row">Protocol</th>
<td>@ThisChannel.Protocol</td>
</tr>
<tr>
<th scope="row">Reactions Possible</th>
<td>@(ThisChannel.ReactionsPossible?.ToString() ?? "inherited")</td>
</tr>
<tr>
<th scope="row">Sub Channels</th>
<td>@(ThisChannel.SubChannels?.Count ?? 0)</td>
</tr>
<tr>
<th scope="row">Accounts</th>
<td>@(ThisChannel.Users?.Count ?? 0)</td>
</tr>
</tbody>
</table>
@section Scripts{
<script type="text/javascript">
@{
var modelAsString = JsonConvert.SerializeObject(ThisChannel, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
}
const channelOnLoad = @Html.Raw(modelAsString);
function jsonifyChannel() {
var channelNow = structuredClone(channelOnLoad);
channelNow.SubChannels = null;
channelNow.ParentChannel = null;
channelNow.Messages = null;
channelNow.Users = null;
channelNow.LewdnessFilterLevel = document.querySelector("#LewdnessFilterLevel").value;
channelNow.MeannessFilterLevel = document.querySelector("#MeannessFilterLevel").value;
console.log(channelNow);
return channelNow;
}
function getTree() {
var tree = @Html.Raw(ViewData["treeString"]);
console.log(tree);
return tree;
}
$('#tree').bstreeview({ data: getTree() });
</script>
}

View File

@ -0,0 +1,42 @@
@model IEnumerable<Channel>
@{
ViewData["Title"] = "Channels";
}
<table class="table">
<thead>
<tr>
<th>
protocol
</th>
<th>type</th>
<th>
display name
</th>
<th>
Lineage
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td class="@item.Protocol">
<div class="protocol-icon">&nbsp;</div>
</td>
<td class="@item.ChannelType">
<div class="channel-type-icon">&nbsp;</div>
</td>
<td>
@Html.DisplayFor(modelItem => item.DisplayName)
</td>
<td>
@item.LineageSummary
</td>
<td>
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
</td>
</tr>
}
</tbody>
</table>

View File

@ -0,0 +1,17 @@
@{
ViewData["Title"] = "Home Page";
}
<div id="tree"></div>
tree above.
@section Scripts{
<script type="text/javascript">
function getTree() {
var tree = @Html.Raw(ViewData["treeString"]);
console.log(tree);
return tree;
}
$('#tree').bstreeview({ data: getTree() });
</script>
}

View File

@ -0,0 +1,25 @@
@model ErrorPageViewModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - vassago</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/css/fontawesome.min.css" />
<link rel="stylesheet" href="~/css/bs.min.treeview.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<link rel="stylesheet" href="~/vassago.styles.css" asp-append-version="true" />
</head>
<body>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/bstreeview.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,48 @@
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
for details on configuring this project to bundle and minify static web assets. */
a.navbar-brand {
white-space: normal;
text-align: center;
word-break: break-all;
}
a {
color: #0077cc;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
button.accept-policy {
font-size: 1rem;
line-height: inherit;
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
white-space: nowrap;
line-height: 60px;
}

View File

@ -0,0 +1,2 @@
<script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>

View File

@ -0,0 +1,86 @@
@model User
@using Newtonsoft.Json
@using System.Text
@{
ViewData["Title"] = "User details";
}
<table class="table">
<tbody>
<tr>
<td><input type="text" id="displayName" value="@Model.DisplayName"></input> <button onclick="patchModel(jsonifyUser(), @Html.Raw("'/api/Users/'"))">update</button></td>
</tr>
<tr>
<td>
<div id="accountsTree"></div>
</td>
</tr>
<tr>
<td>
<div id="tagsTree"></div>
</td>
</tr>
</tbody>
</table>
<a asp-controller="Accounts" asp-action="Details" asp-route-id="1">placeholderlink</a>
@section Scripts{
<script type="text/javascript">
@{
var userAsString = JsonConvert.SerializeObject(Model, new JsonSerializerSettings
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
});
}
const userOnLoad = @Html.Raw(userAsString);
function jsonifyUser() {
var userNow = structuredClone(userOnLoad);
userNow.Accounts = null;
userNow.DisplayName = document.querySelector("#displayName").value;
//userNow.Tag_CanTwitchSummon = document.querySelector("#tagCanTwitchSummon").checked;
console.log(userNow);
return userNow;
}
function getAccountsTree() {
@{
var sb = new StringBuilder();
sb.Append("[{text: \"accounts\", \"expanded\":true, nodes: [");
var first = true;
foreach (var acc in Model.Accounts)
{
if(!first)
sb.Append(',');
sb.Append($"{{text: \"<div class=\\\"account {acc.Protocol}\\\"><div class=\\\"protocol-icon\\\">&nbsp;</div>{acc.SeenInChannel.LineageSummary}/<a href=\\\"/Accounts/Details/{acc.Id}\\\">{acc.DisplayName}</a>\"}}");
first=false;
}
sb.Append("]}]");
}
console.log(@Html.Raw(sb.ToString()));
var tree = @Html.Raw(sb.ToString());
return tree;
}
function getTagsTree() {
@{
sb = new StringBuilder();
sb.Append("[{text: \"permission tags\", \"expanded\":true, nodes: [");
first = true;
for(int i = 0; i < 1; i++)
{
if(!first)
sb.Append(',');
sb.Append($"{{text: \"<input type=\\\"checkbox\\\" > is goated (w/ sauce)</input>\"}}");
first=false;
}
sb.Append("]}]");
}
console.log(@Html.Raw(sb.ToString()));
var tree = @Html.Raw(sb.ToString());
return tree;
}
$('#accountsTree').bstreeview({ data: getAccountsTree() });
$('#tagsTree').bstreeview({ data: getTagsTree() });
document.querySelectorAll("input[type=checkbox]").forEach(node => {node.onchange = () => {patchModel(jsonifyUser(), '/api/Users/')}});
</script>
}

View File

@ -0,0 +1,38 @@
@model IEnumerable<User>
@{
ViewData["Title"] = "Users";
}
<table class="table">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Id)
</th>
<th>
name*
</th>
<th>
number of associated accounts
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Id)
</td>
<td>
@Html.DisplayFor(modelItem => item.DisplayName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Accounts.Count)x
</td>
<td>
<a asp-action="Details" asp-route-id="@item.Id">Details</a>
</td>
</tr>
}
</tbody>
</table>

View File

@ -0,0 +1,3 @@
@using vassago
@using vassago.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -1,4 +0,0 @@
{
"token": "59 chars",
"exchangePairsLocation": "assets/exchangepairs.json"
}

16
appsettings.json Normal file
View File

@ -0,0 +1,16 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"DiscordTokens": [
],
"TwitchConfigs": [
],
"exchangePairsLocation": "assets/exchangepairs.json",
"DBConnectionString": "Host=azure.club;Database=db;Username=user;Password=password"
}

View File

@ -36,6 +36,13 @@
"metre"
]
},
{
"canonical":"mm",
"aliases": [
"millimeter",
"millimetre"
]
},
{
"canonical":"km",
"aliases": [
@ -204,6 +211,34 @@
"aliases": [
"parsec"
]
},
{
"canonical":"mi",
"aliases": [
"mile"
]
},
{
"canonical":"blue whale lengths",
"aliases": [
"bwl",
"whales"
]
},
{
"canonical":"ångströms",
"aliases": [
"angstroms",
"Å"
]
},
{
"canonical":"μm",
"aliases": [
"micrometers",
"micrometres",
"microns"
]
}
],
"linearPairs":[
@ -216,9 +251,12 @@
{"item1":"mi", "item2":"ft", "factor":5280},
{"item1":"m", "item2":"in", "factor":39.37008},
{"item1":"m", "item2":"cm", "factor":100},
{"item1":"cm", "item2":"mm", "factor":10},
{"item1":"m", "item2":"μm", "factor":1000000},
{"item1":"km", "item2":"mi", "factor":0.6213712},
{"item1":"ft", "item2":"in", "factor":12},
{"item1":"yd", "item2":"ft", "factor":3},
{"item1":"football field", "item2":"yd", "factor":100},
{"item1":"chain", "item2":"yd", "factor":22},
{"item1":"chain", "item2":"link", "factor":100},
{"item1":"furlong", "item2":"mi", "factor":8},
@ -226,6 +264,9 @@
{"item1":"AU", "item2":"ly", "factor": 0.0000158125},
{"item1":"ly", "item2":"km", "factor": 946070000000},
{"item1":"pc", "item2":"AU", "factor":206266.3},
{"item1":"blue whale length", "item2": "m", "factor": 29.9},
{"item1":"m", "item2": "ångström", "factor": 10000000000},
{"item1":"smoot", "item2": "ft", "factor": 5.583333333333},
{"item1":"floz", "item2":"mL", "factor":29.57344},
{"item1":"L", "item2":"mL", "factor":1000},
@ -242,14 +283,14 @@
{"item1":"gal", "item2":"floz", "factor":128},
{"item1":"gal", "item2":"qt", "factor":4},
{"item1":"acre", "item2":"yd^2", "factor":4840},
{"item1":"yd^2", "item2":"m^2", "factor":0.836127},
{"item1":"mph", "item2":"knot", "factor":0.868976},
{"item1":"mph", "item2":"kph", "factor":1.609343550606653},
{"item1":"kPa", "item2":"Pa", "factor":1000},
{"item1":"Nm^2", "item2":"Pa", "factor":1},
{"item1":"Pa", "item2":"bar", "factor":100},

View File

@ -17,7 +17,7 @@ What did the teacher do with the students report on cheese? Grated it.
What do you call a man with no legs and arms in a pool? Bob.
I was going to tell a joke about hammers but ...I don't think I'll nail it
why did the can recycler quit his job? because it was so depressing.
They told me to stop impersonating a flamingo. I had to put my foot down.
They told me to stop impersonating a flamingo. I had to put my foot down.
I failed math so many times at school, I cant even count.
When I was a child, I threw a boomerang, but it didn't come back. I live in constant fear.
When life gives you melons, you might be dyslexic.
@ -40,7 +40,6 @@ A ghost walked into a bar and ordered a shot of vodka. The bartender said, So
A blind man walked into a bar. and a table. and a chair.
How do you make holy water? You boil the hell out of it.
My teachers told me Id never amount to much because I procrastinate so much. I told them, “Just you wait!”
ברכב שנוסע על 4 גלגלים, איזה גלגל לא זז? גלגל רזרבי
what's the best part about living in switzerland? well the flag is a big plus.
I asked my date to meet me at the gym today. She didn't show up. That's when I knew we weren't gonna work out.
The CEO of IKEA was elected Prime Minister in Sweden. He should have his cabinet together by the end of the weekend.
@ -76,4 +75,4 @@ Someone broke into my house and stole all my fruits. I'm peachless.
Did I tell you guys about that flat earther i got into an argument with? he got so mad he stormed off saying he'd walk to the edge of the earth to prove me wrong, but he'll come around eventually.
What do you call your friend who stands in a hole? Phil.
What happened when the bear swallowed a clock? He got ticks.
What do you call a wolf who gets lost? A where-wolf.
What do you call a wolf who gets lost? A where-wolf.

View File

@ -7,7 +7,7 @@ using System.Text;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
namespace silverworker_discord
namespace vassago
{
public class ExternalProcess
{

View File

@ -0,0 +1,15 @@
{
"ClassName": "System.ComponentModel.Win32Exception",
"Message": "An error occurred trying to start process 'convert' with working directory '/home/adam/Desktop/vassago'. No such file or directory",
"Data": null,
"InnerException": null,
"HelpURL": null,
"StackTraceString": " at System.Diagnostics.Process.ForkAndExecProcess(ProcessStartInfo startInfo, String resolvedFilename, String[] argv, String[] envp, String cwd, Boolean setCredentials, UInt32 userId, UInt32 groupId, UInt32[] groups, Int32& stdinFd, Int32& stdoutFd, Int32& stderrFd, Boolean usesTerminal, Boolean throwOnNoExec)\n at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)\n at vassago.ExternalProcess.GoPlz(String commandPath, String commandArguments) in /home/adam/Desktop/vassago/externalProcess.cs:line 30",
"RemoteStackTraceString": null,
"RemoteStackIndex": 0,
"ExceptionMethod": null,
"HResult": -2147467259,
"Source": "System.Diagnostics.Process",
"WatsonBuckets": null,
"NativeErrorCode": 2
}

View File

@ -1,38 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<RootNamespace>silverworker_discord</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="discord.net" Version="3.10.0" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="QRCoder" Version="1.4.2" />
<PackageReference Include="youtubedlsharp" Version="0.3.1" />
</ItemGroup>
<ItemGroup>
<None Update="assets/jokes.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/coding and algorithms.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/ekgblip.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/conversion.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/loud sweating.gif">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<NoWarn>$(NoWarn);CA2254</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="bootstrap" Version="5.3.3" />
<PackageReference Include="discord.net" Version="3.10.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="7.0.20" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Configuration" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="5.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
<PackageReference Include="QRCoder" Version="1.4.2" />
<PackageReference Include="RestSharp" Version="110.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.6.2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.6.2" />
<PackageReference Include="TwitchLib" Version="3.5.3" />
<PackageReference Include="youtubedlsharp" Version="0.3.1" />
</ItemGroup>
<ItemGroup>
<None Update="assets/jokes.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/coding and algorithms.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/ekgblip.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/conversion.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="assets/loud sweating.gif">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

10
wwwroot/css/bstreeview.min.css vendored Normal file
View File

@ -0,0 +1,10 @@
/*
@preserve
bstreeview.css
Version: 1.2.0
Authors: Sami CHNITER <sami.chniter@gmail.com>
Copyright 2020
License: Apache License 2.0
Project: https://github.com/nhmvienna/bs5treeview
*/
.bstreeview{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem;padding:0;overflow:hidden}.bstreeview .list-group{margin-bottom:0}.bstreeview .list-group-item{border-radius:0;border-width:1px 0 0 0;padding-top:.5rem;padding-bottom:.5rem;cursor:pointer}.bstreeview .list-group-item:hover{background-color:#dee2e6}.bstreeview>.list-group-item:first-child{border-top-width:0}.bstreeview .state-icon{margin-right:8px}.bstreeview .item-icon{margin-right:5px}

9
wwwroot/css/fontawesome.min.css vendored Normal file

File diff suppressed because one or more lines are too long

46
wwwroot/css/site.css Normal file
View File

@ -0,0 +1,46 @@
html {
font-size: 14px;
}
@media (min-width: 768px) {
html {
font-size: 16px;
}
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
html {
position: relative;
min-height: 100%;
}
body {
margin-bottom: 60px;
}
.protocol-icon,.channel-type-icon{
display:inline-block;
width: 32px;
height: 32px;
background-size: 32px;
}
.discord .protocol-icon{
background-image: url("../imgs/discord_logo1600.png");
}
.twitch .protocol-icon{
background-image: url("../imgs/twitch.png");
}
.Normal .channel-type-icon{
background-color: black;
}
.DM .channel-type-icon{
background-color: black;
}
.Protocol .channel-type-icon{
background-color: black;
}
.OU .channel-type-icon{
background-color: black;
}

BIN
wwwroot/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
wwwroot/imgs/twitch.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

10
wwwroot/js/bstreeview.min.js vendored Normal file
View File

@ -0,0 +1,10 @@
/*
@preserve
bstreeview.js
Version: 1.2.0
Authors: Sami CHNITER <sami.chniter@gmail.com>
Copyright 2020
License: Apache License 2.0
Project:https://github.com/nhmvienna/bs5treeview
*/
!function (t, e, i, s) { "use strict"; var n = { expandIcon: "fa fa-angle-down fa-fw", collapseIcon: "fa fa-angle-right fa-fw", expandClass: 'show', indent: 1.25, parentsMarginLeft: "1.25rem", openNodeLinkOnNewTab: !0 }, a = '<div role="treeitem" class="list-group-item" data-bs-toggle="collapse"></div>', d = '<div role="group" class="list-group collapse" id="itemid"></div>', o = '<i class="state-icon"></i>', r = '<i class="item-icon"></i>'; function l(e, i) { this.element = e, this.itemIdPrefix = e.id + "-item-", this.settings = t.extend({}, n, i), this.init() } t.extend(l.prototype, { init: function () { this.tree = [], this.nodes = [], this.settings.data && (this.settings.data.isPrototypeOf(String) && (this.settings.data = t.parseJSON(this.settings.data)), this.tree = t.extend(!0, [], this.settings.data), delete this.settings.data), t(this.element).addClass("bstreeview"), this.initData({ nodes: this.tree }); var i = this; this.build(t(this.element), this.tree, 0), t(this.element).on("click", ".list-group-item", function (s) { t(".state-icon", this).toggleClass(i.settings.expandIcon).toggleClass(i.settings.collapseIcon), s.target.hasAttribute("href") && (i.settings.openNodeLinkOnNewTab ? e.open(s.target.getAttribute("href"), "_blank") : e.location = s.target.getAttribute("href")) }) }, initData: function (e) { if (e.nodes) { var i = e, s = this; t.each(e.nodes, function (t, e) { e.nodeId = s.nodes.length, e.parentId = i.nodeId, s.nodes.push(e), e.nodes && s.initData(e) }) } }, build: function (e, i, s) { var n = this, l = n.settings.parentsMarginLeft; s > 0 && (l = (n.settings.indent + s * n.settings.indent).toString() + "rem;"), s += 1, t.each(i, function (i, g) { var h = t(a).attr("data-bs-target", "#" + n.itemIdPrefix + g.nodeId).attr("style", "padding-left:" + l).attr("aria-level", s); if (g.nodes) { var c = t(o).addClass((g.expanded)?n.settings.expandIcon:n.settings.collapseIcon); h.append(c) } if (g.icon) { var f = t(r).addClass(g.icon); h.append(f) } if (h.append(g.text), g.href && h.attr("href", g.href), g.class && h.addClass(g.class), g.id && h.attr("id", g.id), e.append(h), g.nodes) { var p = t(d).attr("id", n.itemIdPrefix + g.nodeId); e.append(p), n.build(p, g.nodes, s); if (g.expanded) p.addClass(n.settings.expandClass) } }) } }), t.fn.bstreeview = function (e) { return this.each(function () { t.data(this, "plugin_bstreeview") || t.data(this, "plugin_bstreeview", new l(this, e)) }) } }(jQuery, window, document);

50
wwwroot/js/site.js Normal file
View File

@ -0,0 +1,50 @@
// Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
function Account(displayName, accountId, protocol){
this.displayName = displayName;
this.accountId = accountId;
this.protocol = protocol;
}
//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?
//as it stands, you want something like /api/Channels/, trailing slash intentional
function patchModel(model, apiUrl)
{
//structure the model your (dang) self into a nice object
console.log(model);
//i know the page url.
console.log(window.location.pathname);
var components = window.location.pathname.split('/');
if(components[2] !== "Details")
{
console.log("wtf are you doing? " + components[2] + " is something other than Details");
//add different endpoings here, if you like
}
var type=components[1];
var id=components[3];
console.log("dexter impression: I am now ready to post the following content:");
console.log(JSON.stringify(model));
fetch(apiUrl, {
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);
})
.catch(error => {
console.error('Error:', error);
});
}

View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2011-2021 Twitter, Inc.
Copyright (c) 2011-2021 The Bootstrap Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More