Related
I want to build a customer support chat app. There are users and an admin. Below admin there are multiple sub-admins. Initially the chat is initiated with admin only, but if the admin is offline I need to route the message to sub-admins.
offline_message_hook hook serves the purpose. I'll check if the To is admin, then I need to route the Packet to one of the sub-admins. How do I route/send the packet to other user within offline_message_hook. In short how do I change the To from the packet so that the packet is re-directed to the new sub-admin?
Here is what I've tried:-
offline_message_hook({_Action, #message{from = Peer, to = To} = Pkt} = Acc) ->
?INFO_MSG("Inside offline", []),
ejabberd_router:route(From, To, Packet),
ok.
I'm using ejabberd 17.04.105.
Update
After following user2610053's advice, I did this:-
-spec offline_message_hook({any(), message()}) -> {any(), message()}.
offline_message_hook({_Action, Msg} = Acc) ->
ejabberd_router:route(xmpp:set_to(Msg, 'praful#localhost')),
{routed, Msg}.
Following is the error:-
15:13:12.291 [error] failed to route packet:
#message{id = <<"purple187f6502">>,type = chat,lang = <<"en">>,
from = {jid,<<"praful2">>,<<"localhost">>,<<"Prafuls-MacBook-Pro">>,
<<"praful2">>,<<"localhost">>,<<"Prafuls-MacBook-Pro">>},
to = praful#localhost,subject = [],
body = [#text{lang = <<>>,data = <<"co=umon">>}],
thread = undefined,
sub_els = [{xmlel,<<"active">>,
[{<<"xmlns">>,
<<"http://jabber.org/protocol/chatstates">>}],
[]}],
meta = #{ip => {0,0,0,0,0,0,0,1}}}
Reason = {error,{{badrecord,jid},[{ejabberd_router,do_route,1,[{file,"src/ejabberd_router.erl"},{line,343}]},{ejabberd_router,route,1,[{file,"src/ejabberd_router.erl"},{line,87}]},{mod_sunshine,offline_message_hook,1,[{file,"src/mod_sunshine.erl"},{line,24}]},{ejabberd_hooks,safe_apply,4,[{file,"src/ejabberd_hooks.erl"},{line,380}]},{ejabberd_hooks,run_fold1,4,[{file,"src/ejabberd_hooks.erl"},{line,364}]},{ejabberd_sm,route,1,[{file,"src/ejabberd_sm.erl"},{line,138}]},{ejabberd_local,route,1,[{file,"src/ejabberd_local.erl"},{line,116}]},{ejabberd_router,do_route,1,[{file,"src/ejabberd_router.erl"},{line,348}]}]}}
The user praful#localhost exist. Please advice what exactly is wrong?
Update2 - `UserReceivePacket Hook
In user_receive_packet packet hook, upon using the same function ejabberd_router:route(xmpp:set_to(Packet, jid:decode("praful#localhost"))), it throws an error saying :-
Hook user_receive_packet crashed when running mod_sunshine:user_receive_packet/1:
** Reason = {error,function_clause,[{jid,decode,[{file,"src/jid.erl"},{line,132}],["praful#localhost"]},{mod_sunshine,user_receive_packet,[{file,"src/mod_sunshine.erl"},{line,29}],1},{ejabberd_hooks,safe_apply,[{file,"src/ejabberd_hooks.erl"},{line,380}],4},{ejabberd_hooks,run_fold1,[{file,"src/ejabberd_hooks.erl"},{line,364}],4},{ejabberd_c2s,process_info,[{file,"src/ejabberd_c2s.erl"},{line,231}],2},{ejabberd_hooks,safe_apply,[{file,"src/ejabberd_hooks.erl"},{line,380}],4},{ejabberd_hooks,run_fold1,[{file,"src/ejabberd_hooks.erl"},{line,364}],4},{xmpp_stream_in,handle_info,[{file,"src/xmpp_stream_in.erl"},{line,373}],2}]}
So, I read about function_clause, but couldnt understand the same. What exactly is wrong over here?
I think you're asking about xmpp:set_to/2. Here is an example:
offline_message_hook({_Action, Msg} = Acc) ->
SubAdmins = get_sub_admins(Msg#message.to),
lists:foreach(
fun(Admin) ->
ejabberd_router:route(xmpp:set_to(Msg, Admin))
end, Admins),
{routed, Msg}.
I query the list of users from Mnesia Database in Chicagoboss. I'm getting the error when I try to add the Lists within lists:foreach with ++ operator. My aim is, based on userid I will do ets:lookup to my cache and create a List like - [{{<<"name">>,<<"Batman">>}, {<<"steps">>,2552}, {<<"distance">>,2050}}].
For each user I'll create this list and add with the previous List. So that ultimately I can can sort on <<"steps">> and convert the binary list by json encoding and send it to the client via Websockets.
I'm getting the error at this line:
Reading1 = Reading2 ++ Currentlist
as I've decalred Reading1 as an Empty list.
My question is how can I manipulate the lists within the lists:foreach and then send the result List via websocket?
BelugaUsers = boss_db:find(users, [{accesstoken, 'not_equals', ''}]),
Reading1 = [],
Reading2 = [],
lists:foreach(fun(X) ->
{_,_,BEmail,BName,_,_,BAccessToken,_} = X,
UserKey = BEmail ++ "-" ++ ?MYAPICALL1,
io:format("UserKey for Leader Board: ~n~p~n",[UserKey]),
[Reading] = ets:lookup(myapi_cache, list_to_binary(UserKey)),
{_,Result} = Reading,
ActivitySummary = proplists:get_value(<<"activitySummary">>, Result),
%Print ActivitySummary for the user ....printing fine
io:format("ActivitySummary ==========: ~n~p~n",[ActivitySummary]),
%Create a list of the format
%[{{<<"name">>,<<"Batman">>}, {<<"steps">>,2552}, {<<"distance">>,2050}}]
Currentlist = [{{<<"name">>, list_to_binary(BName)}, {<<"steps">>, proplists:get_value(<<"steps">>, ActivitySummary)}, {<<"distance">>, proplists:get_value(<<"distance">>, ActivitySummary)}}],
%% HERE I'M GETTING error%%
Reading1 = Reading2 ++ Currentlist
end, BelugaUsers),
%sort the list
Reading3 = lists:keysort(2, Reading1),
%reverse the list
Reading4 = lists:reverse(Reading3),
WebSocketId ! {text, jsx:encode(Reading4)},
Erlang variables are single-assignment; once bound to a value, they can't be re-bound to a different value.
The lists:foreach/2 function is not useful for this problem because it can't create a new value and return it to its caller. You should instead use lists:map/2, perhaps like this:
BelugaUsers = boss_db:find(users, [{accesstoken, 'not_equals', ''}]),
Reading = lists:map(
fun(X) ->
{_,_,BEmail,BName,_,_,BAccessToken,_} = X,
UserKey = BEmail ++ "-" ++ ?MYAPICALL1,
io:format("UserKey for Leader Board: ~n~p~n",[UserKey]),
{_,Result} = hd(ets:lookup(myapi_cache, list_to_binary(UserKey))),
ActivitySummary = proplists:get_value(<<"activitySummary">>, Result),
%%Print ActivitySummary for the user ....printing fine
io:format("ActivitySummary ==========: ~n~p~n",[ActivitySummary]),
%%Create a tuple of the format
%%{{<<"name">>,<<"Batman">>}, {<<"steps">>,2552}, {<<"distance">>,2050}}
{{<<"name">>, list_to_binary(BName)},
{<<"steps">>, proplists:get_value(<<"steps">>, ActivitySummary)},
{<<"distance">>, proplists:get_value(<<"distance">>, ActivitySummary)}}
end, BelugaUsers),
%%sort the list
Reading2 = lists:keysort(2, Reading),
%%reverse the list
Reading3 = lists:reverse(Reading2),
WebSocketId ! {text, jsx:encode(Reading3)}.
The lists:map/2 function applies a function to each value in a list to a produce a potentially different value and returns a new list consisting of those new values. This is essentially what you were trying to do with lists:foreach/2 and your attempt to use imperative assignment to add each element to an already-existing list.
You could alternatively use a list comprehension but I think lists:map/2 is clearer in this situation.
I have a big list of some forms with data that needs to be concated with other data from others forms with the same name.
The list format is quite complex and looks like this:
[[{EVAL_SEQ_1, {FORMNAME, ListDataToConcat1}}], [{EVAL_SEQ_2, {FORMNAME, ListDataToConcat2}}], ...]
This is the output that I want:
[[{EVAL_SEQ_1, {FORMNAME, ListDataToConcat1 + ListDataToConcat2}}]}}] ...]
Where:
EVAL_SEQ_1 = Form Sequence Number,
FORMNAME = Form Name
ListDataToConcat = List that Needs to concat
eg.Here is my sample data:
[[{"eval_data_12",
{<<"prvl_mobable_asset_0000_h200401">>,
[{'F_01_0100',[1]},
{'F_01_0090',["3"]},
{'F_01_0080',[]},
{'F_01_0070',[9999]},
{'F_01_0060',[{era,0},{year,[]},{month,[]}]},
{'F_01_0050',[]},
{'F_01_0040',[]},
{'F_01_0030',[]},
{'F_01_0020',<<>>},
{'F_01_0010',<<"4 - 8">>}]}}],
[{"eval_data_11",
{<<"prvl_mobable_asset_0000_h200401">>,
[{'F_01_0100',[]},
{'F_01_0090',["2"]},
{'F_01_0080',[]},
{'F_01_0070',[22222]},
{'F_01_0060',[{era,0},{year,[]},{month,[]}]},
{'F_01_0050',[]},
{'F_01_0040',[]},
{'F_01_0030',[]},
{'F_01_0020',<<>>},
{'F_01_0010',<<"4 - 1">>}]}}], ...]
I want the resultant output like this:
[{"eval_data_11",
{<<"prvl_mobable_asset_0000_h200401">>,
[{'F_01_0100',[[], [1]]},
{'F_01_0090',[["2"], ["3"]]},
{'F_01_0080',[[], []]},
{'F_01_0070',[[22222], [9999]]},
{'F_01_0060',[[{era,0},{year,[]},{month,[]}], [{era,0},{year,[]},{month,[]}]]},
{'F_01_0050',[[], []]},
{'F_01_0040',[[], []]},
{'F_01_0030',[[], []]},
{'F_01_0020',[[<<>>], [<<>>]]},
{'F_01_0010',[[<<"4 - 1">>], [<<"4 - 8">>]}]}}]
I propose you this solution:
[edit]
I modified the code to answer your last comment, there are still fuzzy things:
if Assets are different, creates different record list?
if not what should be done with Asset name? I have chosen to keep the "smallest one"
is the order of records important - I decided no
One remark, I am missing some context, but If I add to collect such informations, I would store it in an ets table. It is faster to update, easy to traverse, and easy to transform into list if needed.
-module (t).
-compile([export_all]).
% rec = {atom,term}
% reclist = [rec,...]
% asset = {bin,reclist}
% eval_data = [{list,asset}]
% eval_set = [eval_data,...]
% recs = {atom,[term]}
% recslist = [recs,...]
addrec({Key,Val},Recslist) ->
Val_list = proplists:get_value(Key, Recslist, []),
[{Key,[Val|Val_list]}|proplists:delete(Key,Recslist)].
merge_rec(Reclist,Recslist) -> lists:foldl(fun(Rec,Acc) -> addrec(Rec,Acc) end,Recslist,Reclist).
merge_eval([{Eval,{Asset,Reclist}}],[]) ->
[{Eval,{Asset,[{Key,[Val]} || {Key,Val} <- Reclist]}}];
merge_eval([{Eval,{Asset,Reclist}}],[{Eval_low,{Asset_low,Recslist}}]) ->
[{min(Eval,Eval_low),{min(Asset,Asset_low),merge_rec(Reclist,Recslist)}}].
merge_set(Eval_set) -> lists:foldl(fun(Eval_data,Acc) -> merge_eval(Eval_data,Acc) end,[],Eval_set).
test() ->
Eval_set = [[{"eval_data_10",
{<<"prvl_mobable_asset_0000_h200401">>,
[{'F_01_0100',[1]},
{'F_01_0090',["3"]},
{'F_01_0080',[]},
{'F_01_0070',[9999]},
{'F_01_0060',[{era,0},{year,[]},{month,[]}]},
{'F_01_0050',[]},
{'F_01_0040',[]},
{'F_01_0030',[]},
{'F_01_0020',<<>>},
{'F_01_0010',<<"4 - 8">>}]}}],
[{"eval_data_11",
{<<"prvl_mobable_asset_0000_h200401">>,
[{'F_01_0100',[]},
{'F_01_0090',["2"]},
{'F_01_0080',[]},
{'F_01_0070',[22222]},
{'F_01_0060',[{era,0},{year,[]},{month,[]}]},
{'F_01_0050',[]},
{'F_01_0040',[]},
{'F_01_0030',[]},
{'F_01_0020',<<>>},
{'F_01_0010',<<"4 - 1">>}]}}]],
merge_set(Eval_set).
#trex:
FormList = [[{EVAL_SEQ_1, {FORMNAME, ListDataToConcat1}}], [{EVAL_SEQ_2, {FORMNAME, ListDataToConcat2}}], ....]
You will need to get a separate the list which has common form names as <<"prvl_mobable_asset_0000_h200401">>
{MobableList, EvalDataList} = lists:partition(fun([{EVAL_SEQ, {FORMNAME, ListData}}]) ->
FORMNAME == <<"prvl_mobable_asset_0000_h200401">>
end, FormList),
Then, Get a separate Tuple of Form sequences and Lists. To separate them
{EvalSeq, MergingList} = lists:foldl(fun(X, {EvalNames, OutList}) ->
[{EVAL_SEQ, {FORMNAME, ListData}}] = X,
{[EVAL_SEQ|EvalNames], [ListData|OutList]}
end, {[], []}, MobableList),
Hence, you will get the new tuple as :
{[EVAL_SEQ_1, EVAL_SEQ_2, EVAL_SEQ_3, ...], [ListDataToConcat1, ListDataToConcat2, ListDataToConcat3,...]}
I'm not sure which sequence Number you want as you havn't mentioned it clearly, Here is the way you can get the minimum Sequence Number.
Evalsequence = lists:min(EvalSeq),
Now merge your code using merge function as shown below or you can refer merging inner list Merge inner lists of a list erlang:
MergedList = merge(MergingList),
And finally a separate list as:
[{Evalsequence, {<<"prvl_mobable_asset_0000_h200401">>, MergedList}}].
merge(ListOfLists) ->
Combined = lists:append(ListOfLists),
Fun = fun(Key) -> {Key,proplists:get_all_values(Key,Combined)} end,
lists:map(Fun,proplists:get_keys(Combined)).
I am using ejabberd hook named "filter-packet" to make a module. Here I want to add an element to packet. How to do it? My Code is -
on_filter_packet({From, To, Packet}=Input) ->
Type = xml:get_tag_attr_s(list_to_binary("type"), Packet),
if (Type == <<"groupchat">>) ->
?INFO_MSG("type is group chat", []),
NPacket={Packet, [{xmlelement, "time",
[],
[{xmlcdata, "testtime"}]}]},
{From, To, NPacket};
true ->
Input
end.
This code is giving error of bad match. Any Help ?
13.12 uses different type for xmlelement.
Packet is a type of record #xmlel, so you need to insert new element to Packet#xmlel.children.
on_filter_packet({From, To, #xmlel{ children=OldChildren } = Packet}=Input) ->
...
TimeElem = #xmlel{ name = <<"time">>,
children =
[{xmlcdata, <<"testtime">>}]},
NPacket = Packet#xmlel{ children = [TimeElem|OldChildren] },
...
Not tested, but will work.
a table named "md" with structure {id,name},I want read records from md use paging query,I tried mnesia:select/4 and mnesia:select/1 as below:
%% first use select/2: "ID < 10",returned [1,2,4,3,8,5,9,7,6]
(ejabberd#localhost)5> mnesia:activity(transaction,fun mnesia:select/2,md, [{{md,'$1','_'},[{'<','$1',10}],['$1']}]).
{atomic,[1,2,4,3,8,5,9,7,6]}
%%but when query with select/4,returned [6], why?
(ejabberd#localhost)7> {atomic,{R1,C1}}=mnesia:activity(transaction,fun mnesia:select/4,md,[{{md,'$1','_'},[{'<','$1',10}],['$1']}],5,read).
{atomic,{[6],
{mnesia_select,md,
{tid,10535470,<0.460.0>},
ejabberd#localhost,disc_only_copies,
{dets_cont,select,5,
<<0,0,0,29,18,52,86,120,0,0,0,21,131,104,3,...>>,
{141720,148792,<<>>},
md,<0.130.0>,<<>>},
[],undefined,undefined,
[{{md,'$1','_'},[{'<','$1',10}],['$1']}]}}}
%% and then use mnesia:select/1 with continuation "C1",got wrong_transaction
(ejabberd#localhost)8> mnesia:activity(transaction,fun mnesia:select/1,C1).
{aborted,wrong_transaction}
how to use mnesia:select/4 and mnesia:select/1 for paging query?
You will have to call select/1 inside the same transaction.
Otherwise the table can change between invocations to select/4 and select/1.
You must use a dirty context if you want to use is as written above.
here is my solution:
use async_dirty instead of transaction
{Record,Cont}=mnesia:activity(async_dirty, fun mnesia:select/4,[md,[{Match_head,[Guard],[Result]}],Limit,read])
then read next Limit number of records:
mnesia:activity(async_dirty, fun mnesia:select/1,[Cont])
full code:
-record(md,{id,name}).
batch_delete(Id,Limit) ->
Match_head = #md{id='$1',name='$2'},
Guard = {'<','$1',Id},
Result = '$_',
{Record,Cont} = mnesia:activity(async_dirty, fun mnesia:select/4,[md,[{Match_head,[Guard],[Result]}],Limit,read]),
delete_next({Record,Cont}).
delete_next('$end_of_table') ->
over;
delete_next({Record,Cont}) ->
delete(Record),
delete_next(mnesia:activity(async_dirty, fun mnesia:select/1,[Cont])).
delete(Records) ->
io:format("delete(~p)~n",[Records]),
F = fun() ->
[ mnesia:delete_object(O) || O <- Records]
end,
mnesia:transaction(F).