├── examples ├── test ├── test_helper.exs ├── discord_elixir_test.exs └── discord_elixir │ ├── voice │ ├── controller_test.exs │ └── buffer_test.exs │ └── permissions_test.exs ├── .tool-versions ├── doc ├── fonts │ ├── icomoon.eot │ ├── icomoon.ttf │ ├── icomoon.woff │ └── icomoon.svg ├── index.html ├── search.html ├── 404.html ├── DiscordEx.EchoBot.html ├── DiscordEx.Voice.Encoder.html ├── DiscordEx.Voice.UDP.html ├── DiscordEx.html ├── api-reference.html ├── DiscordEx.Permissions.html ├── dist │ └── sidebar_items-506f2099ca.js ├── DiscordEx.RestClient.Resources.Image.html ├── DiscordEx.Heartbeat.html ├── DiscordEx.RestClient.html ├── DiscordEx.Voice.Controller.html ├── DiscordEx.RestClient.Resources.Invite.html ├── DiscordEx.Client.Helpers.MessageHelper.html └── DiscordEx.Client.Utility.html ├── .gitignore ├── .travis.yml ├── lib ├── discord_ex.ex ├── discord_ex │ ├── voice │ │ ├── encoder.ex │ │ ├── udp.ex │ │ ├── buffer.ex │ │ ├── controller.ex │ │ └── client.ex │ ├── rest_client │ │ ├── rest.ex │ │ ├── resources │ │ │ ├── image.ex │ │ │ ├── invite.ex │ │ │ ├── user.ex │ │ │ └── channel.ex │ │ └── rest_client.ex │ ├── permissions.ex │ └── client │ │ ├── heartbeat.ex │ │ ├── utility.ex │ │ └── helpers │ │ └── message_helper.ex └── examples │ └── echo_bot.ex ├── LICENSE ├── config └── config.exs ├── mix.exs ├── mix.lock └── README.md /examples: -------------------------------------------------------------------------------- 1 | lib/examples/ -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 20.1 2 | elixir 1.5.2 3 | -------------------------------------------------------------------------------- /doc/fonts/icomoon.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmcafee/discord_ex/HEAD/doc/fonts/icomoon.eot -------------------------------------------------------------------------------- /doc/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmcafee/discord_ex/HEAD/doc/fonts/icomoon.ttf -------------------------------------------------------------------------------- /doc/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmcafee/discord_ex/HEAD/doc/fonts/icomoon.woff -------------------------------------------------------------------------------- /test/discord_elixir_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DiscordExTest do 2 | use ExUnit.Case 3 | doctest DiscordEx 4 | end 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/.build 2 | /_build 3 | /cover 4 | /deps 5 | erl_crash.dump 6 | *.ez 7 | .DS_Store 8 | *.tar 9 | .notes 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | 3 | sudo: false # use faster, container-based Travis 4 | 5 | elixir: 6 | - 1.5.2 7 | 8 | # test last two OTP releases 9 | otp_release: 10 | - 18.3 11 | - 19.0 12 | - 20.1 13 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Discord Elixir v1.1.18 – Documentation 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /lib/discord_ex.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx do 2 | @moduledoc """ 3 | Base Discord Ex Module 4 | """ 5 | 6 | @doc "Discord API URL" 7 | @spec discord_url :: String.t 8 | def discord_url do 9 | "https://discordapp.com/api" 10 | end 11 | 12 | @doc "Discord Bot Authorization URL" 13 | @spec bot_auth_url(number, number) :: String.t 14 | def bot_auth_url(client_id, permissions) do 15 | "https://discordapp.com/oauth2/authorize?client_id=#{client_id}&scope=bot&permissions=#{permissions}" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/discord_elixir/voice/controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.ControllerTest do 2 | use ExUnit.Case 3 | doctest DiscordEx.Voice.Controller 4 | 5 | alias DiscordEx.Voice.Controller 6 | 7 | test "start controller with a buffer, sequence, and agent process" do 8 | controller = Controller.start(spawn fn -> IO.puts "mock voice_client" end) 9 | 10 | refute controller.buffer == nil 11 | refute controller.sequence == nil 12 | refute controller.time == nil 13 | refute controller.voice_client == nil 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/discord_ex/voice/encoder.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.Encoder do 2 | @moduledoc """ 3 | Voice Encoder 4 | """ 5 | alias Porcelain.Process, as: Proc 6 | 7 | @doc "Encode audio file to proper format" 8 | @spec encode_file(String.t, map) :: binary 9 | def encode_file(file_path, opts) do 10 | dca_rs_path = Application.get_env(:discord_ex, :dca_rs_path) 11 | %Proc{out: audio_stream} = Porcelain.spawn(dca_rs_path,["--vol","#{opts[:volume]}","--ac","2","--ar","48000","--as","960","--ab","128","--raw","-i","#{file_path}"],[out: :stream]) 12 | audio_stream 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Rahsun McAfee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/discord_ex/rest_client/rest.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Connections.REST do 2 | @moduledoc """ 3 | Discord uses a REST interface to send data to the API. 4 | """ 5 | use HTTPoison.Base 6 | 7 | def process_url(target_url) do 8 | DiscordEx.discord_url <> target_url 9 | end 10 | 11 | #Overrides 12 | 13 | defp standard_headers do 14 | %{ 15 | "User-Agent" => "DiscordBot (discord-ex, 1.1.18)" 16 | } 17 | end 18 | 19 | defp process_request_headers(headers) when is_map(headers) do 20 | merged_headers = Map.merge(standard_headers(), headers) 21 | Map.to_list(merged_headers) 22 | end 23 | 24 | defp process_request_headers(headers) do 25 | Map.to_list(standard_headers()) ++ headers 26 | end 27 | 28 | defp process_request_body(body) do 29 | case body do 30 | {:form, _} -> body 31 | {:multipart, _} -> body 32 | "" -> body 33 | _ -> Poison.encode!(body) 34 | 35 | end 36 | end 37 | 38 | defp process_response_body(body) do 39 | case Poison.decode(body) do 40 | {:ok, res} -> res 41 | {:error, message} -> message 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/discord_ex/rest_client/resources/image.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.RestClient.Resources.Image do 2 | @moduledoc """ 3 | Convience helper for images 4 | """ 5 | 6 | @doc """ 7 | Get the guild icon URL 8 | 9 | ## Parameters 10 | 11 | - server_id: The server_id to retrieve the icon URL for 12 | - icon_id: The icon_id for the user to retrieve the icon URL for 13 | 14 | ## Examples 15 | 16 | Image.icon_url("99999999993832","f3e8329c329020329") 17 | """ 18 | @spec icon_url(String.t, String.t) :: String.t 19 | def icon_url(server_id, icon_id) do 20 | "#{DiscordEx.discord_url}/guilds/#{server_id}/icons/#{icon_id}.jpg" 21 | end 22 | 23 | @doc """ 24 | Get the user avatar URL 25 | 26 | ## Parameters 27 | 28 | - user_id: The user_id to retrieve the avatar URL for 29 | - avatar_id: The avatar_id for the user to retrieve the avatar URL for 30 | 31 | ## Examples 32 | 33 | Image.avatar_url("99999999993832","f3e8329c329020329") 34 | """ 35 | @spec avatar_url(String.t, String.t) :: String.t 36 | def avatar_url(user_id, avatar_id) do 37 | "#{DiscordEx.discord_url}/users/#{user_id}/avatars/#{avatar_id}.jpg" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure for your application as: 12 | # 13 | # config :discord_ex, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:discord_ex, :key) 18 | # 19 | # Or configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/discord_ex/rest_client/resources/invite.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.RestClient.Resources.Invite do 2 | @moduledoc """ 3 | Convience helper for invites 4 | """ 5 | 6 | @doc """ 7 | Get invite object 8 | 9 | ## Parameters 10 | 11 | - conn: User connection for REST holding auth info 12 | - invite_id: Invite id 13 | 14 | ## Examples 15 | 16 | Invite.get(conn, "99999999993832") 17 | """ 18 | @spec get(pid, String.t) :: map 19 | def get(conn, invite_id) do 20 | DiscordEx.RestClient.resource(conn, :get, "invites/#{invite_id}") 21 | end 22 | 23 | @doc """ 24 | Delete invite object 25 | 26 | Requires the MANAGE_CHANNELS permission. Returns an invite object on success. 27 | 28 | ## Parameters 29 | 30 | - conn: User connection for REST holding auth info 31 | - invite_id: Invite id 32 | 33 | ## Examples 34 | 35 | Invite.delete(conn, "99999999993832") 36 | """ 37 | @spec delete(pid, String.t) :: map 38 | def delete(conn, invite_id) do 39 | DiscordEx.RestClient.resource(conn, :delete, "invites/#{invite_id}") 40 | end 41 | 42 | @doc """ 43 | Accept invite 44 | 45 | Accept an invite. This is not available to bot accounts, and requires the guilds.join OAuth2 scope to accept on behalf of normal users. 46 | 47 | ## Parameters 48 | 49 | - conn: User connection for REST holding auth info 50 | - invite_id: Invite id 51 | 52 | ## Examples 53 | 54 | Invite.accept(conn, "99999999993832") 55 | """ 56 | @spec accept(pid, String.t) :: map 57 | def accept(conn, invite_id) do 58 | DiscordEx.RestClient.resource(conn, :post, "invites/#{invite_id}") 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/examples/echo_bot.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.EchoBot do 2 | @moduledoc """ 3 | An all so original echo and ping bot! 4 | 5 | Type "!example:echo hello!" or "!example:ping" to get a reply back. 6 | """ 7 | require Logger 8 | 9 | alias DiscordEx.Client.Helpers.MessageHelper 10 | alias DiscordEx.RestClient.Resources.Channel 11 | 12 | # Message Handler 13 | def handle_event({:message_create, payload}, state) do 14 | spawn fn -> 15 | if MessageHelper.actionable_message_for?("bk-tester-bot", payload, state) do 16 | _command_parser(payload, state) 17 | end 18 | end 19 | {:ok, state} 20 | end 21 | 22 | # Fallback Handler 23 | def handle_event({event, _payload}, state) do 24 | Logger.info "Received Event: #{event}" 25 | {:ok, state} 26 | end 27 | 28 | # Select command to execute based off message payload 29 | defp _command_parser(payload, state) do 30 | case MessageHelper.msg_command_parse(payload) do 31 | {nil, msg} -> 32 | Logger.info("do nothing for message #{msg}") 33 | {cmd, msg} -> 34 | _execute_command({cmd, msg}, payload, state) 35 | end 36 | end 37 | 38 | # Echo response back to user or channel 39 | defp _execute_command({"example:echo", message}, payload, state) do 40 | msg = String.upcase(message) 41 | Channel.send_message(state[:rest_client], payload.data["channel_id"], %{content: "#{msg} yourself!"}) 42 | end 43 | 44 | # Pong response to ping 45 | defp _execute_command({"example:ping", _message}, payload, state) do 46 | Channel.send_message(state[:rest_client], payload.data["channel_id"], %{content: "Pong!"}) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :discord_ex, 6 | version: "1.1.18", 7 | elixir: "~> 1.5.2", 8 | name: "Discord Elixir", 9 | source_url: "https://github.com/rmcafee/discord_ex", 10 | build_embedded: Mix.env == :prod, 11 | start_permanent: Mix.env == :prod, 12 | package: package(), 13 | deps: deps(), 14 | docs: [extras: ["README.md"]]] 15 | end 16 | 17 | # Configuration for the OTP application 18 | # 19 | # Type "mix help compile.app" for more information 20 | def application do 21 | [applications: [:logger, :httpoison, :porcelain], 22 | env: [dca_rs_path: "/usr/local/bin/dca-rs"]] 23 | end 24 | 25 | # Dependencies can be Hex packages: 26 | # 27 | # {:mydep, "~> 0.3.0"} 28 | # 29 | # Or git/path repositories: 30 | # 31 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 32 | # 33 | # Type "mix help deps" for more examples and options 34 | defp deps do 35 | [ 36 | {:poison, "~> 2.0"}, 37 | {:websocket_client, "~> 1.2.4"}, 38 | {:httpoison, "~> 0.9.0"}, 39 | {:kcl, "~> 0.6.3"}, 40 | {:poly1305, "~> 0.4.2"}, 41 | {:socket, "~> 0.3.5"}, 42 | {:dns, "~> 0.0.3"}, 43 | {:porcelain, "~> 2.0.1"}, 44 | {:temp, "~> 0.4"}, 45 | {:credo, "~> 0.8.7", only: [:dev, :test]}, 46 | 47 | # Docs dependencies 48 | {:earmark, "~> 1.1", only: [:dev, :docs]}, 49 | {:ex_doc, "~> 0.16", only: [:dev, :docs]}, 50 | {:inch_ex, "~> 0.2", only: [:dev, :docs]} 51 | ] 52 | end 53 | 54 | defp package do 55 | [name: :discord_ex, 56 | description: "Library for use with the Discord REST and Realtime API", 57 | files: ["lib", "mix.exs", "README*", "LICENSE*"], 58 | maintainers: ["Rahsun McAfee"], 59 | licenses: ["MIT"], 60 | links: %{"GitHub" => "https://github.com/rmcafee/discord_ex"}] 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/discord_elixir/permissions_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.PermissionsTest do 2 | use ExUnit.Case 3 | doctest DiscordEx.Permissions 4 | 5 | alias DiscordEx.Permissions 6 | 7 | test "permissions are added correctly" do 8 | permissions = 9 | Permissions.add(:create_instant_invite) 10 | |> Permissions.add(:connect) 11 | |> Permissions.add(:speak) 12 | |> Permissions.add(:move_members) 13 | 14 | assert permissions == 19922945 15 | end 16 | 17 | test "permissions can be removed correctly" do 18 | permissions = 19 | Permissions.add(:create_instant_invite) 20 | |> Permissions.add(:connect) 21 | |> Permissions.add(:speak) 22 | |> Permissions.add(:move_members) 23 | |> Permissions.remove(:speak) 24 | |> Permissions.remove(:move_members) 25 | |> Permissions.remove(:create_instant_invite) 26 | 27 | assert permissions == 1048576 28 | end 29 | 30 | test "duplicate permissions don't alter final result" do 31 | permissions = 32 | Permissions.add(:deafen_members) 33 | |> Permissions.add(:deafen_members) 34 | |> Permissions.add(:connect) 35 | |> Permissions.add(:deafen_members) 36 | |> Permissions.add(:connect) 37 | 38 | assert permissions == 9437184 39 | end 40 | 41 | test "current permissions can be converted into a state map" do 42 | permissions = 43 | Permissions.add(:deafen_members) 44 | |> Permissions.add(:connect) 45 | |> Permissions.add(:read_messages) 46 | |> Permissions.add(:send_messages) 47 | |> Permissions.add(:embed_links) 48 | 49 | perm_map = Permissions.to_map(permissions) 50 | assert perm_map[:deafen_members] 51 | assert perm_map[:connect] 52 | assert perm_map[:read_messages] 53 | assert perm_map[:send_messages] 54 | assert perm_map[:embed_links] 55 | 56 | refute perm_map[:speak] 57 | refute perm_map[:move_members] 58 | refute perm_map[:create_instant_invite] 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/discord_ex/permissions.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Permissions do 2 | @moduledoc """ 3 | Easily assign permissions with this helper module. 4 | """ 5 | use Bitwise 6 | 7 | @flags %{ 8 | 0 => :create_instant_invite, # 1 9 | 1 => :kick_members, # 2 10 | 2 => :ban_members, # 4 11 | 3 => :manage_roles, # 8, 12 | 4 => :manage_channels, # 16 13 | 5 => :manage_server, # 32 14 | 10 => :read_messages, # 1024 15 | 11 => :send_messages, # 2048 16 | 12 => :send_tts_messages, # 4096 17 | 13 => :manage_messages, # 8192 18 | 14 => :embed_links, # 16384 19 | 15 => :attach_files, # 32768 20 | 16 => :read_message_history, # 65536 21 | 17 => :mention_everyone, # 131072 22 | 20 => :connect, # 1048576 23 | 21 => :speak, # 2097152 24 | 22 => :mute_members, # 4194304 25 | 23 => :deafen_members, # 8388608 26 | 24 => :move_members, # 16777216 27 | 25 => :use_voice_activity # 33554432 28 | } 29 | 30 | @doc "Add any permission to an existing set of permissions and return the complete permission value." 31 | @spec add(integer, atom) :: integer 32 | def add(existing_permissions \\ 0, new_permission) do 33 | permission = _perm_to_value(new_permission) 34 | existing_permissions |> bor(permission) 35 | end 36 | 37 | @doc "Remove any permission from an existing set of permissions and return updated value." 38 | @spec remove(integer, atom) :: integer 39 | def remove(existing_permissions, new_permission) do 40 | permission = _perm_to_value(new_permission) 41 | existing_permissions |> band(bnot(permission)) 42 | end 43 | 44 | @doc "Take current permission value and convert it to a map of permissions." 45 | @spec to_map(integer) :: map 46 | def to_map(permissions) do 47 | Enum.into @flags, %{}, fn(val) -> 48 | {v, k} = val 49 | value = permissions |> bsr(v) |> band(0x1) 50 | {k, value == 1} 51 | end 52 | end 53 | 54 | @spec _perm_to_value(atom) :: integer 55 | defp _perm_to_value(permission_key) do 56 | {value, _} = Enum.find @flags, 57 | fn({_k, v}) -> v == permission_key 58 | end 59 | bsl(1,value) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/discord_elixir/voice/buffer_test.exs: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.BufferTest do 2 | use ExUnit.Case 3 | doctest DiscordEx.Voice.Buffer 4 | 5 | alias DiscordEx.Voice.Buffer 6 | 7 | setup do 8 | buffer = Buffer.start 9 | {:ok, buffer: buffer} 10 | end 11 | 12 | test "write and read from buffer", state do 13 | binary = <<1,2,3,4>> 14 | Buffer.write(state[:buffer], binary) 15 | assert Buffer.read(state[:buffer], bit_size(binary)) == <<1,2,3,4>> 16 | assert Buffer.read(state[:buffer], 8) == "" 17 | end 18 | 19 | test "write and read from buffer with result size in bytes", state do 20 | Buffer.write(state[:buffer], <<130, 35>>) 21 | Buffer.write(state[:buffer], <<0::size(72720)>>) 22 | assert Buffer.read(state[:buffer], 16, :integer) == 9090 23 | end 24 | 25 | test "buffer size", state do 26 | Buffer.write(state[:buffer], <<0::size(32)>>) 27 | assert Buffer.size(state[:buffer]) == 32 28 | end 29 | 30 | test "buffer dump", state do 31 | Buffer.write(state[:buffer], <<0::size(32)>>) 32 | result = Buffer.dump(state[:buffer]) 33 | assert Buffer.size(state[:buffer]) == 0 34 | assert result, <<0::size(32)>> 35 | end 36 | 37 | test "buffer drain", state do 38 | Buffer.write(state[:buffer], <<1>>) 39 | Buffer.write(state[:buffer], <<2>>) 40 | Buffer.write(state[:buffer], <<3>>) 41 | Buffer.drain state[:buffer], 8, fn(data, time) -> 42 | send self(), {data, time} 43 | end 44 | 45 | assert_received {<<1>>, 0} 46 | assert_received {<<2>>, 0} 47 | assert_received {<<3>>, 0} 48 | 49 | assert Buffer.read(state[:buffer], 8) == "" 50 | end 51 | 52 | test "buffer drain on a dca file", state do 53 | p1_size_in_bits = 2500 * 8 54 | p2_size_in_bits = 5000 * 8 55 | p1_header = 2500 56 | p2_header = 5000 57 | packet_1 = :binary.encode_unsigned(p1_header, :little) <> <<0::size(p1_size_in_bits)>> 58 | packet_2 = :binary.encode_unsigned(p2_header, :little) <> <<0::size(p2_size_in_bits)>> 59 | 60 | Buffer.write(state[:buffer], packet_1) 61 | Buffer.write(state[:buffer], packet_2) 62 | 63 | assert Buffer.size(state[:buffer]) == 60032 64 | 65 | Buffer.drain_dca state[:buffer], fn(data, time) -> 66 | send self(), {data, time} 67 | end 68 | 69 | assert_received {<<0::size(p1_size_in_bits)>>, 0} 70 | assert_received {<<0::size(p2_size_in_bits)>>, 0} 71 | 72 | assert Buffer.size(state[:buffer]) == 0 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/discord_ex/client/heartbeat.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Heartbeat do 2 | @moduledoc """ 3 | Heartbeat service for Discord websocket connection. 4 | Sends heartbeat on interval and detects stale connection if heartbeat ack 5 | is not received. 6 | """ 7 | use GenServer 8 | require Logger 9 | import DiscordEx.Client, only: [opcodes: 0] 10 | import DiscordEx.Client.Utility 11 | 12 | # API 13 | 14 | def start_link(agent_seq_num, interval, socket_pid, opts \\ []) do 15 | GenServer.start_link(__MODULE__, {agent_seq_num, interval, socket_pid}, opts) 16 | end 17 | 18 | def reset(pid) do 19 | GenServer.call(pid, :reset) 20 | end 21 | 22 | def ack(pid) do 23 | GenServer.call(pid, :ack) 24 | end 25 | 26 | # Server 27 | 28 | def init({agent_seq_num, interval, socket_pid}) do 29 | state = %{ 30 | agent_seq_num: agent_seq_num, 31 | interval: interval, 32 | socket_pid: socket_pid, 33 | timer: nil, 34 | ack?: true, 35 | } 36 | # initial beat with ack?=true 37 | send(self(), :beat) 38 | {:ok, state} 39 | end 40 | 41 | @doc "Heartbeat ACK has been received, sends new heartbeat down the wire" 42 | def handle_info(:beat, %{interval: interval, socket_pid: socket_pid, ack?: true} = state) do 43 | value = agent_value(state[:agent_seq_num]) 44 | payload = payload_build(opcode(opcodes(), :heartbeat), value) 45 | :websocket_client.cast(socket_pid, {:binary, payload}) 46 | timer = Process.send_after(self(), :beat, interval) 47 | {:noreply, %{state | ack?: false, timer: timer}} 48 | end 49 | 50 | @doc "Heartbeat ACK not received, connection is stale. Stop heartbeat." 51 | def handle_info(:beat, %{socket_pid: socket_pid, ack?: false} = state) do 52 | send(socket_pid, :heartbeat_stale) 53 | {:noreply, %{state | timer: nil}} 54 | end 55 | 56 | @doc "Receive heartbeat ACK" 57 | def handle_call(:ack, _from, state) do 58 | {:reply, :ok, %{state | ack?: true}} 59 | end 60 | 61 | @doc "Reset heartbeat" 62 | def handle_call(:reset, _from, %{timer: nil} = state) do 63 | send(self(), :beat) 64 | {:reply, :ok, %{state | ack?: true}} 65 | end 66 | def handle_call(:reset, _from, %{timer: timer} = state) do 67 | Process.cancel_timer(timer) 68 | send(self(), :beat) 69 | {:reply, :ok, %{state | ack?: true, timer: nil}} 70 | end 71 | 72 | def handle_call(msg, _from, state) do 73 | Logger.debug(fn -> "Heartbeat called with invalid message #{inspect msg}" end) 74 | {:noreply, state} 75 | end 76 | 77 | end 78 | -------------------------------------------------------------------------------- /doc/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 71 | 84 |
85 |
86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /lib/discord_ex/client/utility.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Client.Utility do 2 | @moduledoc """ 3 | Utilty methods to be used for discord clients. 4 | 5 | Normalizers, Encoders, and Decoders 6 | """ 7 | 8 | @doc "Convert atom to string" 9 | @spec normalize_atom(atom) :: String.t 10 | def normalize_atom(atom) do 11 | atom |> Atom.to_string |> String.downcase |> String.to_atom 12 | end 13 | 14 | @doc "Build a binary payload for discord communication" 15 | @spec payload_build(number, map, number, String.t) :: binary 16 | def payload_build(op, data, seq_num \\ nil, event_name \\ nil) do 17 | load = %{"op" => op, "d" => data} 18 | load 19 | |> _update_payload(seq_num, "s", seq_num) 20 | |> _update_payload(event_name, "t", seq_num) 21 | |> :erlang.term_to_binary 22 | end 23 | 24 | @doc "Build a json payload for discord communication" 25 | @spec payload_build_json(number, map, number, String.t) :: binary 26 | def payload_build_json(op, data, seq_num \\ nil, event_name \\ nil) do 27 | load = %{"op" => op, "d" => data} 28 | load 29 | |> _update_payload(seq_num, "s", seq_num) 30 | |> _update_payload(event_name, "t", seq_num) 31 | |> Poison.encode! 32 | end 33 | 34 | @doc "Decode binary payload received from discord into a map" 35 | @spec payload_decode(list, {atom, binary}) :: map 36 | def payload_decode(codes, {:binary, payload}) do 37 | payload = :erlang.binary_to_term(payload) 38 | %{op: opcode(codes, payload[:op] || payload["op"]), data: (payload[:d] || payload["d"]), seq_num: (payload[:s] || payload["s"]), event_name: (payload[:t] || payload["t"])} 39 | end 40 | 41 | @doc "Decode json payload received from discord into a map" 42 | @spec payload_decode(list, {atom, binary}) :: map 43 | def payload_decode(codes, {:text, payload}) do 44 | payload = Poison.decode!(payload) 45 | %{op: opcode(codes, payload[:op] || payload["op"]), data: (payload[:d] || payload["d"]), seq_num: (payload[:s] || payload["s"]), event_name: (payload[:t] || payload["t"])} 46 | end 47 | 48 | @doc "Get the integer value for an opcode using it's name" 49 | @spec opcode(map, atom) :: integer 50 | def opcode(codes, value) when is_atom(value) do 51 | codes[value] 52 | end 53 | 54 | @doc "Get the atom value of and opcode using an integer value" 55 | @spec opcode(map, integer) :: atom 56 | def opcode(codes, value) when is_integer(value) do 57 | {k, _value} = Enum.find codes, fn({_key, v}) -> v == value end 58 | k 59 | end 60 | 61 | @doc "Generic function for getting the value from an agent process" 62 | @spec agent_value(pid) :: any 63 | def agent_value(agent) do 64 | Agent.get(agent, fn a -> a end) 65 | end 66 | 67 | @doc "Generic function for updating the value of an agent process" 68 | @spec agent_update(pid, any) :: nil 69 | def agent_update(agent, n) do 70 | if n != nil do 71 | Agent.update(agent, fn _a -> n end) 72 | end 73 | end 74 | 75 | # Makes it easy to just update and pipe a payload 76 | defp _update_payload(load, var, key, value) do 77 | if var do 78 | Map.put(load, key, value) 79 | else 80 | load 81 | end 82 | end 83 | 84 | end 85 | -------------------------------------------------------------------------------- /doc/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 404 – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

