Skip to content

Commit

Permalink
rainbowshuffling and bottom encoding (#92)
Browse files Browse the repository at this point in the history
* add owo.toml to gitignore

* add owo.toml to gitignore

* add mensa cog

* mensaing

* mensa command update

* nyaaaa regex

* add stucafé handling

* small mensa cog edit

* add dockerfile

* small changes

* remove re from requirements

* nina, mvg apis, etc meow

* move setup.md contents to readme

* update default toml

* change ssl settings so it defaults to False

* increase duration for task.loop

* remove duplicate toml settings

* add mvg disruptions command

* add thumbnail to NINA warnings

* add mvg disruption levels

* disable mensa menu cache due to errors

* add more disruption classes

* add embeddable domains

* add mvg/nina toggle to settings

* change logging to debug

* remove evil tracking parameters

* change hug logic, add img src

* add context menu cogs + remove_tracking command

* add bottom encoder/decoder

* move purge command to admin cog

* create rainbowshuffle command
  • Loading branch information
fly-san authored Oct 31, 2023
1 parent bfc3133 commit 351b845
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 14 deletions.
20 changes: 20 additions & 0 deletions owobot/cogs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ def __init__(self, bot: Bot):

def cog_check(self, ctx):
return ctx.author.guild_permissions.administrator

@commands.hybrid_command()
async def purge(self, ctx, n):
n = int(n)
if n < 1 or n >= 50:
return

msg = await ctx.send(f"Deleting {n} message(s)...")
deleted = await ctx.channel.purge(limit=int(n), bulk=True, check=lambda m: m != msg and m != ctx.message)
await msg.edit(content=f'Sucessfully deleted {len(deleted)} message(s)')

@commands.hybrid_command()
async def add_embed_url(self, ctx, domain, new_domain):
query = ForceEmbed.insert(url=domain, new_url=new_domain).on_conflict("replace")
await common.try_exe_cute_query(ctx, query)

@commands.hybrid_command()
async def add_evil_parameter(self, ctx, domain, parameter_name):
query = EvilTrackingParameter.insert(url=domain, tracking_parameter=parameter_name).on_conflict("replace")
await common.try_exe_cute_query(ctx, query)

@commands.hybrid_command()
async def add_embed_url(self, ctx, domain, new_domain):
Expand Down
104 changes: 102 additions & 2 deletions owobot/cogs/rainbow.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,113 @@
from owobot.misc import common
from owobot.misc.database import RainbowGuild


def get_rgb_color(i, n):
return [int(255*x) for x in colorsys.hsv_to_rgb(Rainbow.wrap(0.15 + (1.0 / n) * i), 0.82, 0.98)]


async def role_members(ctx, x) -> int:
c = 0
for i, member, role in x:
if i >= len(x*2):
print(i, member, role)
await ctx.send("not enough roles grrr!")
break
ok = await role_user(member,role)
if ok: c+=1
return c

async def role_user(member, role) -> bool:
for r in member.roles:
if r == role:
continue
if "rainbowify" in r.name:
try:
await member.remove_roles(r, reason="rainbow")
except Exception as e:
print(member, role)
print("Error while removing role", e)
if not role in member.roles:
try:
await member.add_roles(role, reason="rainbow")
return True
except Exception as e:
print(member,role)
print("Error while adding role", e)
return False

class Rainbow(commands.Cog):
def __init__(self, bot):
self.bot = bot

async def cog_check(self, ctx):
return await common.is_owner(ctx)

@commands.hybrid_command()
@common.long_running_command
async def rainbowshuffle(self, ctx):
'''
Similar to `rainbowroles activate`, but with a much lower execution time and less ratelimits
(especially for an already rainbowified guild).
Instead of creating new roles, we reorder the existing roles and then match them with a member.
This means, if only one member needs new roles or has changed their nickname, we don't need to
recreate every role, we simply "shuffle" the roles up/down.
Under the hood this uses `get_rgb_color` to procedurally generate a color for a given index.
This means, if we don't have enough roles, we simply create them at the end of the existing roles!
Lastly, we can divide and conquer by splitting the member list into two, giving each to an asyncio task.
'''
await ctx.send(":rainbow: rainbowifying guild!!")
t0 = time.perf_counter()

roles = list(filter(lambda role: role.name.startswith("rainbowify"), ctx.guild.roles))
members = sorted(ctx.guild.members, key=lambda m: m.display_name.lower())

n_members = len(members)
n_roles = len(roles)

created_roles = 0

d = n_roles - n_members
if d < 0:
await ctx.send(f"We need to create more roles! Members: {n_members} Roles: {n_roles}\n:warning: Warning, this might take a while!")
for i in range(0,-d):
if i % 5 == 0: time.sleep(0.5)
rgb = get_rgb_color(n_roles+i, n_members)
role = await ctx.guild.create_role(
name=f"rainbowify_{n_roles+i}_{rgb}",
color=discord.Color.from_rgb(*rgb),
hoist=False,
)
roles.append(role)
created_roles += 1
elif d > 0:
await ctx.send(f"There are more roles than necessary. You might want to consider removing some. Members: {n_members} Roles: {n_roles}")

roles = sorted(roles, key=lambda t: int(t.name.split("_")[1]),reverse=random.randint(0,1))

if created_roles > 0: await ctx.send(f"Added {created_roles} new rainbow roles!")
await ctx.send("Done preprocessing. I will now assign the roles!")

n = len(roles) // 2
x = list(zip(range(0,len(roles)),members,roles,))

tasks = [
asyncio.create_task(role_members(ctx, x[:n])),
asyncio.create_task(role_members(ctx, x[n:])),
]

# Wait for all tasks to complete and retrieve their results
results = await asyncio.gather(*tasks)


# for id, rgb, role in roles:
# print(id, rgb, role)
# print(roles)
await ctx.send(f"rainbowification complete! ive roled {sum(results)} users. This took {time.perf_counter() - t0:.3f} seconds.")


@staticmethod
def wrap(val):
if val >= 1.0:
Expand Down Expand Up @@ -63,7 +163,7 @@ async def _activate(guild):
for (member, color, i) in zip(members, colors, range(0, num_colors)):
try:
role = await guild.create_role(
name=f"rainbowify_{color}",
name=f"rainbowify_{i}_{color}",
color=discord.Color.from_rgb(*color),
hoist=False,
)
Expand Down
31 changes: 19 additions & 12 deletions owobot/cogs/simple_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from discord.ext import commands
from discord import app_commands

from owobot.misc import common, owolib, interactions, discord_emoji
from owobot.misc import common, owolib, interactions, discord_emoji, bottom
from owobot.misc.database import EvilTrackingParameter, MediaDHash, ForceEmbed
from owobot.owobot import OwOBot

Expand Down Expand Up @@ -76,15 +76,12 @@ async def obamamedal(self, ctx):

@commands.hybrid_command()
async def owobamamedal(self, ctx):
print(self)
await ctx.send(
"https://cdn.discordapp.com/attachments/938102328282722345/939605208999264367/Unbenannt.png"
)

@commands.hybrid_command(aliases=["hewwo"])
async def hello(self, ctx):
print(self.x)

await ctx.send(random.choice(["Hello", "Hello handsome :)"]))

@commands.hybrid_command(aliases=["evewyone"])
Expand Down Expand Up @@ -127,15 +124,25 @@ async def slap(self, ctx, member: discord.Member):
async def steal(self, ctx, member: discord.Member):
await ctx.send(member.display_avatar.url)

@commands.hybrid_command(brief="Purges up to n : [1,50] messages")
async def purge(self, ctx, n):
n = int(n)
if n < 1 or n >= 50:
return
@commands.hybrid_command()
async def bottom(self, ctx, text):
result = ""
try:
result = bottom.to_bottom(text)
except Exception as e:
result = e
finally:
await ctx.send(result)

msg = await ctx.send(f"Deleting {n} message(s)...")
deleted = await ctx.channel.purge(limit=int(n), bulk=True, check=lambda m: m != msg and m != ctx.message)
await msg.edit(content=f'Sucessfully deleted {len(deleted)} message(s)')
@commands.hybrid_command()
async def unbottom(self, ctx, text):
result = ""
try:
result = bottom.from_bottom(text)
except Exception as e:
result = e
finally:
await ctx.send(result)

@commands.hybrid_command(brief="Pong is a table tennis–themed twitch arcade sports video game "
"featuring simple graphics.")
Expand Down
53 changes: 53 additions & 0 deletions owobot/misc/bottom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
'''
Originally implemented in https://github.com/bottom-software-foundation/bottom-py/blob/main/bottom.py
No licensing.
Defined in https://github.com/bottom-software-foundation/spec
'''

CHARACTER_VALUES = {
200: "🫂",
50: "💖",
10: "✨",
5: "🥺",
1: ",",
0: "❤️"
}

SECTION_SEPERATOR = '👉👈'


def to_bottom(text: str) -> str:
out = bytearray()

for char in text.encode():
while char != 0:
for value, emoji in CHARACTER_VALUES.items():
if char >= value:
char -= value
out += emoji.encode()
break

out += SECTION_SEPERATOR.encode()

return out.decode('utf-8')


def from_bottom(text: str) -> str:
out = bytearray()
text = text.strip().removesuffix(SECTION_SEPERATOR)

if not all(c in CHARACTER_VALUES.values() for c in text.replace(SECTION_SEPERATOR, '')):
raise TypeError(f'Invalid bottom text: {text}')

for char in text.split(SECTION_SEPERATOR):
rev_mapping = {v: k for k, v in CHARACTER_VALUES.items()}

sub = 0
for emoji in char:
sub += rev_mapping[emoji]

out += sub.to_bytes(1, 'big')

return out.decode()

0 comments on commit 351b845

Please sign in to comment.