1:- module(slack_client, [
    2        slack_start_listener/0,
    3        slack_say/2,
    4        slack_send/1,
    5        slack_ping/0,
    6        slack_get_websocket/1,
    7        is_thread_running/1,
    8        slack_ensure_im/2,
    9        name_to_id/2
   10        ]).

slack_client - Provides a websocket API to write slack clients and bots

*/

   15:- if(exists_source(library(dicts))).   16 :- use_module(library(dicts)).   17:- endif.   18
   19:- use_module(library(http/http_open)).   20:- use_module(library(http/http_client)).   21:- use_module(library(http/http_json)).   22:- use_module(library(url)).   23:- use_module(library(http/json)).   24:- use_module(library(http/json_convert)).   25:- use_module(library(http/websocket)).   26
   27:- if(exists_source(library(logicmoo_common))).   28 :- use_module(library(logicmoo_common)).   29:- endif.   30
   31<<<<<<< HEAD
   32:- if(exists_source(library(dictoo))).
   33 :- use_module(library(dictoo)).   34:- endif.   35=======
   36:- if(exists_source(library(dicts))).
   37	:- use_module(library(dicts)).   38
   39:- if(exists_source(library(udt))).   40    :- use_module(library(udt)).   41:- endif.   42
   43
   44
   45
   46:- oo_class_begin(slack_client).   47
   48% url	A WebSocket Message Server URL.
   49:- oo_class_field(url).   50
   51% self	The authenticated bot user.
   52:- oo_inner_class_begin(clients).   53
   54slack_client:clients:new(Ref):- throw(clients:new(Ref)).
   55
   56:- oo_inner_class_end(clients).   57
   58
   59
   60% self	The authenticated bot user.
   61:- oo_inner_class_begin(self).   62:- oo_inner_class_end(self).   63
   64% self	The authenticated bot user.
   65:- oo_inner_class_begin(self).   66:- oo_inner_class_end(self).   67
   68
   69% team	Details on the authenticated user's team.
   70:- oo_inner_class_begin(team).   71:- oo_inner_class_end(team).   72
   73% users	A hash of user objects by user ID.
   74:- oo_inner_class_begin(users).   75:- oo_inner_class_end(users).   76
   77
   78% channels	A hash of channel objects, one for every channel visible to the authenticated user.
   79:- oo_inner_class_begin(channels).   80:- oo_inner_class_end(channels).   81
   82% groups	A hash of group objects, one for every group the authenticated user is in.
   83:- oo_inner_class_begin(self).   84:- oo_inner_class_end(self).   85
   86% ims	A hash of IM objects, one for every direct message channel visible to the authenticated user.
   87:- oo_inner_class_begin(groups).   88:- oo_inner_class_end(groups).   89
   90% bots	Details of the integrations set up on this team.
   91:- oo_inner_class_begin(bots).   92:- oo_inner_class_end(bots).   93
   94% text	textual utils.
   95:- oo_inner_class_begin(text).   96:- oo_inner_class_end(text).   97
   98% debug	Debugger fidling.
   99:- oo_inner_class_begin(self).  100:- oo_inner_class_end(self).  101
  102% events	Registered callbacks.
  103:- oo_inner_class_begin(events).  104:- oo_inner_class_end(events).  105
  106% files	registered storage.
  107:- oo_inner_class_begin(files).  108:- oo_inner_class_end(files).  109
  110:- oo_class_end(slack_client).  111
  112:- endif.  113
  114
  115
  116/* tests to see if logicmoo utils are installed.. If not, create the predicates it will use */
  117:- if( \+ current_predicate( wdmsg/1 )).  118
  119:- meta_predicate(with_visible_leash(0)).  120with_visible_leash(G):-
  121   '$leash'(A, A),'$visible'(V, V),
  122   (tracing->CU=trace;CU=notrace),
  123   (debugging->CU2=debug;CU2=nodebug),!,
  124   call_cleanup(G, (notrace,'$leash'(_, A),'$visible'(_, V),call(CU2),call(CU))).
  125
  126:- meta_predicate(rtrace(0)).  127rtrace(G):-  with_visible_leash(( notrace,leash(-all),visible(+full),leash(+exception),trace,debug, call(G))).
  128
  129:- meta_predicate(must(0)).  130must(G):- G *->true;throw(must_failed(G)).
  131
  132fresh_line:- format(user_error,'~N',[]).
  133
  134nop(_).
  135>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  136
  137:- if(exists_source(library(udt))).
  138% :- use_module(library(udt)).
  139:- endif.  140                
  141% dbgM(O):- term_to_atom(O,A), eggdrop:say("dmiles",A),!.
  142dbgM(O):- dbgM('~N% ~p.~n',[O]).
  143dbgM(F,O):- format(user_error,F,O),flush_output(user_error).
  144
  145:- use_module(library(eggdrop)).  146:- egg_go.  147
  148:- endif.  149
  150
  151is_thread_running(ID):-
  152  is_thread(ID), thread_property(ID,status(What)),!,
  153   (What==running->true;(thread_join(ID,_ ),!,fail)).
  154
  155
  156:- dynamic(slack_token/1).  157
  158% ===============================================
  159% How this module might find your token:
  160% ===============================================
  161
  162% 1st - Checks for a local declaration 
  163%  (if the next line is uncommented and replaced by a real token )
  164% slack_token('xoxb-01234567890-xxxxxxxxxxxxxxxx').
  165slack_token('xoxb-1030744384576-1077790097232-HYegGh0O4y8SUnuiF6FSCrDC').
  166
  167% 2nd - Checks for a local file called ".slack_auth.pl" for slack_token/1 as above
  168:- if(( \+ slack_token(_) , exists_file('.slack_auth.pl'))).  169:- include('.slack_auth.pl').  170:- endif.  171
  172% 3rd - Checks env for SLACK_API_TOKEN
  173%  ( defined by# export SLACK_API_TOKEN=xoxb-01234567890-xxxxxxxxxxxxxxxx )
  174:- if(( \+ slack_token(_))).  175:- getenv('SLACK_API_TOKEN',Was)->asserta(slack_token(Was));true.  176:- endif.  177
  178% 4th - Checks users config directory for file called ".slack_auth.pl"  slack_token/1 as above
  179:- if(( \+ slack_token(_) , exists_file('~/.slack_auth.pl'))).  180:- include('~/.slack_auth.pl').  181:- endif.  182
  183:- if(( \+ slack_token(_))).  184:- throw(missing(slack_token(_))).  185:- endif.  186
  187
  188
  189
  190:- oo_class_begin(slack_client).  191
  192% url	A WebSocket Message Server URL.
  193:- oo_class_field(url).  194
  195% self	The authenticated bot user.
  196:- oo_inner_class_begin(clients).  197
  198slack_client:clients:new(Ref):- throw(clients:new(Ref)).
  199
  200:- oo_inner_class_end(clients).  201
  202
  203
  204% self	The authenticated bot user.
  205:- oo_inner_class_begin(self).  206:- oo_inner_class_end(self).  207
  208% self	The authenticated bot user.
  209:- oo_inner_class_begin(self).  210:- oo_inner_class_end(self).  211
  212
  213% team	Details on the authenticated user's team.
  214:- oo_inner_class_begin(team).  215:- oo_inner_class_end(team).  216
  217% users	A hash of user objects by user ID.
  218:- oo_inner_class_begin(users).  219:- oo_inner_class_end(users).  220
  221
  222% channels	A hash of channel objects, one for every channel visible to the authenticated user.
  223:- oo_inner_class_begin(channels).  224:- oo_inner_class_end(channels).  225
  226% groups	A hash of group objects, one for every group the authenticated user is in.
  227:- oo_inner_class_begin(self).  228:- oo_inner_class_end(self).  229
  230% ims	A hash of IM objects, one for every direct message channel visible to the authenticated user.
  231:- oo_inner_class_begin(groups).  232:- oo_inner_class_end(groups).  233
  234% bots	Details of the integrations set up on this team.
  235:- oo_inner_class_begin(bots).  236:- oo_inner_class_end(bots).  237
  238% text	textual utils.
  239:- oo_inner_class_begin(text).  240:- oo_inner_class_end(text).  241
  242% debug	Debugger fidling.
  243:- oo_inner_class_begin(self).  244:- oo_inner_class_end(self).  245
  246% events	Registered callbacks.
  247:- oo_inner_class_begin(events).  248:- oo_inner_class_end(events).  249
  250% files	registered storage.
  251:- oo_inner_class_begin(files).  252:- oo_inner_class_end(files).  253
  254:- oo_class_end(slack_client).  255
  256:- dynamic(tmpd:slack_info/3).  257
  258% ===============================================
  259% Utility functions
  260% ===============================================
  261
  262slack_token_string(S):-slack_token(T),atom_string(T,S).
  263
  264slack_get_websocket_url(URL):-
  265  slack_token(Token),
  266  format(atom(GetURL),'https://slack.com/api/rtm.start?token=~w',[Token]),
  267  http_open(GetURL, In, []),
  268  json_read_dict(In,Term),
  269  dict_pairs(Term,_,Pairs),
  270  must(maplist(slack_receive(rtm),Pairs)),
  271  URL=Term.url,
  272<<<<<<< HEAD
  273  % listing(tmpd:slack_info/3),
  274=======
  275  listing(slack_info/3),
  276>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  277  close(In).
  278
  279:- dynamic(slack_websocket/3).  280
  281slack_get_websocket(WS):- slack_websocket(WS,_,_),!.
  282slack_get_websocket(WS):-
  283   slack_get_websocket_url(URL),!,
  284   slack_open_websocket(URL,WS),!.
  285
  286slack_open_websocket(URL,WS):-
  287   ignore(slack_websocket(OLD_WS,_,_)),
  288   http_open_websocket(URL, WS, []),
  289   stream_pair(WS,I,O),
  290   asserta(slack_websocket(WS,I,O)),
  291   (nonvar(OLD_WS)->slack_remove_websocket(OLD_WS);true).
  292
  293slack_remove_websocket(OLD_WS):-
  294   ignore(retract(slack_websocket(OLD_WS,_,_))),
  295   ignore(catch(ws_close(OLD_WS,1000,''),_,true)).
  296
  297% ===============================================
  298% Property Names
  299% ===============================================
  300skip_propname(K):- var(K),!.
  301skip_propname(_-_):-!,fail.
  302skip_propname(Type):-string(Type),!,string_to_atom(Type,K),!,skip_propname(K).
  303skip_propname(rtm).
  304skip_propname(rtm_e).
  305skip_propname(data).
  306skip_propname(var).
  307
  308slack_propname(Type,var):-var(Type),!.
  309slack_propname(Type,K):-string(Type),!,string_to_atom(Type,K).
  310slack_propname(Key-Type,NewType):-!,slack_propname(Key,Type,NewType).
  311%slack_propname(Dict,NewType):- nonvar(Dict),Dict=Key.Type,nonvar(Type),!,slack_propname(Key,Type,NewType).
  312%slack_propname(Key.Type,NewType):-!,slack_propname(Key,Type,NewType).
  313slack_propname(Key,Key).
  314
  315slack_propname(Key,Type,NewType):- skip_propname(Type),!,slack_propname(Key,NewType).
  316slack_propname(Type,Key,NewType):- skip_propname(Type),!,slack_propname(Key,NewType).
  317slack_propname(_Type,Key,NewType):-slack_propname(Key,NewType).
  318
  319
  320slack_start_listener:- is_thread_running(slack_start_listener),!,mmake.
  321slack_start_listener:- thread_create(slack_listener_proc,_,[alias(slack_start_listener)]),!.
  322
  323slack_listener_proc:-
  324 call_cleanup((
  325  repeat,
  326  once(slack_get_websocket(WS)),
  327  flush_output_safe,
  328  once(ws_receive(WS,Data,[format(json)])),
  329  flush_output_safe,
  330  (Data==
  331    end_of_file->!;
  332<<<<<<< HEAD
  333  (once(slack_receive_now(rtm_e,Data)),fail))),
  334=======
  335  (once(slack_receive(rtm_e,Data)),flush_output,fail))),
  336>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  337  slack_remove_websocket(WS)).
  338
  339
  340
  341undict(ID,IDO):- is_dict(ID),ID.IDK=IDV,IDK=id,IDO=IDV.
  342undict(ID,ID).
  343
  344
  345% ignored?
  346slack_event(reconnect_url,Dict):- 
  347  must((Dict.url=URL,
  348   dbgM(reconnect(URL)),!,
  349   dbgM(slack_open_websocket(URL,_)))),
  350  nop(slack_open_websocket(URL,_)).
  351
  352% typify the data objects
  353slack_event(rtm_e,O):- is_dict(O),O.Key=Type,Key=type,!,slack_receive(Type,O),!.
  354
  355<<<<<<< HEAD
  356% simplify the data objects
  357slack_event(Type,O):- is_dict(O),O.Key=Data,Key=data,!,slack_receive(Type,Data),!.
  358=======
  359
  360% text:"This content can't be displayed."
  361>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  362
  363% Notice newly created IMs
  364slack_event(im_open,Dict):- is_dict(Dict),
  365  Dict.channel=IDI,
  366  Dict.user=User,
  367  undict(IDI,ID),
  368  string_to_atom(ID,IDA),
  369<<<<<<< HEAD
  370  asserta(tmpd:slack_info(ims, instance, IDA)),
  371  asserta(tmpd:slack_info(IDA, id, ID)),
  372  asserta(tmpd:slack_info(IDA, user, User)).
  373=======
  374  add_slack_info(ims, instance, IDA),
  375  add_slack_info(IDA, id, ID),
  376  add_slack_info(IDA, user, User).
  377>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  378
  379slack_event(Evt,end_of_file):- throw(slack_event(Evt,end_of_file)).
  380
  381
  382% slack_event(Type,Data):-add_slack_info(now,Type,Data).
  383
  384slack_unused(user_typing).
  385slack_unused(reconnect_url).
  386
  387<<<<<<< HEAD
  388%slack_receive_now(Type,Data):- dbgM(srn(Type,Data)),fail.
  389slack_receive_now(Type,Data):-
  390  nb_setval(websocket_in,Data),
  391  slack_receive(Type,Data),!.
  392
  393slack_receive( Var-Type, Data) :- Var==(var),!,slack_receive(Type,Data).
  394
  395%slack_receive(Type,Data):- dbgM(slack_receive(Type,Data)),fail.
  396slack_receive(Type,Data):- string(Data),(string_to_dict(Data,Dict)->true;string_to_atom(Data,Dict)),!,slack_receive(Type,Dict).
  397slack_receive(Type,Data):- slack_propname(Type,NewType)-> Type\==NewType,!,slack_receive(NewType,Data).
  398slack_receive(Type,Dict):- type_to_url(K,Type)-> K \== Type,!,slack_receive(K,Dict).
  399slack_receive(Type,Data):- slack_event(Type,Data),!.
  400slack_receive(Type,Data):- slack_inform(Type,Data),!.
  401slack_receive(Type,Data):- slack_unused(Type), dbgM(unused(slack_receive(Type,Data))),!.
  402slack_receive(Type,Data):- (nb_current(websocket_in,Data2)->true;Data2=[]),dbgM(unknown(slack_receive(Type,Data):-Data2)).
  403
  404=======
  405slack_receive(Type,Data):- (string(Data),(string_to_dict(Data,Dict)->true;string_to_atom(Data,Dict)))->slack_receive(Type,Dict),!.
  406slack_receive(Type,Data):- slack_propname(Type,NewType)-> Type\==NewType,!,slack_receive(NewType,Data).
  407slack_receive(Type,Dict):- type_to_url(K,Type)-> K\==Type,!, slack_receive(K,Dict).
  408slack_receive(Type,Data):- slack_event(Type,Data),!.
  409slack_receive(Type,Data):- slack_inform(Type,Data),!.
  410slack_receive(Type,Data):- slack_unused(Type), nop(dmsg(unused(slack_receive(Type,Data)))),!.
  411slack_receive(Type,Data):- fmt(unknown(slack_receive(Type,Data))).
  412>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  413
  414
  415% :- dynamic(tmpd:slack_info/3).
  416
  417slack_inform(Type,Data):-is_dict(Data),Data.Key=ID,Key=id,!,string_to_atom(ID,Atom), add_slack_info(Type,Atom,Data).
  418slack_inform(Type,Data):-is_dict(Data),dict_pairs(Data,_Tag,Pairs),!,slack_inform(Type,Pairs).
  419
  420slack_inform(rtm,Data):- is_list(Data),!, maplist(slack_receive(rtm),Data).
  421
  422
  423slack_inform(Type,Key-[A|Data]):-is_dict(A),is_list(Data),!,maplist(slack_receive(Type-Key),[A|Data]).
  424
  425slack_inform(Type,Key-Data):- % atomic(Key),atomic_list_concat([Type,Key],'_',TypeKey),fmt(list(slack_receive(TypeKey,Data))),
  426  is_list(Data),!,
  427  retractall(slack_info(Type,Key,_)),
  428  maplist(add_slack_info1(Type,Key),Data).
  429
  430slack_inform(Type,Key-Data):- atomic(Data),add_slack_info(Type,Key,Data).
  431slack_inform(Type,Key-Data):- is_dict(Data),dict_pairs(Data,Tag,Pairs),maplist(slack_receive(Type-Key-Tag),Pairs).
  432slack_inform(Type,Key-Data):- add_slack_info(Type,Key,Data).
  433
  434
  435
  436add_slack_info(Type,ID,Data):- is_dict(Data),dict_pairs(Data,_Tag,Pairs),!, 
  437   add_slack_info(Type,instance,ID),
  438   maplist(add_slack_info(Type,ID),Pairs).
  439add_slack_info(Type,ID,K-V):- atom(Type),!,add_slack_info(ID,K,V).
  440add_slack_info(Type,ID,Data):-
  441  retractall(slack_info(Type,ID,_)),
  442  add_slack_info1(Type,ID,Data).
  443
  444
  445<<<<<<< HEAD
  446add_slack_info(Type,ID,Data):- is_dict(Data),dict_pairs(Data,_Tag,Pairs),!, 
  447   add_slack_info1(Type,instance,ID),
  448   maplist(add_slack_info1(Type,ID),Pairs).
  449
  450add_slack_info(Type,ID,Data):-add_slack_info1(Type,ID,Data).
  451
  452add_slack_info1(Type,Profile,Data):- is_dict(Data),dict_pairs(Data,_Tag,Pairs),!,add_slack_info1(Profile,Type,Pairs).
  453add_slack_info1(Type,ID,K-V):- Type==var, !,add_slack_info1(ID,K,V).
  454add_slack_info1(Type,ID,K-V):- Type==profile, !,add_slack_info1(ID,K,V).
  455add_slack_info1(Type,ID,Data):- is_list(Data),!,maplist(add_slack_info1(Type,ID),Data).
  456add_slack_info1(Type,ID,Data):- dbgM(add_slack_info1(Type,ID,Data)),fail.
  457add_slack_info1(Type,ID,K-V):- atom(Type),!,add_slack_info1(ID,K,V).
  458add_slack_info1(Type,ID,Data):-assert(tmpd:slack_info(Type,ID,Data)).
  459
  460get_slack_info(Object, Prop, Value):- tmpd:slack_info(Object, Prop, Value).
  461
  462name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(ID,name,NameS),ID\==var,!.
  463name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(ID,real_name,NameS),ID\==var,!.
  464name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(_,instance,ID), get_slack_info(ID,_,NameS),ID\==var,!.
  465
  466same_ids(ID,IDS):-text_to_string(ID,IDA),text_to_string(IDS,IDB),IDA==IDB.
  467
  468slack_ensure_im2(To,IM):- name_to_id(To,ID), get_slack_info(IM,user,IDS),same_ids(ID,IDS),get_slack_info(ims,instance,IM),!.
  469=======
  470add_slack_info1(Type,ID,Data):- assert(slack_info(Type,ID,Data)),
  471 fmt(assert(slack_info(Type,ID,Data))).
  472
  473get_slack_info(Type,ID,Data):- slack_info(Type,ID,Data)*->true;get_slack_info2(Type,ID,Data).
  474
  475get_slack_info2(Type,ID,Data):- string(Type),atom_string(Type,Atom),!,get_slack_info(Atom,ID,Data).
  476get_slack_info2(Type,ID,Data):- atom(Data),atom_string(Data,String),!,get_slack_info(Type,ID,String).
  477
  478name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(ID,name,NameS),team\==ID,!.
  479%name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(_,instance,ID), get_slack_info(ID,_,NameS),!.
  480
  481same_ids(ID,IDS):-text_to_string(ID,IDA),text_to_string(IDS,IDB),IDA==IDB.
  482
  483slack_ensure_im2(ID,IM):- get_slack_info(IM,user,ID),!.
  484
  485slack_ensure_im(To,IM):- get_slack_info(IM, name, To), get_slack_info(IM, is_channel, true),!.
  486slack_ensure_im(To,IM):- name_to_id(To,ID),!, slack_ensure_im(ID,IM).
  487>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  488slack_ensure_im(To,IM):- slack_ensure_im2(To,IM),!.
  489% OLD slack_ensure_im(To,IM):- name_to_id(To,ID), slack_send({type:'im_open',user:ID}),!,must(slack_ensure_im2(To,IM)),!.
  490slack_ensure_im(To,IM):- slack_send({type:'conversations_open',users:To}),!,must(slack_ensure_im2(To,IM)),!.
  491
  492
  493slack_id_time(ID,TS):-flag(slack_id,OID,OID+1),ID is OID+1,get_time(Time),number_string(Time,TS).
  494
  495
  496<<<<<<< HEAD
  497slack_self(Self):- get_slack_info(Self, real_name, "prolog_bot"),!.
  498                                  
  499=======
  500slack_self(Self):-get_slack_info(self, id, Self).
  501
  502>>>>>>> 0aa5b7fd7a245b402c6c3583738544b1c6a9636a
  503%  {"id":2,"type":"ping","time":1484999912}
  504slack_ping :- slack_id_time(ID,_),get_time(Time),TimeRnd is round(Time),slack_send({"id":ID,"type":"ping", "time":TimeRnd}).
  505
  506% {"id":3,"type":"message","channel":"D3U47CE4W","text":"hi there"}
  507slack_say :- slack_say(logicmoo,"test message to logicmoo").
  508slack_say2:- slack_say(dmiles,"test message to dmiles").
  509slack_say3:- slack_say(general,"test message to general channel").
  510
  511slack_info(Str):-
  512 forall(slack_info(X,Y,Z),
  513 ignore((sformat(S,'~q.',[slack_info(X,Y,Z)]),sub_string(S, _Offset0, _Length, _After, Str),
  514   fmt(S)))).
  515
  516slack_say(To,Msg):-
  517  slack_ensure_im(To,IM),
  518  slack_send_im(IM,'PrologMUD',Msg).
  519
  520slack_send_im(IM,From,Msg):-
  521    slack_send({
  522            type: "message", 
  523            username:From,
  524	    channel: IM,
  525            text: Msg
  526	   }),!.
  527
  528slack_post(Cmd,Params,NewDict):- 
  529          slack_token(Token),
  530	  make_url_params(Params,URLParams),
  531	  format(string(S),'https://slack.com/api/~w?token=~w&~w',[Cmd,Token,URLParams]),
  532	  dbgM('~N SLACK-POST ~q ~n',[S]),!,
  533	  http_open(S,Out,[]),!,
  534	  json_read_dict(Out,Dict),
  535	  dict_append_curls(Dict,Params,NewDict),!.
  536	  
  537
  538slack_post(Cmd,Params):-
  539  slack_post(Cmd,Params,NewDict),
  540	  slack_receive(Cmd,NewDict).
  541
  542dict_append_curls(Dict,Params,NewDict):-any_to_curls(Params,Curly),
  543	dict_append_curls3(Dict,Curly,NewDict).
  544
  545dict_append_curls3(Dict,{},Dict):-!.
  546dict_append_curls3(Dict,{Curly},NewDict):-!,dict_append_curls3(Dict,Curly,NewDict).
  547dict_append_curls3(Dict,(A,B),NewDict):-!,dict_append_curls3(Dict,A,NewDictM),dict_append_curls3(NewDictM,B,NewDict).
  548dict_append_curls3(Dict,KS:V,NewDict):- string_to_atom(KS,K), put_dict(K,Dict,V,NewDict).
  549
  550slack_history(To,History):- slack_ensure_im(To,IM),
  551 % (get_slack_info(IM, is_channel, true)-> Method = 'channels.history' ; Method = 'conversations.history'),
  552  Method = 'conversations.history',
  553  slack_send_receive({type:Method,channel:IM},History).
  554
  555
  556
  557string_to_dict:-
  558 string_to_dict("{\"type\":\"dnd_updated_user\",\"user\":\"U3T3R279S\",\"dnd_status\":{\"dnd_enabled\":false,\"next_dnd_start_ts\":1,\"next_dnd_end_ts\":1},\"event_ts\":\"1485012634.280271\"}",Dict),
  559  dbgM(Dict).
  560
  561string_to_dict(String,Dict):-
  562   open_string(String,Stream),
  563   catch(json_read_dict(Stream,Dict),_,fail),!.
  564
  565
  566
  567type_to_url("message",'chat.postMessage').
  568type_to_url("im_open",'im.open').
  569type_to_url("conversations_open",'conversations.open').
  570type_to_url(X,X):-!.
  571
  572make_url_params({In},Out):-!,make_url_params(In,Out).
  573make_url_params((A,B),Out):-!,make_url_params(A,AA),make_url_params(B,BB),format(atom(Out),'~w&~w',[AA,BB]).
  574make_url_params([A|B],Out):-!,make_url_params(A,AA),make_url_params(B,BB),format(atom(Out),'~w&~w',[AA,BB]).
  575make_url_params([A],Out):-!,make_url_params(A,Out).
  576make_url_params(KV,Out):-get_kv_local(KV,K,A),www_form_encode(A,AA),format(atom(Out),'~w=~w',[K,AA]).
  577
  578get_kv_local(K:V,K,V):- must(nonvar(K);throw(get_kv_local(K:V,K,V))).
  579get_kv_local(K-V,K,V).
  580get_kv_local(K=V,K,V).
  581
  582slack_send(DataI):- any_to_curls(DataI,Data),slack_send000(Data).
  583slack_send_receive(DataI,Recv):- any_to_curls(DataI,Data),slack_send_receive000(Data,Recv).
  584
  585
  586slack_send_receive000({"type":TypeT,Params},Recv):- text_to_string(TypeT,Type), type_to_url(Type,Cmd),!, slack_post(Cmd,Params,Recv).
  587% @TODO comment the above and fix this next block
  588slack_send_receive000(Data,Recv):- slack_send_ws(WebSocket,Data),!, once(ws_receive(WebSocket,Recv,[format(json)])),!.
  589
  590
  591slack_send000({"type":TypeT,Params}):- text_to_string(TypeT,Type), type_to_url(Type,Cmd),!, slack_post(Cmd,Params).
  592% @TODO comment the above and fix this next block
  593slack_send000(Data):- slack_send_ws(_WebSocket,Data),!.
  594
  595
  596
  597slack_send_ws(WebSocket,Data):- slack_websocket(WebSocket, _WsInput, WsOutput), !, flush_output(WsOutput), slack_send_ws(WsOutput,Data),!.
  598slack_send_ws(WsOutput,Data):- format(WsOutput,'~q',[Data]),flush_output(WsOutput),fmt(slack_sent(Data)),flush_output.
  599
  600
  601dict_to_curly(Dict,{type:Type,Data}):- del_dict(type,Dict,Type,DictOut),dict_pairs(DictOut,_,Pairs),any_to_curls(Pairs,Data).
  602dict_to_curly(Dict,{type:Type,Data}):- dict_pairs(Dict,Type,Pairs),nonvar(Type),any_to_curls(Pairs,Data).
  603dict_to_curly(Dict,{Data}):- dict_pairs(Dict,_,Pairs),any_to_curls(Pairs,Data).
  604
  605any_to_curls(Dict,Out):- is_dict(Dict),!,dict_to_curly(Dict,Data),any_to_curls(Data,Out).
  606any_to_curls(Var,"var"):- \+ must(\+ var(Var)),!.
  607any_to_curls({DataI},{Data}):-!,any_to_curls(DataI,Data).
  608any_to_curls((A,B),(AA,BB)):-!,any_to_curls(A,AA),any_to_curls(B,BB).
  609any_to_curls([A|B],(AA,BB)):-!,any_to_curls(A,AA),any_to_curls(B,BB).
  610any_to_curls([A],AA):-!,any_to_curls(A,AA).
  611any_to_curls(KV,AA:BB):-get_kv_local(KV,A,B),!,any_to_curls(A,AA),any_to_curls(B,BB).
  612any_to_curls(A,AA):- catch(text_to_string(A,AA),_,fail),!.
  613any_to_curls(A,A).
  614
  615<<<<<<< HEAD
  616slack_send(WsOutput,Data):- format(WsOutput,'~q',[Data]),dbgM(slack_sent(Data)).
  617=======