Page not found

70 | 71 |

Sorry, but the page you were trying to get to, does not exist. You 72 | may want to try searching this site using the sidebar or using our 73 | API Reference page to find what 74 | you were looking for.

75 | 76 | 89 |
90 |
91 |
92 |
93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /lib/discord_ex/voice/udp.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.UDP do 2 | @moduledoc """ 3 | Voice UDP Setup functions 4 | """ 5 | @spec self_discovery(String.t, number, number) :: {tuple, number} 6 | def self_discovery(endpoint, discord_port, ssrc) do 7 | discord_ip = resolve(endpoint) 8 | 9 | # Setup to send discovery packet 10 | data = << ssrc :: size(560) >> 11 | 12 | # Random UDP for connection and send discovery packet 13 | udp_options = [:binary, active: false, reuseaddr: true] 14 | {:ok, send_socket} = :gen_udp.open(0, udp_options) 15 | :gen_udp.send(send_socket, discord_ip, discord_port, data) 16 | 17 | # Receive the returned discovery data 18 | {:ok, discovery_data} = :gen_udp.recv(send_socket,70) 19 | discord_data = discovery_data |> Tuple.to_list |> List.last 20 | 21 | << _padding :: size(32), 22 | my_ip :: bitstring-size(112), 23 | _null_data :: size(400), 24 | my_port :: size(16) >> = discord_data 25 | 26 | {my_ip, my_port, discord_ip, discord_port, send_socket} 27 | end 28 | 29 | @spec send_audio(binary, pid, number, number) :: atom 30 | def send_audio(data, voice_client, sequence, time) do 31 | settings = _extract_voice_settings(voice_client) 32 | header = _header(sequence, time, settings[:ssrc]) 33 | packet = header <> _encrypt_audio_packet(header, data, settings[:secret]) 34 | :gen_udp.send(settings[:send_socket], settings[:address], settings[:port], packet) 35 | end 36 | 37 | defp resolve(endpoint) do 38 | try do 39 | endpoint |> String.replace(":80","") |> DNS.resolve 40 | rescue 41 | _e -> resolve(endpoint) 42 | end 43 | end 44 | 45 | defp _extract_voice_settings(vc) do 46 | %{send_socket: _send_socket(vc), 47 | address: _address(vc), 48 | port: _port(vc), 49 | ssrc: _ssrc(vc), 50 | secret: _secret_key(vc)} 51 | end 52 | 53 | defp _header(sequence, time, ssrc) do 54 | <<0x80::size(8), 0x78::size(8), sequence::size(16), time::size(32), ssrc::size(32)>> 55 | end 56 | 57 | defp _encrypt_audio_packet(header, data, secret) do 58 | nonce = (header <> <<0::size(96)>>) 59 | Kcl.secretbox(data, nonce, secret) 60 | end 61 | 62 | defp _secret_key(voice_client) do 63 | task = Task.async fn -> 64 | send(voice_client, {:get_state, :secret_key, self()}) 65 | receive do 66 | {:secret_key, value} -> :erlang.list_to_binary(value) 67 | end 68 | end 69 | Task.await(task, 10_000) 70 | end 71 | 72 | defp _port(voice_client) do 73 | task = Task.async fn -> 74 | send(voice_client, {:get_state, "port", self()}) 75 | receive do 76 | {"port", value} -> value 77 | end 78 | end 79 | Task.await(task, 10_000) 80 | end 81 | 82 | defp _address(voice_client) do 83 | task = Task.async fn -> 84 | send(voice_client, {:get_state, :udp_ip_send, self()}) 85 | receive do 86 | {:udp_ip_send, value} -> Socket.Address.parse(value) 87 | end 88 | end 89 | Task.await(task, 10_000) 90 | end 91 | 92 | defp _ssrc(voice_client) do 93 | task = Task.async fn -> 94 | send(voice_client, {:get_state, "ssrc", self()}) 95 | receive do 96 | {"ssrc", value} -> value 97 | end 98 | end 99 | Task.await(task, 10_000) 100 | end 101 | 102 | def _send_socket(voice_client) do 103 | task = Task.async fn -> 104 | send(voice_client, {:get_state, :udp_socket_send, self()}) 105 | receive do 106 | {:udp_socket_send, socket} -> socket 107 | end 108 | end 109 | Task.await(task, 5000) 110 | end 111 | 112 | end 113 | -------------------------------------------------------------------------------- /lib/discord_ex/rest_client/rest_client.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.RestClient do 2 | @moduledoc """ 3 | Discord RestClient. Used a GenServer so that you can have multiple 4 | clients in one application. 5 | """ 6 | use GenServer 7 | 8 | alias DiscordEx.Connections.REST 9 | 10 | @typedoc """ 11 | Response body with related options 12 | """ 13 | @type request_reply :: {atom, map, map} 14 | 15 | # GenServer API 16 | 17 | @doc """ 18 | Start process and HTTP Client. 19 | {:ok, conn} = DiscordEx.RestClient.start_link(%{token: token}) 20 | """ 21 | def start_link(opts, server_opts \\ []) do 22 | GenServer.start_link(__MODULE__, opts, server_opts) 23 | end 24 | 25 | @spec resource(pid, atom, String.t, map) :: request_reply 26 | def resource(connection, method, path, payload \\ nil) do 27 | GenServer.call connection, {:resource, method, path, payload} 28 | end 29 | 30 | # Server Callbacks 31 | 32 | def init(:ok, opts) do 33 | REST.start 34 | opts 35 | end 36 | 37 | def handle_call({:resource, :get, path, nil}, _from, opts) do 38 | response = REST.get!("/#{path}",%{"Authorization" => opts.token}) 39 | {:reply, response.body, opts} 40 | end 41 | 42 | def handle_call({:resource, :get, path, payload}, _from, opts) do 43 | response = REST.get!("/#{path}", %{"Authorization" => opts.token}, params: payload) 44 | {:reply, response.body, opts} 45 | end 46 | 47 | def handle_call({:resource, :post, path, nil}, _from, opts) do 48 | response = REST.post!("/#{path}", %{"Authorization" => opts.token}) 49 | {:reply, response.body, opts} 50 | end 51 | 52 | def handle_call({:resource, :post, path, payload}, _from, opts) do 53 | response = REST.post!("/#{path}", payload, %{"Authorization" => opts.token, "Content-Type" => "application/json"}) 54 | {:reply, response.body, opts} 55 | end 56 | 57 | def handle_call({:resource, :post_multipart, path, payload}, _from, opts) do 58 | payload_no_file = payload 59 | |> Map.drop([:file]) 60 | |> _map_atom_to_string 61 | |> Enum.into(Keyword.new) 62 | 63 | file_formatted = _format_file(payload[:file]) 64 | combined_data = Enum.concat(payload_no_file, file_formatted) 65 | 66 | response = REST.post!("/#{path}", {:multipart, combined_data}, %{"Authorization" => opts.token, "Content-type" => "multipart/form-data"}) 67 | {:reply, response.body, opts} 68 | end 69 | 70 | def handle_call({:resource, :patch_form, path, payload}, _from, opts) do 71 | response = REST.patch!("/#{path}", {:form, Enum.into(payload, Keyword.new)}, %{"Authorization" => opts.token, "Content-type" => "application/x-www-form-urlencoded"}) 72 | {:reply, response.body, opts} 73 | end 74 | 75 | def handle_call({:resource, :patch, path, payload}, _from, opts) do 76 | response = REST.patch!("/#{path}", payload, %{"Authorization" => opts.token, "Content-type" => "application/json"}) 77 | {:reply, response.body, opts} 78 | end 79 | 80 | def handle_call({:resource, :put, path, payload}, _from, opts) do 81 | response = REST.put!("/#{path}", payload, %{"Authorization" => opts.token}) 82 | {:reply, response.body, opts} 83 | end 84 | 85 | def handle_call({:resource, :delete, path, nil}, _from, opts) do 86 | response = REST.delete!("/#{path}",%{"Authorization" => opts.token}) 87 | {:reply, response.body, opts} 88 | end 89 | 90 | defp _format_file(file_path) do 91 | filename_full = file_path |> String.split("/") |> List.last 92 | filename = filename_full |> String.split(".") |> List.first 93 | [ 94 | {:file, file_path, {["form-data"], [name: filename, filename: filename_full]},[]} 95 | ] 96 | end 97 | 98 | defp _map_atom_to_string(atom_key_map) do 99 | for {key, val} <- atom_key_map, into: %{}, do: {Atom.to_string(key), val} 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /lib/discord_ex/voice/buffer.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.Buffer do 2 | @moduledoc """ 3 | Buffer Module for holding and reading audio. 4 | """ 5 | @doc "Create a new queue" 6 | @spec start :: pid 7 | def start do 8 | {:ok, queue} = Agent.start_link fn -> <<>> end 9 | queue 10 | end 11 | 12 | @doc "Write to the buffer/queue binary data" 13 | @spec write(pid, binary) :: atom 14 | def write(queue, new_data) do 15 | data = new_data |> :erlang.binary_to_list |> Enum.reverse |> :erlang.list_to_binary 16 | Agent.update(queue, fn(existing_data) -> (data <> existing_data) end) 17 | end 18 | 19 | @doc "Read off of the buffer based on a set bit size" 20 | @spec read(pid, integer) :: binary 21 | def read(queue, size_in_bits) do 22 | data = Agent.get(queue, fn data -> data end) 23 | {remaining_data, capture_data} = _slice_data_in_bits(data, size_in_bits) 24 | Agent.update(queue, fn(_existing_data) -> remaining_data end) 25 | capture_data |> :erlang.binary_to_list |> Enum.reverse |> :erlang.list_to_binary 26 | end 27 | 28 | @doc "Read off of the buffer based on a set bit size and return the integer format" 29 | @spec read(pid, integer, atom) :: binary 30 | def read(queue, size_in_bits, :integer) do 31 | data = Agent.get(queue, fn data -> data end) 32 | if data != "" do 33 | {remaining_data, capture_data} = _slice_data_in_bits(data, size_in_bits, :integer) 34 | Agent.update(queue, fn(_existing_data) -> remaining_data end) 35 | capture_data 36 | else 37 | 0 38 | end 39 | end 40 | 41 | @doc "Drain the buffer based off the bit size and apply the result to the function - you don't actually have to use time to make use of this" 42 | @spec drain(pid, integer, function) :: binary 43 | def drain(queue, size_in_bits, function, time \\ 0) do 44 | data = read(queue, size_in_bits) 45 | 46 | unless data == <<>> do 47 | function.(data, time) 48 | drain(queue, size_in_bits, function, time) 49 | end 50 | end 51 | 52 | @doc "Drain the buffer which is assumed to contain just a DCA file with opus packets which have a header that dictate the size of a frame and the packets passed to the function" 53 | @spec drain_dca(pid, function, integer) :: binary 54 | def drain_dca(queue, function, time \\ 0) do 55 | packet_size_in_bytes = read(queue, 16, :integer) 56 | if packet_size_in_bytes != "" && packet_size_in_bytes != 0 do 57 | data = read(queue, packet_size_in_bytes * 8) 58 | unless data == <<>> do 59 | function.(data, time) 60 | drain_dca(queue, function, time) 61 | end 62 | else 63 | data = read(queue, 9_999_999_999_999) 64 | function.(data, time) 65 | end 66 | end 67 | 68 | @doc "Get the size of the buffer" 69 | @spec size(pid) :: integer 70 | def size(queue) do 71 | queue |> Agent.get(fn data -> data end) |> bit_size 72 | end 73 | 74 | @doc "Dump everything out of the buffer" 75 | @spec dump(pid) :: atom 76 | def dump(queue) do 77 | Agent.get(queue, fn data -> data end) 78 | Agent.update(queue, fn(_existing_data) -> <<>> end) 79 | end 80 | 81 | # For binary data 82 | defp _slice_data_in_bits(data, limit_in_bits) when (bit_size(data) >= limit_in_bits) do 83 | top_size = bit_size(data) - limit_in_bits 84 | << remaining_data::bitstring-size(top_size), capture_data::binary >> = data 85 | {remaining_data, capture_data} 86 | end 87 | 88 | # Use to handle empty data 89 | defp _slice_data_in_bits(data, _limit_in_bits) do 90 | {<<>>, data} 91 | end 92 | 93 | # For packet size information specifically using opus packets 94 | defp _slice_data_in_bits(data, limit_in_bits, :integer) do 95 | top_size = bit_size(data) - limit_in_bits 96 | # somehow this is coming in as a big unsigned integer instead of a little one 97 | # this may be happening in DCA conversion 98 | << remaining_data::bitstring-size(top_size), capture_data::big-unsigned-integer-size(limit_in_bits) >> = data 99 | {remaining_data, capture_data} 100 | end 101 | end 102 | -------------------------------------------------------------------------------- /doc/fonts/icomoon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by IcoMoon 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /lib/discord_ex/voice/controller.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.Controller do 2 | @moduledoc """ 3 | Voice control to make voice interaction a lot easier. 4 | """ 5 | alias DiscordEx.Voice.Buffer 6 | alias DiscordEx.Voice.Encoder 7 | alias DiscordEx.Voice.UDP 8 | 9 | require Logger 10 | 11 | def listen_socket(voice_client) do 12 | task = Task.async fn -> 13 | send(voice_client, {:get_state, :udp_socket_recv, self()}) 14 | receive do 15 | {:udp_socket_recv, socket} -> socket 16 | end 17 | end 18 | Task.await(task, 5000) 19 | end 20 | 21 | def start(voice_client) do 22 | {:ok, seq} = Agent.start_link(fn -> :rand.uniform(93_920_290) end) 23 | {:ok, time} = Agent.start_link(fn -> :rand.uniform(83_290_239) end) 24 | 25 | %{buffer: Buffer.start(), 26 | voice_client: voice_client, 27 | sequence: seq, 28 | time: time} 29 | end 30 | 31 | @doc """ 32 | Stop audio from playing in channel and clear buffer 33 | 34 | ## Parameters 35 | 36 | - voice_client: The voice client so the library knows how to play it and where to 37 | 38 | ## Examples 39 | 40 | DiscordEx.Controller.stop(voice_client) 41 | """ 42 | def stop(voice_client) do 43 | controller = _get_controller(voice_client) 44 | Buffer.dump(controller.buffer) 45 | send(controller.voice_client, {:speaking, false}) 46 | end 47 | 48 | @doc """ 49 | Play some audio to a channel 50 | 51 | ## Parameters 52 | 53 | - voice_client: The voice client so the library knows how to play it and where to 54 | - path: The path where your audio file lives 55 | - opts: Options like volume 56 | 57 | ## Examples 58 | 59 | DiscordEx.Controller.play(voice_client, "/my/awesome/audio.wav", %{volume: 128}) 60 | """ 61 | def play(voice_client, path, opts \\ %{}) when is_bitstring(path) do 62 | controller = _get_controller(voice_client) 63 | if Buffer.size(controller.buffer) == 0 do 64 | options = %{volume: 128} 65 | merged_options = Map.merge(options, opts) 66 | _play_io(controller, Encoder.encode_file(path, merged_options)) 67 | else 68 | Logger.info "Tried to play audio but audio already playing." 69 | end 70 | end 71 | 72 | defp _play_io(controller, io_data) do 73 | # Fill Buffer 74 | Enum.each io_data, fn(d) -> Buffer.write(controller.buffer, d) end 75 | 76 | try do 77 | send(controller.voice_client, {:speaking, true}) 78 | 79 | Buffer.drain_dca controller.buffer, fn(data, _time) -> 80 | last_time = :os.system_time(:milli_seconds) 81 | UDP.send_audio(data, 82 | controller.voice_client, 83 | _read_agent(controller.sequence), 84 | _read_agent(controller.time)) 85 | _increment_agent(controller.sequence, 1) 86 | _increment_agent(controller.time, 960) 87 | :timer.sleep _sleep_timer(:os.system_time(:milli_seconds), last_time) 88 | end 89 | 90 | # Send 5 frames of silence 91 | Enum.each (0..5), fn(_) -> 92 | silence = <<0xF8, 0xFF, 0xFE>> 93 | UDP.send_audio(silence, 94 | controller.voice_client, 95 | _read_agent(controller.sequence), 96 | _read_agent(controller.time)) 97 | _increment_agent(controller.sequence, 1) 98 | _increment_agent(controller.time, 960) 99 | :timer.sleep 20 100 | end 101 | 102 | send(controller.voice_client, {:speaking, false}) 103 | rescue 104 | Error -> 105 | Logger.error("Something went wrong while playing audio!") 106 | send(controller.voice_client, {:speaking, false}) 107 | end 108 | end 109 | 110 | defp _sleep_timer(now_time, last_time, delay_time \\ 17) do 111 | if now_time - last_time < delay_time do 112 | delay_time - (now_time - last_time) 113 | else 114 | 0 115 | end 116 | end 117 | 118 | defp _read_agent(pid) do 119 | Agent.get(pid, fn data -> data end) 120 | end 121 | 122 | defp _increment_agent(pid, incr) do 123 | Agent.update(pid, fn(current_number) -> (current_number + incr) end) 124 | end 125 | 126 | defp _get_controller(vc) do 127 | task = Task.async fn -> 128 | send(vc, {:get_controller, self()}) 129 | receive do controller -> controller end 130 | end 131 | Task.await(task, 5000) 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /lib/discord_ex/client/helpers/message_helper.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Client.Helpers.MessageHelper do 2 | @moduledoc """ 3 | Bot Message Helpers 4 | """ 5 | alias DiscordEx.RestClient.Resources.User 6 | 7 | # Message Helper Functions 8 | 9 | @doc """ 10 | Actionable Mention and DM Message 11 | 12 | This checks that an incoming message is private or is a mention to the defined user. 13 | 14 | ## Parameters 15 | 16 | - bot_name: Name of the bot you are using. 17 | - payload: Data from the triggered event. 18 | - state: Current state of bot. 19 | 20 | ## Example 21 | 22 | MessageHelper.actionable_message_for?("Mr.Botman", payload, state) 23 | #=> true 24 | """ 25 | @spec actionable_message_for?(String.t, map, map) :: boolean 26 | def actionable_message_for?(bot_name, payload, state) do 27 | author_name = payload.data["author"]["username"] 28 | channel_id = payload.data["channel_id"] 29 | mentions = payload.data["mentions"] 30 | 31 | if author_name != bot_name do 32 | _message_in_private_channel?(channel_id, state) || _message_mentions_user?(mentions, bot_name) 33 | else 34 | false 35 | end 36 | end 37 | 38 | @doc """ 39 | Actionable Mention and DM Message 40 | This checks that an incoming message is private or is a mention to the current user. 41 | ## Parameters 42 | - payload: Data from the triggered event. 43 | - state: Current state of bot. 44 | ## Example 45 | MessageHelper.actionable_message_for_me?(payload, state) 46 | #=> true 47 | """ 48 | @spec actionable_message_for_me?(map, map) :: boolean 49 | def actionable_message_for_me?(payload, state) do 50 | 51 | author_id = payload.data["author"]["id"] 52 | channel_id = payload.data["channel_id"] 53 | mentions = payload.data["mentions"] 54 | 55 | if author_id != state[:client_id] do 56 | _message_in_private_channel?(channel_id, state) || _message_mentions_me?(mentions, state) 57 | else 58 | false 59 | end 60 | end 61 | 62 | @doc """ 63 | Parses a message payload which is content leading with '!'. 64 | Returns a tuple with the command and the message. 65 | 66 | ## Parameters 67 | 68 | - payload: Data from the triggered event. 69 | 70 | ## Example 71 | 72 | MessageHelper.msg_command_parse(payload) 73 | #=> {"ping", "me please!"} 74 | """ 75 | @spec msg_command_parse(map) :: {String.t, String.t} 76 | def msg_command_parse(payload) do 77 | cmd = case Regex.scan(~r/!(.\w*:\w*:\w*|.\w*:\w*|.\w*){1}/, payload.data["content"]) do 78 | [] -> nil 79 | result -> result |> List.last |> List.last 80 | end 81 | msg = 82 | payload.data["content"] 83 | |> String.replace("!#{cmd}", "") 84 | |> String.trim 85 | 86 | {cmd, msg} 87 | end 88 | 89 | @doc """ 90 | Parses a message payload which is content leading with provided prefix. 91 | Returns a tuple with the command and the message. 92 | 93 | ## Parameters 94 | 95 | - prefix: prefix for your command 96 | - payload: Data from the triggered event. 97 | 98 | ## Example 99 | 100 | MessageHelper.msg_command_parse(payload, "!") 101 | #=> {"ping", "me please!"} 102 | """ 103 | @spec msg_command_parse(map, String) :: {String.t, String.t} 104 | def msg_command_parse(payload, prefix) do 105 | content = payload.data["content"] 106 | if String.starts_with?(content, prefix) do 107 | tmp = _take_prefix(content, prefix) 108 | [cmd|tail] = String.split(tmp, ~r/\s/u, parts: 2) 109 | msg = List.first(tail) 110 | {cmd, msg} 111 | else 112 | {nil, content} 113 | end 114 | end 115 | 116 | # Private Functions 117 | 118 | # fast, as proposed in Elixir documentation 119 | defp _take_prefix(full, prefix) do 120 | base = byte_size(prefix) 121 | <<_::binary-size(base), rest::binary>> = full 122 | rest 123 | end 124 | 125 | defp _message_mentions_user?(mentions, username) do 126 | Enum.find(mentions, fn(m) -> m["username"] == username end) != nil 127 | end 128 | 129 | defp _message_in_private_channel?(channel_id, state) do 130 | state[:rest_client] 131 | |> User.dm_channels 132 | |> Enum.find(fn(c) -> String.to_integer(c["id"]) == channel_id end) != nil 133 | end 134 | 135 | defp _message_mentions_me?(mentions, state) do 136 | Enum.find(mentions, fn(m) -> m["id"] == state[:client_id] end) != nil 137 | end 138 | end 139 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"atomic_map": {:hex, :atomic_map, "0.9.0"}, 2 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, 3 | "certifi": {:hex, :certifi, "0.4.0", "a7966efb868b179023618d29a407548f70c52466bf1849b9e8ebd0e34b7ea11f", [:rebar3], [], "hexpm"}, 4 | "chacha20": {:hex, :chacha20, "0.3.3", "84f579b53a0f21375ae10192e9d1f6f01f7f9de374bdb6b097195df1eb08e087", [:mix], [], "hexpm"}, 5 | "credo": {:hex, :credo, "0.8.7", "b1aad9cd3aa7acdbaea49765bfc9f1605dc4555023a037dc9ea7a70539615bc8", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "curve25519": {:hex, :curve25519, "0.1.1", "51bc1d6eacfb8107142dd59a36f2dd0277ae8ec7869624d1e9136fec8292cf31", [:mix], [], "hexpm"}, 7 | "dns": {:hex, :dns, "0.0.3", "d2acbeeb105deb2692faeedccf65d3512c88fea8118f0451f926de06f1f7e0ad", [:mix], [], "hexpm"}, 8 | "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"}, 9 | "ed25519": {:hex, :ed25519, "0.2.2", "8ffe7660687bc51c2687b6f2ed9281de159900a62aa28a71323803ffb9c433c3", [:mix], [], "hexpm"}, 10 | "enacl": {:git, "https://github.com/jlouis/enacl.git", "2ee171bcbf9fae247dc838227fc69296368f7d52", []}, 11 | "equivalex": {:hex, :equivalex, "0.1.1", "af8d2c2aa09d46a5de7f8fa64b3ee436171520b9eee96eb5bcb8d7e48d42e522", [:mix], [], "hexpm"}, 12 | "ex_doc": {:hex, :ex_doc, "0.18.1", "37c69d2ef62f24928c1f4fdc7c724ea04aecfdf500c4329185f8e3649c915baf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, 13 | "hackney": {:hex, :hackney, "1.6.1", "ddd22d42db2b50e6a155439c8811b8f6df61a4395de10509714ad2751c6da817", [:rebar3], [{:certifi, "0.4.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "1.2.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, 14 | "httpoison": {:hex, :httpoison, "0.9.0", "68187a2daddfabbe7ca8f7d75ef227f89f0e1507f7eecb67e4536b3c516faddb", [:mix], [{:hackney, "~> 1.6.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, 15 | "idna": {:hex, :idna, "1.2.0", "ac62ee99da068f43c50dc69acf700e03a62a348360126260e87f2b54eced86b2", [:rebar3], [], "hexpm"}, 16 | "inch_ex": {:hex, :inch_ex, "0.5.3", "39f11e96181ab7edc9c508a836b33b5d9a8ec0859f56886852db3d5708889ae7", [:mix], [{:poison, "~> 1.5 or ~> 2.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, 17 | "kcl": {:hex, :kcl, "0.6.3", "29477171d12ee85f14d7d13539e61fbb62c38b78a9ea990dabff015377146a10", [:mix], [{:curve25519, "~> 0.1", [hex: :curve25519, repo: "hexpm", optional: false]}, {:ed25519, "~> 0.2", [hex: :ed25519, repo: "hexpm", optional: false]}, {:poly1305, "~> 0.4", [hex: :poly1305, repo: "hexpm", optional: false]}, {:salsa20, "~> 0.3", [hex: :salsa20, repo: "hexpm", optional: false]}], "hexpm"}, 18 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, 19 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, 20 | "nacl": {:hex, :nacl, "0.3.0", "aa7009e7d72c91948b0fefc707b4d81bb23bf54d8e9908ca6dfc6d26f34a114e", [:rebar], []}, 21 | "poison": {:hex, :poison, "2.2.0", "4763b69a8a77bd77d26f477d196428b741261a761257ff1cf92753a0d4d24a63", [:mix], [], "hexpm"}, 22 | "poly1305": {:hex, :poly1305, "0.4.2", "92f0459edb6a2ac5ff2c2fd05bb61c6f9a3f2e22f83a77b0e7663168a450f34c", [:mix], [{:chacha20, "~> 0.3", [hex: :chacha20, repo: "hexpm", optional: false]}, {:equivalex, "~> 0.1", [hex: :equivalex, repo: "hexpm", optional: false]}], "hexpm"}, 23 | "porcelain": {:hex, :porcelain, "2.0.1", "9c3db2b47d8cf6879c0d9ac79db8657333974a88faff09e856569e00c1b5e119", [:mix], [], "hexpm"}, 24 | "salsa20": {:hex, :salsa20, "0.3.1", "684a635b87c71fc8e8e0a21629d22f56944e2f032cc650f542c85e6bae38fc19", [:mix], [], "hexpm"}, 25 | "savory": {:hex, :savory, "0.0.2", "edad30ca5cd11e36000e19ddc68daf6fd04d5142beb1899ff79e1afbe64f63ee", [:make, :mix], []}, 26 | "socket": {:hex, :socket, "0.3.5", "6c23772e7eda60f129f36dd69a61930aab18ae9b8d74d7f767c85355cf0b585d", [:mix], [], "hexpm"}, 27 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.0", "edee20847c42e379bf91261db474ffbe373f8acb56e9079acb6038d4e0bf414f", [:make, :rebar], [], "hexpm"}, 28 | "temp": {:hex, :temp, "0.4.1", "5e8a0fa2013837b4bbe7ae8bd613c1d44879250e72026564ecae53cedab4fdd6", [:mix], [], "hexpm"}, 29 | "websocket_client": {:hex, :websocket_client, "1.2.4", "14ec1ca4b6d247b44ccd9a80af8f6ca98328070f6c1d52a5cb00bc9d939d63b8", [:rebar3], [], "hexpm"}} 30 | -------------------------------------------------------------------------------- /doc/DiscordEx.EchoBot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.EchoBot – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.EchoBot 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

