Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
333a7cb
- Created Email.Protocol.Pop3 for pop3 protocol
d4rk5eed Jan 30, 2018
1978190
- added get_updates
Jan 31, 2018
533953d
wip
d4rk5eed Feb 1, 2018
4333247
email helpers
Feb 17, 2018
6a7af56
remove commented dependencies
Feb 19, 2018
8775159
Merge pull request #3 from Victoria91/email_helpers
d4rk5eed Feb 19, 2018
a64cff9
refactoring and test fix
Feb 19, 2018
a2e4a6b
fix helper module name
Feb 19, 2018
4a68718
Merge pull request #4 from Victoria91/fix-module-name
d4rk5eed Feb 20, 2018
549a077
fetch update interval from config
Feb 21, 2018
f3ca092
Merge pull request #5 from Victoria91/update_interval_to_config
d4rk5eed Feb 21, 2018
da842f3
Merge pull request #1 from agalaframework/add-pop3-mailer
Victoria91 Feb 22, 2018
dd511b2
delete messages and quit
Feb 22, 2018
d81549f
Merge pull request #6 from Victoria91/delete_and_quit
d4rk5eed Feb 22, 2018
f030468
add missing methods to pop3_mock
Feb 22, 2018
c7a1f96
Merge pull request #7 from Victoria91/delete_and_quit
d4rk5eed Feb 22, 2018
776a43d
Merge pull request #2 from agalaframework/add-pop3-mailer
Victoria91 Feb 26, 2018
f49c8af
Merge pull request #8 from Victoria91/add-pop3-mailer
d4rk5eed Feb 26, 2018
18344bf
add email headers for reply
Mar 5, 2018
93f8865
remove mail sending form agala_email
Mar 12, 2018
ee29ed7
Merge pull request #9 from Victoria91/add_email_reply
d4rk5eed Mar 13, 2018
82c19b7
Merge pull request #3 from agalaframework/add-pop3-mailer
Victoria91 Mar 14, 2018
578bbaa
provide loggging on get updates
Mar 14, 2018
4aec18b
Merge pull request #10 from Victoria91/logging_on_get_updates
d4rk5eed Mar 15, 2018
a2dcf0e
agala3 update
Jul 16, 2018
da46e40
Merge pull request #13 from Victoria91/agala3-update
Virviil Jul 18, 2018
6ae615c
Updateing agala version
Virviil Jul 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
erlang: 20.3
elixir 1.6.3
13 changes: 6 additions & 7 deletions lib/agala_email.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
defmodule Agala.Provider.Email do
use Agala.Provider

@moduledoc """
Module providing email connection.
"""

use Agala.Provider

def init(bot_params) do
{
:ok,
bot_params
}
defmacro __using__(:handler) do
quote location: :keep do
import Agala.Provider.Email.Helpers
end
end
end
10 changes: 10 additions & 0 deletions lib/helpers.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule Agala.Provider.Email.Helpers do
import Bamboo.Email

def generate_message(to, from, subject) do
new_email
|> to(to)
|> from(from)
|> subject(subject)
end
end
63 changes: 63 additions & 0 deletions lib/poller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Agala.Provider.Email.Poller do
@pop3 Agala.Provider.Email.Protocol.Pop3
@pop3_mock Agala.Provider.Email.Protocol.Pop3.Mock

use Agala.Bot.Common.Poller
alias Agala.BotParams

#######################################################################################
### Initialize section
#######################################################################################

@spec bootstrap(Agala.BotParams.t()) :: {:ok, Agala.BotParams}
def bootstrap(bot_params) do
{
:ok,
Map.put(bot_params, :private, %{
mail_fetcher_module: (Mix.env() == :test && @pop3_mock) || @pop3
})
}
end

#######################################################################################
### Get updates section
#######################################################################################

@spec get_updates(bot_params :: Agala.BotParams.t()) :: {list(), Agala.BotParams.t()}
def get_updates(bot_params = %BotParams{}) do
mail_proto = private_options(bot_params)

{:ok, client} = mail_proto.connect(mail_options(bot_params))
{:ok, mails} = mail_proto.scan(client)

Logger.debug("Email retrieving started. Total count of messages in box: #{length(mails)}")

updates =
Enum.map(mails, fn {id, _} ->
# @todo: use poolboy and asynk tasks
{:ok, bin_message} = mail_proto.retrieve(client, id)

mail_proto.delete(client, id)
mail_proto.parse_binary(bin_message)
end)

mail_proto.disconnect(client)

:timer.sleep(updates_interval(bot_params))

{updates, bot_params}
end

defp private_options(%BotParams{
private: %{
mail_fetcher_module: mail_fetcher_module
}
}),
do: mail_fetcher_module

defp updates_interval(%BotParams{provider_params: %{updates_interval: interval}}), do: interval

