From a906ea5f202e675796eb31ea2ff8db888a31c478 Mon Sep 17 00:00:00 2001 From: vKxni Date: Thu, 7 Jul 2022 21:15:15 +0200 Subject: [PATCH] beep boop --- .env.example | 2 ++ .formatter.exs | 4 +++ .gitignore | 29 ++++++++++++++++++++ LICENSE | 24 +++++++++++++++++ README.md | 39 +++++++++++++++++++++++++++ config/runtime.exs | 15 +++++++++++ goodmorning.txt | 10 +++++++ goodnight.txt | 10 +++++++ lib/bot/application.ex | 21 +++++++++++++++ lib/bot/command.ex | 28 +++++++++++++++++++ lib/bot/commands.ex | 40 +++++++++++++++++++++++++++ lib/bot/commands/goodmorning.ex | 48 +++++++++++++++++++++++++++++++++ lib/bot/commands/goodnight.ex | 48 +++++++++++++++++++++++++++++++++ lib/bot/commands/intro.ex | 40 +++++++++++++++++++++++++++ lib/bot/commands/ping.ex | 24 +++++++++++++++++ lib/bot/commands/random.ex | 39 +++++++++++++++++++++++++++ lib/bot/commands/say.ex | 44 ++++++++++++++++++++++++++++++ lib/bot/consumer.ex | 41 ++++++++++++++++++++++++++++ lib/bot/consumer/supervisor.ex | 33 +++++++++++++++++++++++ lib/psglx.ex | 18 +++++++++++++ mix.exs | 30 +++++++++++++++++++++ mix.lock | 18 +++++++++++++ random.txt | 36 +++++++++++++++++++++++++ test/psglx_test.exs | 8 ++++++ test/test_helper.exs | 1 + 25 files changed, 650 insertions(+) create mode 100644 .env.example create mode 100644 .formatter.exs create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 config/runtime.exs create mode 100644 goodmorning.txt create mode 100644 goodnight.txt create mode 100644 lib/bot/application.ex create mode 100644 lib/bot/command.ex create mode 100644 lib/bot/commands.ex create mode 100644 lib/bot/commands/goodmorning.ex create mode 100644 lib/bot/commands/goodnight.ex create mode 100644 lib/bot/commands/intro.ex create mode 100644 lib/bot/commands/ping.ex create mode 100644 lib/bot/commands/random.ex create mode 100644 lib/bot/commands/say.ex create mode 100644 lib/bot/consumer.ex create mode 100644 lib/bot/consumer/supervisor.ex create mode 100644 lib/psglx.ex create mode 100644 mix.exs create mode 100644 mix.lock create mode 100644 random.txt create mode 100644 test/psglx_test.exs create mode 100644 test/test_helper.exs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f4791e0 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +TOKEN= +GUILD_ID= \ No newline at end of file diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26a7eb1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +nostrum_boilerplate-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +# config files +.env \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3c577b0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..54449b6 --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +# Psglx :: Nostrum +A random utility chat bot for Discord, made for private and fun usage. + +## 👩🏻‍💼 Getting started + +1. Install dependencies with `mix deps.get`. +2. Rename `.env.example` to `.env`. +3. Add your token and optionally developer guild id to the `.env` file. + +## 👩🏻‍🏫 Information +> Versions +* You **need** OTP Version 23 or higher. Latest Version is fine too, just not below 23/22. + +> Deployment +* You do not need to push the commands, it is automatically done once you run the bot. + +## 🚴🏻 Up and running +> Shell commands +``` +$ iex -S mix +``` +This will start the bot, if you make any additional changes you you have to stop the terminal with `CTRL + C` and then run the same iex command again. I might add auto-refresh later. + +> Bot commands +``` +/ping +/random +/goodmorning +/goodnight +``` +Ping returns a simple "Ping Pong" while random returns a random quote from the `random.txt` file. You can modify that if you want like add more quotes/messages/facts ... + +## 🧏🏻 What I have used + + - [Elixir Programming Language](https://elixir-lang.org) + + - [Nostrum Discord Library](https://kraigie.github.io/nostrum/intro.html) + + diff --git a/config/runtime.exs b/config/runtime.exs new file mode 100644 index 0000000..3112a2e --- /dev/null +++ b/config/runtime.exs @@ -0,0 +1,15 @@ +import Config + +# develope mode while production isn't ready +if Config.config_env() == :dev do + DotenvParser.load_file(".env") +end + +# define the token for the discord api wrapper +config :nostrum, + token: System.get_env("TOKEN") + +# for development mode; local slash commands +config :psglx, + env: Config.config_env(), + GUILD_ID: System.get_env("GUILD_ID") diff --git a/goodmorning.txt b/goodmorning.txt new file mode 100644 index 0000000..7233761 --- /dev/null +++ b/goodmorning.txt @@ -0,0 +1,10 @@ +https://i.imgur.com/zVFXlgN.gif +https://i.imgur.com/8kolGy1.jpeg +https://i.imgur.com/HGjV6Rk.gif +https://i.imgur.com/qLioM3w.jpeg +https://i.imgur.com/K3lh8e2.jpeg +https://i.imgur.com/knvEUwa.jpeg +https://i.imgur.com/lpIx2Dk.jpeg +https://i.imgur.com/8mFkY55.jpeg +https://i.imgur.com/bJlg6hw.jpeg +https://i.imgur.com/JXPNjXnb.jpg \ No newline at end of file diff --git a/goodnight.txt b/goodnight.txt new file mode 100644 index 0000000..3a342e9 --- /dev/null +++ b/goodnight.txt @@ -0,0 +1,10 @@ +https://i.imgur.com/4DDzfxa.jpeg +https://i.imgur.com/AmWThvwb.jpg +https://i.imgur.com/MXarSsK.gif +https://i.imgur.com/6MMNDqU.gif +https://i.imgur.com/Rbdlo3B.jpeg +https://i.imgur.com/CihyGAm.jpeg +https://i.imgur.com/dm6HXna.jpeg +https://i.imgur.com/ajmzbAF.jpeg +https://i.imgur.com/VFbKXIl.jpeg +https://i.imgur.com/3B3Zpy7.jpeg \ No newline at end of file diff --git a/lib/bot/application.ex b/lib/bot/application.ex new file mode 100644 index 0000000..72744ff --- /dev/null +++ b/lib/bot/application.ex @@ -0,0 +1,21 @@ +defmodule Psglx.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + @impl true + def start(_type, _args) do + children = [ + # supervisor + {Psglx.Consumer.Supervisor, []} + # Psglx.Consumer.Supervisor + ] + + # See https://hexdocs.pm/elixir/Supervisor.html + # for other strategies and supported options + opts = [strategy: :one_for_one, name: Psglx.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/lib/bot/command.ex b/lib/bot/command.ex new file mode 100644 index 0000000..b479e90 --- /dev/null +++ b/lib/bot/command.ex @@ -0,0 +1,28 @@ +defmodule Psglx.Command do + @moduledoc """ + Behaviour for application command implementations. + """ + + alias Nostrum.Struct.Interaction + + @doc """ + Used to define the spec for the command to be used for command registration. + See https://hexdocs.pm/nostrum/slash-commands.html for more info on the + required shape for the spec. + """ + @callback spec(name :: String.t()) :: map() + + @doc """ + Called when the command is invoked. + """ + @callback handle_interaction(Interaction.t()) :: any() + + @doc """ + Gets an option by it's name from the interaction. Returns `nil` if the + option is not present in the interaction. + """ + @spec get_option(Interaction.t(), String.t()) :: + Nostrum.Struct.ApplicationCommandInteractionData.options() | nil + def get_option(interaction, name), + do: Enum.find(interaction.data.options || [], fn %{name: n} -> n == name end) +end diff --git a/lib/bot/commands.ex b/lib/bot/commands.ex new file mode 100644 index 0000000..87c40f9 --- /dev/null +++ b/lib/bot/commands.ex @@ -0,0 +1,40 @@ +defmodule Psglx.Commands do + @doc """ + Handling and routing for commands and interactions. + """ + + # Add your commands here. The command name will be passed as an argument to + # your command's `spec/1` function, so you can see all of the command names + # here and ensure they don't collide. + @commands %{ + "ping" => Psglx.Commands.Ping, + "random" => Psglx.Commands.Random, + "goodmorning" => Psglx.Commands.Morning, + "goodnight" => Psglx.Commands.Night, + "intro" => Psglx.Commands.Intro, + "say" => Psglx.Commands.Say + } + + @command_names for {name, _} <- @commands, do: name + + def register_commands() do + commands = for {name, command} <- @commands, do: command.spec(name) + + # Global application commands take a couple of minutes to update in Discord, + # so we use a test guild when in dev mode. + if Application.get_env(:psglx, :env) == :dev do + guild_id = Application.get_env(:psglx, :GUILD_ID) + Nostrum.Api.bulk_overwrite_guild_application_commands(guild_id, commands) + else + Nostrum.Api.bulk_overwrite_global_application_commands(commands) + end + end + + def handle_interaction(interaction) do + if interaction.data.name in @command_names do + @commands[interaction.data.name].handle_interaction(interaction) + else + :ok + end + end +end diff --git a/lib/bot/commands/goodmorning.ex b/lib/bot/commands/goodmorning.ex new file mode 100644 index 0000000..210057c --- /dev/null +++ b/lib/bot/commands/goodmorning.ex @@ -0,0 +1,48 @@ +defmodule Psglx.Commands.Morning do + @moduledoc false + + @behaviour Psglx.Command + + alias Nostrum.Api + alias Psglx.Command + + @impl Command + + def spec(name) do + %{ + name: name, + description: "Wish someone a good morning!", + options: [ + %{ + type: 6, + name: "user", + description: "Who do you want to wish a good morning?", + required: true + } + ] + } + end + + @impl Command + def handle_interaction(interaction) do + %{value: name} = Command.get_option(interaction, "user") + + # fetch a random image + random = + File.stream!("goodmorning.txt") + |> Enum.random() + + # wrap everything into an embed and send it into the channel + embed = %Nostrum.Struct.Embed{ + description: "Good morning <@#{name}>! ☕\n\nHave a nice day! 🌞", + thumbnail: %Nostrum.Struct.Embed.Thumbnail{ + url: random + } + } + + Api.create_interaction_response(interaction, %{ + type: 4, + data: %{embeds: [embed]} + }) + end +end diff --git a/lib/bot/commands/goodnight.ex b/lib/bot/commands/goodnight.ex new file mode 100644 index 0000000..a5ce980 --- /dev/null +++ b/lib/bot/commands/goodnight.ex @@ -0,0 +1,48 @@ +defmodule Psglx.Commands.Night do + @moduledoc false + + @behaviour Psglx.Command + + alias Nostrum.Api + alias Psglx.Command + + @impl Command + + def spec(name) do + %{ + name: name, + description: "Wish someone a good night!", + options: [ + %{ + type: 6, + name: "user", + description: "Who do you want to wish a good night?", + required: true + } + ] + } + end + + @impl Command + def handle_interaction(interaction) do + %{value: name} = Command.get_option(interaction, "user") + + # fetch a random image + random = + File.stream!("goodnight.txt") + |> Enum.random() + + # wrap everything into an embed and send it into the channel + embed = %Nostrum.Struct.Embed{ + description: "Good night <@#{name}>! 🌙\n\nSleep well and come back soon 😴", + thumbnail: %Nostrum.Struct.Embed.Thumbnail{ + url: random + } + } + + Api.create_interaction_response(interaction, %{ + type: 4, + data: %{embeds: [embed]} + }) + end +end diff --git a/lib/bot/commands/intro.ex b/lib/bot/commands/intro.ex new file mode 100644 index 0000000..b23c5b2 --- /dev/null +++ b/lib/bot/commands/intro.ex @@ -0,0 +1,40 @@ +defmodule Psglx.Commands.Intro do + @moduledoc false + + @behaviour Psglx.Command + + alias Nostrum.Api + alias Psglx.Command + + @impl Command + def spec(name) do + %{ + name: name, + description: "A small intro of myself" + } + end + + @impl Command + def handle_interaction(interaction) do + embed = %Nostrum.Struct.Embed{ + description: + "Hello there #{interaction.user}, let me introduce myself!\n\nI'm \`Psglx\`, a bot made by **Koni#9521**.\nI have a lot of fun commands, and I'm constantly improving myself.\n\nI'm currently in **development**, so I'm not sure if I'll be able to do much more than what I already have.\n\nIf you have any questions, feel free to ask me!", + thumbnail: %Nostrum.Struct.Embed.Thumbnail{ + url: + "https://images-ext-1.discordapp.net/external/rDN1l5js_itKv7r3H63hu9p81P-my2OiiqKAOXk3eWk/https/cdn.discordapp.com/avatars/945313842337042442/d2e7c0ed02fff6c1d314c2676f832b5f.png" + }, + footer: %Nostrum.Struct.Embed.Footer{ + text: "Version: Psglx v1.3.0", + icon_url: + "https://images-ext-1.discordapp.net/external/rDN1l5js_itKv7r3H63hu9p81P-my2OiiqKAOXk3eWk/https/cdn.discordapp.com/avatars/945313842337042442/d2e7c0ed02fff6c1d314c2676f832b5f.png" + }, + # black + color: 000_000 + } + + Api.create_interaction_response(interaction, %{ + type: 4, + data: %{embeds: [embed]} + }) + end +end diff --git a/lib/bot/commands/ping.ex b/lib/bot/commands/ping.ex new file mode 100644 index 0000000..d238d8a --- /dev/null +++ b/lib/bot/commands/ping.ex @@ -0,0 +1,24 @@ +defmodule Psglx.Commands.Ping do + @moduledoc false + + @behaviour Psglx.Command + + alias Nostrum.Api + alias Psglx.Command + + @impl Command + def spec(name) do + %{ + name: name, + description: "A simple bot check" + } + end + + @impl Command + def handle_interaction(interaction) do + Api.create_interaction_response(interaction, %{ + type: 4, + data: %{content: "Pong!"} + }) + end +end diff --git a/lib/bot/commands/random.ex b/lib/bot/commands/random.ex new file mode 100644 index 0000000..8bacbf2 --- /dev/null +++ b/lib/bot/commands/random.ex @@ -0,0 +1,39 @@ +defmodule Psglx.Commands.Random do + @moduledoc false + + @behaviour Psglx.Command + + alias Nostrum.Api + alias Psglx.Command + use Nostrum.Struct.Embed + + @impl Command + def spec(name) do + %{ + name: name, + description: "Returns a random quote" + } + end + + @impl Command + def handle_interaction(interaction) do + # read the text file && fetch a random quote.. + + file = + File.stream!("random.txt") + |> Enum.random() + + embed = %Nostrum.Struct.Embed{ + title: "Daily quote", + description: file, + color: 000_000 + # icon_url: "https://media.discordapp.net/attachments/942860191320387587/967350735207542784/quotes.png" + } + + Api.create_interaction_response(interaction, %{ + type: 4, + # data: %{content: file} + data: %{embeds: [embed]} + }) + end +end diff --git a/lib/bot/commands/say.ex b/lib/bot/commands/say.ex new file mode 100644 index 0000000..2300f12 --- /dev/null +++ b/lib/bot/commands/say.ex @@ -0,0 +1,44 @@ +defmodule Psglx.Commands.Say do + @moduledoc false + + @behaviour Psglx.Command + + alias Nostrum.Api + alias Psglx.Command + + use Bitwise + + @impl Command + def spec(name) do + %{ + name: name, + description: "Echo'ing your words", + options: [ + %{ + type: 3, + name: "string", + description: "What do you want me to say?", + required: true + } + ] + } + end + + @impl Command + def handle_interaction(interaction) do + %{value: name} = Command.get_option(interaction, "string") + + # Here we reply to the interaction to avoid the "did not respond" error + # TODO: Make it ephemeral + Api.create_interaction_response(interaction, %{ + type: 4, + data: %{content: "Done!"}, + flags: 1 <<< 6 + }) + + # Send the message as a direct message into the channel (no reply to the interaction) + Api.create_message(interaction.channel_id, %{ + content: name + }) + end +end diff --git a/lib/bot/consumer.ex b/lib/bot/consumer.ex new file mode 100644 index 0000000..699b103 --- /dev/null +++ b/lib/bot/consumer.ex @@ -0,0 +1,41 @@ +defmodule Psglx.Consumer do + use Nostrum.Consumer + use Bitwise + + @moduledoc """ + + This is the "hearth" of the client (our bot) and is responsible for + connecting to the APi. + + Here we also handle the events (READY + INTERACTION_CREATE) that are necessary for commands + + Oh, and we use GenServers and Supervisors, of course ... + + """ + + alias Psglx.Commands + require Logger + + def start_link do + Consumer.start_link(__MODULE__) + end + + # once the bot goes online, register the bots commands + def handle_event({:READY, ready_data, _ws_state}) do + Commands.register_commands() + Logger.info("#{__MODULE__} starting ...") + Logger.info("Logged in as #{ready_data.user.username}##{ready_data.user.discriminator}") + + # Update the bot status, (online, dnd, idle, offline) + Nostrum.Api.update_status(:dnd, "Supervising") + end + + # handle the interactions (slash comands) + def handle_event({:INTERACTION_CREATE, interaction, _ws_state}) do + Commands.handle_interaction(interaction) + end + + def handle_event(_data) do + :ok + end +end diff --git a/lib/bot/consumer/supervisor.ex b/lib/bot/consumer/supervisor.ex new file mode 100644 index 0000000..6b0b78d --- /dev/null +++ b/lib/bot/consumer/supervisor.ex @@ -0,0 +1,33 @@ +defmodule Psglx.Consumer.Supervisor do + use Supervisor + + @moduledoc """ + + DOCS: https://hexdocs.pm/elixir/Supervisor.html + + A supervisor is a process which supervises other processes, which we refer to as child processes. + Supervisors are used to build a hierarchical process structure called a supervision tree. + Supervision trees provide fault-tolerance and encapsulate how our applications start and shutdown. + + A supervisor may be started directly with a list of children via start_link/2 or + you may define a module-based supervisor that implements the required callbacks. + The sections below use start_link/2 to start supervisors in most examples, + but it also includes a specific section on module-based ones. + + Here, I use the supervision module, however, you can also create it + by passing the supervision structure to start_link/2 + """ + + def start_link(args) do + Supervisor.start_link(__MODULE__, args, name: __MODULE__) + end + + @impl true + def init(_init_arg) do + children = [ + Psglx.Consumer + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/lib/psglx.ex b/lib/psglx.ex new file mode 100644 index 0000000..3799fc7 --- /dev/null +++ b/lib/psglx.ex @@ -0,0 +1,18 @@ +defmodule Psglx do + @moduledoc """ + Documentation for `Psglx`. + """ + + @doc """ + Hello world. + + ## Examples + + iex> Psglx.hello() + :world + + """ + def hello do + :world + end +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..a26c857 --- /dev/null +++ b/mix.exs @@ -0,0 +1,30 @@ +defmodule Psglx.MixProject do + use Mix.Project + + def project do + [ + app: :psglx, + version: "1.1.0", + description: "Functional bot that handles fun and activities within your Discord server", + elixir: "~> 1.13", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {Psglx.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:dotenv_parser, "~> 2.0"}, + {:nostrum, "~> 0.6.0"} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..3371bad --- /dev/null +++ b/mix.lock @@ -0,0 +1,18 @@ +%{ + "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "chacha20": {:hex, :chacha20, "1.0.3", "26372176993172260968b36b4e7bc2e007e6b2b397ae08083e4836df67cdf03c", [:mix], [], "hexpm", "27f23b680e63f04b5bced77a9d8867033b381c091f92d544de0ad93accfb7cec"}, + "cowlib": {:hex, :remedy_cowlib, "2.11.1", "7abb4d0779a7d1c655f7642dc0bd0af754951e95005dfa01b500c68fe35a5961", [:rebar3], [], "hexpm", "0b613dc308e080cb6134285f1b1b55c3873e101652e70c70010fc6651c91b130"}, + "curve25519": {:hex, :curve25519, "1.0.4", "e570561b832c29b3ce4fd8b9fcd9c9546916188860568f1c1fc9428d7cb00894", [:mix], [], "hexpm", "1a068bf9646e7067bf6aa5bf802b404002734e09bb5300f098109df03e31f9f5"}, + "dotenv_parser": {:hex, :dotenv_parser, "2.0.0", "0f999196857e4ee18cbba1413018d5e4980ab16b397e3a2f8d0cf541fe683181", [:mix], [], "hexpm", "e769bde2dbff5b0cd0d9d877a9ccfd2c6dd84772dfb405d5a43cceb4f93616c5"}, + "ed25519": {:hex, :ed25519, "1.4.0", "3eee373a77c8230ac25ab1d557f436eb6ec584946d7b76ca311f9fa18db84b55", [:mix], [], "hexpm", "420b52c68e2eda14a822c1d15df6c3e386f7b604d4351d6621a2f6baa68ed6dd"}, + "equivalex": {:hex, :equivalex, "1.0.2", "b9a9aaf79f2556288f514218653beaddb15afa2af407bfec37c5c4906e39f514", [:mix], [], "hexpm", "f7f8127c59be715ee6288f8c59fa8fc40e6428fb5c9bd2a001de2c9b1ff3f1c2"}, + "gen_stage": {:hex, :gen_stage, "1.1.2", "b1656cd4ba431ed02c5656fe10cb5423820847113a07218da68eae5d6a260c23", [:mix], [], "hexpm", "9e39af23140f704e2b07a3e29d8f05fd21c2aaf4088ff43cb82be4b9e3148d02"}, + "gun": {:hex, :remedy_gun, "2.0.1", "0f0caed812ed9e4da4f144df2d5bf73b0a99481d395ecde990a3791decf321c6", [:rebar3], [{:cowlib, "~> 2.11.1", [hex: :remedy_cowlib, repo: "hexpm", optional: false]}], "hexpm", "b6685a85fbd12b757f86809be1b3d88fcef365b77605cd5aa34db003294c446e"}, + "jason": {:hex, :jason, "1.3.0", "fa6b82a934feb176263ad2df0dbd91bf633d4a46ebfdffea0c8ae82953714946", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "53fc1f51255390e0ec7e50f9cb41e751c260d065dcba2bf0d08dc51a4002c2ac"}, + "kcl": {:hex, :kcl, "1.4.1", "9ae313d567c990371ae7a4f7d98e4adae512ca4f315dc03b8995244cc01a5c2f", [: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", "1c9a7604d55e933c6536f30a8523e5d1ce58bf595ca16fbd9d8d1a2519e91a01"}, + "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, + "nostrum": {:hex, :nostrum, "0.6.0", "773b0a8953f74abb3d7633be94d5dc2d780f8ecaabba161e57f656afd6620c92", [:mix], [{:certifi, "~> 2.8", [hex: :certifi, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.11 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:gun, "== 2.0.1", [hex: :remedy_gun, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:kcl, "~> 1.4", [hex: :kcl, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "f44846457e5e40137948164633ed2ea4cc21c434c224c69073f4df1cc90a3f90"}, + "poly1305": {:hex, :poly1305, "1.0.3", "9ec6b5b3b96d96b15d4f2b8a3629aad2a2f5fe82f4ea0f13e5a3512737af4ee7", [:mix], [{:chacha20, "~> 1.0", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 1.0", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm", "fbe549d59e74e7cde680e7ae6baf7fe2b5f9053a84c1b5c866f703d0651d6b22"}, + "porcelain": {:hex, :porcelain, "2.0.3", "2d77b17d1f21fed875b8c5ecba72a01533db2013bd2e5e62c6d286c029150fdc", [:mix], [], "hexpm", "dc996ab8fadbc09912c787c7ab8673065e50ea1a6245177b0c24569013d23620"}, + "salsa20": {:hex, :salsa20, "1.0.3", "fb900fc9b26b713a98618f3c6d6b6c35a5514477c6047caca8d03f3a70175ab0", [:mix], [], "hexpm", "91cbfa537f369d074a79f926f36a6c2ac24fba12cbadcb23aa04a759282887fe"}, +} diff --git a/random.txt b/random.txt new file mode 100644 index 0000000..412b951 --- /dev/null +++ b/random.txt @@ -0,0 +1,36 @@ +Don't worry about what anybody else is going to do. The best way to predict the future is to invent it. +Premature optimization is the root of all evil (or at least most of it) in programming. +Lisp has jokingly been called "the most intelligent way to misuse a computer". +I think that description is a great compliment because it transmits the full flavor of liberation: it has assisted a number of our most gifted fellow humans in thinking previously impossible thoughts. +Keep away from people who try to belittle your ambitions. +Small people always do that, but the really great make you feel that you, too, can become great. +What Paul does, and does very well, is to take ideas and concepts that are beautiful in the abstract, and brings them down to a real world level. That's a rare talent to find in writing these days. +Since programmers create programs out of nothing, imagination is our only limitation. +Thus, in the world of programming, the hero is the one who has great vision. Paul Graham is one of our contemporary heroes. He has the ability to embrace the vision, and to express it plainly. His works are my favorites, especially the ones describing language design. He explains secrets of programming, languages, and human nature that can only be learned from the hacker experience. This book shows you his great vision, and tells you the truth about the nature of hacking. +To follow the path: look to the master, follow the master, walk with the master, see through the master, become the master. +No problem should ever have to be solved twice. +Attitude is no substitute for competence. +It is said that the real winner is the one who lives in today but able to see tomorrow. +Fools ignore complexity. Pragmatists suffer it. Some can avoid it. Geniuses remove it. +A year spent in artificial intelligence is enough to make one believe in God. +Dealing with failure is easy: Work hard to improve. +Success is also easy to handle: You've solved the wrong problem. Work hard to improve. +Within a computer natural language is unnatural. +You think you know when you learn, are more sure when you can write, even more when you can teach, but certain when you can program. +Adapting old programs to fit new machines usually means adapting new machines to behave like old ones. +A little learning is a dangerous thing +Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter. +Einstein argued that there must be simplified explanations of nature, because God is not capricious or arbitrary. +Students should be evaluated on how well they can achieve the goals they strived to achieve within a realistic context. +Students need to learn to do things, not know things. +We remember what we learn when we care about performing better and when we believe that what we have been asked to do is representative of reality. +There really is no learning without doing. +We really have to get over the idea that some stuff is just worth knowing even if you never do anything with it. Human memories happily erase stuff that has no purpose, so why try to fill up children's heads with such stuff? +La tactique, c'est ce que vous faites quand il y a quelque chose à faire; la stratégie, c'est ce que vous faites quand il n'y a rien à faire. +The only problems we can really solve in a satisfactory manner are those that finally admit a nicely factored solution. +The best way to learn to live with our limitations is to know them. +Ce n'est que par les relations qu'on entretient entre nos différentes connaissances qu'elles nous restent accessibles. +We now come to the decisive step of mathematical abstraction: we forget about what the symbols stand for. ...[The mathematician] need not be idle; there are many operations which he may carry out with these symbols, without ever having to look at the things they stand for. +The only thing we have to learn is to know what we have to learn. +You can't teach a student to program. You can only teach him to think. + diff --git a/test/psglx_test.exs b/test/psglx_test.exs new file mode 100644 index 0000000..c19374c --- /dev/null +++ b/test/psglx_test.exs @@ -0,0 +1,8 @@ +defmodule PsglxTest do + use ExUnit.Case + doctest Psglx + + test "greets the world" do + assert Psglx.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()