An all so original echo and ping bot!

83 |

Type “!example:echo hello!” or “!example:ping” to get a reply back.

84 | 85 |
86 | 87 | 88 | 89 |
90 |

91 | 92 | 93 | Link to this section 94 | 95 | Summary 96 |

97 | 98 | 99 | 100 |
101 |

102 | Functions 103 |

104 |
105 | 108 | 109 |
110 | 111 |
112 | 113 | 114 | 115 | 116 |
117 | 118 | 119 | 120 | 121 | 122 |
123 |

124 | 125 | 126 | Link to this section 127 | 128 | Functions 129 |

130 |
131 | 132 | 133 |
134 | 135 | 136 | Link to this function 137 | 138 | handle_event(arg, state) 139 | 140 | 141 | 142 | View Source 143 | 144 | 145 | 146 | 147 |
148 |
149 | 150 |
151 |
152 | 153 |
154 | 155 | 156 | 157 | 170 |
171 |
172 |
173 |
174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /lib/discord_ex/rest_client/resources/user.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.RestClient.Resources.User do 2 | @moduledoc """ 3 | Convience helper for user resource 4 | """ 5 | 6 | @doc """ 7 | User login that returns connection 8 | 9 | ## Parameters 10 | 11 | - email: User's email 12 | - password: User's password 13 | 14 | ## Examples 15 | 16 | User.login("user39023@discordapp.com","password") 17 | #=> {:ok, #PID<0.200.0>} 18 | """ 19 | @spec login(String.t, String.t) :: map 20 | def login(email, password) do 21 | HTTPoison.start 22 | response = HTTPoison.post!("#{DiscordEx.discord_url}/auth/login", {:form, [email: email, password: password]}, %{"Content-type" => "application/x-www-form-urlencoded"}) 23 | data = response.body |> Poison.decode! 24 | if data["token"] do 25 | DiscordEx.RestClient.start_link(%{token: data["token"]}) 26 | else 27 | {:error, data} 28 | end 29 | end 30 | 31 | @doc """ 32 | User logout which kills the connection process 33 | 34 | ## Parameters 35 | 36 | - conn: User connection for REST holding auth info 37 | 38 | ## Examples 39 | 40 | User.logout(conn) 41 | #=> :ok 42 | """ 43 | @spec logout(pid) :: atom 44 | def logout(conn) do 45 | response = DiscordEx.RestClient.resource(conn, :post, "/auth/logout") 46 | case response do 47 | :invalid -> 48 | Process.exit(conn, "Logout") 49 | :ok 50 | _ -> :error 51 | end 52 | end 53 | 54 | @doc """ 55 | Query users with results from mutual guilds 56 | 57 | ## Parameters 58 | 59 | - conn: User connection for REST holding auth info 60 | - username: Nick of user who you are looking up 61 | - limit: How many results to return. (default: 25) 62 | 63 | ## Examples 64 | 65 | User.query(conn, "tontoXL3902", 10) 66 | """ 67 | @spec query(pid, String.t, number) :: list 68 | def query(conn, username, limit \\ 25) do 69 | DiscordEx.RestClient.resource(conn, :get, "users", %{q: username, limit: limit}) 70 | end 71 | 72 | @doc """ 73 | Get currrent user 74 | 75 | ## Parameters 76 | 77 | - conn: User connection for REST holding auth info 78 | 79 | ## Examples 80 | 81 | User.current(conn) 82 | """ 83 | @spec current(pid) :: map 84 | def current(conn) do 85 | DiscordEx.RestClient.resource(conn, :get, "users/@me") 86 | end 87 | 88 | @doc """ 89 | Get user data for a specific user id 90 | 91 | ## Parameters 92 | 93 | - conn: User connection for REST holding auth info 94 | - user_id: User ID 95 | 96 | ## Examples 97 | 98 | User.get(conn, 329230923902390) 99 | """ 100 | @spec get(pid, number) :: map 101 | def get(conn, user_id) do 102 | DiscordEx.RestClient.resource(conn, :get, "users/#{user_id}") 103 | end 104 | 105 | @doc """ 106 | Modify the current user 107 | 108 | ## Parameters 109 | 110 | - conn: User connection for REST holding auth info 111 | - options: Updateable options which include (username, avatar) and email, password. 112 | 113 | ## Examples 114 | 115 | User.modify(conn, %{username: "SuperUserKato", email: "superuserkato@discordapp.com", password: "password"}) 116 | """ 117 | @spec modify(pid, map) :: map 118 | def modify(conn, options) do 119 | email = options[:email] 120 | password = options[:password] 121 | username = options[:username] 122 | avatar = options[:avatar] 123 | DiscordEx.RestClient.resource(conn, :patch_form, "users/@me", %{username: username, avatar: avatar, email: email, password: password}) 124 | end 125 | 126 | @doc """ 127 | Current user guilds 128 | 129 | ## Parameters 130 | 131 | - conn: User connection for REST holding auth info 132 | 133 | ## Examples 134 | 135 | User.guilds(conn) 136 | """ 137 | @spec guilds(pid) :: list 138 | def guilds(conn) do 139 | DiscordEx.RestClient.resource(conn, :get, "users/@me/guilds") 140 | end 141 | 142 | @doc """ 143 | Leave guild 144 | 145 | ## Parameters 146 | 147 | - conn: User connection for REST holding auth info 148 | - guild_id: The identifier for the guild in which the user wishes to leave 149 | 150 | ## Examples 151 | 152 | User.guilds(conn, 81323847812384) 153 | """ 154 | @spec leave_guild(pid, number) :: nil 155 | def leave_guild(conn, guild_id) do 156 | response = DiscordEx.RestClient.resource(conn, :delete, "users/@me/guilds/#{guild_id}") 157 | case response do 158 | :invalid -> :ok 159 | _ -> :error 160 | end 161 | end 162 | 163 | @doc """ 164 | Get user direct message channels 165 | 166 | ## Parameters 167 | 168 | - conn: User connection for REST holding auth info 169 | 170 | ## Examples 171 | 172 | User.dm_channels(conn) 173 | """ 174 | @spec dm_channels(pid) :: map 175 | def dm_channels(conn) do 176 | DiscordEx.RestClient.resource(conn, :get, "users/@me/channels") 177 | end 178 | 179 | @doc """ 180 | Create direct message channel for user 181 | 182 | ## Parameters 183 | 184 | - conn: User connection for REST holding auth info 185 | - recipient_id: The user id in which a direct message channel should be open 186 | 187 | ## Examples 188 | 189 | User.create_dm_channel(conn, 9999991384736571) 190 | """ 191 | @spec create_dm_channel(pid, number) :: map 192 | def create_dm_channel(conn, recipient_id) do 193 | DiscordEx.RestClient.resource(conn, :post, "users/@me/channels", %{recipient_id: recipient_id}) 194 | end 195 | 196 | @doc """ 197 | Get all user connections 198 | 199 | ## Parameters 200 | 201 | - conn: User connection for REST holding auth info 202 | 203 | ## Examples 204 | 205 | User.connections(conn) 206 | """ 207 | @spec connections(pid) :: list 208 | def connections(conn) do 209 | DiscordEx.RestClient.resource(conn, :get, "users/@me/connections") 210 | end 211 | end 212 | -------------------------------------------------------------------------------- /doc/DiscordEx.Voice.Encoder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Voice.Encoder – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Voice.Encoder 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Voice Encoder

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Encode audio file to proper format

