Current section

10 Versions

Jump to

Compare versions

23 files changed
+452 additions
-317 deletions
  @@ -1,4 +1,4 @@
1 - # ExWechat
1 + # ExWechat [![Build Status](https://travis-ci.org/h1u2i3/ex_wechat.svg?branch=master)](https://travis-ci.org/h1u2i3/ex_wechat.svg?branch=master)
2 2
3 3 Elixir/Phoenix wechat api, ([documentation](http://hexdocs.pm/ex_wechat/)).
4 4 Issues and pullrequests are welcome.
  @@ -11,7 +11,7 @@ Still in development, so there may be some bugs.
11 11
12 12 ```elixir
13 13 def deps do
14 - [{:ex_wechat, "~> 0.1.3"}]
14 + [{:ex_wechat, "~> 0.1.4"}]
15 15 end
16 16 ```
17 17
  @@ -71,14 +71,39 @@ Then your application should work.
71 71 defp on_text_responder(conn) do
72 72 message = conn.assigns[:message]
73 73 case message do
74 + %{content: "我要图"} ->
75 + reply_with(conn, build_message(
76 + %{
77 + fromusername: message.tousername,
78 + tousername: message.fromusername,
79 + msgtype: "news",
80 + articlecount: "2",
81 + articles:
82 + %{
83 + item: [
84 + %{
85 + title: "title",
86 + description: "description",
87 + picurl: "picurl",
88 + url: "url"
89 + },
90 + %{
91 + title: "title",
92 + description: "description",
93 + picurl: "picurl",
94 + url: "url"
95 + }]
96 + }
97 + }))
74 98 %{content: content} ->
75 - reply_with(conn, build_message(%{
99 + reply_with(conn, build_message(
100 + %{
101 + fromusername: message.tousername,
102 + tousername: message.fromusername,
76 103 msgtype: "text",
77 - from: message.tousername,
78 - to: message.fromusername,
79 104 content: String.reverse(content)
80 105 }))
81 - _ ->
106 + _ ->
82 107 conn
83 108 end
84 109 end
  @@ -173,6 +198,42 @@ For example:
173 198 iex(2)> File.write! "/Users/xiaohui/Desktop/y.jpg", ExWechat.get_media media_id: "NgGUXaSTGizWyG5Kc0xpydHHIm2PGy68ZViXmpXnojYLV7pw-6zuZaRkTu1cnhja"
174 199 :ok
175 200 ```
201 + 3. Because we got the base usage with Wechat Api, we can define our own module to do more semantic work.
202 + Media example:
203 +
204 + ```elixir
205 + defmodule Wechat.Media do
206 + use ExWechat.Api
207 +
208 + @api [:media]
209 +
210 + def upload_image(path) do
211 + upload_media {:multipart, [{:file, path}]}, type: "image"
212 + end
213 +
214 + def download(media_id) do
215 + get_media media_id: media_id
216 + end
217 + end
218 + ```
219 + User example:
220 +
221 + ```elixir
222 + defmodule Wechat.User do
223 + use ExWechat.Api
224 +
225 + @api [:user]
226 +
227 + def info(openid)
228 + get_user_info openid: openid
229 + end
230 +
231 + def list do
232 + get_user_list
233 + end
234 + end
235 + ```
236 + in this Wechat Api sdk, I don't add these modules, I let you to create your own.
176 237
177 238 ## License
178 239 MIT license.
  @@ -4,23 +4,29 @@
4 4 {<<"elixir">>,<<"~> 1.3">>}.
5 5 {<<"files">>,
6 6 [<<"lib/ex_wechat.ex">>,<<"lib/ex_wechat/api.ex">>,
7 - <<"lib/ex_wechat/apis/access_token">>,<<"lib/ex_wechat/apis/card">>,
7 + <<"lib/ex_wechat/apis/access_token">>,
8 8 <<"lib/ex_wechat/apis/custom_service">>,<<"lib/ex_wechat/apis/media">>,
9 9 <<"lib/ex_wechat/apis/menu">>,<<"lib/ex_wechat/apis/message">>,
10 10 <<"lib/ex_wechat/apis/qrcode">>,<<"lib/ex_wechat/apis/server_ip">>,
11 11 <<"lib/ex_wechat/apis/shorturl">>,<<"lib/ex_wechat/apis/user">>,
12 12 <<"lib/ex_wechat/base.ex">>,<<"lib/ex_wechat/helpers/api_helper.ex">>,
13 + <<"lib/ex_wechat/helpers/api_parser.ex">>,
13 14 <<"lib/ex_wechat/helpers/crypto_helper.ex">>,
14 - <<"lib/ex_wechat/helpers/time_helper.ex">>,<<"lib/ex_wechat/message.ex">>,
15 + <<"lib/ex_wechat/helpers/method_generator.ex">>,
16 + <<"lib/ex_wechat/helpers/params_parser.ex">>,
17 + <<"lib/ex_wechat/helpers/time_helper.ex">>,
18 + <<"lib/ex_wechat/helpers/xml_parser.ex">>,
19 + <<"lib/ex_wechat/helpers/xml_render.ex">>,<<"lib/ex_wechat/message.ex">>,
15 20 <<"lib/ex_wechat/plugs/wechat_message_parser.ex">>,
16 21 <<"lib/ex_wechat/plugs/wechat_signature_responder.ex">>,
22 + <<"lib/ex_wechat/plugs/wechat_website.ex">>,
17 23 <<"lib/ex_wechat/responder.ex">>,<<"lib/ex_wechat/templates/image.eex">>,
18 24 <<"lib/ex_wechat/templates/music.eex">>,
19 25 <<"lib/ex_wechat/templates/news.eex">>,
20 26 <<"lib/ex_wechat/templates/text.eex">>,
21 27 <<"lib/ex_wechat/templates/video.eex">>,
22 28 <<"lib/ex_wechat/templates/voice.eex">>,<<"lib/ex_wechat/token.ex">>,
23 - <<"mix.exs">>,<<"README.md">>]}.
29 + <<"lib/ex_wechat/website.ex">>,<<"mix.exs">>,<<"README.md">>]}.
24 30 {<<"licenses">>,[<<"MIT">>]}.
25 31 {<<"links">>,[{<<"GitHub">>,<<"https://github.com/h1u2i3/ex_wechat">>}]}.
26 32 {<<"maintainers">>,[<<"h1u2i3">>]}.
  @@ -42,4 +48,4 @@
42 48 {<<"name">>,<<"floki">>},
43 49 {<<"optional">>,false},
44 50 {<<"requirement">>,<<"~> 0.11.0">>}]]}.
45 - {<<"version">>,<<"0.1.3">>}.
51 + {<<"version">>,<<"0.1.4">>}.
  @@ -13,160 +13,58 @@ defmodule ExWechat.Api do
13 13 use ExWechat.Base
14 14
15 15 import ExWechat.Helpers.ApiHelper
16 + import ExWechat.Helpers.MethodGenerator
17 +
16 18 alias ExWechat.Token
17 19
18 20 @doc """
19 21 Return the access_token
20 22 """
21 - def access_token do
22 - Token._access_token
23 - end
24 -
25 - @doc """
26 - Use when access_token invalid
27 - """
28 - def renew_access_token do
29 - Token._force_get_access_token
30 - end
31 -
32 - @doc false
33 - def encode_post_body(body) do
34 - case body do
35 - %{} -> Poison.encode!(body)
36 - _ -> body
37 - end
38 - end
39 -
40 - @doc """
41 - Generate all the methods define in the api definitions.
42 - """
43 - def define_api_method(%{endpoint: endpoint, params: params, http: http,
44 - path: path, function: function, doc: doc}) do
45 - case http do
46 - :get ->
47 - quote do
48 - @doc false
49 - def unquote((String.replace(path, "/", "_") <> "url") |> String.to_atom)() do
50 - unquote(endpoint)
51 - end
52 -
53 - @doc """
54 - #{unquote(doc)}
55 - """
56 - def unquote(function)(added_params \\ []) do
57 - case __MODULE__.get(unquote(path), [],
58 - params: unquote(params)
59 - |> unquote(__MODULE__).do_parse_params([], "", "")
60 - |> Keyword.merge(added_params)) do
61 - {:ok, response} ->
62 - body = response.body
63 - case body do
64 - %{errcode: 40001} ->
65 - unquote(__MODULE__).renew_access_token
66 - apply(__MODULE__, unquote(function), [added_params])
67 - _ -> body
68 - end
69 - {:error, error} ->
70 - case error.reason do
71 - :closed ->
72 - apply(__MODULE__, unquote(function), [added_params])
73 - _ ->
74 - %{error: error.reason}
75 - end
76 - end
77 - end
78 - end
79 - :post ->
80 - quote do
81 - @doc false
82 - def unquote((String.replace(path, "/", "_") <> "url") |> String.to_atom)() do
83 - unquote(endpoint)
84 - end
85 -
86 - @doc """
87 - #{unquote(doc)}
88 - """
89 - def unquote(function)(post_body, added_params \\ []) do
90 - case __MODULE__.post(unquote(path), unquote(__MODULE__).encode_post_body(post_body), [],
91 - params: unquote(params)
92 - |> unquote(__MODULE__).do_parse_params([], "", "")
93 - |> Keyword.merge(added_params)) do
94 - {:ok, response} ->
95 - body = response.body
96 - case body do
97 - %{errcode: 40001} ->
98 - unquote(__MODULE__).renew_access_token
99 - apply(__MODULE__, unquote(function), [post_body, added_params])
100 - _ -> body
101 - end
102 - {:error, error} ->
103 - case error.reason do
104 - :closed ->
105 - apply(__MODULE__, unquote(function), [post_body, added_params])
106 - _ ->
107 - %{error: error.reason}
108 - end
109 - end
110 - end
111 - end
112 - end
113 - end
23 + def access_token, do: Token._access_token
114 24
115 25 @doc """
116 26 Generate the AST data of method definiitons.
117 27 """
118 28 def compile(origin) do
119 - values = origin
120 - |> Map.values
121 - |> Enum.reject(&Enum.empty?/1)
122 - |> Enum.flat_map(fn(x) ->
123 - case x do
124 - [[[head | tail]]] -> [head | tail]
125 - _ -> x
126 - end
127 - end)
128 -
129 - ast_data = for data <- values do
130 - define_api_method(data)
131 - end
29 + ast_data = generate_methods(origin)
132 30
133 31 quote do
134 32 unquote(ast_data)
135 33 end
136 34 end
137 35
36 + defmacro __using__(_opts) do
37 + quote do
38 + use HTTPoison.Base
138 39
40 + import ExWechat.Helpers.ParamsParser
139 41
140 - @doc false
141 - def do_parse_params(params, result, key, value)
142 - def do_parse_params("", result, "", "") do
143 - result
144 - end
145 - def do_parse_params("=" <> rest, result, key, value) do
146 - result = result |> Keyword.put(key |> String.to_atom, value)
147 - do_parse_params(rest, result, key, value)
148 - end
149 - def do_parse_params(", " <> rest, result, key, "") do
150 - key = key |> String.to_atom
151 - result = result |> Keyword.put(key, apply(ExWechat.Api, key, []))
152 - do_parse_params(rest, result, "", "")
153 - end
154 - def do_parse_params(", " <> rest, result, key, value) do
155 - key = key |> String.to_atom
156 - result = result |> Keyword.put(key, value)
157 - do_parse_params(rest, result, "", "")
158 - end
159 - def do_parse_params(<<binary::8>> <> rest, result, key, value) do
160 - case Keyword.has_key?(result, String.to_atom(key)) do
161 - false ->
162 - do_parse_params(rest, result, key <> IO.chardata_to_string([binary]), "")
163 - true ->
164 - do_parse_params(rest, result, key, value <> IO.chardata_to_string([binary]))
42 + @before_compile unquote(__MODULE__)
43 +
44 + @doc """
45 + This method can be used in you own defined module.
46 + You can add this method in your module and afford the needed params.
47 + """
48 + def get_params(param) do
49 + :not_set
50 + end
51 + defoverridable [get_params: 1]
52 +
53 + defp process_response_body(body)
54 + defp process_response_body("{" <> _ = body), do: Poison.decode!(body, keys: :atoms)
55 + defp process_response_body(body), do: body
56 +
57 + defp process_url(url) do
58 + apply(__MODULE__, url_method_name(url), []) <> url
59 + end
60 +
61 + defp url_method_name(url) do
62 + "#{Regex.named_captures(~r/(?<name>.+)\?/, url)["name"]}/url"
63 + |> String.replace("/", "_")
64 + |> String.to_atom
65 + end
165 66 end
166 67 end
167 - def do_parse_params("", result, key, value) do
168 - do_parse_params(", ", result, key, value)
169 - end
170 68
171 69 defmacro __before_compile__(env) do
172 70 env.module
  @@ -174,28 +72,4 @@ defmodule ExWechat.Api do
174 72 |> process_api_definition_data
175 73 |> compile
176 74 end
177 -
178 - defmacro __using__(_opts) do
179 - quote do
180 - use HTTPoison.Base
181 - @before_compile unquote(__MODULE__)
182 -
183 - @doc false
184 - def process_response_body(body) do
185 - case body do
186 - "{" <> _ ->
187 - Poison.decode!(body, keys: :atoms)
188 - _ ->
189 - body
190 - end
191 - end
192 -
193 - @doc false
194 - def process_url(url) do
195 - path = url |> String.split("?") |> List.first
196 - endpoint = apply __MODULE__, (String.replace(path, "/", "_") <> "url") |> String.to_atom, []
197 - endpoint <> url
198 - end
199 - end
200 - end
201 75 end
  @@ -3,93 +3,45 @@ defmodule ExWechat.Helpers.ApiHelper do
3 3 praser data from api description file.
4 4 """
5 5 use ExWechat.Base
6 + import ExWechat.Helpers.ApiParser
6 7
7 - def process_api_definition_data(needed_api_kinds) do
8 - case needed_api_kinds do
9 - nil ->
10 - process_api_definition_data(:all)
11 - :all ->
12 - all_api_definition_data
13 - _ ->
14 - Map.take(all_api_definition_data, needed_api_kinds)
15 - end
16 - end
8 + @api_path Path.join(__DIR__, "../apis")
17 9
18 - # read single file and return api definition data
19 - defp api_definition_data(path) do
20 - data = File.stream!(path, [], :line)
21 - |> Stream.map(&String.trim/1)
22 - |> Stream.reject(&(String.length(&1) == 0))
23 - |> Enum.to_list
24 - do_parse_api_data(%{}, [], data)
10 + def process_api_definition_data(needed)
11 + def process_api_definition_data(nil), do: process_api_definition_data(:all)
12 + def process_api_definition_data(:all), do: all_api_definition_data
13 + def process_api_definition_data(needed), do: Map.take(all_api_definition_data, needed)
14 +
15 + defp all_definition_files, do: _all_definition_files(api_definition_files)
16 + defp _all_definition_files(user_define_path)
17 + defp _all_definition_files(nil), do: all_files_in_folder(@api_path)
18 + defp _all_definition_files(user_define_path), do: all_files_in_folder(@api_path) ++ all_files_in_folder(user_define_path)
19 +
20 + defp all_files_in_folder(path) do
21 + Path.wildcard(path <> "/*")
25 22 end
26 23
27 24 # get all the data of all the api definition file
28 - defp all_api_definition_data do
29 - all_definition_files = case api_definition_files do
30 - nil -> Path.wildcard(Path.join(__DIR__, "../apis/*"))
31 - _ -> Path.wildcard(Path.join(__DIR__, "../apis/*")) ++ Path.wildcard(api_definition_files <> "/*")
32 - end
33 - Enum.reduce(all_definition_files, %{}, fn(path, acc) ->
34 - {_, acc} = Map.get_and_update(acc, path |> String.split("/") |> List.last |> String.to_atom, fn(current) ->
35 - case current do
36 - nil -> {current, path |> api_definition_data }
37 - _ -> {current, current ++ (path |> api_definition_data)}
38 - end
39 - end)
40 - acc
41 - end)
42 - end
43 -
44 - # parse data from api definition file with parttern match
45 - defp do_parse_api_data(temp, result, endpoint \\ nil, lines)
46 - defp do_parse_api_data(temp, result, endpoint, [ "//" <> _ | tail ]) do
47 - do_parse_api_data(temp, result, endpoint, tail)
48 - end
49 - defp do_parse_api_data(temp, result, endpoint, [ "# " <> rest | tail ]) do
50 - {_, temp} = Map.get_and_update(temp, :doc, fn(current) ->
51 - case current do
52 - nil -> {current, rest}
53 - _ -> {current, current <> "\n" <> rest}
54 - end
55 - end)
56 - do_parse_api_data(temp, result, endpoint, tail)
57 - end
58 - defp do_parse_api_data(temp, result, endpoint, [ "@endpoint " <> rest | tail ]) do
59 - temp = temp |> Map.put(:endpoint, rest |> String.trim)
60 - case endpoint do
61 - nil -> do_parse_api_data(temp, result, rest, tail)
62 - _ -> do_parse_api_data(temp, result, endpoint, tail)
63 - end
64 - end
65 - defp do_parse_api_data(temp, result, endpoint, [ "function: " <> rest | tail ]) do
66 - temp
67 - |> Map.put(:function, rest |> String.trim |> String.to_atom)
68 - |> do_parse_api_data(result, endpoint, tail)
69 - end
70 - defp do_parse_api_data(temp, result, endpoint, [ "path: " <> rest | tail ]) do
71 - temp
72 - |> Map.put(:path, rest |> String.trim)
73 - |> do_parse_api_data(result, endpoint, tail)
74 - end
75 - defp do_parse_api_data(temp, result, endpoint, [ "http: " <> rest | tail ]) do
76 - temp
77 - |> Map.put(:http, rest |> String.trim |> String.to_atom)
78 - |> do_parse_api_data(result, endpoint, tail)
79 - end
80 - defp do_parse_api_data(temp, result, endpoint, [ "params:" <> rest | tail ]) do
81 - temp = case Map.get(temp, :endpoint) do
82 - nil ->
83 - temp
84 - |> Map.put(:endpoint, endpoint)
85 - |> Map.put(:params, rest |> String.trim)
86 - _ ->
87 - temp
88 - |> Map.put(:params, rest |> String.trim)
89 - end
90 - do_parse_api_data(%{}, result ++ [temp], endpoint, tail)
91 - end
92 - defp do_parse_api_data(_temp, result, _, []) do
25 + defp all_api_definition_data, do: _all_definition_data(all_definition_files)
26 + defp _all_definition_data(files, result \\ %{})
27 + defp _all_definition_data([], result) do
28 + # IO.inspect result
93 29 result
94 30 end
31 + defp _all_definition_data([file | tail], result) do
32 + key = get_key_from_path(file)
33 + data = Map.put(result, key,
34 + Map.get(result, key, []) ++ (file |> api_definition_data))
35 + _all_definition_data(tail, data)
36 + end
37 +
38 + defp get_key_from_path(path), do: path |> String.split("/") |> List.last |> String.to_atom
39 +
40 + defp api_definition_data(path) do
41 + File.stream!(path, [], :line)
42 + |> Stream.map(&String.trim/1)
43 + |> Stream.reject(&(String.length(&1) == 0))
44 + |> Enum.to_list
45 + |> do_parse_api_data
46 + end
95 47 end
Loading more files…