├── test ├── test_helper.exs ├── one_signal_test.exs └── one_signal │ └── param_test.exs ├── .gitignore ├── lib ├── one_signal │ ├── api.ex │ ├── utils.ex │ ├── notification.ex │ └── param.ex └── one_signal.ex ├── mix.exs ├── README.md ├── config └── config.exs └── mix.lock /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start(exclude: [:skip]) 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /cover 3 | /deps 4 | erl_crash.dump 5 | *.ez 6 | -------------------------------------------------------------------------------- /test/one_signal_test.exs: -------------------------------------------------------------------------------- 1 | defmodule OneSignalTest do 2 | use ExUnit.Case 3 | doctest OneSignal 4 | 5 | test "create one signal structure" do 6 | assert %OneSignal.Param{} = OneSignal.new() 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/one_signal/api.ex: -------------------------------------------------------------------------------- 1 | defmodule OneSignal.API do 2 | 3 | def get(url, query \\ []) do 4 | HTTPoison.start 5 | query = OneSignal.Utils.encode_body(query) 6 | 7 | unless String.length(query) == 0 do 8 | url = "#{url}?#{query}" 9 | end 10 | 11 | HTTPoison.get!(url, OneSignal.auth_header) 12 | |> handle_response 13 | end 14 | 15 | def post(url, body) do 16 | HTTPoison.start 17 | 18 | req_body = Poison.encode!(body) 19 | 20 | HTTPoison.post!(url, req_body, OneSignal.auth_header) 21 | |> handle_response 22 | end 23 | 24 | def delete(url) do 25 | HTTPoison.start 26 | HTTPoison.delete!(url, OneSignal.auth_header) 27 | |> handle_response 28 | end 29 | 30 | defp handle_response(%HTTPoison.Response{body: body, status_code: code}) when code in 200..299 do 31 | {:ok, Poison.decode!(body)} 32 | end 33 | 34 | defp handle_response(%HTTPoison.Response{body: body, status_code: _}) do 35 | {:error, Poison.decode!(body)} 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule OneSignal.Mixfile do 2 | use Mix.Project 3 | 4 | @description "Elixir wrapper of OneSignal" 5 | 6 | def project do 7 | [app: :one_signal, 8 | version: "0.0.9", 9 | elixir: "~> 1.2", 10 | build_embedded: Mix.env == :prod, 11 | start_permanent: Mix.env == :prod, 12 | deps: deps(), 13 | description: @description, 14 | package: package()] 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], 22 | mod: {OneSignal, []}] 23 | end 24 | 25 | defp package() do 26 | [maintainers: ["Takuma Yoshida"], 27 | licenses: ["MIT"], 28 | links: %{"GitHub" => "https://github.com/yoavlt/one_signal"}, 29 | ] 30 | end 31 | 32 | defp deps() do 33 | [ 34 | {:poison, "~> 3.1.0"}, 35 | {:httpoison, "~> 1.5.0"}, 36 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} 37 | ] 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OneSignal 2 | 3 | Elixir wrapper of [OneSignal](https://onesignal.com) 4 | 5 | ## Installation 6 | 7 | If [available in Hex](https://hex.pm/docs/publish), the package can be installed as: 8 | 9 | 1. Add one_signal to your list of dependencies in `mix.exs`: 10 | 11 | ```elixir 12 | def deps do 13 | [{:one_signal, "~> 0.0.6"}] 14 | end 15 | ``` 16 | 17 | 2. Ensure one_signal is started before your application: 18 | 19 | ```elixir 20 | def application do 21 | [applications: [:one_signal]] 22 | end 23 | ``` 24 | 25 | 3. Puts config your `config.exs` 26 | 27 | ```elixir 28 | config :one_signal, OneSignal, 29 | api_key: "your api key", 30 | app_id: "your app id", 31 | ``` 32 | 33 | 34 | ## Composable design, Data structure oriented 35 | 36 | ```elixir 37 | import OneSignal.Param 38 | OneSignal.new 39 | |> put_heading("Welcome!") 40 | |> put_message(:en, "Hello") 41 | |> put_message(:ja, "はろー") 42 | |> put_segment("Free Players") 43 | |> put_segment("New Players") 44 | |> notify 45 | ``` 46 | -------------------------------------------------------------------------------- /lib/one_signal/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule OneSignal.Utils do 2 | @doc """ 3 | Encode request body 4 | """ 5 | def encode_body(query) do 6 | Enum.map_join query, "&", fn x -> 7 | pair(x) 8 | end 9 | end 10 | 11 | defp pair({key, value}) do 12 | if Enumerable.impl_for(value) do 13 | pair(to_string(key), [], value) 14 | else 15 | param_name = key |> to_string |> URI.encode 16 | param_value = value |> to_string |> URI.encode 17 | 18 | "#{param_name}=#{param_value}" 19 | end 20 | end 21 | 22 | defp pair(root, parents, values) do 23 | Enum.map_join values, "&", fn {key, value} -> 24 | if Enumerable.impl_for(value) do 25 | pair(root, parents ++ [key], value) 26 | else 27 | build_key(root, parents ++ [key]) <> to_string(value) 28 | end 29 | end 30 | end 31 | 32 | defp build_key(root, parents) do 33 | path = Enum.map_join parents, "", fn x -> 34 | param = x |> to_string |> URI.encode 35 | "[#{param}]" 36 | end 37 | 38 | "#{root}#{path}=" 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/one_signal.ex: -------------------------------------------------------------------------------- 1 | defmodule OneSignal do 2 | use Application 3 | 4 | # See http://elixir-lang.org/docs/stable/elixir/Application.html 5 | # for more information on OTP Applications 6 | def start(_type, _args) do 7 | import Supervisor.Spec, warn: false 8 | 9 | children = [] 10 | 11 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html 12 | # for other strategies and supported options 13 | opts = [strategy: :one_for_one, name: OneSignal.Supervisor] 14 | Supervisor.start_link(children, opts) 15 | end 16 | 17 | def endpoint, do: "https://onesignal.com/api/v1" 18 | 19 | def new do 20 | %OneSignal.Param{} 21 | end 22 | 23 | def auth_header do 24 | %{"Authorization" => "Basic " <> fetch_api_key(), "Content-type" => "application/json"} 25 | end 26 | 27 | defp config do 28 | Application.get_env(:one_signal, OneSignal) 29 | end 30 | 31 | defp fetch_api_key do 32 | config()[:api_key] || System.get_env("ONE_SIGNAL_API_KEY") 33 | end 34 | 35 | def fetch_app_id do 36 | config()[:app_id] || System.get_env("ONE_SIGNAL_APP_ID") 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/one_signal/notification.ex: -------------------------------------------------------------------------------- 1 | defmodule OneSignal.Notification do 2 | defstruct id: nil, recipients: 0 3 | 4 | def post_notification_url() do 5 | OneSignal.endpoint <> "/notifications" 6 | end 7 | 8 | @doc """ 9 | Send push notification 10 | iex> OneSignal.Notification.send(%{"en" => "Hello!", "ja" => "はろー"}, %{"included_segments" => ["All"], "isAndroid" => true}) 11 | """ 12 | def send(contents, opts) do 13 | param = %{"contents" => contents, "app_id" => OneSignal.fetch_app_id} 14 | body = Map.merge(param, opts) 15 | url = post_notification_url() 16 | case OneSignal.API.post(url, body) do 17 | {:ok, response} -> 18 | response = Enum.map(response, &to_key_atom/1) 19 | struct(__MODULE__, response) 20 | err -> err 21 | end 22 | end 23 | 24 | def send(body) do 25 | case OneSignal.API.post(post_notification_url, body) do 26 | {:ok, response} -> 27 | response = Enum.map(response, &to_key_atom/1) 28 | struct(__MODULE__, response) 29 | err -> err 30 | end 31 | end 32 | 33 | def to_key_atom({k, v}) do 34 | {String.to_atom(k), v} 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /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 :one_signal, key: :value 14 | # 15 | # And access this configuration in your application as: 16 | # 17 | # Application.get_env(:one_signal, :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 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "earmark": {:hex, :earmark, "1.3.5", "0db71c8290b5bc81cb0101a2a507a76dca659513984d683119ee722828b424f6", [:mix], [], "hexpm"}, 4 | "ex_doc": {:hex, :ex_doc, "0.21.1", "5ac36660846967cd869255f4426467a11672fec3d8db602c429425ce5b613b90", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, 6 | "httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, 7 | "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "makeup": {:hex, :makeup, "1.0.0", "671df94cf5a594b739ce03b0d0316aa64312cee2574b6a44becb83cd90fb05dc", [:mix], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 9 | "makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, 10 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, 11 | "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, 12 | "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"}, 13 | "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, 14 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, 15 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.4", "f0eafff810d2041e93f915ef59899c923f4568f4585904d010387ed74988e77b", [:make, :mix, :rebar3], [], "hexpm"}, 16 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}, 17 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"}, 18 | } 19 | -------------------------------------------------------------------------------- /test/one_signal/param_test.exs: -------------------------------------------------------------------------------- 1 | defmodule OneSignal.ParamTest do 2 | use ExUnit.Case 3 | import OneSignal.Param 4 | 5 | test "put message" do 6 | param = OneSignal.new 7 | |> put_message(:en, "Hello") 8 | |> put_message(:ja, "はろー") 9 | 10 | assert param.messages == %{:en => "Hello", :ja => "はろー"} 11 | end 12 | 13 | test "put message without specifying languages" do 14 | param = OneSignal.new |> put_message("Hello") 15 | assert param.messages == %{:en => "Hello"} 16 | end 17 | 18 | test "put heading" do 19 | param = OneSignal.new 20 | |> put_heading("Title") 21 | assert param.headings == %{:en => "Title"} 22 | 23 | param = OneSignal.new 24 | |> put_heading(:en, "Title") 25 | |> put_heading(:ja, "タイトル") 26 | assert param.headings == %{:en => "Title", :ja => "タイトル"} 27 | end 28 | 29 | test "put segment" do 30 | param = OneSignal.new 31 | |> put_segment("Free Players") 32 | |> put_segment("New Players") 33 | refute Enum.empty?(param.included_segments) 34 | assert Enum.all?(param.included_segments, 35 | &(&1 in ["Free Players", "New Players"])) 36 | end 37 | 38 | test "put segments" do 39 | segs = ["Free Players", "New Players"] 40 | param = put_segments(OneSignal.new, segs) 41 | refute Enum.empty?(param.included_segments) 42 | assert Enum.all?(param.included_segments, &(&1 in segs)) 43 | end 44 | 45 | test "drop segment" do 46 | segs = ["Free Players", "New Payers"] 47 | param = OneSignal.new 48 | |> put_segments(segs) 49 | |> drop_segments(segs) 50 | assert Enum.empty?(param.included_segments) 51 | end 52 | 53 | test "exclude segment" do 54 | param = OneSignal.new 55 | |> exclude_segment("Free Players") 56 | |> exclude_segment("New Players") 57 | assert Enum.all?(param.excluded_segments, 58 | &(&1 in ["Free Players", "New Players"])) 59 | end 60 | 61 | test "exclude segments" do 62 | segs = ["Free Players", "New Players"] 63 | param = 64 | exclude_segments(OneSignal.new, segs) 65 | refute Enum.empty?(param.excluded_segments) 66 | assert Enum.all?(param.excluded_segments, &(&1 in segs)) 67 | end 68 | 69 | test "build parameter" do 70 | param = OneSignal.new 71 | |> put_heading("Welcome!") 72 | |> put_message(:en, "Hello") 73 | |> put_message(:ja, "はろー") 74 | |> exclude_segment("Free Players") 75 | |> exclude_segment("New Players") 76 | |> build 77 | 78 | assert param["contents"] 79 | assert param["app_id"] 80 | assert param["headings"] 81 | assert param["excluded_segments"] 82 | end 83 | 84 | test "push notification" do 85 | notified = OneSignal.new 86 | |> put_heading("Welcome!") 87 | |> put_message(:en, "Hello") 88 | |> put_message(:ja, "はろー") 89 | |> put_segment("Free Players") 90 | |> put_segment("New Players") 91 | |> notify 92 | assert %OneSignal.Notification{} = notified 93 | end 94 | 95 | test "push notification with filter" do 96 | notified = OneSignal.new 97 | |> put_heading("Welcome!") 98 | |> put_message(:en, "Hello") 99 | |> put_message(:ja, "はろー") 100 | |> put_filter(%{field: "tag", key: "userId", value: "123", relation: "="}) 101 | |> notify 102 | assert %OneSignal.Notification{} = notified 103 | end 104 | 105 | test "put player id" do 106 | param = put_player_id(OneSignal.new, "aiueo") 107 | refute Enum.empty?(param.include_player_ids) 108 | end 109 | 110 | test "exclude player id" do 111 | param = exclude_player_id(OneSignal.new, "aiueo") 112 | refute Enum.empty?(param.exclude_player_ids) 113 | end 114 | 115 | test "put data" do 116 | world = OneSignal.new 117 | |> put_data("Hello", "World!") 118 | |> build 119 | |> get_in(["data", "Hello"]) 120 | assert world == "World!" 121 | end 122 | 123 | test "put url" do 124 | url = "https://github.com/yoavlt/one_signal" 125 | param = OneSignal.new() 126 | |> put_url(url) 127 | assert param.url == url 128 | end 129 | 130 | end 131 | -------------------------------------------------------------------------------- /lib/one_signal/param.ex: -------------------------------------------------------------------------------- 1 | defmodule OneSignal.Param do 2 | alias OneSignal.Param 3 | 4 | defstruct android_channel_id: nil, messages: %{}, headings: nil, platforms: nil, included_segments: nil, excluded_segments: nil, include_external_user_ids: nil, exclude_external_user_ids: nil, include_player_ids: nil, exclude_player_ids: nil, filters: [],tags: nil, data: nil, ios_params: nil, android_params: nil, adm_params: nil, wp_params: nil, chrome_params: nil, firefox_params: nil, send_after: nil, url: nil, subtitle: nil 5 | 6 | defp to_string_key({k, v}) do 7 | {to_string(k), v} 8 | end 9 | 10 | defp to_body({:headings, headings}) do 11 | body = headings 12 | |> Enum.map(&to_string_key/1) 13 | |> Enum.into(%{}) 14 | {:headings, body} 15 | end 16 | defp to_body(body), do: body 17 | 18 | @doc """ 19 | Send push notification from parameters 20 | """ 21 | def notify(%Param{} = param) do 22 | param 23 | |> build 24 | |> OneSignal.Notification.send 25 | end 26 | 27 | @doc """ 28 | Build notifications parameter of request 29 | """ 30 | def build(%Param{} = param) do 31 | required = %{ 32 | "app_id" => OneSignal.fetch_app_id, 33 | "contents" => Enum.map(param.messages, &to_string_key/1) |> Enum.into(%{}), 34 | "filters" => param.filters 35 | } 36 | 37 | reject_params = [:messages, :filters, :platforms, :ios_params, 38 | :android_params, :adm_params, :wp_params, 39 | :chrome_params, :firefox_params] 40 | optionals = param 41 | |> Map.from_struct 42 | |> Enum.reject(fn {k, v} -> 43 | k in reject_params or is_nil(v) 44 | end) 45 | |> Enum.map(&to_body/1) 46 | |> Enum.map(&to_string_key/1) 47 | |> Enum.into(%{}) 48 | 49 | Map.merge(required, optionals) 50 | end 51 | 52 | @doc """ 53 | Put message in parameters 54 | 55 | iex> OneSignal.new 56 | |> put_message(:en, "Hello") 57 | |> put_message(:ja, "はろー") 58 | """ 59 | def put_message(%Param{} = param, message) do 60 | put_message(param, :en, message) 61 | end 62 | def put_message(%Param{} = param, language, message) do 63 | messages = Map.put(param.messages, language, message) 64 | %{param | messages: messages} 65 | end 66 | 67 | @doc """ 68 | Put notification title. 69 | Notification title to send to Android, Amazon, Chrome apps, and Chrome Websites. 70 | 71 | iex> OneSignal.new 72 | |> put_heading("App Notice!") 73 | |> put_message("Hello") 74 | """ 75 | def put_heading(%Param{} = param, heading) do 76 | put_heading(param, :en, heading) 77 | end 78 | def put_heading(%Param{headings: nil} = param, language, heading) do 79 | %{param | headings: %{language => heading}} 80 | end 81 | def put_heading(%Param{headings: headings} = param, language, heading) do 82 | headings = Map.put(headings, language, heading) 83 | %{param | headings: headings} 84 | end 85 | 86 | @doc """ 87 | Put specific target segment 88 | 89 | iex> OneSignal.new 90 | |> put_message("Hello") 91 | |> put_segment("Top-Rank") 92 | """ 93 | def put_segment(%Param{included_segments: nil} = param, segment) do 94 | %{param | included_segments: [segment]} 95 | end 96 | def put_segment(%Param{included_segments: seg} = param, segment) do 97 | %{param | included_segments: [segment|seg]} 98 | end 99 | 100 | @doc """ 101 | Put specific filter 102 | 103 | iex> OneSignal.new 104 | |> put_message("Hello") 105 | |> put_filter("{userId: asdf}") 106 | """ 107 | def put_filter(%Param{filters: filters} = param, filter) do 108 | %{param | filters: [filter | filters]} 109 | end 110 | 111 | @doc """ 112 | Put segments 113 | """ 114 | def put_segments(%Param{} = param, segs) do 115 | Enum.reduce(segs, param, fn next, acc -> put_segment(acc, next) end) 116 | end 117 | 118 | @doc """ 119 | Drop specific target segment 120 | 121 | iex> OneSignal.new 122 | |> put_segment("Free Players") 123 | |> drop_segment("Free Players") 124 | """ 125 | def drop_segment(%Param{included_segments: nil} = param, _seg) do 126 | param 127 | end 128 | def drop_segment(%Param{} = param, seg) do 129 | segs = Enum.reject(param.included_segments, &(&1 == seg)) 130 | %{param | included_segments: segs} 131 | end 132 | 133 | @doc """ 134 | Drop specific target segments 135 | """ 136 | def drop_segments(%Param{} = param, segs) do 137 | Enum.reduce(segs, param, fn next, acc -> drop_segment(acc, next) end) 138 | end 139 | 140 | @doc """ 141 | Exclude specific segment 142 | """ 143 | def exclude_segment(%Param{excluded_segments: nil} = param, seg) do 144 | %{param | excluded_segments: [seg]} 145 | end 146 | def exclude_segment(%Param{excluded_segments: segs} = param, seg) do 147 | %{param | excluded_segments: [seg|segs]} 148 | end 149 | 150 | @doc """ 151 | Exclude segments 152 | """ 153 | def exclude_segments(%Param{} = param, segs) do 154 | Enum.reduce(segs, param, fn next, acc -> exclude_segment(acc, next) end) 155 | end 156 | 157 | @doc """ 158 | Put external user id 159 | """ 160 | def put_external_user_id(%Param{include_external_user_ids: nil} = param, user_id) do 161 | %{param | include_external_user_ids: [user_id]} 162 | end 163 | def put_external_user_id(%Param{include_external_user_ids: ids} = param, user_id) do 164 | %{param | include_external_user_ids: [user_id|ids]} 165 | end 166 | 167 | def put_external_user_ids(%Param{} = param, user_ids) when is_list(user_ids) do 168 | Enum.reduce(user_ids, param, fn next, acc -> 169 | put_external_user_id(acc, next) 170 | end) 171 | end 172 | 173 | @doc """ 174 | Exclude external user id 175 | """ 176 | def exclude_external_user_id(%Param{exclude_external_user_ids: nil} = param, user_id) do 177 | %{param | exclude_external_user_ids: [user_id]} 178 | end 179 | def exclude_external_user_id(%Param{exclude_external_user_ids: ids} = param, user_id) do 180 | %{param | exclude_external_user_ids: [user_id|ids]} 181 | end 182 | 183 | def exclude_external_user_ids(%Param{} = param, user_ids) when is_list(user_ids) do 184 | Enum.reduce(user_ids, param, fn next, acc -> 185 | exclude_external_user_id(acc, next) 186 | end) 187 | end 188 | 189 | @doc """ 190 | Put player id 191 | """ 192 | def put_player_id(%Param{include_player_ids: nil} = param, player_id) do 193 | %{param | include_player_ids: [player_id]} 194 | end 195 | def put_player_id(%Param{include_player_ids: ids} = param, player_id) do 196 | %{param | include_player_ids: [player_id|ids]} 197 | end 198 | 199 | def put_player_ids(%Param{} = param, player_ids) when is_list(player_ids) do 200 | Enum.reduce(player_ids, param, fn next, acc -> 201 | put_player_id(acc, next) 202 | end) 203 | end 204 | 205 | @doc """ 206 | Exclude player id 207 | """ 208 | def exclude_player_id(%Param{exclude_player_ids: nil} = param, player_id) do 209 | %{param | exclude_player_ids: [player_id]} 210 | end 211 | def exclude_player_id(%Param{exclude_player_ids: ids} = param, player_id) do 212 | %{param | exclude_player_ids: [player_id|ids]} 213 | end 214 | 215 | def exclude_player_ids(%Param{} = param, player_ids) when is_list(player_ids) do 216 | Enum.reduce(player_ids, param, fn next, acc -> 217 | exclude_player_id(acc, next) 218 | end) 219 | end 220 | 221 | @doc """ 222 | Put data 223 | """ 224 | def put_data(%Param{data: nil} = param, key, value) do 225 | %{param | data: %{key => value}} 226 | end 227 | 228 | def put_data(%Param{data: data} = param, key, value) do 229 | %{param | data: Map.put(data, key, value)} 230 | end 231 | 232 | @doc """ 233 | Set android channel/category for notification 234 | """ 235 | def set_android_channel_id(param, channel_id) do 236 | %{param | android_channel_id: channel_id} 237 | end 238 | 239 | @doc """ 240 | Set destination URL. 241 | """ 242 | def put_url(param, nil), do: param 243 | def put_url(param, url) do 244 | %{param | url: url} 245 | end 246 | 247 | @doc """ 248 | Set subtitle. 249 | """ 250 | def put_subtitle(param, nil), do: param 251 | def put_subtitle(param, subtitle) do 252 | %{param | subtitle: subtitle} 253 | end 254 | end 255 | --------------------------------------------------------------------------------