109 |
110 | 111 |
112 | 113 |
114 | 115 | 116 | 117 | 118 |
119 | 120 | 121 | 122 | 123 | 124 |
125 |

126 | 127 | 128 | Link to this section 129 | 130 | Functions 131 |

132 |
133 | 134 | 135 |
136 | 137 | 138 | Link to this function 139 | 140 | encode_file(file_path, opts) 141 | 142 | 143 | 144 | View Source 145 | 146 | 147 | 148 | 149 |
150 | 151 |
encode_file(String.t(), map()) :: binary()
152 | 153 |
154 | 155 |
156 |
157 |

Encode audio file to proper format

158 | 159 |
160 |
161 | 162 |
163 | 164 | 165 | 166 | 179 |
180 |
181 |
182 |
183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /doc/DiscordEx.Voice.UDP.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Voice.UDP – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Voice.UDP 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Voice UDP Setup functions

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |
109 |
110 | 113 | 114 |
115 | 116 |
117 | 118 | 119 | 120 | 121 |
122 | 123 | 124 | 125 | 126 | 127 |
128 |

129 | 130 | 131 | Link to this section 132 | 133 | Functions 134 |

135 |
136 | 137 | 138 |
139 | 140 | 141 | Link to this function 142 | 143 | self_discovery(endpoint, discord_port, ssrc) 144 | 145 | 146 | 147 | View Source 148 | 149 | 150 | 151 | 152 |
153 | 154 |
self_discovery(String.t(), number(), number()) :: {tuple(), number()}
155 | 156 |
157 | 158 |
159 |
160 | 161 |
162 |
163 |
164 | 165 | 166 |
167 | 168 | 169 | Link to this function 170 | 171 | send_audio(data, voice_client, sequence, time) 172 | 173 | 174 | 175 | View Source 176 | 177 | 178 | 179 | 180 |
181 | 182 |
send_audio(binary(), pid(), number(), number()) :: atom()
183 | 184 |
185 | 186 |
187 |
188 | 189 |
190 |
191 | 192 |
193 | 194 | 195 | 196 | 209 |
210 |
211 |
212 |
213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /doc/DiscordEx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Base Discord Ex Module

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Discord Bot Authorization URL

109 |
110 | 111 |
112 |
113 |
114 | discord_url() 115 |
116 | 117 |

Discord API URL

118 |
119 | 120 |
121 | 122 |
123 | 124 | 125 | 126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 |
134 |

135 | 136 | 137 | Link to this section 138 | 139 | Functions 140 |

141 |
142 | 143 | 144 |
145 | 146 | 147 | Link to this function 148 | 149 | bot_auth_url(client_id, permissions) 150 | 151 | 152 | 153 | View Source 154 | 155 | 156 | 157 | 158 |
159 | 160 |
bot_auth_url(number(), number()) :: String.t()
161 | 162 |
163 | 164 |
165 |
166 |

Discord Bot Authorization URL

167 | 168 |
169 |
170 |
171 | 172 | 173 |
174 | 175 | 176 | Link to this function 177 | 178 | discord_url() 179 | 180 | 181 | 182 | View Source 183 | 184 | 185 | 186 | 187 |
188 | 189 |
discord_url() :: String.t()
190 | 191 |
192 | 193 |
194 |
195 |

Discord API URL

196 | 197 |
198 |
199 | 200 |
201 | 202 | 203 | 204 | 217 |
218 |
219 |
220 |
221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /doc/api-reference.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | API Reference – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | API Reference 72 |

73 | 74 | 75 |
76 |

77 | 78 | Modules 79 |

80 | 81 |
82 |
83 | 84 | 85 |

Base Discord Ex Module

86 |
87 | 88 |
89 |
90 | 91 | 92 |

Connect to Discord to recieve and send data in realtime

93 |
94 | 95 |
96 |
97 | 98 | 99 |

Bot Message Helpers

100 |
101 | 102 |
103 |
104 | 105 | 106 |

Utilty methods to be used for discord clients

107 |
108 | 109 |
110 |
111 | 112 | 113 |

Discord uses a REST interface to send data to the API

114 |
115 | 116 |
117 |
118 | 119 | 120 |

An all so original echo and ping bot!

121 |
122 | 123 |
124 |
125 | 126 | 127 |

Heartbeat service for Discord websocket connection. 128 | Sends heartbeat on interval and detects stale connection if heartbeat ack 129 | is not received

130 |
131 | 132 |
133 |
134 | 135 | 136 |

Easily assign permissions with this helper module

137 |
138 | 139 |
140 |
141 | 142 | 143 |

Discord RestClient. Used a GenServer so that you can have multiple 144 | clients in one application

145 |
146 | 147 |
148 |
149 | 150 | 151 |

Convience helper for channel

152 |
153 | 154 |
155 |
156 | 157 | 158 |

Convience helper for guild resource

159 |
160 | 161 |
162 |
163 | 164 | 165 |

Convience helper for images

166 |
167 | 168 |
169 |
170 | 171 | 172 |

Convience helper for invites

173 |
174 | 175 |
176 |
177 | 178 | 179 |

Convience helper for user resource

180 |
181 | 182 |
183 |
184 | 185 | 186 |

Buffer Module for holding and reading audio

187 |
188 | 189 |
190 |
191 | 192 | 193 |

This client is for specifically working with voice. You can pass this process 194 | to your regular client if you wish to use it with your bot

195 |
196 | 197 |
198 |
199 | 200 | 201 |

Voice control to make voice interaction a lot easier

202 |
203 | 204 |
205 |
206 | 207 | 208 |

Voice Encoder

209 |
210 | 211 |
212 |
213 | 214 | 215 |

Voice UDP Setup functions

216 |
217 | 218 |
219 | 220 |
221 |
222 | 223 | 224 | 225 | 226 | 227 | 228 | 241 |
242 |
243 |
244 |
245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | -------------------------------------------------------------------------------- /doc/DiscordEx.Permissions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Permissions – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Permissions 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Easily assign permissions with this helper module.

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Add any permission to an existing set of permissions and return the complete permission value

109 |
110 | 111 |
112 |
113 | 116 | 117 |

Remove any permission from an existing set of permissions and return updated value

118 |
119 | 120 |
121 |
122 |
123 | to_map(permissions) 124 |
125 | 126 |

Take current permission value and convert it to a map of permissions

127 |
128 | 129 |
130 | 131 |
132 | 133 | 134 | 135 | 136 |
137 | 138 | 139 | 140 | 141 | 142 |
143 |

144 | 145 | 146 | Link to this section 147 | 148 | Functions 149 |