defp mail_options(%BotParams{} = opts) do
Map.get(opts, :provider_params)
end
end
51 changes: 51 additions & 0 deletions lib/protocol/pop3.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
defmodule Agala.Provider.Email.Protocol.Pop3 do
@moduledoc """
Implementation of Pop3 mail protocol for Agala framework.
Based on nico-amsterdam/pop3mail
"""

def connect(%{email: email, password: password, server: server, port: port}) do
:epop_client.connect(email, password, [{:addr, server}, {:port, port}, {:user, email}, :ssl])
end

def scan(client) do
:epop_client.scan(client)
end

def retrieve(client, id) do
:epop_client.bin_retrieve(client, id)
end

def delete(client, id) do
:epop_client.delete(client, id)
end

def disconnect(client) do
:epop_client.quit(client)
end

def parse_binary(raw_binary) do
{:message, h, c} =
raw_binary
|> :epop_message.bin_parse()
|> parse_content
|> parse_headers

{h, c}
end

def parse_headers({:message, headers, content}) do
parsed_headers =
headers
|> Enum.reduce(%{}, fn {:header, key, val}, acc ->
Map.put(acc, key, val)
end)

{:message, parsed_headers, content}
end

def parse_content({:message, headers, content}) do
parsed_content = Pop3mail.decode_body_content(headers, content)
{:message, headers, parsed_content}
end
end
45 changes: 45 additions & 0 deletions lib/protocol/pop3_mock.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
defmodule Agala.Provider.Email.Protocol.Pop3.Mock do
@moduledoc """
Implementation of mock module for Pop3 mail protocol of Agala framework.
For testing purposes
"""
alias Agala.Provider.Email.Protocol.Pop3

def connect(%{email: _, password: _, server: _, port: _, login: _}) do
{:ok,
{:sk, 'user', 'mail.example.com',
{:sslsocket, {:gen_tcp, "some_port", :tls_connection, :undefined}, self()}, 995, false,
false, true}}
end

def scan(_client) do
{:ok, [{1, 1948}, {2, 1923}, {3, 1123}]}
end

def retrieve(_client, id) do
{:ok,
"To: <[email protected]@yandex.ru>\r\nFrom: Sender <[email protected]>\r\nSubject: =?UTF-8?B?0LLQvtC/0YDQvtGBINC/0L4g0LrRgNC10LTQuNGC0YM=?=\r\nDate: Tue, 30 Jan 2018 16:22:53 +0300\r\nUser-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101\r\n Thunderbird/52.5.2\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=\"utf-8\"; format=flowed\r\nContent-Transfer-Encoding: 8bit\r\nContent-Language: en-US\r\n\r\nmessage number #{
id
}\r\n\r\n"}
end

def delete(_client, _id) do
:ok
end

def disconnect(_client) do
:ok
end

def parse_binary(raw_binary) do
Pop3.parse_binary(raw_binary)
end

def parse_headers({:message, headers, content}) do
Pop3.parse_headers({:message, headers, content})
end

def parse_content({:message, headers, content}) do
Pop3.parse_content({:message, headers, content})
end
end
9 changes: 5 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ defmodule AgalaEmail.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:agala, "~> 2.0.2"},
{:agala, "~> 3.0"},
{:ex_doc, "> 0.0.0", only: :dev},
{:credo, "~> 0.8", only: [:dev, :test]}
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
{:credo, "~> 0.8", only: [:dev, :test]},
{:pop3mail, "~> 1.3"},
{:erlpop, github: "nico-amsterdam/erlpop"},
{:bamboo, "~> 0.8"}
]
end

