Related
Currently, there is no api for creating a MIX channel.
I'm written a custom module for the same.
So far, I have written the following code. But I'm not how to proceed further.
I would really appreciate someone's guidance here. Thanks in advance.
-module(mod_custom).
-behaviour(gen_mod).
-include("logger.hrl").
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2]).
-export([
% Create channel
add_channel/4
]).
-include("ejabberd_commands.hrl").
-include("ejabberd_sm.hrl").
-include("xmpp.hrl").
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[].
get_commands_spec() ->
[
#ejabberd_commands{name = add_channel, tags = [group],
desc = "Create a WhatsApp like group",
module = ?MODULE, function = add_channel,
args = [{jid, binary}, {channel, binary}, {id, binary}],
args_example = [<<"admin#localhost">>, <<"testgroup123#localhost">>, <<"abc123456">>],
args_desc = ["Admin JID", "Channel JID", "Unique ID"],
result = {res, rescode}}
].
add_channel(JID, Channel, ID) ->
%%% Create channel code goes here...
ok.
mod_options(_) -> [].
Try something like this:
-module(mod_custom).
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2]).
-export([create_channel/3]).
-include("logger.hrl").
-include("ejabberd_commands.hrl").
-include("ejabberd_sm.hrl").
-include_lib("xmpp/include/xmpp.hrl").
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[].
get_commands_spec() ->
[#ejabberd_commands{name = create_channel, tags = [group],
desc = "Create a WhatsApp like group",
module = ?MODULE, function = create_channel,
args = [{from, binary},
{channel, binary},
{service, binary}],
args_example = [<<"admin#localhost">>,
<<"testgroup123">>,
<<"mix.localhost">>],
args_desc = ["From JID", "Channel Name", "MIX Service"],
result = {res, rescode}}
].
create_channel(From, ChannelName, Service) ->
try xmpp:decode(
#xmlel{name = <<"iq">>,
attrs = [{<<"to">>, Service},
{<<"from">>, From},
{<<"type">>, <<"set">>},
{<<"id">>, p1_rand:get_string()}],
children =
[#xmlel{name = <<"create">>,
attrs = [{<<"channel">>, ChannelName},
{<<"xmlns">>, ?NS_MIX_CORE_0}]}
]},
?NS_CLIENT, []) of
#iq{type = set} = Iq ->
case mod_mix:process_mix_core(Iq) of
#iq{type = result} ->
ok;
_ ->
{error, unexpected_response}
end
catch _:{xmpp_codec, Why} ->
{error, xmpp:format_error(Why)}
end.
mod_options(_) -> [].
I am trying to bind a list of input items on my html page to my controller.
My form is defined as:
def clientForm = Form( tuple(
"clients[]" -> seq( tuple(
"firstname" -> text,
"lastname" -> text) )
) )
In my HTML I have tried the following:
#b3.text(thisForm("clients[0]"), '_label -> "first client", 'value -> "('John','Snow')")
#b3.text(thisForm("clients[1]"), '_label -> "second client", 'value -> "('Frank','Carson')")
I have also tried:
#b3.text(thisForm("clients[0].firstname"), '_label -> "first client fistname", 'value -> "John")
#b3.text(thisForm("clients[0].lastname"), '_label -> "first client lastname", 'value -> "Snow")
#b3.text(thisForm("clients[1].firstname"), '_label -> "second client fistname", 'value -> "Frank")
#b3.text(thisForm("clients[2].lastname"), '_label -> "second client lastname", 'value -> "Carson")
In debug mode upon POST I can see that these values get bound to the form within the controller:
val boundFrom = inForm.bindFromRequest
But when I boundForm.fold mapping to clientForm the values to not map correctly to my "clients[]" element.
I'm at a loss and have spent ages looking for the answer to no avail.
Any help greatly appreciated
EDIT:
Here's a screenshot in debug mode. The values are being bound to the form but then do not get assigned.
Debug screenshot
Please note that your form definition can be as following -
def clientForm = Form(
single(
"clients[]" -> seq(
tuple(
"firstname" -> text,
"lastname" -> text
)
)
)
)
Then you second way of writing template is correct as you can see in documentation here https://www.playframework.com/documentation/2.6.x/ScalaForms#Repeated-values
Now what you will get when you fold this form is that a seq of tuples and you can use it like following -
clientForm.bindFromRequest.fold(
error => // Handle error
clients => {// this clients is of signature Seq[(String, String)]
clients.map{client => logger.log("First name: " + client._1 + ", Last name: " + client_2)}
}
)
Please note that you have to use tuple ._1 and ._2 to access your first name and last name here. As you are getting a tuple (String, String) not a Map[String, String] here.
I'm trying to find URLs in a nested JSON response and map them. My function so far looks like this:
def list(env, id) do
Service.get_document(env, id)
|> Poison.decode!
|> Enum.find(fn {_key, val} -> String.starts_with?(val, 'https') end)
end
The JSON looks roughly like this:
"stacks": [
{
"boxes": [
{
"content": "https://ddd.cloudfront.net/photos/uploaded_images/000/001/610/original/1449447147677.jpg?1505956120",
"box": "photo"
}
]
}
],
"logo": "https://ddd.cloudfront.net/users/cmyk_banners/000/000/002/original/banner_CMYK.jpg?1397201875"
So URLs can have any key, and be at any level.
With that code I get this error:
no function clause matching in String.starts_with?/2
Anyone got a better way to find in JSON responses?
You'll have to use recursive function for this, which handles three types of data:
For map, it recurses over all its values.
For list, it recurses over all its elements.
For string, it selects strings that start with "https"
Here's a simple implementation which accepts a term and a string to check with starts_with?:
defmodule A do
def recursive_starts_with(thing, start, acc \\ [])
def recursive_starts_with(binary, start, acc) when is_binary(binary) do
if String.starts_with?(binary, start) do
[binary | acc]
else
acc
end
end
def recursive_starts_with(map, start, acc) when is_map(map) do
Enum.reduce(map, acc, fn {_, v}, acc -> A.recursive_starts_with(v, start, acc) end)
end
def recursive_starts_with(list, start, acc) when is_list(list) do
Enum.reduce(list, acc, fn v, acc -> A.recursive_starts_with(v, start, acc) end)
end
end
data = %{
"stacks" => [
%{
"boxes" => [
%{
"content" => "https://ddd.cloudfront.net/photos/uploaded_images/000/001/610/original/1449447147677.jpg?1505956120",
"box" => "photo"
}
]
}
],
"logo" => "https://ddd.cloudfront.net/users/cmyk_banners/000/000/002/original/banner_CMYK.jpg?1397201875"
}
data |> A.recursive_starts_with("https") |> IO.inspect
Output:
["https://ddd.cloudfront.net/photos/uploaded_images/000/001/610/original/1449447147677.jpg?1505956120",
"https://ddd.cloudfront.net/users/cmyk_banners/000/000/002/original/banner_CMYK.jpg?1397201875"]
I want to parse XML strings to erlang list and then to JSON.
Example Input :
<?xml version="1.0" encoding="UTF-8"?>
<!--some message here-->
<start>
<data>
<number id="333">test message</number>
<data>current date</data>
</data>
<mass>
<client>35</client>
<address>lattitude</address>
<code>3454343</code>
<foo tipo="casa">Some text message 2</foo>
<product>TEST</product>
</mass>
</start>
Output should be:
{
"start": {
"data": {
"number": {
"#id": "333",
"#text": "test message"
},
"data": "current date"
},
"mass": {
"client": "35",
"address": "lattitude",
"code": "3454343",
"foo": {
"#tipo": "casa",
"#text": "Some text message 2"
},
"product": "TEST"
}
}
}
I am trying to use erlsom:simple_form(Xml).
and getting :
{ok,{"start",[],
[{"data",[],
[{"number",[{"id","333"}],["test message"]},
{"data",[],["current date"]}]},
{"mass",[],
[{"client",[],["35"]},
{"address",[],["lattitude"]},
{"code",[],["3454343"]},
{"foo",[{"tipo","casa"}],["Some text message 2"]},
{"product",[],["TEST"]}]}]},
[]}
Now I want to delete these empty attrs. Is there any simple way to do this?
thanks in advance.
UPDATE: Make it work w/ solution from
Erlang xml to tuples and lists
BUT Getting
{"start",
[{"data",
[{"number","test message"},{"data","current date"}]},
{"mass",
[{"client","35"},
{"address","lattitude"},
{"code","3454343"},
{"foo","Some text message 2"},
{"product","TEST"}]}]}
there is no [{"id","333"}] & [{"tipo","casa"}] lists :(
The output of your simple parsing is in a set format: {Node, Attributes, Children}, so you can write a simple parser that turns that structure you have into a nested proplist. With that, you can either use mochijson or jsx to turn that proplist into a JSON string.
-module(transform).
-export([test/0]).
test() -> parse(data()).
parse({Node, [], [Value]}) when is_list(Value) ->
[{Node, Value}];
parse({Node, [], Children}) ->
V = children_to_struct(Children, []),
[{Node, V}];
parse({Node, Attributes, Children}) ->
V = attributes_to_struct(Attributes, []) ++ children_to_struct(Children, []),
[{Node, V}].
children_to_struct([], Acc) -> Acc;
children_to_struct([Value], Acc) when is_list(Value) ->
Acc ++ [{"#text", Value}];
children_to_struct([Value | T], Acc) when is_tuple(Value) ->
children_to_struct(T, Acc ++ parse(Value)).
attributes_to_struct([], Acc) -> Acc;
attributes_to_struct([{K, V}|T], Acc) ->
attributes_to_struct(T, Acc ++ [{"#" ++ K, V}]).
data() ->
{"start",[],
[{"data",[],
[{"number",[{"id","333"}],["test message"]},
{"data",[],["current date"]}]},
{"mass",[],
[{"client",[],["35"]},
{"address",[],["lattitude"]},
{"code",[],["3454343"]},
{"foo",[{"tipo","casa"}],["Some text message 2"]},
{"product",[],["TEST"]}]}]}.
Running it in the shell with mochijson:
Eshell V7.3 (abort with ^G)
1> c(transform).
{ok,transform}
2> T = transform:test().
[{"start",
[{"data",
[{"number",[{"#id","333"},{"#text","test message"}]},
{"data","current date"}]},
{"mass",
[{"client","35"},
{"address","lattitude"},
{"code","3454343"},
{"foo",[{"#tipo","casa"},{"#text","Some text message 2"}]},
{"product","TEST"}]}]}]
3>
4> iolist_to_binary(mochijson2:encode(T)).
<<"{\"start\":{\"data\":{\"number\":{\"#id\":[51,51,51],\"#text\":[116,101,115,116,32,109,101,115,115,97,103,101]},\"data\":{\"#text"...>>
i suggest to use jiffy for JSON and exml for XML.
jiffy and exml have native code which means they are so fast.
Clone and compile them.
before compiling them, you should install g++ and libexpat-dev
Example:
-module(test).
-export([convert/1]).
-include("exml/include/exml.hrl"). %% In my test
convert(XML) when erlang:is_binary(XML) ->
{ok, XMLEl} = exml:parse(XML),
jiffy:encode({[convert2(XMLEl)]}).
convert2(#xmlel{name = Name
,attrs = []
,children = [{xmlcdata, Data}]}) ->
{Name, Data};
convert2(#xmlel{name = Name
,attrs = Attrs
,children = Children}) ->
{Name, {convert_attrs(Attrs) ++ convert_children(Children)}}.
convert_attrs(Attrs) ->
convert_attrs(Attrs,[]).
convert_attrs([Attr|Attrs1], Attrs2) ->
convert_attrs(Attrs1, [convert_attr(Attr)|Attrs2]);
convert_attrs([], Attrs2) ->
lists:reverse(Attrs2).
convert_attr({Attr, Value}) ->
{<<$#, Attr/binary>>, Value}.
convert_children(Children) ->
convert_children(Children, []).
convert_children([Child|Children1], Children2) ->
convert_children(Children1, [convert_child(Child)|Children2]);
convert_children([], Children2) ->
lists:reverse(Children2).
convert_child({xmlcdata, Data}) ->
{<<"#text">>, Data};
convert_child(#xmlel{}=XMLEl) ->
convert2(XMLEl).
In the shell:
p#jahanbakhsh ~/Projects/test $ ls
exml jiffy test.erl
p#jahanbakhsh ~/Projects/test $ erl -pa jiffy/ebin exml/ebin
Erlang/OTP 19 [erts-8.2.2] [source-1ca84a4] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V8.2.2 (abort with ^G)
1> c(test).
{ok,test}
2> XML = <<"<start><data><number id=\"333\">test message</number><data>current date</data></data><mass><client>35</client><address>lattitude</address><code>3454343</code><foo tipo=\"casa\">Some text message 2</foo><product>TEST</product></mass></start>">>.
<<"<start><data><number id=\"333\">test message</number><data>current date</data></data><mass><client>35</client><address"...>>
3> test:convert(XML).
<<"{\"start\":{\"data\":{\"number\":{\"#id\":\"333\",\"#text\":\"test message\"},\"data\":\"current date\"},\"mass\":{\"client\":\"35\",\"addres"...>>
4> io:format("~s~n", [test:convert(XML)]).
{"start":{"data":{"number":{"#id":"333","#text":"test message"},"data":"current date"},"mass":{"client":"35","address":"lattitude","code":"3454343","foo":{"#tipo":"casa","#text":"Some text message 2"},"product":"TEST"}}}
ok
5>
I test with this code :
get_fee(Transaction,SourceNumber,Amount, Currency) ->
Url = lists:concat(["http://localhost/test.php","?transaction=", Transaction, "&saccount=", SourceNumber,Amount,"¤cy=",Currency]),
inets:start(),
{Flag, Response} = http:request(get, {Url, []}, [], []),
case Flag of
ok ->
{ { _, ReturnCode, _ }, _, Body } = Response,
if ReturnCode =:= 200 ->
{ok,{_,[{_,Code},{_,Permission},{_,Payer},{_,Payee}]}} = json:decode_string(Body),
case Permission of true ->
if Code =:= 200 ->
{ok,{Code, Payer, Payee}};
Code =:= 204 ->
{nok,{Code, not_found}};
true ->
{nok,{Code, parameter_error}}
end;
false ->
{nok,{Code, parameter_error}}
end;
true->
{error, http_error}
end;
error ->
case Response of
nxdomain -> {error, dns_error};
_ -> {error, network_error}
end
end.
the response from the http is : {"code":200,"permission":true,"fee_payer":0,"fee_payee":19}
But now I like to do the same think but the return of http in this case for example is :
{"CIN":"08321224","Name":21}
so I have just CIN and Name in this case
I try to change the previous
get_fee(Num) ->
Url = lists:concat(["http://localhost/GTW/Operation.php","?ACCOUNT_NUM=", Num]),
inets:start(),
{Flag, Response} = http:request(get, {Url, []}, [], []),
case Flag of
ok ->
{ { _, ReturnCode, _ }, _, Body } = Response,
%% for debug
io:format("~p~n",[ReturnCode]),
if ReturnCode =:= "08321224" ->
{ok,{_,[{_,CIN},{_,Name}]}} = json:decode_string(Body),
case Name of 21 ->
io:format(CIN),
io:format(Name),
if CIN =:= "08321224"->
{ok,{CIN, Name}};
CIN =:= 204 ->
{nok,{CIN, not_found}};
true ->
{nok,{CIN, parameter_error}}
end;
false ->
{nok,{CIN, parameter_error}}
end;
true->
{error, http_error}
end;
error ->
case Response of
nxdomain -> {error, dns_error};
_ -> {error, network_error}
%% for debug
%%io:format("pass2~n ~p~n",[Response]),
end
end.
but it displys :
test:get_fee("0001").
200
{error,http_error}
So I'll nitpick on the style here, because you will be far better off if you follow the semantical idea of Erlang:
get_fee(Num) ->
Url = lists:concat(["http://localhost/GTW/Operation.php","?ACCOUNT_NUM=", Num]),
inets:start(),
This is the wrong place to start inets. It should be started outside this function as you only need to
do this one.
{Flag, Response} = http:request(get, {Url, []}, [], []),
This part is better coded with a pattern match. The discrimination of Flag and Response can
be decoded directly with a simple match. Write,
case http:request(get, {Url, []}, [], []) of
{ok, {{_, 200, _}, _, Body}} ->
{ok, R} = json:decode_string(Body),
get_fee_decode_(get_cin(R), get_name(R));
{error, Reason} -> {error, Reason}
end.
I would recommend against changing {error, nxdomain} to {error, dns_error} since nxdomain perfectly
codes this case in any case. Just pass the error tuple to the caller and have him handle it.
get_fee_decode_("08321224" = CIN, 21 = Name) -> {ok, {CIN, Name}};
get_fee_decode_("204" = CIN, 21) -> {nok, {CIN, not_found}};
get_fee_decode_(CIN, _Name) -> {nok, {CIN, parameter_error}};
Introduce a new function like this to handle the inner parts of your code base. And hoist the matching to
top-level. This helps in the long run by decoupling your code into functions.
Do note that in a JSON structure, there is no order on an "object" so you can't assume that the structure is
{"code":200,"permission":true,"fee_payer":0,"fee_payee":19}
But a decode does not have to preserve this structure, according to JSON. So a valid decode may be:
[{"fee_payee", 19}, {"fee_payer", 0}, {"permission", true}, {"code", 200}]
This will fail to match in your code and you are setting yourself up for some nasty errors later on.
You want something along the lines of:
get_fee_payer(PL) -> proplists:get_value("fee_payer", PL).
Another thing which will be a problem with your programming style is that you are hiding error-information cases. In Erlang, you can often get away with only handling the "happy path" through the code and leave all the error handling out until you know what kind of errors are there in the code base. Then you can begin adding in error handling slowly. Defensive programming is not a thing you should be doing if you can avoid it.
You changed:
if ReturnCode =:= 200 ->
to:
if ReturnCode =:= "08321224" ->
However, that needs to stay the same in your version. 200 is the HTTP status code for "OK" - the first step here is to verify that the server actually processed the request and returned a positive reply. You'll find that number only in Body - that is what the if CIN =:= "08321224"-> part is for.