150 |
151 | 152 | 153 | 154 | 155 |
156 | 157 | 158 | Link to this function 159 | 160 | add(existing_permissions \\ 0, new_permission) 161 | 162 | 163 | 164 | View Source 165 | 166 | 167 | 168 | 169 |
170 | 171 |
add(integer(), atom()) :: integer()
172 | 173 |
174 | 175 |
176 |
177 |

Add any permission to an existing set of permissions and return the complete permission value.

178 | 179 |
180 |
181 |
182 | 183 | 184 |
185 | 186 | 187 | Link to this function 188 | 189 | remove(existing_permissions, new_permission) 190 | 191 | 192 | 193 | View Source 194 | 195 | 196 | 197 | 198 |
199 | 200 |
remove(integer(), atom()) :: integer()
201 | 202 |
203 | 204 |
205 |
206 |

Remove any permission from an existing set of permissions and return updated value.

207 | 208 |
209 |
210 |
211 | 212 | 213 |
214 | 215 | 216 | Link to this function 217 | 218 | to_map(permissions) 219 | 220 | 221 | 222 | View Source 223 | 224 | 225 | 226 | 227 |
228 | 229 |
to_map(integer()) :: map()
230 | 231 |
232 | 233 |
234 |
235 |

Take current permission value and convert it to a map of permissions.

236 | 237 |
238 |
239 | 240 |
241 | 242 | 243 | 244 | 257 |
258 |
259 |
260 |
261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | -------------------------------------------------------------------------------- /lib/discord_ex/voice/client.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.Voice.Client do 2 | @moduledoc """ 3 | This client is for specifically working with voice. You can pass this process 4 | to your regular client if you wish to use it with your bot. 5 | 6 | ## Examples 7 | 8 | token = "" 9 | DiscordEx.Voice.Client.connect(base_client, %{guild_id: 392090239, channel_id: 23208203092390) 10 | #=> {:ok, #PID<0.180.0>} 11 | """ 12 | import DiscordEx.Client.Utility 13 | 14 | alias DiscordEx.Voice.Controller 15 | alias DiscordEx.Voice.UDP 16 | 17 | require Logger 18 | 19 | def opcodes() do 20 | %{ 21 | :identify => 0, 22 | :select_protocol => 1, 23 | :ready => 2, 24 | :heartbeat => 3, 25 | :session_description => 4, 26 | :speaking => 5, 27 | :heartbeat_interval => 8 28 | } 29 | end 30 | 31 | @behaviour :websocket_client 32 | 33 | @doc "Initialize a voice connection" 34 | @spec connect(pid, map) :: {:ok, pid} 35 | def connect(base_client, options \\ %{}) do 36 | task = Task.async fn -> 37 | send(base_client, {:start_voice_connection_listener, self()}) 38 | send(base_client, {:start_voice_connection, options}) 39 | receive do opts -> opts end 40 | end 41 | results = Task.await(task, 10_000) 42 | start_link(results) 43 | end 44 | 45 | @doc "Kill a voice connection" 46 | @spec disconnect(pid) :: atom 47 | def disconnect(voice_client) do 48 | send(voice_client, :disconnect) 49 | :ok 50 | end 51 | 52 | @doc "Reconnect or initiate voice connection" 53 | @spec reconnect(pid, map) :: {:ok, pid} 54 | def reconnect(client_pid, options) do 55 | {:ok, voice_client} = connect(client_pid, options) 56 | send(client_pid, {:update_state, %{voice_client: voice_client}}) 57 | {:ok, voice_client} 58 | end 59 | 60 | def start_link(opts) do 61 | task = Task.async fn -> 62 | url = socket_url(opts[:endpoint]) 63 | :crypto.start() 64 | :ssl.start() 65 | :websocket_client.start_link(url, __MODULE__, opts) 66 | end 67 | Task.await(task, 10_000) 68 | end 69 | 70 | # Required Functions and Default Callbacks ( you shouldn't need to touch these to use client) 71 | def init(state) do 72 | controller = Controller.start(self()) 73 | new_state = Map.merge(state, %{controller: controller}) 74 | {:once, new_state} 75 | end 76 | 77 | def onconnect(_WSReq, state) do 78 | identify(state) 79 | {:ok, state} 80 | end 81 | 82 | def ondisconnect({:remote, :closed}, _state) do 83 | # Stub for beter actions later 84 | end 85 | 86 | def websocket_info(:start, _conn_state, state) do 87 | {:ok, state} 88 | end 89 | 90 | @doc "Look into state - grab key value and pass it back to calling process" 91 | def websocket_info({:get_state, key, pid}, _connection, state) do 92 | value = if state[key], do: state[key], else: state.data[Atom.to_string(key)] 93 | send(pid, {key, value}) 94 | {:ok, state} 95 | end 96 | 97 | @doc "Ability to update state" 98 | def websocket_info({:update_state, update_values}, _connection, state) do 99 | {:ok, Map.merge(state, update_values)} 100 | end 101 | 102 | @doc "Send all the information over to base client in order to kill this client" 103 | def websocket_info(:disconnect, _connection, state) do 104 | # Disconnect form Server 105 | DiscordEx.Client.voice_state_update(state[:client_pid], 106 | state[:guild_id], 107 | nil, 108 | state[:user_id], 109 | %{self_deaf: true, self_mute: false}) 110 | 111 | # Make sure the state for the base client is cleared 112 | send(state[:client_pid], {:clear_from_state, [:voice, 113 | :voice_client, 114 | :start_voice_connection_listener, 115 | :voice_token, 116 | :start_voice_connection]}) 117 | 118 | # Get rid of any controller process that feels it wants to go rogue 119 | Process.exit(state[:controller][:buffer], :kill) 120 | Process.exit(state[:controller][:sequence], :kill) 121 | Process.exit(state[:controller][:time], :kill) 122 | 123 | # Kill thyself 124 | Process.exit(self(), "Killing voice client process ...") 125 | :ok 126 | end 127 | 128 | @doc "Send all the information over to base client in order to kill this client" 129 | def websocket_info({:get_controller, calling_pid}, _connection, state) do 130 | send(calling_pid, state[:controller]) 131 | {:ok, state} 132 | end 133 | 134 | @doc "Ability to update speaking state" 135 | def websocket_info({:speaking, value}, _connection, state) do 136 | data = %{ 137 | "delay" => 0, 138 | "speaking" => value 139 | } 140 | payload = payload_build_json(opcode(opcodes(), :speaking), data) 141 | :websocket_client.cast(self(), {:text, payload}) 142 | {:ok, state} 143 | end 144 | 145 | def websocket_handle({:text, payload}, _socket, state) do 146 | data = payload_decode(opcodes(), {:text, payload}) 147 | event = data.op 148 | handle_event({event, data}, state) 149 | end 150 | 151 | def websocket_terminate(reason, _conn_state, state) do 152 | Logger.info "Websocket closed in state #{inspect state} wih reason #{inspect reason}" 153 | if state[:udp_send_socket], do: :gen_udp.close(state[:udp_send_socket]) 154 | if state[:udp_recv_socket], do: :gen_udp.close(state[:udp_recv_socket]) 155 | :ok 156 | end 157 | 158 | def handle_event({:ready, payload}, state) do 159 | new_state = Map.merge(state, payload.data) 160 | _heartbeat_loop(state, payload.data["heartbeat_interval"], self()) 161 | _establish_udp_connect(state, payload) 162 | {:ok, new_state} 163 | end 164 | 165 | def handle_event({:session_description, payload}, state) do 166 | new_state = Map.merge(state, payload) 167 | {:ok, new_state} 168 | end 169 | 170 | def handle_event({event, _payload}, state) do 171 | # Heartbeat and Speaking will destroy logs :) 172 | unless event == :heartbeat || event == :speaking do 173 | Logger.info "Voice Connection Received Event: #{event}" 174 | end 175 | {:ok, state} 176 | end 177 | 178 | @spec socket_url(map) :: String.t 179 | def socket_url(url) do 180 | full_url = "wss://" <> url 181 | full_url |> String.replace(":80","") |> String.to_charlist 182 | end 183 | 184 | def identify(state) do 185 | data = %{ 186 | "server_id" => state[:guild_id], 187 | "user_id" => state[:user_id], 188 | "session_id" => state[:session_id], 189 | "token" => state[:token] 190 | } 191 | payload = payload_build_json(opcode(opcodes(), :identify), data) 192 | :websocket_client.cast(self(), {:text, payload}) 193 | end 194 | 195 | # Establish UDP Connection 196 | defp _establish_udp_connect(state, ready_payload) do 197 | {my_ip, my_port, discord_ip, discord_port, send_socket} = UDP.self_discovery(state.endpoint, ready_payload.data["port"], ready_payload.data["ssrc"]) 198 | Logger.warn "Make sure you open '#{my_ip}:#{my_port}' in your firewall to receive messages!" 199 | 200 | # Set UDP Sockets on state for reuse 201 | _update_udp_connection_state(my_port, discord_ip, discord_port, send_socket) 202 | 203 | # Send payload to client on local udp information 204 | load = %{"protocol" => "udp", "data" => %{"address" => my_ip, "port" => my_port, "mode" => "xsalsa20_poly1305"}} 205 | payload = payload_build_json(opcode(DiscordEx.Voice.Client.opcodes(), :select_protocol), load) 206 | :websocket_client.cast(self(), {:text, payload}) 207 | end 208 | 209 | defp _update_udp_connection_state(my_port, discord_ip, discord_port, send_socket) do 210 | udp_recv_options = [:binary, active: false, reuseaddr: true] 211 | {:ok, recv_socket} = :gen_udp.open(my_port, udp_recv_options) 212 | send(self(), {:update_state, %{udp_ip_send: discord_ip, 213 | udp_port_send: discord_port, 214 | udp_socket_send: send_socket, 215 | udp_socket_recv: recv_socket}}) 216 | end 217 | 218 | # Connection Heartbeat 219 | defp _heartbeat_loop(state, interval, socket_process) do 220 | spawn_link(fn -> _heartbeat(state, interval, socket_process) end) 221 | :ok 222 | end 223 | 224 | defp _heartbeat(state, interval, socket_process) do 225 | payload = payload_build_json(opcode(opcodes(), :heartbeat), nil) 226 | :websocket_client.cast(socket_process, {:text, payload}) 227 | :timer.sleep(interval) 228 | _heartbeat_loop(state, interval, socket_process) 229 | end 230 | end 231 | -------------------------------------------------------------------------------- /doc/dist/sidebar_items-506f2099ca.js: -------------------------------------------------------------------------------- 1 | sidebarNodes={"extras":[{"id":"api-reference","title":"API Reference","group":"","headers":[{"id":"Modules","anchor":"modules"}]},{"id":"readme","title":"Discord Ex - Discord Elixir Library","group":"","headers":[{"id":"Installation","anchor":"installation"},{"id":"Realtime and Bot Client Usage","anchor":"realtime-and-bot-client-usage"},{"id":"Voice Client Usage","anchor":"voice-client-usage"},{"id":"REST Client Usage","anchor":"rest-client-usage"},{"id":" The following Resources are supported - you can look at their docs for examples and more information: ","anchor":"the-following-resources-are-supported-you-can-look-at-their-docs-for-examples-and-more-information"},{"id":"TODOS","anchor":"todos"}]}],"exceptions":[],"modules":[{"id":"DiscordEx","title":"DiscordEx","group":"","functions":[{"id":"bot_auth_url/2","anchor":"bot_auth_url/2"},{"id":"discord_url/0","anchor":"discord_url/0"}]},{"id":"DiscordEx.Client","title":"DiscordEx.Client","group":"","functions":[{"id":"_voice_setup_gather_data/3","anchor":"_voice_setup_gather_data/3"},{"id":"handle_event/2","anchor":"handle_event/2"},{"id":"identify/1","anchor":"identify/1"},{"id":"init/1","anchor":"init/1"},{"id":"onconnect/2","anchor":"onconnect/2"},{"id":"ondisconnect/2","anchor":"ondisconnect/2"},{"id":"opcodes/0","anchor":"opcodes/0"},{"id":"socket_url/1","anchor":"socket_url/1"},{"id":"start_link/1","anchor":"start_link/1"},{"id":"status_update/2","anchor":"status_update/2"},{"id":"voice_state_update/5","anchor":"voice_state_update/5"},{"id":"websocket_handle/3","anchor":"websocket_handle/3"},{"id":"websocket_info/3","anchor":"websocket_info/3"},{"id":"websocket_terminate/3","anchor":"websocket_terminate/3"}]},{"id":"DiscordEx.Client.Helpers.MessageHelper","title":"DiscordEx.Client.Helpers.MessageHelper","group":"","functions":[{"id":"actionable_message_for?/3","anchor":"actionable_message_for?/3"},{"id":"actionable_message_for_me?/2","anchor":"actionable_message_for_me?/2"},{"id":"msg_command_parse/1","anchor":"msg_command_parse/1"},{"id":"msg_command_parse/2","anchor":"msg_command_parse/2"}]},{"id":"DiscordEx.Client.Utility","title":"DiscordEx.Client.Utility","group":"","functions":[{"id":"agent_update/2","anchor":"agent_update/2"},{"id":"agent_value/1","anchor":"agent_value/1"},{"id":"normalize_atom/1","anchor":"normalize_atom/1"},{"id":"opcode/2","anchor":"opcode/2"},{"id":"payload_build/4","anchor":"payload_build/4"},{"id":"payload_build_json/4","anchor":"payload_build_json/4"},{"id":"payload_decode/2","anchor":"payload_decode/2"}]},{"id":"DiscordEx.Connections.REST","title":"DiscordEx.Connections.REST","group":"","functions":[{"id":"delete/3","anchor":"delete/3"},{"id":"delete!/3","anchor":"delete!/3"},{"id":"get/3","anchor":"get/3"},{"id":"get!/3","anchor":"get!/3"},{"id":"head/3","anchor":"head/3"},{"id":"head!/3","anchor":"head!/3"},{"id":"options/3","anchor":"options/3"},{"id":"options!/3","anchor":"options!/3"},{"id":"patch/4","anchor":"patch/4"},{"id":"patch!/4","anchor":"patch!/4"},{"id":"post/4","anchor":"post/4"},{"id":"post!/4","anchor":"post!/4"},{"id":"process_url/1","anchor":"process_url/1"},{"id":"put/4","anchor":"put/4"},{"id":"put!/4","anchor":"put!/4"},{"id":"request/5","anchor":"request/5"},{"id":"request!/5","anchor":"request!/5"},{"id":"start/0","anchor":"start/0"}],"types":[{"id":"body/0","anchor":"t:body/0"},{"id":"headers/0","anchor":"t:headers/0"}]},{"id":"DiscordEx.EchoBot","title":"DiscordEx.EchoBot","group":"","functions":[{"id":"handle_event/2","anchor":"handle_event/2"}]},{"id":"DiscordEx.Heartbeat","title":"DiscordEx.Heartbeat","group":"","functions":[{"id":"ack/1","anchor":"ack/1"},{"id":"handle_call/3","anchor":"handle_call/3"},{"id":"handle_info/2","anchor":"handle_info/2"},{"id":"reset/1","anchor":"reset/1"},{"id":"start_link/4","anchor":"start_link/4"}]},{"id":"DiscordEx.Permissions","title":"DiscordEx.Permissions","group":"","functions":[{"id":"add/2","anchor":"add/2"},{"id":"remove/2","anchor":"remove/2"},{"id":"to_map/1","anchor":"to_map/1"}]},{"id":"DiscordEx.RestClient","title":"DiscordEx.RestClient","group":"","functions":[{"id":"init/2","anchor":"init/2"},{"id":"resource/4","anchor":"resource/4"},{"id":"start_link/2","anchor":"start_link/2"}],"types":[{"id":"request_reply/0","anchor":"t:request_reply/0"}]},{"id":"DiscordEx.RestClient.Resources.Channel","title":"DiscordEx.RestClient.Resources.Channel","group":"","functions":[{"id":"bulk_delete_messages/3","anchor":"bulk_delete_messages/3"},{"id":"create_invite/3","anchor":"create_invite/3"},{"id":"delete/2","anchor":"delete/2"},{"id":"delete_message/3","anchor":"delete_message/3"},{"id":"delete_permissions/3","anchor":"delete_permissions/3"},{"id":"edit_permissions/4","anchor":"edit_permissions/4"},{"id":"get/2","anchor":"get/2"},{"id":"get_invites/2","anchor":"get_invites/2"},{"id":"messages/2","anchor":"messages/2"},{"id":"modify/3","anchor":"modify/3"},{"id":"send_file/3","anchor":"send_file/3"},{"id":"send_message/3","anchor":"send_message/3"},{"id":"trigger_typing/2","anchor":"trigger_typing/2"},{"id":"update_message/4","anchor":"update_message/4"}]},{"id":"DiscordEx.RestClient.Resources.Guild","title":"DiscordEx.RestClient.Resources.Guild","group":"","functions":[{"id":"ban_member/4","anchor":"ban_member/4"},{"id":"bans/2","anchor":"bans/2"},{"id":"batch_modify_roles/3","anchor":"batch_modify_roles/3"},{"id":"begin_prune/3","anchor":"begin_prune/3"},{"id":"channels/2","anchor":"channels/2"},{"id":"create/2","anchor":"create/2"},{"id":"create_channel/3","anchor":"create_channel/3"},{"id":"create_empty_role/2","anchor":"create_empty_role/2"},{"id":"create_integration/3","anchor":"create_integration/3"},{"id":"delete/2","anchor":"delete/2"},{"id":"delete_integration/3","anchor":"delete_integration/3"},{"id":"delete_role/3","anchor":"delete_role/3"},{"id":"embed/2","anchor":"embed/2"},{"id":"get/2","anchor":"get/2"},{"id":"integrations/2","anchor":"integrations/2"},{"id":"invites/2","anchor":"invites/2"},{"id":"kick_member/3","anchor":"kick_member/3"},{"id":"member/3","anchor":"member/3"},{"id":"members/3","anchor":"members/3"},{"id":"modify/3","anchor":"modify/3"},{"id":"modify_embed/2","anchor":"modify_embed/2"},{"id":"modify_integration/4","anchor":"modify_integration/4"},{"id":"modify_member/4","anchor":"modify_member/4"},{"id":"modify_role/4","anchor":"modify_role/4"},{"id":"prune_count/3","anchor":"prune_count/3"},{"id":"roles/2","anchor":"roles/2"},{"id":"sync_integration/3","anchor":"sync_integration/3"},{"id":"unban_member/3","anchor":"unban_member/3"},{"id":"voice_regions/2","anchor":"voice_regions/2"}]},{"id":"DiscordEx.RestClient.Resources.Image","title":"DiscordEx.RestClient.Resources.Image","group":"","functions":[{"id":"avatar_url/2","anchor":"avatar_url/2"},{"id":"icon_url/2","anchor":"icon_url/2"}]},{"id":"DiscordEx.RestClient.Resources.Invite","title":"DiscordEx.RestClient.Resources.Invite","group":"","functions":[{"id":"accept/2","anchor":"accept/2"},{"id":"delete/2","anchor":"delete/2"},{"id":"get/2","anchor":"get/2"}]},{"id":"DiscordEx.RestClient.Resources.User","title":"DiscordEx.RestClient.Resources.User","group":"","functions":[{"id":"connections/1","anchor":"connections/1"},{"id":"create_dm_channel/2","anchor":"create_dm_channel/2"},{"id":"current/1","anchor":"current/1"},{"id":"dm_channels/1","anchor":"dm_channels/1"},{"id":"get/2","anchor":"get/2"},{"id":"guilds/1","anchor":"guilds/1"},{"id":"leave_guild/2","anchor":"leave_guild/2"},{"id":"login/2","anchor":"login/2"},{"id":"logout/1","anchor":"logout/1"},{"id":"modify/2","anchor":"modify/2"},{"id":"query/3","anchor":"query/3"}]},{"id":"DiscordEx.Voice.Buffer","title":"DiscordEx.Voice.Buffer","group":"","functions":[{"id":"drain/4","anchor":"drain/4"},{"id":"drain_dca/3","anchor":"drain_dca/3"},{"id":"dump/1","anchor":"dump/1"},{"id":"read/2","anchor":"read/2"},{"id":"read/3","anchor":"read/3"},{"id":"size/1","anchor":"size/1"},{"id":"start/0","anchor":"start/0"},{"id":"write/2","anchor":"write/2"}]},{"id":"DiscordEx.Voice.Client","title":"DiscordEx.Voice.Client","group":"","functions":[{"id":"connect/2","anchor":"connect/2"},{"id":"disconnect/1","anchor":"disconnect/1"},{"id":"handle_event/2","anchor":"handle_event/2"},{"id":"identify/1","anchor":"identify/1"},{"id":"init/1","anchor":"init/1"},{"id":"onconnect/2","anchor":"onconnect/2"},{"id":"ondisconnect/2","anchor":"ondisconnect/2"},{"id":"opcodes/0","anchor":"opcodes/0"},{"id":"reconnect/2","anchor":"reconnect/2"},{"id":"socket_url/1","anchor":"socket_url/1"},{"id":"start_link/1","anchor":"start_link/1"},{"id":"websocket_handle/3","anchor":"websocket_handle/3"},{"id":"websocket_info/3","anchor":"websocket_info/3"},{"id":"websocket_terminate/3","anchor":"websocket_terminate/3"}]},{"id":"DiscordEx.Voice.Controller","title":"DiscordEx.Voice.Controller","group":"","functions":[{"id":"listen_socket/1","anchor":"listen_socket/1"},{"id":"play/3","anchor":"play/3"},{"id":"start/1","anchor":"start/1"},{"id":"stop/1","anchor":"stop/1"}]},{"id":"DiscordEx.Voice.Encoder","title":"DiscordEx.Voice.Encoder","group":"","functions":[{"id":"encode_file/2","anchor":"encode_file/2"}]},{"id":"DiscordEx.Voice.UDP","title":"DiscordEx.Voice.UDP","group":"","functions":[{"id":"self_discovery/3","anchor":"self_discovery/3"},{"id":"send_audio/4","anchor":"send_audio/4"}]}],"tasks":[]} -------------------------------------------------------------------------------- /doc/DiscordEx.RestClient.Resources.Image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.RestClient.Resources.Image – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.RestClient.Resources.Image 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Convience helper for images

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Get the user avatar URL

