forked from adam/discord-bot-shtik
Compare commits
No commits in common. "e7b70468aede25355dd489ab6893148aae89666a" and "8c6087d557f37ac1ed8ee0ec4fc636b06a6d99d5" have entirely different histories.
e7b70468ae
...
8c6087d557
@ -28,4 +28,4 @@ public abstract class Behavior
|
|||||||
///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.
|
///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.
|
///As opposed to LaughAtOwnJoke, which only needs to be created to wait for 1 punchline one time.
|
||||||
///</summary>
|
///</summary>
|
||||||
public class StaticPlzAttribute : Attribute {}
|
public class StaticPlzAttribute : Attribute {}
|
@ -22,7 +22,7 @@ public class GeneralSnarkCloudNative : Behavior
|
|||||||
if(Behaver.Instance.Selves.Any(acc => acc.Id == message.Author.Id))
|
if(Behaver.Instance.Selves.Any(acc => acc.Id == message.Author.Id))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if(message.Channel.EffectivePermissions.ReactionsPossible)
|
if(message.Channel.EffectivePermissions.ReactionsPossible == true)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
|
if((MeannessFilterLevel)message.Channel.EffectivePermissions.MeannessFilterLevel < MeannessFilterLevel.Medium)
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,14 +11,7 @@ public class TwitchSummon : Behavior
|
|||||||
|
|
||||||
public override string Trigger => "!twitchsummon";
|
public override string Trigger => "!twitchsummon";
|
||||||
|
|
||||||
//TODO: Permission! anyone can summon from anywhere... anyone can summon to themselves.
|
//TODO: Permission!
|
||||||
//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 true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async Task<bool> ActOn(Message message)
|
public override async Task<bool> ActOn(Message message)
|
||||||
{
|
{
|
||||||
|
@ -16,10 +16,7 @@ public class TwitchDismiss : Behavior
|
|||||||
if(message.MentionsMe &&
|
if(message.MentionsMe &&
|
||||||
(Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b")))
|
(Regex.IsMatch(message.Content.ToLower(), "\\bbegone\\b") || Regex.IsMatch(message.Content.ToLower(), "\\bfuck off\\b")))
|
||||||
{
|
{
|
||||||
//TODO: PERMISSION! who can dismiss me? pretty simple list:
|
//TODO: PERMISSION!
|
||||||
//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 true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -26,6 +26,8 @@ public class Account
|
|||||||
}
|
}
|
||||||
public bool IsBot { get; set; } //webhook counts
|
public bool IsBot { get; set; } //webhook counts
|
||||||
public Channel SeenInChannel { get; set; }
|
public Channel SeenInChannel { get; set; }
|
||||||
|
//permissions are per account-in-channel or per-user, and always propagate down. and since protocol will be a channel, I'll set the "is adam" permission on myself 1x/protocol.
|
||||||
|
public List<Enumerations.WellknownPermissions> PermissionTags{get;set;}
|
||||||
public string Protocol { get; set; }
|
public string Protocol { get; set; }
|
||||||
public User IsUser {get; set;}
|
public User IsUser {get; set;}
|
||||||
}
|
}
|
@ -14,7 +14,7 @@ public class Channel
|
|||||||
public string ExternalId { get; set; }
|
public string ExternalId { get; set; }
|
||||||
public string DisplayName { get; set; }
|
public string DisplayName { get; set; }
|
||||||
public bool IsDM { get; set; }
|
public bool IsDM { get; set; }
|
||||||
public ChannelPermissions Permissions { get; set; }
|
public PermissionSettings Permissions { get; set; }
|
||||||
public List<Channel> SubChannels { get; set; }
|
public List<Channel> SubChannels { get; set; }
|
||||||
public Channel ParentChannel { get; set; }
|
public Channel ParentChannel { get; set; }
|
||||||
public string Protocol { get; set; }
|
public string Protocol { get; set; }
|
||||||
@ -33,11 +33,11 @@ public class Channel
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
ChannelPermissions toReturn = Permissions ?? new ChannelPermissions();
|
PermissionSettings toReturn = Permissions ?? new PermissionSettings();
|
||||||
return GetEffectivePermissions(ref toReturn).Definite();
|
return GetEffectivePermissions(ref toReturn).Definite();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private ChannelPermissions GetEffectivePermissions(ref ChannelPermissions settings)
|
private PermissionSettings GetEffectivePermissions(ref PermissionSettings settings)
|
||||||
{
|
{
|
||||||
if(settings == null) throw new ArgumentNullException();
|
if(settings == null) throw new ArgumentNullException();
|
||||||
settings.LewdnessFilterLevel = settings.LewdnessFilterLevel ?? Permissions?.LewdnessFilterLevel;
|
settings.LewdnessFilterLevel = settings.LewdnessFilterLevel ?? Permissions?.LewdnessFilterLevel;
|
||||||
|
@ -9,7 +9,7 @@ public class ChattingContext : DbContext
|
|||||||
public DbSet<Channel> Channels { get; set; }
|
public DbSet<Channel> Channels { get; set; }
|
||||||
//public DbSet<Emoji> Emoji {get;set;}
|
//public DbSet<Emoji> Emoji {get;set;}
|
||||||
public DbSet<Message> Messages { get; set; }
|
public DbSet<Message> Messages { get; set; }
|
||||||
public DbSet<ChannelPermissions> PermissionSettings{get;set;}
|
public DbSet<PermissionSettings> PermissionSettings{get;set;}
|
||||||
public DbSet<Account> Accounts { get; set; }
|
public DbSet<Account> Accounts { get; set; }
|
||||||
public DbSet<User> Users { get; set; }
|
public DbSet<User> Users { get; set; }
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ public static class Enumerations
|
|||||||
[Description("polite company")]
|
[Description("polite company")]
|
||||||
Moderate,
|
Moderate,
|
||||||
[Description(";) ;) ;)")]
|
[Description(";) ;) ;)")]
|
||||||
Unrestricted
|
unrestricted
|
||||||
}
|
}
|
||||||
public enum MeannessFilterLevel
|
public enum MeannessFilterLevel
|
||||||
{
|
{
|
||||||
@ -23,17 +23,13 @@ public static class Enumerations
|
|||||||
Strict,
|
Strict,
|
||||||
[Description("a bit cheeky")]
|
[Description("a bit cheeky")]
|
||||||
Medium,
|
Medium,
|
||||||
[Description("387.44m mi of printed circuits")]
|
[Description("387.44 million miles of printed circuits, etc")]
|
||||||
Unrestricted
|
Unrestricted
|
||||||
}
|
}
|
||||||
public enum VerbosityFilterLevel
|
public enum WellknownPermissions
|
||||||
{
|
{
|
||||||
[Description("stfu")]
|
Master, //e.g., me. not that I think this would ever be released?
|
||||||
Quiet,
|
TwitchSummon,
|
||||||
[Description("pithy")]
|
|
||||||
Pithy,
|
|
||||||
[Description("you want text i'll GIVE you text")]
|
|
||||||
Unrestricted
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDescription<T>(this T enumerationValue)
|
public static string GetDescription<T>(this T enumerationValue)
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
namespace vassago.Models;
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.WebSocket;
|
|
||||||
using static vassago.Models.Enumerations;
|
|
||||||
|
|
||||||
public enum WellknownPermissions
|
|
||||||
{
|
|
||||||
Administrator,
|
|
||||||
TwitchSummon,
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FeaturePermission
|
|
||||||
{
|
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
public string InternalName { get; set; }
|
|
||||||
public WellknownPermissions? InternalTag { get; set; }
|
|
||||||
|
|
||||||
//a permissions-needing-feature can determine how to use these, but a default "matches" is provided
|
|
||||||
//for a message to "match", it must match in every category for which there are candidates.
|
|
||||||
//e.g., Administrator is going to be restricted to Users only, and that'll be me
|
|
||||||
//e.g., my future Torrent feature would be restricted to accounts and channels.
|
|
||||||
//hmmm, what would be inheritable and what wouldn't?
|
|
||||||
public IEnumerable<User> RestrictedToUsers { get; set; }
|
|
||||||
public IEnumerable<Account> RestrictedToAccounts { get; set; }
|
|
||||||
public IEnumerable<Channel> RestrictedToChannels { get; set; }
|
|
||||||
public bool Inheritable { get; set; } = true;
|
|
||||||
|
|
||||||
public bool Matches(Message message)
|
|
||||||
{
|
|
||||||
if(RestrictedToUsers?.Count() > 0)
|
|
||||||
{
|
|
||||||
if(RestrictedToUsers.FirstOrDefault(u => u.Id == message.Author.IsUser.Id) == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(RestrictedToChannels?.Count() > 0)
|
|
||||||
{
|
|
||||||
if(Inheritable)
|
|
||||||
{
|
|
||||||
var found = false;
|
|
||||||
var walker = message.Channel;
|
|
||||||
if (RestrictedToChannels.FirstOrDefault(c => c.Id == walker.Id) != null)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
while (walker.ParentChannel != null)
|
|
||||||
{
|
|
||||||
walker = walker.ParentChannel;
|
|
||||||
if(walker.Users.FirstOrDefault(a => a.ExternalId == message.Author.ExternalId) == null)
|
|
||||||
{
|
|
||||||
//the chain is broken; I don't exist in this channel
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (RestrictedToChannels.FirstOrDefault(c => c.Id == walker.Id) != null)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(RestrictedToAccounts?.Count() > 0)
|
|
||||||
{
|
|
||||||
//walker is the "actual" restricted-to channel, but we're inheriting
|
|
||||||
if(walker.Users.FirstOrDefault(a => a.Id == message.Author.Id) == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(RestrictedToChannels.FirstOrDefault(c => c.Id == message.Channel.Id) == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(RestrictedToAccounts?.Count() > 0)
|
|
||||||
{
|
|
||||||
if(RestrictedToAccounts.FirstOrDefault(a => a.Id == message.Author.Id) == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//if I got all the way down here, I must be good
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ namespace vassago.Models;
|
|||||||
using System;
|
using System;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
public class ChannelPermissions
|
public class PermissionSettings
|
||||||
{
|
{
|
||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
@ -13,7 +13,6 @@ public class ChannelPermissions
|
|||||||
public bool? ReactionsPossible { get; set; }
|
public bool? ReactionsPossible { get; set; }
|
||||||
public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; }
|
public Enumerations.LewdnessFilterLevel? LewdnessFilterLevel { get; set; }
|
||||||
public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; }
|
public Enumerations.MeannessFilterLevel? MeannessFilterLevel { get; set; }
|
||||||
public Enumerations.VerbosityFilterLevel? VerbosityFilterLevel { get; set; }
|
|
||||||
|
|
||||||
internal DefinitePermissionSettings Definite()
|
internal DefinitePermissionSettings Definite()
|
||||||
{
|
{
|
||||||
@ -24,7 +23,6 @@ public class ChannelPermissions
|
|||||||
LinksAllowed = this.LinksAllowed ?? false,
|
LinksAllowed = this.LinksAllowed ?? false,
|
||||||
LewdnessFilterLevel = this.LewdnessFilterLevel ?? Enumerations.LewdnessFilterLevel.G,
|
LewdnessFilterLevel = this.LewdnessFilterLevel ?? Enumerations.LewdnessFilterLevel.G,
|
||||||
MeannessFilterLevel = this.MeannessFilterLevel ?? Enumerations.MeannessFilterLevel.Strict,
|
MeannessFilterLevel = this.MeannessFilterLevel ?? Enumerations.MeannessFilterLevel.Strict,
|
||||||
VerbosityFilterLevel = this.VerbosityFilterLevel ?? Enumerations.VerbosityFilterLevel.Pithy,
|
|
||||||
ReactionsPossible = this.ReactionsPossible ?? false
|
ReactionsPossible = this.ReactionsPossible ?? false
|
||||||
};
|
};
|
||||||
}
|
}
|
@ -10,16 +10,6 @@ public class User
|
|||||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
public List<Account> Accounts { get; set; }
|
public List<Account> Accounts { get; set; }
|
||||||
|
//permissions are per account-in-channel or per-user, and always propagate down. and since protocol will be a channel, I'll set the "is adam" permission on myself 1x/protocol.
|
||||||
public string DisplayName
|
public List<Enumerations.WellknownPermissions> PermissionTags{get;set;}
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return Accounts.Select(a => a.DisplayName).Distinct()
|
|
||||||
.MaxBy(distinctName =>
|
|
||||||
Accounts.Select(a => a.DisplayName)
|
|
||||||
.Where(selectedName => selectedName == distinctName).Count()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -58,7 +58,7 @@ public class DiscordInterface
|
|||||||
protocolAsChannel = new Channel()
|
protocolAsChannel = new Channel()
|
||||||
{
|
{
|
||||||
DisplayName = "discord (itself)",
|
DisplayName = "discord (itself)",
|
||||||
Permissions = new Models.ChannelPermissions()
|
Permissions = new PermissionSettings()
|
||||||
{
|
{
|
||||||
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Strict,
|
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Strict,
|
||||||
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.Moderate,
|
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.Moderate,
|
||||||
@ -291,7 +291,7 @@ public class DiscordInterface
|
|||||||
c.Protocol = protocolAsChannel.Protocol;
|
c.Protocol = protocolAsChannel.Protocol;
|
||||||
c.ParentChannel = protocolAsChannel;
|
c.ParentChannel = protocolAsChannel;
|
||||||
c.SubChannels = c.SubChannels ?? new List<Channel>();
|
c.SubChannels = c.SubChannels ?? new List<Channel>();
|
||||||
c.Permissions = c.Permissions ?? new Models.ChannelPermissions();
|
c.Permissions = c.Permissions ?? new PermissionSettings();
|
||||||
c.Permissions.MaxAttachmentBytes = channel.MaxUploadLimit;
|
c.Permissions.MaxAttachmentBytes = channel.MaxUploadLimit;
|
||||||
|
|
||||||
c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); };
|
c.SendMessage = (t) => { throw new InvalidOperationException($"channel {channel.Name} is guild; cannot accept text"); };
|
||||||
|
@ -38,7 +38,7 @@ public class TwitchInterface
|
|||||||
protocolAsChannel = new Channel()
|
protocolAsChannel = new Channel()
|
||||||
{
|
{
|
||||||
DisplayName = "twitch (itself)",
|
DisplayName = "twitch (itself)",
|
||||||
Permissions = new ChannelPermissions()
|
Permissions = new PermissionSettings()
|
||||||
{
|
{
|
||||||
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Medium,
|
MeannessFilterLevel = Enumerations.MeannessFilterLevel.Medium,
|
||||||
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.G,
|
LewdnessFilterLevel = Enumerations.LewdnessFilterLevel.G,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
@Html.DisplayNameFor(model => model.Id)
|
@Html.DisplayNameFor(model => model.Id)
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
name*
|
@Html.DisplayNameFor(model => model.PermissionTags)
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
number of associated accounts
|
number of associated accounts
|
||||||
@ -24,7 +24,7 @@
|
|||||||
@Html.DisplayFor(modelItem => item.Id)
|
@Html.DisplayFor(modelItem => item.Id)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@Html.DisplayFor(modelItem => item.DisplayName)
|
@Html.DisplayFor(modelItem => item.PermissionTags)
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@Html.DisplayFor(modelItem => item.Accounts.Count)x
|
@Html.DisplayFor(modelItem => item.Accounts.Count)x
|
||||||
|
@ -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.
|
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
|
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.
|
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 can’t even count.
|
I failed math so many times at school, I can’t even count.
|
||||||
When I was a child, I threw a boomerang, but it didn't come back. I live in constant fear.
|
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.
|
When life gives you melons, you might be dyslexic.
|
||||||
@ -40,6 +40,7 @@ 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.
|
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.
|
How do you make holy water? You boil the hell out of it.
|
||||||
My teachers told me I’d never amount to much because I procrastinate so much. I told them, “Just you wait!”
|
My teachers told me I’d 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.
|
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.
|
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.
|
The CEO of IKEA was elected Prime Minister in Sweden. He should have his cabinet together by the end of the weekend.
|
||||||
@ -75,4 +76,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.
|
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 do you call your friend who stands in a hole? Phil.
|
||||||
What happened when the bear swallowed a clock? He got ticks.
|
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.
|
Loading…
Reference in New Issue
Block a user