Expand Down
25 changes: 18 additions & 7 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
%{
"agala": {:hex, :agala, "2.0.2", "9a4706cafb4f371ceca503a60b317c33b78fc7e618676ffa5bffba7bcf931748", [:mix], [{:excoveralls, "~> 0.7.4", [hex: :excoveralls, repo: "hexpm", optional: false]}], "hexpm"},
"agala": {:hex, :agala, "3.0.0", "00eba9262858c65b835cc9eebdf488137c70d9a26d1473349663b1a8a930d218", [:mix], [], "hexpm"},
"bamboo": {:hex, :bamboo, "0.8.0", "573889a3efcb906bb9d25a1c4caa4ca22f479235e1b8cc3260d8b88dabeb4b14", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
"certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"},
"credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], [], "hexpm"},
"ex_doc": {:hex, :ex_doc, "0.18.2", "993e0a95e9fbb790ac54ea58e700b45b299bd48bc44b4ae0404f28161f37a83e", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
"earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
"erlpop": {:git, "https://github.com/nico-amsterdam/erlpop.git", "811c20ba5566e38eb3f724d2bf3b76fb4d286df2", []},
"ex_doc": {:hex, :ex_doc, "0.18.4", "4406b8891cecf1352f49975c6d554e62e4341ceb41b9338949077b0d4a97b949", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
"excoveralls": {:hex, :excoveralls, "0.7.5", "339e433e5d3bce09400dc8de7b9040741a409c93917849916c136a0f51fdc183", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
"hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.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.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"fastglobal": {:hex, :fastglobal, "1.0.0", "f3133a0cda8e9408aac7281ec579c4b4a8386ce0e99ca55f746b9f58192f455b", [:mix], [], "hexpm"},
"hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [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.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"iconv": {:hex, :iconv, "1.0.6", "3b424a80039059767f1037dc6a49ff07c2f88df14068c16dc938c4f377a77b4c", [:rebar3], [{:p1_utils, "1.0.10", [hex: :p1_utils, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
"mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
"p1_utils": {:hex, :p1_utils, "1.0.10", "a6d6927114bac79cf6468a10824125492034af7071adc6ed5ebc4ddb443845d4", [:rebar3], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
"plug": {:hex, :plug, "1.6.1", "c62fe7623d035020cf989820b38490460e6903ab7eee29e234b7586e9b6c91d6", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], [], "hexpm"},
"pop3mail": {:hex, :pop3mail, "1.3.0", "5eba88226a54572debe30667ceadd2f8e4ea104f3309fea8f74a768d71ee512c", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
}
22 changes: 22 additions & 0 deletions test/email_receiver_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule EmailReceiverTest do
use ExUnit.Case
doctest Agala.Provider.Email
alias Agala.BotParams
alias Agala.Provider.Email.Poller

@bot_configuration %BotParams{
provider_params: %{
login: "user",
password: "secure",
server: "mail.server.ru",
port: 995,
email: "[email protected]",
updates_interval: 5 * 1_000
}
}

test "get_updates call applies handler" do
{:ok, bot_params} = Poller.bootstrap(@bot_configuration)
assert {_updates, ^bot_params} = Poller.get_updates(bot_params)
end
end
16 changes: 14 additions & 2 deletions test/email_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@ defmodule EmailTest do
test "init returns :ok and bot params" do
bot_configuration = %BotParams{
provider_params: %{
username: "user", password: "secure", server: "mail.server.ru"
username: "user",
password: "secure",
server: "mail.server.ru"
}
}

assert {:ok, ^bot_configuration} = Agala.Provider.Email.init(bot_configuration)
expected_bot_configuration =
Map.put(
bot_configuration,
:private,
%{
mail_fetcher_module: Agala.Provider.Email.Protocol.Pop3.Mock
}
)

assert {:ok, ^expected_bot_configuration} =
Agala.Provider.Email.Poller.bootstrap(bot_configuration)
end
end
63 changes: 63 additions & 0 deletions test/protocol/pop3_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Email.Protocol.Pop3Test do
use ExUnit.Case
doctest Agala.Provider.Email.Protocol.Pop3
alias Agala.Provider.Email.Protocol.Pop3

test "parse_headers returns map of headers" do
exp_result = %{
"To" => "<[email protected]>",
"From" => "Sender <[email protected]>",
"Subject" =>
"=?UTF-8?B?0LLQvtC/0YDQvtGBINC/0L4g0LrRgNC10LTQuNGC0YM=?=",
"Message-ID" => "<[email protected]>",
"Date" => "Tue, 30 Jan 2018 16:22:53 +0300",
"User-Agent" =>
"Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.5.2",
"MIME-Version" => "1.0",
"Content-Type" => "text/plain; charset=\"utf-8\"; format=flowed",
"Content-Transfer-Encoding" => "8bit",
"Content-Language" => "en-US"
}
assert {:message, ^exp_result, _} = Pop3.parse_headers({:message, mock_headers(), mock_content()})
end

test "parse_content returns list of parts" do
exp_result = [
%Pop3mail.Part{
boundary: "",
charset: "utf-8",
content: "пожалуйста не присылайте коллекторов\r\n\r\n",
content_id: "",
content_location: "",
filename: "",
filename_charset: "us-ascii",
index: 1,
inline: nil,
media_type: "text/plain",
path: ""
}
]
assert {:message, _, ^exp_result} = Pop3.parse_content({:message, mock_headers(), mock_content()})
end

defp mock_headers() do
[
{:header, "To", "<[email protected]>"},
{:header, "From", "Sender <[email protected]>"},
{:header, "Subject",
"=?UTF-8?B?0LLQvtC/0YDQvtGBINC/0L4g0LrRgNC10LTQuNGC0YM=?="},
{:header, "Message-ID", "<[email protected]>"},
{:header, "Date", "Tue, 30 Jan 2018 16:22:53 +0300"},
{:header, "User-Agent",
"Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Thunderbird/52.5.2"},
{:header, "MIME-Version", "1.0"},
{:header, "Content-Type", "text/plain; charset=\"utf-8\"; format=flowed"},
{:header, "Content-Transfer-Encoding", "8bit"},
{:header, "Content-Language", "en-US"}
]
end

defp mock_content() do
"пожалуйста не присылайте коллекторов\r\n\r\n"
end
end