109 |
110 | 111 |
112 |
113 | 116 | 117 |

Get the guild icon URL

118 |
119 | 120 |
121 | 122 |
123 | 124 | 125 | 126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 |
134 |

135 | 136 | 137 | Link to this section 138 | 139 | Functions 140 |

141 |
142 | 143 | 144 |
145 | 146 | 147 | Link to this function 148 | 149 | avatar_url(user_id, avatar_id) 150 | 151 | 152 | 153 | View Source 154 | 155 | 156 | 157 | 158 |
159 | 160 |
avatar_url(String.t(), String.t()) :: String.t()
161 | 162 |
163 | 164 |
165 |
166 |

Get the user avatar URL

167 |

168 | 169 | Parameters 170 |

171 | 172 |
    173 |
  • user_id: The user_id to retrieve the avatar URL for 174 |
  • 175 |
  • avatar_id: The avatar_id for the user to retrieve the avatar URL for 176 |
  • 177 |
178 |

179 | 180 | Examples 181 |

182 | 183 |
Image.avatar_url("99999999993832","f3e8329c329020329")
184 | 185 |
186 |
187 |
188 | 189 | 190 |
191 | 192 | 193 | Link to this function 194 | 195 | icon_url(server_id, icon_id) 196 | 197 | 198 | 199 | View Source 200 | 201 | 202 | 203 | 204 |
205 | 206 |
icon_url(String.t(), String.t()) :: String.t()
207 | 208 |
209 | 210 |
211 |
212 |

Get the guild icon URL

213 |

214 | 215 | Parameters 216 |

217 | 218 |
    219 |
  • server_id: The server_id to retrieve the icon URL for 220 |
  • 221 |
  • icon_id: The icon_id for the user to retrieve the icon URL for 222 |
  • 223 |
224 |

225 | 226 | Examples 227 |

228 | 229 |
Image.icon_url("99999999993832","f3e8329c329020329")
230 | 231 |
232 |
233 | 234 |
235 | 236 | 237 | 238 |
239 |

240 | 241 | Built using 242 | ExDoc (v0.18.1), 243 | 244 | 245 | designed by 246 | Friedel Ziegelmayer. 247 | 248 |

