diff --git a/.gitignore b/.gitignore index 3072b7b..c0492d5 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ nohup.out #database files db/* +!db/.keep dev_db.db prod_db.db priv/repo/structure.sql diff --git a/config/example_config.exs b/config/example_config.exs index ec0f5c5..61c1bc0 100644 --- a/config/example_config.exs +++ b/config/example_config.exs @@ -18,6 +18,9 @@ config :nostrum, :guilds ] +config :mnesia, + dir: ~c'./mnesia/dev' + config :logger, level: :info, backends: [ @@ -143,16 +146,4 @@ config :adjutant, }, style: 1 } - ], - # these can be strings for unicode emojis, or tupules with {name, id} or {name, id, animated} - # where name is the name of the emoji, id is the id of the emoji, and animated is a boolean - # for using custom emojis - troll_emojis: [ - "👿", - "🍆", - "🤡", - "🔥", - "💀", - "🇹🇩", - "🗿" ] diff --git a/config/test.exs b/config/test.exs index 8daa8e7..837d218 100644 --- a/config/test.exs +++ b/config/test.exs @@ -133,16 +133,4 @@ config :adjutant, }, style: 1 } - ], - # these can be strings for unicode emojis, or tupules with {name, id} or {name, id, animated} - # where name is the name of the emoji, id is the id of the emoji, and animated is a boolean - # for using custom emojis - troll_emojis: [ - "👿", - "🍆", - "🤡", - "🔥", - "💀", - "🇹🇩", - "🗿" ] diff --git a/db/.keep b/db/.keep new file mode 100644 index 0000000..e69de29 diff --git a/lib/adjutant/application.ex b/lib/adjutant/application.ex index 7afb63a..9d5a48c 100644 --- a/lib/adjutant/application.ex +++ b/lib/adjutant/application.ex @@ -7,8 +7,8 @@ defmodule Adjutant.Application do def start(_type, _args) do children = [ - Adjutant.BotSupervisor, - Adjutant.Repo.Supervisor + Adjutant.Repo.Supervisor, + Adjutant.BotSupervisor ] Supervisor.start_link(children, strategy: :one_for_one) diff --git a/lib/adjutant/command.ex b/lib/adjutant/command.ex index bf0b001..8abe0c1 100644 --- a/lib/adjutant/command.ex +++ b/lib/adjutant/command.ex @@ -22,8 +22,6 @@ defmodule Adjutant.Command do Adjutant.Command.Slash.Ping, Adjutant.Command.Slash.Shuffle, Adjutant.Command.Slash.Hidden, - Adjutant.Command.Slash.Insults, - Adjutant.Command.Slash.PsychoEffects, Adjutant.Command.Slash.RemindMe, Adjutant.Command.Slash.HOTG.Team ] diff --git a/lib/adjutant/command/slash/insults.ex b/lib/adjutant/command/slash/insults.ex deleted file mode 100644 index a92b8c3..0000000 --- a/lib/adjutant/command/slash/insults.ex +++ /dev/null @@ -1,229 +0,0 @@ -defmodule Adjutant.Command.Slash.Insults do - @moduledoc """ - Module that holds all functions used for modifying "troll" effects. - """ - - alias Nostrum.Api - - require Logger - - @owner_cmd_scope Application.compile_env!(:adjutant, :owner_command_scope) - - use Adjutant.Command.Slash, permissions: :owner, scope: @owner_cmd_scope - - @impl true - def get_create_map do - %{ - type: 1, - name: "insults", - description: "Modify the troll effects.", - options: [ - %{ - type: 1, - name: "add", - description: "Add a new insult.", - options: [ - %{ - type: 3, - name: "insult", - description: "The insult to add.", - required: true - } - ] - }, - %{ - type: 1, - name: "remove", - description: "Remove an insult.", - options: [ - %{ - type: 4, - name: "id", - description: "The id of the insult to remove.", - required: true - } - ] - }, - %{ - type: 1, - name: "list", - description: "List all insults." - }, - %{ - type: 1, - name: "edit", - description: "Edit an insult.", - options: [ - %{ - type: 4, - name: "id", - description: "The id of the insult to edit.", - required: true - }, - %{ - type: 3, - name: "insult", - description: "The new insult.", - required: true - } - ] - } - ] - } - end - - @impl true - def call_slash(%Nostrum.Struct.Interaction{type: 2} = inter) do - [sub_cmd] = inter.data.options - - case sub_cmd.name do - "add" -> - [opt] = sub_cmd.options - insult = opt.value - add_insult(inter, insult) - - "remove" -> - [opt] = sub_cmd.options - id = opt.value - remove_insult(inter, id) - - "list" -> - list_insults(inter) - - "edit" -> - [id_opt, insult_opt] = sub_cmd.options - id = id_opt.value - insult = insult_opt.value - edit_insult(inter, id, insult) - end - - :ignore - end - - defp add_insult(inter, insult) do - new_insult = Adjutant.PsychoEffects.Insults.add_new!(insult) - - Api.create_interaction_response!(inter, %{ - type: 4, - data: %{ - content: "Insult added, with id: #{new_insult.id}" - } - }) - end - - defp remove_insult(inter, id) do - insult = Adjutant.PsychoEffects.Insults.get_by_id(id) - - uuid = - System.unique_integer([:positive]) - # constrain to be between 0 and 0xFF_FF_FF - |> Bitwise.band(0xFF_FF_FF) - - buttons = Adjutant.ButtonAwait.make_yes_no_buttons(uuid) - - Api.create_interaction_response!(inter, %{ - type: 4, - data: %{ - content: "Are you sure you want to remove the insult:\n\n#{insult.insult}", - components: buttons - } - }) - - btn_response = Adjutant.ButtonAwait.await_btn_click(uuid, nil) - - case btn_response do - {btn_inter, "yes"} -> - Adjutant.PsychoEffects.Insults.delete(insult) - edit_btn_response(btn_inter, "Insult removed.") - - {btn_inter, "no"} -> - edit_btn_response(btn_inter, "Insult not removed.") - - nil -> - Api.edit_interaction_response(inter, %{ - content: "Timed out waiting for a response.", - components: [] - }) - end - end - - defp list_insults(inter) do - content = - Adjutant.PsychoEffects.Insults.get_all() - |> Enum.map(fn insult -> "`#{insult.id}`: #{insult.insult}" end) - |> Enum.intersperse("\n\n") - - content_size = IO.iodata_length(content) - - mesage_count = ceil(content_size / 1900) - - if mesage_count == 1 do - Api.create_interaction_response!(inter, %{ - type: 4, - data: %{ - content: IO.iodata_to_binary(content) - } - }) - else - Api.create_interaction_response!(inter, %{ - type: 5 - }) - - chunk_size = ceil(length(content) / mesage_count) - - Stream.chunk_every(content, chunk_size) - |> Enum.each(fn chunk -> - Api.create_followup_message!(inter.token, %{ - content: IO.iodata_to_binary(chunk) - }) - end) - end - end - - defp edit_insult(inter, id, insult) do - old_insult = Adjutant.PsychoEffects.Insults.get_by_id(id) - - uuid = - System.unique_integer([:positive]) - # constrain to be between 0 and 0xFF_FF_FF - |> Bitwise.band(0xFF_FF_FF) - - buttons = Adjutant.ButtonAwait.make_yes_no_buttons(uuid) - - Api.create_interaction_response!(inter, %{ - type: 4, - data: %{ - content: - "Are you sure you want to edit the insult:\n\n#{old_insult.insult}\n\nTo:\n\n#{insult}", - components: buttons - } - }) - - btn_response = Adjutant.ButtonAwait.await_btn_click(uuid, nil) - - case btn_response do - {btn_inter, "yes"} -> - Adjutant.PsychoEffects.Insults.update!(old_insult, insult) - edit_btn_response(btn_inter, "Insult edited.") - - {btn_inter, "no"} -> - edit_btn_response(btn_inter, "Insult not edited.") - - nil -> - Api.edit_interaction_response(inter, %{ - content: "Timed out waiting for a response.", - components: [] - }) - end - end - - defp edit_btn_response(btn_inter, msg) do - Api.create_interaction_response(btn_inter, %{ - type: 7, - data: %{ - content: msg, - components: [] - } - }) - end -end diff --git a/lib/adjutant/command/slash/psycho_effects.ex b/lib/adjutant/command/slash/psycho_effects.ex deleted file mode 100644 index 4232128..0000000 --- a/lib/adjutant/command/slash/psycho_effects.ex +++ /dev/null @@ -1,127 +0,0 @@ -defmodule Adjutant.Command.Slash.PsychoEffects do - @moduledoc """ - Command for denying the psycho effects in a channel. - - Note: This only means that the phrase "resolve all psycho effects" will not - work in the channel. It does not mean that the psycho effects will not be - applied to the channel. - """ - - alias Nostrum.Api - alias Nostrum.Struct.ApplicationCommandInteractionDataOption, as: Option - alias Nostrum.Struct.Interaction - - require Logger - - @cmd_scope Application.compile_env!(:adjutant, :primary_guild_id) - - use Adjutant.Command.Slash, permissions: [:admin, :owner], scope: @cmd_scope - - @impl true - def get_create_map do - perms_string = - :manage_channels - |> Nostrum.Permission.to_bit() - |> to_string() - - %{ - type: 1, - name: "psycho-effects", - description: "Allow or deny the psycho effects in a channel.", - default_member_permissions: perms_string, - options: [ - %{ - type: 1, - name: "deny", - description: "Deny \"resolve all psycho effects\" in a channel.", - options: [ - %{ - type: 7, - name: "channel", - description: "The channel to deny the psycho effects in.", - channel_types: [0], - required: true - } - ] - }, - %{ - type: 1, - name: "allow", - description: "Allow \"resolve all psycho effects\" in a channel.", - options: [ - %{ - type: 7, - name: "channel", - description: "The channel to allow the psycho effects in.", - channel_types: [0], - required: true - } - ] - } - ] - } - end - - @impl true - def call_slash(%Interaction{type: 2} = inter) do - Logger.debug("Psycho effects command called, #{inspect(inter)}") - - case inter.data.options do - [%Option{name: "allow"} | _] -> - allow(inter) - - [%Option{name: "deny"} | _] -> - deny(inter) - - _ -> - Api.create_interaction_response(inter, %{ - type: 4, - data: %{ - content: "This command is not meant to be used directly. Please use the subcommands.", - flags: 64 - } - }) - end - end - - defp allow(inter) do - {channel_id, _channel_data} = - inter.data.resolved.channels - |> Map.to_list() - |> List.first() - - Adjutant.PsychoEffects.Channel.allow_channel(channel_id) - - Api.create_interaction_response(inter, %{ - type: 4, - data: %{ - content: "Psycho effects allowed in channel <##{channel_id}>.", - flags: 64 - } - }) - end - - def deny(inter) do - {channel_id, channel_data} = - inter.data.resolved.channels - |> Map.to_list() - |> List.first() - - guild_id = channel_data.guild_id - user_id = inter.member.user_id - - Adjutant.PsychoEffects.Channel.deny_channel(%{ - id: channel_id, - guild_id: guild_id, - set_by: user_id - }) - - Api.create_interaction_response(inter, %{ - type: 4, - data: %{ - content: "Psycho effects denied in channel <##{channel_id}>.", - flags: 64 - } - }) - end -end diff --git a/lib/adjutant/consumer.ex b/lib/adjutant/consumer.ex index 6e79dbb..5f58912 100644 --- a/lib/adjutant/consumer.ex +++ b/lib/adjutant/consumer.ex @@ -36,20 +36,25 @@ defmodule Adjutant.Consumer do end def handle_event({:MESSAGE_CREATE, %Message{} = msg, _ws_state}) do - Task.start(fn -> - try do - Adjutant.PsychoEffects.maybe_resolve_random_effect(msg) - Adjutant.PsychoEffects.maybe_resolve_user_effect(msg) - rescue - e -> - Logger.error(Exception.format(:error, e, __STACKTRACE__)) - Adjutant.Util.dm_owner("An error has occurred", true) - end - end) - Adjutant.Command.dispatch(msg) end + def handle_event({:MESSAGE_UPDATE, {nil, new_msg}, _ws_state}) do + Logger.debug("Got a message update event and the old was not cached\n#{inspect(new_msg)}") + end + + def handle_event({:MESSAGE_UPDATE, {old_msg, new_msg}, _ws_state}) do + Logger.debug("Got a message update event\n#{inspect(old_msg)}\n#{inspect(new_msg)}") + end + + def handle_event({:MESSAGE_DELETE, %{deleted_message: nil}, _ws_state}) do + Logger.debug("Got a message delete event and the message was not cached") + end + + def handle_event({:MESSAGE_DELETE, %{deleted_message: msg}, _ws_state}) do + Logger.debug("Got a message delete event and the message was cached\n#{inspect(msg)}") + end + def handle_event({:GUILD_MEMBER_ADD, {@primary_guild_id, %Guild.Member{} = member}, _ws_state}) do text = "Welcome to the Busters & Battlechips Discord <@#{member.user_id}>. \ Assign yourself roles in <##{@primary_guild_role_channel_id}>" @@ -109,9 +114,6 @@ defmodule Adjutant.Consumer do id = String.to_integer(id, 16) Adjutant.ButtonAwait.resp_to_btn(inter, id, yn) - <> when kind in [?c, ?n, ?v] -> - Adjutant.ButtonAwait.resp_to_persistent_btn(inter, kind, name) - <<"r_", id::binary>> -> Adjutant.RoleBtn.handle_role_btn_click(inter, id) diff --git a/lib/adjutant/psycho_effects.ex b/lib/adjutant/psycho_effects.ex deleted file mode 100644 index 47f2218..0000000 --- a/lib/adjutant/psycho_effects.ex +++ /dev/null @@ -1,472 +0,0 @@ -defmodule Adjutant.PsychoEffects do - @moduledoc """ - Module that holds all functions used for "troll" effects. - """ - @random_effects [ - :resolve_role_effect, - :resolve_timeout_effect, - :resolve_shadowban_effect, - :resolve_disconnect_effect, - :resolve_troll_effect, - :resolve_react_effect - ] - - # If the user isn't in a voice channel, disconnect doesn't make sense - # so don't include it in the list of possible random effects - @non_special_random_effects @random_effects -- [:resolve_disconnect_effect] - - # If the user is the guild owner, don't include effects that would - # not work on them. Note: also removing disconnect effect because - # the ability to do that should be checked before if the user is - # the guild owner - @guild_owner_effects @random_effects -- - [ - :resolve_disconnect_effect, - :resolve_timeout_effect, - :resolve_role_effect - ] - - @troll_emojis Application.compile_env!(:adjutant, :troll_emojis) - - @primary_guild_id Application.compile_env!(:adjutant, :primary_guild_id) - - @atomic_ref_key {__MODULE__, :psycho_effects_ref} - - alias Nostrum.Api - alias Nostrum.Snowflake - alias Nostrum.Struct.{Emoji, Message} - - import Adjutant.Util, only: [admin_msg?: 1, owner_msg?: 1] - - require Logger - - # Slot 1 holds the unix time in seconds - # of the last time a random effect was resolved - @last_psycho_effect_time_key 1 - - # Slot 2 holds a counter for the number of messages - # that have been sent since the last effect was resolved - @last_psycho_effect_counter_key 2 - - # Slot 3 holds the id of the user who got the effect - @last_user_afflicted_key 3 - - # Slot 4 holds the enum of the effect for the effected user - # if the effect is one that happens over time - @over_time_effect_key 4 - - # How big the array needs to be - @atomic_slot_count 4 - - @over_time_effect_none 0 - @over_time_effect_shadowban 1 - @over_time_effect_troll 2 - @over_time_effect_react 3 - - @spec maybe_resolve_random_effect(Message.t()) :: :ignore - def maybe_resolve_random_effect(%Message{} = msg) do - atomic_ref = get_atomic_ref() - - if should_resolve?(msg, atomic_ref) do - Logger.info("Resolving random effect") - resolve_random_effect(msg, atomic_ref) - end - - :ignore - end - - @spec should_resolve?(Message.t(), :atomics.atomics_ref()) :: boolean() - def should_resolve?(%Message{} = msg, ref) do - Logger.debug("Checking if we should resolve a random effect") - - %{ - guild_id: guild_id, - author: %{id: user_id}, - member: %{roles: roles}, - channel_id: channel_id - } = msg - - # admins and owners can always be psychoed - can_be_psychoed = - guild_id == @primary_guild_id and - (Enum.member?(roles, ranger_role_id()) or admin_msg?(msg) or owner_msg?(msg)) and - Adjutant.PsychoEffects.Channel.psychoable_channel?(channel_id) - - Logger.debug(["User is able to be psychoed: ", inspect(can_be_psychoed)]) - - res = can_be_psychoed and :rand.uniform(sliding_probability(user_id, ref)) == 1 - - Logger.debug(["Should resolve random effect: ", to_string(res)]) - - if res do - # reset the counter - :atomics.put(ref, @last_psycho_effect_counter_key, 0) - end - - res - rescue - e -> - Logger.error(["Error in should_resolve?(): ", Exception.format(:error, e, __STACKTRACE__)]) - false - end - - @spec maybe_resolve_user_effect(Message.t()) :: any() - def maybe_resolve_user_effect(%Message{} = msg) do - ref = get_atomic_ref() - - Logger.debug("Checking if we should resolve a user effect") - - over_time_effect = :atomics.get(ref, @over_time_effect_key) - - if over_time_effect == @over_time_effect_none do - Logger.debug("No over time effect to resolve, halting") - throw(:halt) - end - - Logger.debug("Over time effect key is #{over_time_effect}") - - %{id: message_id, channel_id: channel_id, author: %{id: user_id}} = msg - - last_user = :atomics.get(ref, @last_user_afflicted_key) - - unless last_user == user_id do - Logger.debug("User is not the last user afflicted, halting") - throw(:halt) - end - - last_date_unix_seconds = :atomics.get(ref, @last_psycho_effect_time_key) - now = System.os_time(:second) - - case over_time_effect do - @over_time_effect_shadowban when now - last_date_unix_seconds <= 5 * 60 -> - Logger.debug("Attempting to resolve shadowban effect") - - Task.start(fn -> shadowban(channel_id, message_id) end) - - @over_time_effect_troll when now - last_date_unix_seconds <= 10 * 60 -> - Logger.debug("Attempting to resolve troll effect") - - Task.start(fn -> troll(channel_id, message_id) end) - - @over_time_effect_react when now - last_date_unix_seconds <= 15 * 60 -> - Logger.debug("Attempting to resolve react effect") - - Task.start(fn -> react(channel_id, message_id) end) - - _ -> - # shouldn't resolve anything - # attempt to set the effect to none to speed up the next check - Logger.debug("Over time effect expired, attempting to set to none") - - :atomics.compare_exchange( - ref, - @over_time_effect_key, - over_time_effect, - @over_time_effect_none - ) - end - catch - :halt -> - :ok - end - - @spec resolve_random_effect(Message.t(), :atomics.atomics_ref()) :: :ok - def resolve_random_effect(%Message{} = msg, ref) do - Logger.debug("Selecting random effect") - - %{ - channel_id: channel_id, - id: msg_id, - author: %{id: author_id, username: author_username} - } = msg - - now = System.os_time(:second) - effect = select_random_effect(msg) - - Logger.debug("Selected psycho effect: #{effect}") - - effect_enum_val = - case effect do - :resolve_shadowban_effect -> @over_time_effect_shadowban - :resolve_troll_effect -> @over_time_effect_troll - :resolve_react_effect -> @over_time_effect_react - _ -> @over_time_effect_none - end - - :atomics.put(ref, @last_psycho_effect_time_key, now) - :atomics.put(ref, @last_user_afflicted_key, author_id) - :atomics.put(ref, @over_time_effect_key, effect_enum_val) - - Logger.debug("Sending message about resolving effect") - - Api.create_message!(channel_id, %{ - content: "Resolve all psycho effects!", - message_reference: %{ - message_id: msg_id - } - }) - - Logger.notice("Resolving random effect: #{effect} on #{author_username}") - - apply(__MODULE__, effect, [msg, ref]) - :ok - rescue - e -> - Logger.error(Exception.format(:error, e, __STACKTRACE__)) - - Api.create_message( - msg.channel_id, - "An error has occurred, inform Major\n#{Exception.message(e)}" - ) - - :ok - end - - defp select_random_effect(msg) do - %{owner_id: owner_id, voice_states: voice_states} = - Nostrum.Cache.GuildCache.get!(msg.guild_id) - - is_guild_owner = msg.author.id == owner_id - - is_in_voice_channel = - Enum.any?(voice_states, fn %{user_id: user_id} -> user_id == msg.author.id end) - - cond do - is_in_voice_channel -> - :resolve_disconnect_effect - - is_guild_owner -> - Enum.random(@guild_owner_effects) - - true -> - Enum.random(@non_special_random_effects) - end - end - - def resolve_role_effect(%Message{} = msg, _ref) do - roles = Application.get_env(:adjutant, :roles) - role_ids = Enum.map(roles, fn role -> role.id end) - - Enum.each(role_ids, fn role_id -> - Api.remove_guild_member_role( - msg.guild_id, - msg.author.id, - role_id, - "Resolve all psycho effects!" - ) - end) - end - - def resolve_timeout_effect(%Message{} = msg, ref) do - guild_id = msg.guild_id - user_id = msg.author.id - - muted_until = - DateTime.utc_now() - |> DateTime.add(10 * 60) - |> DateTime.to_iso8601() - - Api.modify_guild_member( - guild_id, - user_id, - %{communication_disabled_until: muted_until}, - "Resolve all psycho effects!" - ) - |> case do - {:ok, _} -> - :ok - - {:error, reason} -> - :atomics.put(ref, @over_time_effect_key, @over_time_effect_shadowban) - Logger.info("Failed to mute user: #{inspect(reason)}, shadow banning instead") - end - end - - # This is a no-op since this is an async effect - def resolve_shadowban_effect(_msg, _ref) do - :ok - end - - # This is a no-op since this is an async effect - def resolve_troll_effect(_msg, _ref) do - :ok - end - - # This is a no-op since this is an async effect - def resolve_react_effect(_msg, _ref) do - :ok - end - - def resolve_disconnect_effect(%Message{} = msg, _ref) do - voice_states = Nostrum.Cache.GuildCache.get!(msg.guild_id).voice_states - - # abuse the fact that we know the inner workings of nostrum here - Enum.reduce(voice_states, :gen_statem.reqids_new(), fn %{user_id: user_id}, acc -> - req_map = %{ - method: :patch, - route: Nostrum.Constants.guild_member(msg.guild_id, user_id), - body: %{ - channel_id: nil - }, - params: [], - headers: [ - {"x-audit-log-reason", "Resolve all psycho effects!"}, - {"content-type", "application/json"} - ] - } - - req = :gen_statem.send_request(Nostrum.Api.Ratelimiter, {:queue, req_map}) - :gen_statem.reqids_add(req, "Disconnect #{user_id}", acc) - end) - |> await_api_responses() - end - - def shadowban(channel_id, message_id) do - Api.delete_message(channel_id, message_id) - end - - def troll(channel_id, message_id) do - if :rand.uniform(2) == 1 do - troll_msg = Adjutant.PsychoEffects.Insults.get_random() - - Api.create_message(channel_id, %{ - content: troll_msg.insult, - message_reference: %{ - message_id: message_id - } - }) - end - end - - def react(channel_id, message_id) do - requests = - Enum.map(@troll_emojis, &map_emojis(channel_id, message_id, &1)) - |> Enum.shuffle() - |> Enum.reduce(:gen_statem.reqids_new(), fn req_map, acc -> - req = :gen_statem.send_request(Nostrum.Api.Ratelimiter, {:queue, req_map}) - :gen_statem.reqids_add(req, req_map.route, acc) - end) - - await_api_responses(requests) - end - - defp await_api_responses(request_list) do - :gen_statem.wait_response(request_list, :infinity, true) - |> case do - {{:reply, resp}, label, new_list} -> - Logger.debug("Got response: #{inspect(resp)} for request: #{inspect(label)}") - await_api_responses(new_list) - - {{:error, {reason, _s_ref}}, label, new_list} -> - Logger.error("Got error: #{inspect(reason)} for request: #{inspect(label)}") - await_api_responses(new_list) - - :no_request -> - Logger.info("No more requests") - :ok - end - end - - defp map_emojis(channel_id, message_id, emoji) do - # gonna abuse the fact that we know the inner workings of nostrum here too - - emoji = - case emoji do - {name, id} -> - Emoji.api_name(%Emoji{ - name: name, - id: id - }) - - {name, id, animated} -> - Emoji.api_name(%Emoji{ - name: name, - id: id, - animated: animated - }) - - emoji when is_binary(emoji) -> - emoji - end - - route = Nostrum.Constants.channel_reaction_me(channel_id, message_id, emoji) - - %{ - method: :put, - route: route, - body: "", - params: [], - headers: [{"content-type", "application/json"}] - } - end - - @spec sliding_probability(Snowflake.t(), :atomics.atomics_ref()) :: non_neg_integer() - defp sliding_probability(author_id, ref) do - Logger.debug("Checking if #{author_id} has been psycho'd recently") - - now = System.os_time(:second) - - last_date_unix_seconds = - :atomics.get(ref, @last_psycho_effect_time_key) - |> case do - 0 -> - Logger.debug("Last psycho effect time was 0, setting to an hour ago") - - assumed_default = now - 60 * 60 - :atomics.compare_exchange(ref, @last_psycho_effect_time_key, 0, assumed_default) - assumed_default - - val -> - val - end - - Logger.debug("Last psycho effect time in unix seconds: #{last_date_unix_seconds}") - - last_user = :atomics.get(ref, @last_user_afflicted_key) - - Logger.debug("Last user afflicted: #{last_user}") - - counter = :atomics.add_get(ref, @last_psycho_effect_counter_key, 1) - - Logger.debug("Counter: #{counter}") - - probability = - cond do - # author is the same as last user and it's been less than 2 hours - author_id == last_user and last_date_unix_seconds + 60 * 60 * 2 >= now -> - 500_000 - counter + :rand.uniform(42) - - # its been less than 20 minutes - last_date_unix_seconds + 60 * 20 >= now -> - 300_000 - counter + :rand.uniform(42) - - true -> - diff = now - last_date_unix_seconds - # number of seconds in three days less the difference - # since the last psycho effect - # minimum of 100 - random number between 1 and 42 + 1 - - max(60 * 60 * 24 * 3 - diff, 100 - :rand.uniform(42) + 1) - end - - Logger.debug("Determined probability: 1 in #{probability}") - probability - end - - defp get_atomic_ref do - case :persistent_term.get(@atomic_ref_key, nil) do - nil -> - Logger.debug("Creating new atomic ref") - ref = :atomics.new(@atomic_slot_count, signed: false) - :persistent_term.put(@atomic_ref_key, ref) - ref - - ref -> - ref - end - end - - defp ranger_role_id do - Application.get_env(:adjutant, :ranger_role_id) - end -end diff --git a/lib/adjutant/psycho_effects/channel.ex b/lib/adjutant/psycho_effects/channel.ex deleted file mode 100644 index cda5ea1..0000000 --- a/lib/adjutant/psycho_effects/channel.ex +++ /dev/null @@ -1,49 +0,0 @@ -defmodule Adjutant.PsychoEffects.Channel do - @moduledoc """ - Defines the schema for which channels a user can trigger - the phrase "resolve all psycho effects" in. - """ - - use Ecto.Schema - - require Logger - - import Ecto.Query - - # has implicit :id field - # which will be the id of a channel - # in which this cannot be triggered - schema "psycho_effect_channel" do - field :guild_id, :integer - field :set_by, :integer - timestamps() - end - - def deny_channel(args) do - Ecto.Changeset.cast( - %__MODULE__{}, - args, - [:id, :guild_id, :set_by] - ) - |> Ecto.Changeset.validate_required([:guild_id, :set_by]) - |> Adjutant.Repo.SQLite.insert(on_conflict: :nothing) - end - - def allow_channel(channel_id) do - # channel_ids are unique, so we don't need to worry about guild_id - from(c in __MODULE__, where: c.id == ^channel_id) - |> Adjutant.Repo.SQLite.delete_all() - end - - def psychoable_channel?(channel_id) do - exists = - from( - c in __MODULE__, - where: c.id == ^channel_id - ) - |> Adjutant.Repo.SQLite.exists?() - - # if it doesn't exist, it's psychoable - not exists - end -end diff --git a/lib/adjutant/psycho_effects/insults.ex b/lib/adjutant/psycho_effects/insults.ex deleted file mode 100644 index 931d1d6..0000000 --- a/lib/adjutant/psycho_effects/insults.ex +++ /dev/null @@ -1,57 +0,0 @@ -defmodule Adjutant.PsychoEffects.Insults do - @moduledoc """ - Defines the troll insults that the bot can use. - """ - use Ecto.Schema - import Ecto.Query - - @type t :: %__MODULE__{ - id: integer(), - insult: String.t(), - inserted_at: NaiveDateTime.t() - } - - schema "random_insults" do - field :insult, :string - timestamps(updated_at: false) - end - - @spec add_new!(String.t()) :: __MODULE__.t() | no_return() - def add_new!(insult) do - %__MODULE__{insult: insult} - |> Adjutant.Repo.SQLite.insert!() - end - - @spec get_by_id(integer()) :: __MODULE__.t() | nil - def get_by_id(id) do - Adjutant.Repo.SQLite.get(__MODULE__, id) - end - - @spec delete(t()) :: {:ok, t()} | {:error, Ecto.Changeset.t()} - def delete(insult) do - Adjutant.Repo.SQLite.delete(insult) - end - - def get_all do - Adjutant.Repo.SQLite.all(__MODULE__) - end - - def update!(insult, new_text) do - Ecto.Changeset.cast(insult, %{insult: new_text}, [:insult]) - |> Adjutant.Repo.SQLite.update!() - end - - @doc """ - Returns a random insult. - - Raises if the table is empty. - """ - @spec get_random() :: __MODULE__.t() | no_return() - def get_random do - from(i in __MODULE__, - limit: 1, - order_by: fragment("RANDOM()") - ) - |> Adjutant.Repo.SQLite.one!() - end -end diff --git a/lib/adjutant/react_btn.ex b/lib/adjutant/react_btn.ex index c56f97b..18896f0 100644 --- a/lib/adjutant/react_btn.ex +++ b/lib/adjutant/react_btn.ex @@ -3,87 +3,9 @@ defmodule Adjutant.ButtonAwait do Module for creating buttons that wait for a response from the user. """ - alias Adjutant.Library.{Battlechip, NCP, Virus} alias Nostrum.Struct.Component.{ActionRow, Button} require Logger - # alias Nostrum.Api - - @doc """ - Creates a list of ActionRows on the input by calling `Adjutant.Library.LibObj.to_btn/2` - on each list item. - - Raises if there are more than 25 buttons or if the list is empty - """ - @spec generate_msg_buttons([struct()], boolean()) :: [ActionRow.t()] | no_return() - def generate_msg_buttons(buttons, disabled \\ false) - - def generate_msg_buttons([], _disabled) do - raise "Empty List" - end - - def generate_msg_buttons(content, _disabled) when length(content) > 25 do - raise "Too many buttons" - end - - def generate_msg_buttons(content, disabled) do - row_chunks = Enum.chunk_every(content, 5) - - Enum.map(row_chunks, fn row -> - Enum.map(row, &Adjutant.Library.LibObj.to_btn(&1, disabled)) |> ActionRow.action_row() - end) - end - - @doc """ - Creates a list of ActionRows on the input by calling `Adjutant.Library.LibObj.to_btn/3` - on each list item. - - Expects an integer uuid to associate with the buttons which must be within the range 0 and 16,777,215 (0xFF_FF_FF) - - Raises if there are more than 25 buttons, if the list is empty, or if the uuid is out of range - """ - @spec generate_msg_buttons_with_uuid([struct()], boolean(), pos_integer()) :: - [ActionRow.t()] | no_return() - def generate_msg_buttons_with_uuid(buttons, disabled \\ false, uuid) - - def generate_msg_buttons_with_uuid([], _disabled, _uuid) do - raise "Empty List" - end - - def generate_msg_buttons_with_uuid(content, _disabled, _uuid) when length(content) > 25 do - raise "Too many buttons" - end - - def generate_msg_buttons_with_uuid(content, disabled, uuid) when uuid in 0..0xFF_FF_FF do - # uuid = System.unique_integer([:positive]) |> rem(1000) - row_chunks = Enum.chunk_every(content, 5) - - Enum.map(row_chunks, fn row -> - Enum.map(row, &Adjutant.Library.LibObj.to_btn_with_uuid(&1, disabled, uuid)) - |> ActionRow.action_row() - end) - end - - @spec generate_persistent_buttons([struct()], boolean()) :: - [ActionRow.t()] | no_return() - def generate_persistent_buttons(buttons, disabled \\ false) - - def generate_persistent_buttons([], _disabled) do - raise "Empty List" - end - - def generate_persistent_buttons(content, _disabled) when length(content) > 25 do - raise "Too many buttons" - end - - def generate_persistent_buttons(content, disabled) do - row_chunks = Enum.chunk_every(content, 5) - - Enum.map(row_chunks, fn row -> - Enum.map(row, &Adjutant.Library.LibObj.to_persistent_btn(&1, disabled)) - |> ActionRow.action_row() - end) - end def make_yes_no_buttons(uuid) when uuid in 0..0xFF_FF_FF do uuid_str = @@ -235,32 +157,6 @@ defmodule Adjutant.ButtonAwait do end end - def resp_to_persistent_btn(%Nostrum.Struct.Interaction{} = inter, kind, name) do - res = - case kind do - ?c -> Battlechip.get(name) - ?n -> NCP.get(name) - ?v -> Virus.get(name) - end - - resp = - if is_nil(res) do - "Seems that one couldn't be found, please inform Major" - else - to_string(res) - end - - Nostrum.Api.create_interaction_response!( - inter, - %{ - type: 4, - data: %{ - content: resp - } - } - ) - end - # default timeout is 30 seconds defp await_btn_click_inner(timeout) do receive do diff --git a/mix.exs b/mix.exs index 8dd4ee3..c9bce8e 100644 --- a/mix.exs +++ b/mix.exs @@ -31,10 +31,10 @@ defmodule ElixirBot.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:nostrum, - git: "https://github.com/Th3-M4jor/nostrum.git", - ref: "391d5dca4e678e6175ebf2f1837643f83dcfa988"}, - # {:nostrum, path: "../nostrum"}, + # {:nostrum, + # git: "https://github.com/Th3-M4jor/nostrum.git", + # ref: "aaa1df57714d514e37df2844c6660720de284e4a"}, + {:nostrum, path: "../nostrum"}, {:jason, "~> 1.2"}, {:ecto_sql, "~> 3.0"}, {:ecto_sqlite3, "~> 0.15"}, diff --git a/mix.lock b/mix.lock index 46b8ee9..9c86156 100644 --- a/mix.lock +++ b/mix.lock @@ -5,13 +5,13 @@ "certifi": {:hex, :certifi, "2.13.0", "e52be248590050b2dd33b0bb274b56678f9068e67805dca8aa8b1ccdb016bbf6", [:rebar3], [], "hexpm", "8f3d9533a0f06070afdfd5d596b32e21c6580667a492891851b0e2737bc507a1"}, "chacha20": {:hex, :chacha20, "1.0.4", "0359d8f9a32269271044c1b471d5cf69660c362a7c61a98f73a05ef0b5d9eb9e", [:mix], [], "hexpm", "2027f5d321ae9903f1f0da7f51b0635ad6b8819bc7fe397837930a2011bc2349"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "credo": {:hex, :credo, "1.7.5", "643213503b1c766ec0496d828c90c424471ea54da77c8a168c725686377b9545", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f799e9b5cd1891577d8c773d245668aa74a2fcd15eb277f51a0131690ebfb3fd"}, + "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, "curve25519": {:hex, :curve25519, "1.0.5", "f801179424e4012049fcfcfcda74ac04f65d0ffceeb80e7ef1d3352deb09f5bb", [:mix], [], "hexpm", "0fba3ad55bf1154d4d5fc3ae5fb91b912b77b13f0def6ccb3a5d58168ff4192d"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.2", "c7cc7f812af571e50b80294dc2e535821b3b795ce8008d07aa5f336591a185a8", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "73c07f995ac17dbf89d3cfaaf688fcefabcd18b7b004ac63b0dc4ef39499ed6b"}, "ecto_sqlite3": {:hex, :ecto_sqlite3, "0.15.1", "40f2fbd9e246455f8c42e7e0a77009ef806caa1b3ce6f717b2a0a80e8432fcfd", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.19", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "28b16e177123c688948357176662bf9ff9084daddf950ef5b6baf3ee93707064"}, "ed25519": {:hex, :ed25519, "1.4.1", "479fb83c3e31987c9cad780e6aeb8f2015fb5a482618cdf2a825c9aff809afc4", [:mix], [], "hexpm", "0dacb84f3faa3d8148e81019ca35f9d8dcee13232c32c9db5c2fb8ff48c80ec7"}, "elixir_make": {:hex, :elixir_make, "0.8.3", "d38d7ee1578d722d89b4d452a3e36bcfdc644c618f0d063b874661876e708683", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "5c99a18571a756d4af7a4d89ca75c28ac899e6103af6f223982f09ce44942cc9"}, @@ -24,7 +24,7 @@ "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "kcl": {:hex, :kcl, "1.4.2", "8b73a55a14899dc172fcb05a13a754ac171c8165c14f65043382d567922f44ab", [:mix], [{:curve25519, ">= 1.0.4", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 1.3", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 1.0", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 1.0", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm", "9f083dd3844d902df6834b258564a82b21a15eb9f6acdc98e8df0c10feeabf05"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "nostrum": {:git, "https://github.com/Th3-M4jor/nostrum.git", "391d5dca4e678e6175ebf2f1837643f83dcfa988", [ref: "391d5dca4e678e6175ebf2f1837643f83dcfa988"]}, + "nostrum": {:git, "https://github.com/Th3-M4jor/nostrum.git", "aaa1df57714d514e37df2844c6660720de284e4a", [ref: "aaa1df57714d514e37df2844c6660720de284e4a"]}, "oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"}, "poly1305": {:hex, :poly1305, "1.0.4", "7cdc8961a0a6e00a764835918cdb8ade868044026df8ef5d718708ea6cc06611", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "e14e684661a5195e149b3139db4a1693579d4659d65bba115a307529c47dbc3b"}, "salsa20": {:hex, :salsa20, "1.0.4", "404cbea1fa8e68a41bcc834c0a2571ac175580fec01cc38cc70c0fb9ffc87e9b", [:mix], [], "hexpm", "745ddcd8cfa563ddb0fd61e7ce48d5146279a2cf7834e1da8441b369fdc58ac6"}, diff --git a/mnesia/dev/.gitignore b/mnesia/dev/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/mnesia/dev/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/mnesia/prod/.gitignore b/mnesia/prod/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/mnesia/prod/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/priv/sqlite/migrations/20240525001633_drop_psycho_effect_tables.exs b/priv/sqlite/migrations/20240525001633_drop_psycho_effect_tables.exs new file mode 100644 index 0000000..ba4ead3 --- /dev/null +++ b/priv/sqlite/migrations/20240525001633_drop_psycho_effect_tables.exs @@ -0,0 +1,21 @@ +defmodule Adjutant.Repo.SQLite.Migrations.DropPsychoEffectTables do + use Ecto.Migration + + def up do + drop table("psycho_effect_channel") + drop table("random_insults") + end + + def down do + create table("random_insults") do + add :insult, :text + timestamps(updated_at: false) + end + + create table("psycho_effect_channel") do + add :guild_id, :integer, null: false + add :set_by, :integer, null: false + timestamps() + end + end +end diff --git a/priv/sqlite/structure.sql b/priv/sqlite/structure.sql index 148f607..ad1ce0c 100644 --- a/priv/sqlite/structure.sql +++ b/priv/sqlite/structure.sql @@ -2,11 +2,9 @@ CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" INTEGER PRIMARY KEY, " CREATE TABLE IF NOT EXISTS "bot_log" ("id" INTEGER PRIMARY KEY, "level" TEXT, "message" TEXT, "inserted_at" TEXT_DATETIME NOT NULL); CREATE TABLE IF NOT EXISTS "banlist" ("id" INTEGER PRIMARY KEY, "added_by" INTEGER NOT NULL, "to_ban" INTEGER NOT NULL, "inserted_at" TEXT_DATETIME NOT NULL); CREATE TABLE IF NOT EXISTS "created_commands" ("name" TEXT NOT NULL PRIMARY KEY, "state" BLOB NOT NULL, "inserted_at" TEXT_DATETIME NOT NULL, "updated_at" TEXT_DATETIME NOT NULL, "cmd_ids" BLOB); -CREATE TABLE IF NOT EXISTS "random_insults" ("id" INTEGER PRIMARY KEY, "insult" TEXT, "inserted_at" TEXT_DATETIME NOT NULL); CREATE TABLE IF NOT EXISTS "oban_jobs" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "state" TEXT DEFAULT 'available' NOT NULL, "queue" TEXT DEFAULT 'default' NOT NULL, "worker" TEXT NOT NULL, "args" JSON DEFAULT ('{}') NOT NULL, "meta" JSON DEFAULT ('{}') NOT NULL, "tags" JSON DEFAULT ('[]') NOT NULL, "errors" JSON DEFAULT ('[]') NOT NULL, "attempt" INTEGER DEFAULT 0 NOT NULL, "max_attempts" INTEGER DEFAULT 20 NOT NULL, "priority" INTEGER DEFAULT 0 NOT NULL, "inserted_at" TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, "scheduled_at" TEXT DEFAULT CURRENT_TIMESTAMP NOT NULL, "attempted_at" TEXT, "attempted_by" JSON DEFAULT ('[]') NOT NULL, "cancelled_at" TEXT, "completed_at" TEXT, "discarded_at" TEXT); CREATE TABLE sqlite_sequence(name,seq); CREATE INDEX "oban_jobs_state_queue_priority_scheduled_at_id_index" ON "oban_jobs" ("state", "queue", "priority", "scheduled_at", "id"); -CREATE TABLE IF NOT EXISTS "psycho_effect_channel" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "guild_id" INTEGER NOT NULL, "set_by" INTEGER NOT NULL, "inserted_at" TEXT NOT NULL, "updated_at" TEXT NOT NULL); CREATE INDEX "bot_log_inserted_at_index" ON "bot_log" ("inserted_at"); INSERT INTO schema_migrations VALUES(20211003173517,'2021-10-03T18:33:03'); INSERT INTO schema_migrations VALUES(20211015184140,'2021-10-15T19:41:05'); @@ -16,3 +14,4 @@ INSERT INTO schema_migrations VALUES(20220813171203,'2022-08-13T17:13:03'); INSERT INTO schema_migrations VALUES(20230516030631,'2023-05-16T03:10:22'); INSERT INTO schema_migrations VALUES(20230629015706,'2023-06-29T01:59:05'); INSERT INTO schema_migrations VALUES(20231117044416,'2023-11-17T04:45:10'); +INSERT INTO schema_migrations VALUES(20240525001633,'2024-05-25T00:18:20'); diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..9e912bf --- /dev/null +++ b/src/README.md @@ -0,0 +1,3 @@ +This folder contains a few of the erlang modules that the bot uses. +Why are they Erlang instead of Elixir? Because I'm experimenting with mnesia and calling qlc functions from Elixir +has speed issues and are a "won't fix". So I'm using Erlang for the mnesia stuff and Elixir for the rest.