249 | 250 |
251 |
252 |
253 |
254 |
255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Discord Ex - Discord Elixir Library 2 | 3 | [![Build Status](https://travis-ci.org/rmcafee/discord_ex.svg?branch=master)](https://travis-ci.org/rmcafee/discord_ex) [![Hex pm](https://img.shields.io/hexpm/v/discord_ex.svg)](https://hex.pm/packages/discord_ex) 4 | 5 | ### Due to time constraints and Discord changes this project has become stale. A new co-maintainer is in the works to help work on a major update that should make the library easier to use and resolve some issues. 6 | 7 | Discord library for Elixir. I needed it and figured I'd share. 8 | 9 | This library is useful for making calls and implementing a bot as well. 10 | 11 | Please always use [Discord Developer Docs](https://discordapp.com/developers/docs) as a reference. 12 | 13 | ## Installation 14 | 15 | 1. Add discord_ex to your list of dependencies in `mix.exs`: 16 | 17 | ```elixir 18 | # From Hex 19 | def deps do 20 | [{:discord_ex, "~> 1.1.18"}] 21 | end 22 | ``` 23 | 24 | ```elixir 25 | # From Github 26 | def deps do 27 | [{:discord_ex, git: "https://github.com/rmcafee/discord_ex.git", tag: "1.1.18"}] 28 | end 29 | ``` 30 | 31 | 2. Ensure discord_ex is started before your application: 32 | 33 | ```elixir 34 | def application do 35 | [applications: [:discord_ex]] 36 | end 37 | ``` 38 | 39 | 3. Look at the examples/echo_bot.ex file and you should honestly be 40 | good to go. 41 | 42 | ## Realtime and Bot Client Usage 43 | 44 | So you want to create a bot huh? Easy peezy. 45 | 46 | 1) Create a bot with a default handler to handle any event: 47 | 48 | ```elixir 49 | # Default Fallback Handler for all events! 50 | # This way things don't blow up when you get an event you 51 | # have not set up a handler for. 52 | 53 | def handle_event({event, _payload}, state) do 54 | IO.puts "Received Event: #{event}" 55 | {:ok, state} 56 | end 57 | ``` 58 | 59 | 2) Setup an event handler to handle whatever event you want: 60 | 61 | ```elixir 62 | def handle_event({:message_create, payload}, state) do 63 | # Your stuff happens here # 64 | {:ok, state} 65 | end 66 | ``` 67 | 68 | 3) Now to start your client it is as easy as: 69 | 70 | ```elixir 71 | {:ok, bot_client } = DiscordEx.Client.start_link(%{ 72 | token: , 73 | handler: YourBotHandler 74 | }) 75 | ``` 76 | where `YourBotHandler` is name of your module which implements `handle_event`. 77 | 78 | Alright you are done. Go forth and prosper! 79 | 80 | **As a note your bot_client is a gen_server that will have state properties that contain:** 81 | 82 | **:client_id** - your ClientID, so you don't have to constantly ask API for it 83 | 84 | **:rest_client** - you can use this process to make calls without having to setup another rest client connection. So in your callback you can do this in your callback with ease: 85 | 86 | ### Possible events: 87 | 88 | Those events are described [here](https://discordapp.com/developers/docs/topics/gateway#events). 89 | 90 | ```elixir 91 | :resumed 92 | :channel_create 93 | :channel_update 94 | :channel_delete 95 | :guild_update 96 | :guild_delete 97 | :guild_ban_add 98 | :guild_ban_remove 99 | :guild_emoji_update 100 | :guild_integrations_update 101 | :guild_member_add 102 | :guild_member_remove 103 | :guild_member_update 104 | :guild_members_chunk 105 | :guild_role_create 106 | :guild_role_update 107 | :guild_role_delete 108 | :message_create 109 | :message_update 110 | :message_delete 111 | :message_delete_bulk 112 | :presence_update 113 | :typing_start 114 | :user_settings_update 115 | :user_update 116 | :voice_server_update 117 | ``` 118 | 119 | ```elixir 120 | alias DiscordEx.RestClient.Resources.User 121 | 122 | User.current(state[:rest_client]) 123 | ``` 124 | 125 | 126 | ## Voice Client Usage 127 | 128 | Keep in mind you will need to have [ffmpeg](https://ffmpeg.org) and [dca-rs](https://github.com/nstafie/dca-rs) installed. 129 | 130 | If your dca-rs is not installed as `/usr/local/bin/dca-rs` you can provide path 131 | to it in your `config.exs` with: 132 | ```elixir 133 | config :discord_ex, dca_rs_path: "/your/path/dca-rs" 134 | ``` 135 | 136 | **For best results that include easy accessibility and efficient process management include voice information whenoy create your client.** 137 | 138 | 1) Create a connection with initial voice channel information and voice options: 139 | 140 | ```elixir 141 | {:ok, client} = DiscordEx.Client.start_link(%{ 142 | token: token, 143 | handler: YourHandler, 144 | voice: %{ 145 | guild_id: , 146 | channel_id: , 147 | self_deaf: false} 148 | }) 149 | ``` 150 | 151 | 2) Now in your handler you can access the voice client in your handlers state. 152 | 153 | * state[:voice_client] 154 | 155 | 156 | **If you want to seperate your voice client from the general client:** 157 | 158 | 1) Create a connection like before. You can attach a bot handler if you wish. 159 | 160 | ```elixir 161 | {:ok, client } = DiscordEx.Client.start_link(%{token: }) 162 | ``` 163 | 164 | 2) Now create a voice client as you piggy back off the bot. 165 | 166 | ```elixir 167 | {:ok, voice_client} = DiscordEx.Voice.Client.connect(client, %{ 168 | guild_id: , 169 | channel_id: 170 | }) 171 | ``` 172 | 173 | **Now just pick the audio file you want to use and play it:** 174 | 175 | ```elixir 176 | DiscordEx.Voice.Controller.play(voice_client,"/path/to/your/audio.wav", %{volumne: 128}) 177 | ``` 178 | 179 | **If you want to stop it so you can play something else just stop it:** 180 | 181 | ```elixir 182 | DiscordEx.Voice.Controller.stop(voice_client) 183 | ``` 184 | 185 | ## REST Client Usage 186 | 187 | The easy way to use discord resources is by doing the following. 188 | 189 | ```elixir 190 | # alias the resource you wish to use to make it easy on yourself 191 | 192 | alias DiscordEx.RestClient.Resources.User 193 | 194 | # Establish a connection 195 | {:ok, connection} = User.login("","") 196 | 197 | # Call on the resource and reap the results 198 | 199 | User.guilds(connection) 200 | ``` 201 | 202 | If you like going the longer route and obtained your token already - you can use resources like this: 203 | 204 | ```elixir 205 | # Create a connection 206 | token = "" 207 | {:ok, conn} = DiscordEx.RestClient.start_link(%{token: token}) 208 | 209 | # Get to using the resource function for the rest client 210 | DiscordEx.RestClient.resource(conn, :get, "users/@me/channels") 211 | 212 | # You can also user other method resources like 'post': 213 | DiscordEx.RestClient.resource(conn, :post, "users/@me/channels", %{recipient_id: }) 214 | ``` 215 | 216 | The 'resource' function makes it a lot easier to use the library. The following methods are supported. 217 | 218 | ```elixir 219 | DiscordEx.RestClient.resource(conn, :get, ) 220 | DiscordEx.RestClient.resource(conn, :post, , ) 221 | DiscordEx.RestClient.resource(conn, :put, , ) 222 | DiscordEx.RestClient.resource(conn, :patch, , ) 223 | DiscordEx.RestClient.resource(conn, :delete, ) 224 | ``` 225 | 226 | 227 | ** The following Resources are supported - you can look at their docs for examples and more information: ** 228 | ---- 229 | 230 | ```elixir 231 | alias DiscordEx.RestClient.Resources.Channel 232 | 233 | Channel.bulk_delete_messages/3 234 | Channel.create_invite/3 235 | Channel.delete/2 236 | Channel.delete_message/3 237 | Channel.delete_permissions/3 238 | Channel.edit_permissions/4 239 | Channel.get/2 240 | Channel.get_invites/2 241 | Channel.messages/2 242 | Channel.modify/3 243 | Channel.send_file/3 244 | Channel.send_message/3 245 | Channel.trigger_typing/2 246 | Channel.update_message/4 247 | ``` 248 | 249 | [channel-resource-doc](DiscordEx.RestClient.Resources.Channel.html) 250 | 251 | ---- 252 | 253 | ```elixir 254 | alias DiscordEx.RestClient.Resources.Guild 255 | 256 | Guild.ban_member/4 257 | Guild.bans/2 258 | Guild.batch_modify_roles/3 259 | Guild.begin_prune/3 260 | Guild.channels/2 261 | Guild.create/2 262 | Guild.create_channel/3 263 | Guild.create_empty_role/2 264 | Guild.create_integration/3 265 | Guild.delete/2 266 | Guild.delete_integration/3 267 | Guild.delete_role/3 268 | Guild.embed/2 269 | Guild.get/2 270 | Guild.integrations/2 271 | Guild.invites/2 272 | Guild.kick_member/3 273 | Guild.member/3 274 | Guild.members/3 275 | Guild.modify/3 276 | Guild.modify_embed/2 277 | Guild.modify_integration/4 278 | Guild.modify_member/4 279 | Guild.modify_role/4 280 | Guild.prune_count/3 281 | Guild.roles/2 282 | Guild.sync_integration/3 283 | Guild.unban_member/3 284 | Guild.voice_regions/2 285 | ``` 286 | 287 | [guild-resource-doc](DiscordEx.RestClient.Resources.Guild.html) 288 | 289 | ---- 290 | 291 | ```elixir 292 | alias DiscordEx.RestClient.Resources.Image 293 | 294 | Image.avatar_url/2 295 | Image.icon_url/2 296 | ``` 297 | 298 | [image-resource-doc](DiscordEx.RestClient.Resources.Image.html) 299 | 300 | ---- 301 | 302 | ```elixir 303 | alias DiscordEx.RestClient.Resources.Invite 304 | 305 | Invite.accept/2 306 | Invite.delete/2 307 | Invite.get/2 308 | ``` 309 | 310 | [invite-resource-doc](DiscordEx.RestClient.Resources.Invite.html) 311 | 312 | ---- 313 | 314 | ```elixir 315 | alias DiscordEx.RestClient.Resources.User 316 | 317 | User.connections/1 318 | User.create_dm_channel/2 319 | User.current/1 320 | User.dm_channels/1 321 | User.get/2 322 | User.guilds/1 323 | User.leave_guild/2 324 | User.login/2 325 | User.logout/1 326 | User.modify/2 327 | User.query/3 328 | ``` 329 | 330 | [user-resource-doc](DiscordEx.RestClient.Resources.User.html) 331 | 332 | ## TODOS 333 | 334 | * Would love more tests! 335 | * DRY up some similar behaviour. 336 | * Use it more and more - to see where developer usability can be made better 337 | -------------------------------------------------------------------------------- /doc/DiscordEx.Heartbeat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Heartbeat – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Heartbeat 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Heartbeat service for Discord websocket connection. 83 | Sends heartbeat on interval and detects stale connection if heartbeat ack 84 | is not received.

85 | 86 |
87 | 88 | 89 | 90 |
91 |

92 | 93 | 94 | Link to this section 95 | 96 | Summary 97 |

98 | 99 | 100 | 101 |
102 |

103 | Functions 104 |

105 |
106 |
107 | ack(pid) 108 |
109 | 110 |
111 |
112 | 115 | 116 |

Reset heartbeat

117 |
118 | 119 |
120 |
121 | 124 | 125 |

Heartbeat ACK not received, connection is stale. Stop heartbeat

126 |
127 | 128 |
129 |
130 |
131 | reset(pid) 132 |
133 | 134 |
135 | 141 | 142 |
143 | 144 | 145 | 146 | 147 |
148 | 149 | 150 | 151 | 152 | 153 |
154 |

155 | 156 | 157 | Link to this section 158 | 159 | Functions 160 |

161 |
162 | 163 | 164 |
165 | 166 | 167 | Link to this function 168 | 169 | ack(pid) 170 | 171 | 172 | 173 | View Source 174 | 175 | 176 | 177 | 178 |
179 |
180 | 181 |
182 |
183 |
184 | 185 | 186 |
187 | 188 | 189 | Link to this function 190 | 191 | handle_call(msg, from, state) 192 | 193 | 194 | 195 | View Source 196 | 197 | 198 | 199 | 200 |
201 |
202 |

Reset heartbeat

203 | 204 |
205 |
206 |
207 | 208 | 209 |
210 | 211 | 212 | Link to this function 213 | 214 | handle_info(msg, state) 215 | 216 | 217 | 218 | View Source 219 | 220 | 221 | 222 | 223 |
224 |
225 |

Heartbeat ACK not received, connection is stale. Stop heartbeat.

226 | 227 |
228 |
229 |
230 | 231 | 232 |
233 | 234 | 235 | Link to this function 236 | 237 | reset(pid) 238 | 239 | 240 | 241 | View Source 242 | 243 | 244 | 245 | 246 |
247 |
248 | 249 |
250 |
251 |
252 | 253 | 254 | 255 | 256 |
257 | 258 | 259 | Link to this function 260 | 261 | start_link(agent_seq_num, interval, socket_pid, opts \\ []) 262 | 263 | 264 | 265 | View Source 266 | 267 | 268 | 269 | 270 |
271 |
272 | 273 |
274 |
275 | 276 |
277 | 278 | 279 | 280 |
281 |

282 | 283 | Built using 284 | ExDoc (v0.18.1), 285 | 286 | 287 | designed by 288 | Friedel Ziegelmayer. 289 | 290 |

291 | 292 |
293 |
294 |
295 |
296 |
297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | -------------------------------------------------------------------------------- /lib/discord_ex/rest_client/resources/channel.ex: -------------------------------------------------------------------------------- 1 | defmodule DiscordEx.RestClient.Resources.Channel do 2 | @moduledoc """ 3 | Convience helper for channel 4 | """ 5 | 6 | @doc """ 7 | Get channel 8 | 9 | ## Parameters 10 | 11 | - conn: User connection for REST holding auth info 12 | - channel_id: Channel id 13 | 14 | ## Examples 15 | 16 | Channel.get(conn, 99999999993832) 17 | """ 18 | @spec get(pid, number) :: map 19 | def get(conn, channel_id) do 20 | DiscordEx.RestClient.resource(conn, :get, "channels/#{channel_id}") 21 | end 22 | 23 | @doc """ 24 | Update a channels settings 25 | 26 | Requires the 'MANAGE_GUILD' permission for the guild containing the channel. 27 | 28 | ## Parameters 29 | 30 | - conn: User connection for REST holding auth info 31 | - channel_id: Channel id 32 | - options: Updateable options which include (name, position, topic, bitrate) 33 | 34 | ## Examples 35 | 36 | Channel.modify(conn, 3290293092093, %{name: "my-channel", topic" "we all are friends here"}) 37 | """ 38 | @spec modify(pid, number, map) :: map 39 | def modify(conn, channel_id, options) do 40 | DiscordEx.RestClient.resource(conn, :patch, "channels/#{channel_id}", options) 41 | end 42 | 43 | @doc """ 44 | Delete a guild channel, or close a private message 45 | 46 | Requires the 'MANAGE_GUILD' permission for the guild containing the channel. 47 | 48 | ## Parameters 49 | 50 | - conn: User connection for REST holding auth info 51 | - channel_id: Channel id 52 | 53 | ## Examples 54 | 55 | Channel.delete(conn, 93209293090239) 56 | """ 57 | @spec delete(pid, number) :: map 58 | def delete(conn, channel_id) do 59 | DiscordEx.RestClient.resource(conn, :delete, "channels/#{channel_id}") 60 | end 61 | 62 | @doc """ 63 | Retrieve messages for a channel 64 | 65 | Requires the 'READ_MESSAGES' permission for the guild containing the channel. 66 | 67 | ## Parameters 68 | 69 | - conn: User connection for REST holding auth info 70 | - channel_id: Channel id 71 | 72 | ## Examples 73 | 74 | Channel.messages(conn, 439093409304934) 75 | """ 76 | @spec messages(pid, number) :: map 77 | def messages(conn, channel_id) do 78 | DiscordEx.RestClient.resource(conn, :get, "channels/#{channel_id}/messages") 79 | end 80 | 81 | @doc """ 82 | Post a message to a guild text or DM channel 83 | 84 | Requires the 'SEND_MESSAGES' permission to be present on the current user. 85 | 86 | ## Parameters 87 | 88 | - conn: User connection for REST holding auth info 89 | - channel_id: Channel id 90 | - message_data: message data which include (content, nonce, tts) 91 | 92 | ## Examples 93 | 94 | Channel.send_message(conn, 439093409304934, %{content: "Hi! Friends!"}) 95 | """ 96 | @spec send_message(pid, number, String.t) :: map 97 | def send_message(conn, channel_id, message_data) do 98 | DiscordEx.RestClient.resource(conn, :post, "channels/#{channel_id}/messages", message_data) 99 | end 100 | 101 | @doc """ 102 | Post a file and message to a guild text or DM channel 103 | 104 | Requires the 'SEND_MESSAGES' permission to be present on the current user. 105 | 106 | ## Parameters 107 | 108 | - conn: User connection for REST holding auth info 109 | - channel_id: Channel id 110 | - file_data: filed data which include (content, nonce, tts, file) 111 | 112 | ## Examples 113 | 114 | Channel.send_messages(conn, 439093409304934, %{content: "Check this out!", file: "/local/path/to/file.jpg"}) 115 | """ 116 | @spec send_file(pid, number, map) :: map 117 | def send_file(conn, channel_id, file_data) do 118 | DiscordEx.RestClient.resource(conn, :post_multipart, "channels/#{channel_id}/messages", file_data) 119 | end 120 | 121 | @doc """ 122 | Edit a previously sent message 123 | 124 | You can only edit messages that have been sent by the current user. 125 | 126 | ## Parameters 127 | 128 | - conn: User connection for REST holding auth info 129 | - channel_id: Channel id 130 | - message_id: The message id that you want to edit 131 | - message: Message you wish to update the sent message with 132 | 133 | ## Examples 134 | 135 | Channel.update_message(conn, 439093409304934, 32892398238, "Updating this message!") 136 | """ 137 | @spec update_message(pid, number, number, String.t) :: map 138 | def update_message(conn, channel_id, message_id, message) do 139 | DiscordEx.RestClient.resource(conn, :patch, "channels/#{channel_id}/messages/#{message_id}", %{content: message}) 140 | end 141 | 142 | @doc """ 143 | Delete a previously sent message 144 | 145 | This endpoint can only be used on guild channels and requires the 'MANAGE_MESSAGES' permission. 146 | 147 | ## Parameters 148 | 149 | - conn: User connection for REST holding auth info 150 | - channel_id: Channel id 151 | - message_id: The message id that you want to edit 152 | 153 | ## Examples 154 | 155 | Channel.delete_message(conn, 439093409304934, 32892398238) 156 | """ 157 | @spec delete_message(pid, number, number) :: atom 158 | def delete_message(conn, channel_id, message_id) do 159 | response = DiscordEx.RestClient.resource(conn, :delete, "channels/#{channel_id}/messages/#{message_id}") 160 | case response do 161 | :invalid -> :ok 162 | _ -> :error 163 | end 164 | end 165 | 166 | @doc """ 167 | Bulk Delete previously sent messages 168 | 169 | This endpoint can only be used on guild channels and requires the 'MANAGE_MESSAGES' permission. 170 | 171 | ## Parameters 172 | 173 | - conn: User connection for REST holding auth info 174 | - channel_id: Channel id 175 | - message_ids: list of msssage ids to delete 176 | 177 | ## Examples 178 | 179 | Channel.bulk_delete_messages(conn, 439093409304934, [32902930920932,3290239832023,237727828932]) 180 | """ 181 | @spec bulk_delete_messages(pid, number, list) :: atom 182 | def bulk_delete_messages(conn, channel_id, message_ids) do 183 | response = DiscordEx.RestClient.resource(conn, :post, "channels/#{channel_id}/messages/bulk_delete", %{messages: message_ids}) 184 | case response do 185 | :invalid -> :ok 186 | data -> data 187 | end 188 | end 189 | 190 | @doc """ 191 | Edit channel permissions 192 | 193 | Edit the channel permission overwrites for a user or role in a channel. Only usable for guild channels. Requires the 'MANAGE_ROLES' permission. 194 | 195 | ## Parameters 196 | 197 | - conn: User connection for REST holding auth info 198 | - channel_id: Channel id 199 | - overwrite_id: The role or user to override permissions in channel for 200 | - options: The permissions to allow or deny 201 | 202 | ## Examples 203 | 204 | Channel.edit_permissions(conn, 9999999999383, 2382830923, %{allow: 66321471}) 205 | """ 206 | @spec edit_permissions(pid, number, number, map) :: map 207 | def edit_permissions(conn, channel_id, overwrite_id, options) do 208 | DiscordEx.RestClient.resource(conn, :post, "channels/#{channel_id}/permissions/#{overwrite_id}", options) 209 | end 210 | 211 | @doc """ 212 | Delete channel permissions 213 | 214 | Delete a channel permission overwrite for a user or role in a channel. Only usable for guild channels. Requires the 'MANAGE_ROLES' permission. 215 | 216 | ## Parameters 217 | 218 | - conn: User connection for REST holding auth info 219 | - channel_id: Channel id 220 | - overwrite_id: The role or user to override permissions in channel for 221 | 222 | ## Examples 223 | 224 | Channel.delete_permissions(conn, 9999999999383, 3279283989823) 225 | """ 226 | @spec delete_permissions(pid, number, number) :: map 227 | def delete_permissions(conn, channel_id, overwrite_id) do 228 | response = DiscordEx.RestClient.resource(conn, :delete, "channels/#{channel_id}/permissions/#{overwrite_id}") 229 | case response do 230 | :invalid -> :ok 231 | _ -> :error 232 | end 233 | end 234 | 235 | @doc """ 236 | Get channel invites 237 | 238 | Requires the 'MANAGE_CHANNELS' permission. 239 | 240 | ## Parameters 241 | 242 | - conn: User connection for REST holding auth info 243 | - channel_id: Channel id 244 | 245 | ## Examples 246 | 247 | Channel.get_invites(conn, 9999999999383) 248 | """ 249 | @spec get_invites(pid, number) :: list 250 | def get_invites(conn, channel_id) do 251 | DiscordEx.RestClient.resource(conn, :get, "channels/#{channel_id}/invites") 252 | end 253 | 254 | @doc """ 255 | Create channel invites 256 | 257 | Requires the CREATE_INSTANT_INVITE permission. 258 | 259 | ## Parameters 260 | 261 | - conn: User connection for REST holding auth info 262 | - channel_id: Channel id 263 | - options: Invite creation options which include (max_age [default 24 hours], max_uses, temporary, xkcdpass) 264 | 265 | ## Examples 266 | 267 | Channel.create_invite(conn, 3290293092093, %{max_age: 86400, max_uses: 1, temporary: false, xkcdpass: false}) 268 | """ 269 | @spec create_invite(pid, number, map) :: map 270 | def create_invite(conn, channel_id, options) do 271 | DiscordEx.RestClient.resource(conn, :post, "channels/#{channel_id}/invites", options) 272 | end 273 | 274 | @doc """ 275 | Trigger typing indicator 276 | 277 | Post a typing indicator for the specified channel. Generally bots should not implement this route. 278 | However, if a bot is responding to a command and expects the computation to take a few seconds, this 279 | endpoint may be called to let the user know that the bot is processing their message. 280 | 281 | ## Parameters 282 | 283 | - conn: User connection for REST holding auth info 284 | - channel_id: Channel id 285 | 286 | ## Examples 287 | 288 | Channel.trigger_typing(conn, 3290293092093) 289 | """ 290 | @spec trigger_typing(pid, number) :: atom 291 | def trigger_typing(conn, channel_id) do 292 | response = DiscordEx.RestClient.resource(conn, :post, "channels/#{channel_id}/typing") 293 | case response do 294 | :invalid -> :ok 295 | _ -> :error 296 | end 297 | end 298 | 299 | end 300 | -------------------------------------------------------------------------------- /doc/DiscordEx.RestClient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.RestClient – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.RestClient 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Discord RestClient. Used a GenServer so that you can have multiple 83 | clients in one application.

84 | 85 |
86 | 87 | 88 | 89 |
90 |

91 | 92 | 93 | Link to this section 94 | 95 | Summary 96 |

97 | 98 |
99 |

100 | Types 101 |

102 |
103 |
104 | request_reply() 105 |
106 | 107 |

Response body with related options

108 |
109 | 110 |
111 | 112 |
113 | 114 | 115 | 116 |
117 |

118 | Functions 119 |

120 |
121 |
122 | init(atom, opts) 123 |
124 | 125 |
126 | 132 |
133 | 136 | 137 |

Start process and HTTP Client. 138 | {:ok, conn} = DiscordEx.RestClient.start_link(%{token: token})

139 |
140 | 141 |
142 | 143 |
144 | 145 | 146 | 147 | 148 |
149 | 150 | 151 | 152 |
153 |

154 | 155 | 156 | Link to this section 157 | 158 | Types 159 |

160 |
161 |
162 | 163 | 164 |
165 | 166 | 167 | Link to this type 168 | 169 | request_reply() 170 | 171 | 172 | 173 | View Source 174 | 175 | 176 | 177 | 178 |
179 | 180 |
request_reply() :: {atom(), map(), map()}
181 | 182 |
183 | 184 |
185 |
186 |

Response body with related options

187 | 188 |
189 |
190 | 191 |
192 |
193 | 194 | 195 | 196 |
197 |

198 | 199 | 200 | Link to this section 201 | 202 | Functions 203 |

204 |
205 | 206 | 207 |
208 | 209 | 210 | Link to this function 211 | 212 | init(atom, opts) 213 | 214 | 215 | 216 | View Source 217 | 218 | 219 | 220 | 221 |
222 |
223 | 224 |
225 |
226 |
227 | 228 | 229 | 230 | 231 |
232 | 233 | 234 | Link to this function 235 | 236 | resource(connection, method, path, payload \\ nil) 237 | 238 | 239 | 240 | View Source 241 | 242 | 243 | 244 | 245 |
246 | 247 |
resource(pid(), atom(), String.t(), map()) :: request_reply()
248 | 249 |
250 | 251 |
252 |
253 | 254 |
255 |
256 |
257 | 258 | 259 | 260 | 261 |
262 | 263 | 264 | Link to this function 265 | 266 | start_link(opts, server_opts \\ []) 267 | 268 | 269 | 270 | View Source 271 | 272 | 273 | 274 | 275 |
276 |
277 |

Start process and HTTP Client. 278 | {:ok, conn} = DiscordEx.RestClient.start_link(%{token: token})

279 | 280 |
281 |
282 | 283 |
284 | 285 | 286 | 287 |
288 |

289 | 290 | Built using 291 | ExDoc (v0.18.1), 292 | 293 | 294 | designed by 295 | Friedel Ziegelmayer. 296 | 297 |

298 | 299 |
300 |
301 |
302 |
303 |
304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | -------------------------------------------------------------------------------- /doc/DiscordEx.Voice.Controller.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Voice.Controller – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Voice.Controller 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Voice control to make voice interaction a lot easier.

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |
109 |
110 | 113 | 114 |

Play some audio to a channel

115 |
116 | 117 |
118 |
119 |
120 | start(voice_client) 121 |
122 | 123 |
124 |
125 |
126 | stop(voice_client) 127 |
128 | 129 |

Stop audio from playing in channel and clear buffer

130 |
131 | 132 |
133 | 134 |
135 | 136 | 137 | 138 | 139 |
140 | 141 | 142 | 143 | 144 | 145 |
146 |

147 | 148 | 149 | Link to this section 150 | 151 | Functions 152 |

153 |
154 | 155 | 156 |
157 | 158 | 159 | Link to this function 160 | 161 | listen_socket(voice_client) 162 | 163 | 164 | 165 | View Source 166 | 167 | 168 | 169 | 170 |
171 |
172 | 173 |
174 |
175 |
176 | 177 | 178 | 179 | 180 |
181 | 182 | 183 | Link to this function 184 | 185 | play(voice_client, path, opts \\ %{}) 186 | 187 | 188 | 189 | View Source 190 | 191 | 192 | 193 | 194 |
195 |
196 |

Play some audio to a channel

197 |

198 | 199 | Parameters 200 |

201 | 202 |
    203 |
  • voice_client: The voice client so the library knows how to play it and where to 204 |
  • 205 |
  • path: The path where your audio file lives 206 |
  • 207 |
  • opts: Options like volume 208 |
  • 209 |
210 |

211 | 212 | Examples 213 |

214 | 215 |
DiscordEx.Controller.play(voice_client, "/my/awesome/audio.wav", %{volume: 128})
216 | 217 |
218 |
219 |
220 | 221 | 222 |
223 | 224 | 225 | Link to this function 226 | 227 | start(voice_client) 228 | 229 | 230 | 231 | View Source 232 | 233 | 234 | 235 | 236 |
237 |
238 | 239 |
240 |
241 |
242 | 243 | 244 |
245 | 246 | 247 | Link to this function 248 | 249 | stop(voice_client) 250 | 251 | 252 | 253 | View Source 254 | 255 | 256 | 257 | 258 |
259 |
260 |

Stop audio from playing in channel and clear buffer

261 |

262 | 263 | Parameters 264 |

265 | 266 |
    267 |
  • voice_client: The voice client so the library knows how to play it and where to 268 |
  • 269 |
270 |

271 | 272 | Examples 273 |

274 | 275 |
DiscordEx.Controller.stop(voice_client)
276 | 277 |
278 |
279 | 280 |
281 | 282 | 283 | 284 |
285 |

286 | 287 | Built using 288 | ExDoc (v0.18.1), 289 | 290 | 291 | designed by 292 | Friedel Ziegelmayer. 293 | 294 |

295 | 296 |
297 |
298 |
299 |
300 |
301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /doc/DiscordEx.RestClient.Resources.Invite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.RestClient.Resources.Invite – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.RestClient.Resources.Invite 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Convience helper for invites

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Accept invite

109 |
110 | 111 |
112 |
113 | 116 | 117 |

Delete invite object

118 |
119 | 120 |
121 |
122 |
123 | get(conn, invite_id) 124 |
125 | 126 |

Get invite object

127 |
128 | 129 |
130 | 131 |
132 | 133 | 134 | 135 | 136 |
137 | 138 | 139 | 140 | 141 | 142 |
143 |

144 | 145 | 146 | Link to this section 147 | 148 | Functions 149 |

150 |
151 | 152 | 153 |
154 | 155 | 156 | Link to this function 157 | 158 | accept(conn, invite_id) 159 | 160 | 161 | 162 | View Source 163 | 164 | 165 | 166 | 167 |
168 | 169 |
accept(pid(), String.t()) :: map()
170 | 171 |
172 | 173 |
174 |
175 |

Accept invite

176 |

Accept an invite. This is not available to bot accounts, and requires the guilds.join OAuth2 scope to accept on behalf of normal users.

177 |

178 | 179 | Parameters 180 |

181 | 182 |
    183 |
  • conn: User connection for REST holding auth info 184 |
  • 185 |
  • invite_id: Invite id 186 |
  • 187 |
188 |

189 | 190 | Examples 191 |

192 | 193 |
Invite.accept(conn, "99999999993832")
194 | 195 |
196 |
197 |
198 | 199 | 200 |
201 | 202 | 203 | Link to this function 204 | 205 | delete(conn, invite_id) 206 | 207 | 208 | 209 | View Source 210 | 211 | 212 | 213 | 214 |
215 | 216 |
delete(pid(), String.t()) :: map()
217 | 218 |
219 | 220 |
221 |
222 |

Delete invite object

223 |

Requires the MANAGE_CHANNELS permission. Returns an invite object on success.

224 |

225 | 226 | Parameters 227 |

228 | 229 |
    230 |
  • conn: User connection for REST holding auth info 231 |
  • 232 |
  • invite_id: Invite id 233 |
  • 234 |
235 |

236 | 237 | Examples 238 |

239 | 240 |
Invite.delete(conn, "99999999993832")
241 | 242 |
243 |
244 |
245 | 246 | 247 |
248 | 249 | 250 | Link to this function 251 | 252 | get(conn, invite_id) 253 | 254 | 255 | 256 | View Source 257 | 258 | 259 | 260 | 261 |
262 | 263 |
get(pid(), String.t()) :: map()
264 | 265 |
266 | 267 |
268 |
269 |

Get invite object

270 |

271 | 272 | Parameters 273 |

274 | 275 |
    276 |
  • conn: User connection for REST holding auth info 277 |
  • 278 |
  • invite_id: Invite id 279 |
  • 280 |
281 |

282 | 283 | Examples 284 |

285 | 286 |
Invite.get(conn, "99999999993832")
287 | 288 |
289 |
290 | 291 |
292 | 293 | 294 | 295 |
296 |

297 | 298 | Built using 299 | ExDoc (v0.18.1), 300 | 301 | 302 | designed by 303 | Friedel Ziegelmayer. 304 | 305 |

306 | 307 |
308 |
309 |
310 |
311 |
312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | -------------------------------------------------------------------------------- /doc/DiscordEx.Client.Helpers.MessageHelper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Client.Helpers.MessageHelper – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Client.Helpers.MessageHelper 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Bot Message Helpers

83 | 84 |
85 | 86 | 87 | 88 |
89 |

90 | 91 | 92 | Link to this section 93 | 94 | Summary 95 |

96 | 97 | 98 | 99 |
100 |

101 | Functions 102 |

103 |
104 | 107 | 108 |

Actionable Mention and DM Message

109 |
110 | 111 |
112 |
113 | 116 | 117 |

Actionable Mention and DM Message 118 | This checks that an incoming message is private or is a mention to the current user.

119 |

Parameters

120 |
    121 |
  • payload: Data from the triggered event. 122 |
  • 123 |
  • state: Current state of bot. 124 |
  • 125 |
126 |

Example

127 |
MessageHelper.actionable_message_for_me?(payload, state)
128 | #=> true
129 |
130 | 131 |
132 |
133 | 136 | 137 |

Parses a message payload which is content leading with ‘!’. 138 | Returns a tuple with the command and the message

139 |
140 | 141 |
142 |
143 | 146 | 147 |

Parses a message payload which is content leading with provided prefix. 148 | Returns a tuple with the command and the message

149 |
150 | 151 |
152 | 153 |
154 | 155 | 156 | 157 | 158 |
159 | 160 | 161 | 162 | 163 | 164 |
165 |

166 | 167 | 168 | Link to this section 169 | 170 | Functions 171 |

172 |
173 | 174 | 175 |
176 | 177 | 178 | Link to this function 179 | 180 | actionable_message_for?(bot_name, payload, state) 181 | 182 | 183 | 184 | View Source 185 | 186 | 187 | 188 | 189 |
190 | 191 |
actionable_message_for?(String.t(), map(), map()) :: boolean()
192 | 193 |
194 | 195 |
196 |
197 |

Actionable Mention and DM Message

198 |

This checks that an incoming message is private or is a mention to the defined user.

199 |

200 | 201 | Parameters 202 |

203 | 204 |
    205 |
  • bot_name: Name of the bot you are using. 206 |
  • 207 |
  • payload: Data from the triggered event. 208 |
  • 209 |
  • state: Current state of bot. 210 |
  • 211 |
212 |

213 | 214 | Example 215 |

216 | 217 |
MessageHelper.actionable_message_for?("Mr.Botman", payload, state)
218 | #=> true
219 | 220 |
221 |
222 |
223 | 224 | 225 |
226 | 227 | 228 | Link to this function 229 | 230 | actionable_message_for_me?(payload, state) 231 | 232 | 233 | 234 | View Source 235 | 236 | 237 | 238 | 239 |
240 | 241 |
actionable_message_for_me?(map(), map()) :: boolean()
242 | 243 |
244 | 245 |
246 |
247 |

Actionable Mention and DM Message 248 | This checks that an incoming message is private or is a mention to the current user.

249 |

250 | 251 | Parameters 252 |

253 | 254 |
    255 |
  • payload: Data from the triggered event. 256 |
  • 257 |
  • state: Current state of bot. 258 |
  • 259 |
260 |

261 | 262 | Example 263 |

264 | 265 |
MessageHelper.actionable_message_for_me?(payload, state)
266 | #=> true
267 | 268 |
269 |
270 |
271 | 272 | 273 |
274 | 275 | 276 | Link to this function 277 | 278 | msg_command_parse(payload) 279 | 280 | 281 | 282 | View Source 283 | 284 | 285 | 286 | 287 |
288 | 289 |
msg_command_parse(map()) :: {String.t(), String.t()}
290 | 291 |
292 | 293 |
294 |
295 |

Parses a message payload which is content leading with ‘!’. 296 | Returns a tuple with the command and the message.

297 |

298 | 299 | Parameters 300 |

301 | 302 |
    303 |
  • payload: Data from the triggered event. 304 |
  • 305 |
306 |

307 | 308 | Example 309 |

310 | 311 |
MessageHelper.msg_command_parse(payload)
312 | #=> {"ping", "me please!"}
313 | 314 |
315 |
316 |
317 | 318 | 319 |
320 | 321 | 322 | Link to this function 323 | 324 | msg_command_parse(payload, prefix) 325 | 326 | 327 | 328 | View Source 329 | 330 | 331 | 332 | 333 |
334 | 335 |
msg_command_parse(map(), String) :: {String.t(), String.t()}
336 | 337 |
338 | 339 |
340 |
341 |

Parses a message payload which is content leading with provided prefix. 342 | Returns a tuple with the command and the message.

343 |

344 | 345 | Parameters 346 |

347 | 348 |
    349 |
  • prefix: prefix for your command 350 |
  • 351 |
  • payload: Data from the triggered event. 352 |
  • 353 |
354 |

355 | 356 | Example 357 |

358 | 359 |
MessageHelper.msg_command_parse(payload, "!")
360 | #=> {"ping", "me please!"}
361 | 362 |
363 |
364 | 365 |
366 | 367 | 368 | 369 |
370 |

371 | 372 | Built using 373 | ExDoc (v0.18.1), 374 | 375 | 376 | designed by 377 | Friedel Ziegelmayer. 378 | 379 |

380 | 381 |
382 |
383 |
384 |
385 |
386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | -------------------------------------------------------------------------------- /doc/DiscordEx.Client.Utility.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | DiscordEx.Client.Utility – Discord Elixir v1.1.18 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 27 | 63 | 64 |
65 |
66 |
67 | 68 | 69 |

70 | Discord Elixir v1.1.18 71 | DiscordEx.Client.Utility 72 | 73 | 74 | 75 | View Source 76 | 77 | 78 |

79 | 80 | 81 |
82 |

Utilty methods to be used for discord clients.

83 |

Normalizers, Encoders, and Decoders

84 | 85 |
86 | 87 | 88 | 89 |
90 |

91 | 92 | 93 | Link to this section 94 | 95 | Summary 96 |

97 | 98 | 99 | 100 |
101 |

102 | Functions 103 |

104 |
105 | 108 | 109 |

Generic function for updating the value of an agent process

110 |
111 | 112 |
113 |
114 |
115 | agent_value(agent) 116 |
117 | 118 |

Generic function for getting the value from an agent process

119 |
120 | 121 |
122 |
123 |
124 | normalize_atom(atom) 125 |
126 | 127 |

Convert atom to string

128 |
129 | 130 |
131 |
132 |
133 | opcode(codes, value) 134 |
135 | 136 |

Get the atom value of and opcode using an integer value

137 |
138 | 139 |
140 |
141 | 144 | 145 |

Build a binary payload for discord communication

146 |
147 | 148 |
149 |
150 | 153 | 154 |

Build a json payload for discord communication

155 |
156 | 157 |
158 |
159 | 162 | 163 |

Decode json payload received from discord into a map

164 |
165 | 166 |
167 | 168 |
169 | 170 | 171 | 172 | 173 |
174 | 175 | 176 | 177 | 178 | 179 |
180 |

181 | 182 | 183 | Link to this section 184 | 185 | Functions 186 |

187 |
188 | 189 | 190 |
191 | 192 | 193 | Link to this function 194 | 195 | agent_update(agent, n) 196 | 197 | 198 | 199 | View Source 200 | 201 | 202 | 203 | 204 |
205 | 206 |
agent_update(pid(), any()) :: nil
207 | 208 |
209 | 210 |
211 |
212 |

Generic function for updating the value of an agent process

213 | 214 |
215 |
216 |
217 | 218 | 219 |
220 | 221 | 222 | Link to this function 223 | 224 | agent_value(agent) 225 | 226 | 227 | 228 | View Source 229 | 230 | 231 | 232 | 233 |
234 | 235 |
agent_value(pid()) :: any()
236 | 237 |
238 | 239 |
240 |
241 |

Generic function for getting the value from an agent process

242 | 243 |
244 |
245 |
246 | 247 | 248 |
249 | 250 | 251 | Link to this function 252 | 253 | normalize_atom(atom) 254 | 255 | 256 | 257 | View Source 258 | 259 | 260 | 261 | 262 |
263 | 264 |
normalize_atom(atom()) :: String.t()
265 | 266 |
267 | 268 |
269 |
270 |

Convert atom to string

271 | 272 |
273 |
274 |
275 | 276 | 277 |
278 | 279 | 280 | Link to this function 281 | 282 | opcode(codes, value) 283 | 284 | 285 | 286 | View Source 287 | 288 | 289 | 290 | 291 |
292 | 293 |
opcode(map(), integer()) :: atom()
294 | 295 |
opcode(map(), atom()) :: integer()
296 | 297 |
298 | 299 |
300 |
301 |

Get the atom value of and opcode using an integer value

302 | 303 |
304 |
305 |
306 | 307 | 308 | 309 | 310 | 311 | 312 |
313 | 314 | 315 | Link to this function 316 | 317 | payload_build(op, data, seq_num \\ nil, event_name \\ nil) 318 | 319 | 320 | 321 | View Source 322 | 323 | 324 | 325 | 326 |
327 | 328 |
payload_build(number(), map(), number(), String.t()) :: binary()
329 | 330 |
331 | 332 |
333 |
334 |

Build a binary payload for discord communication

335 | 336 |
337 |
338 |
339 | 340 | 341 | 342 | 343 | 344 | 345 |
346 | 347 | 348 | Link to this function 349 | 350 | payload_build_json(op, data, seq_num \\ nil, event_name \\ nil) 351 | 352 | 353 | 354 | View Source 355 | 356 | 357 | 358 | 359 |
360 | 361 |
payload_build_json(number(), map(), number(), String.t()) :: binary()
362 | 363 |
364 | 365 |
366 |
367 |

Build a json payload for discord communication

368 | 369 |
370 |
371 |
372 | 373 | 374 |
375 | 376 | 377 | Link to this function 378 | 379 | payload_decode(codes, arg) 380 | 381 | 382 | 383 | View Source 384 | 385 | 386 | 387 | 388 |
389 | 390 |
payload_decode(list(), {atom(), binary()}) :: map()
391 | 392 |
payload_decode(list(), {atom(), binary()}) :: map()
393 | 394 |
395 | 396 |
397 |
398 |

Decode json payload received from discord into a map

399 | 400 |
401 |
402 | 403 |
404 | 405 | 406 | 407 |
408 |

409 | 410 | Built using 411 | ExDoc (v0.18.1), 412 | 413 | 414 | designed by 415 | Friedel Ziegelmayer. 416 | 417 |

418 | 419 |
420 |
421 |
422 |
423 |
424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | --------------------------------------------------------------------------------