1/* <module> slack_client
    2% Provides a websocket API to slack
    3
    4
    5Created initial Prolog Language Bindings.  
    6
    7https://github.com/swi-to-yap/slack_prolog/
    8
    9
   10I am developing a MUD in prolog and needed it so team members could play the MUD chatting a bot.
   11
   12
   13It works .
   14
   15getting data over the RTM.
   16Posts over the https.
   17
   18Still has a way to go but it gets users started by seeing how easy it was.
   19If you could list it on Language API bindings that be great!
   20Thank you in advance
   21
   22Douglas Miles
   23Dec 13, 2035
   24
   25
   26root@ubuntu:/mnt/dddd/workspace/runtime# cat .slack_auth.pl
   27
   28slack_token('xoxb-130154379991-ogFL0OFP3w6AwdJuK7wLojpK').
   29
   30
   31*/
   32
   33:- module(slack_client_old, [
   34        slack_start_listener/0,
   35        slack_chat/2,
   36        slack_send/1,
   37        slack_ping/0,
   38        is_thread_running/1,
   39        slack_ensure_im/2,
   40        name_to_id/2
   41        ]).   42
   43
   44:- use_module(library(http/http_open)).   45:- use_module(library(http/http_client)).   46:- use_module(library(http/http_json)).   47:- use_module(library(url)).   48:- use_module(library(http/json)).   49:- use_module(library(http/json_convert)).   50:- use_module(library(http/websocket)).   51
   52:- if( \+ current_predicate( wdmsg/1 )).   53
   54:- meta_predicate(with_visible_leash(0)).   55with_visible_leash(G):-
   56   '$leash'(A, A),'$visible'(V, V),
   57   (tracing->CU=trace;CU=notrace),
   58   (debugging->CU2=debug;CU2=nodebug),!,
   59   call_cleanup(G, (notrace,'$leash'(_, A),'$visible'(_, V),call(CU2),call(CU))).
   60
   61:- meta_predicate(rtrace(0)).   62rtrace(G):-  with_visible_leash(( notrace,leash(-all),visible(+full),leash(+exception),trace,debug, call(G))).
   63
   64:- meta_predicate(must(0)).   65must(G):- G *->true;throw(must_failed(G)).
   66
   67dmsg(O):- format(user_error,'~N ~w ~n',[O]).
   68
   69:- endif.   70
   71
   72is_thread_running(ID):-
   73  is_thread(ID), thread_property(ID,status(What)),!,
   74   (What==running->true;(thread_join(ID,_ ),!,fail)).
   75
   76
   77
   78:- if( \+ current_predicate( slack_token/1 )).   79:- if(exists_file('.slack_auth.pl')).   80:- include('.slack_auth.pl').   81:- else.   82:- if(exists_file('~/.slack_auth.pl')).   83:- include('~/.slack_auth.pl').   84:- endif.   85:- endif.   86:- endif.   87
   88
   89%  throws if missing
   90:- slack_token(_).   91
   92
   93slack_token_string(S):-slack_token(T),atom_string(T,S).
   94
   95
   96:- dynamic(slack_info/3).   97:- dynamic(slack_websocket/3).   98
   99
  100slack_get_websocket_url(URL):-
  101  slack_token(Token),
  102  format(atom(GetURL),'https://slack.com/api/rtm.start?token=~w',[Token]),
  103  http_open(GetURL, In, []),
  104  json_read_dict(In,Term),
  105  dict_pairs(Term,_,Pairs),
  106  must(maplist(slack_receive(rtm),Pairs)),
  107  URL=Term.url,
  108  listing(slack_info/3),
  109  close(In).
  110
  111slack_get_websocket(WS):- slack_websocket(WS,_,_),!.
  112slack_get_websocket(WS):-
  113   slack_get_websocket_url(URL),!,
  114   slack_open_websocket(URL,WS),!.
  115
  116slack_open_websocket(URL,WS):-
  117   ignore(slack_websocket(OLD_WS,_,_)),
  118   http_open_websocket(URL, WS, []),
  119   stream_pair(WS,I,O),
  120   asserta(slack_websocket(WS,I,O)),
  121   (nonvar(OLD_WS)->slack_remove_websocket(OLD_WS);true).
  122
  123slack_remove_websocket(OLD_WS):-
  124   ignore(retract(slack_websocket(OLD_WS,_,_))),
  125   ignore(catch(ws_close(OLD_WS,1000,''),_,true)).
  126
  127lame_key(K):- var(K),!.
  128lame_key(_-_):-!,fail.
  129lame_key(Type):-string(Type),!,string_to_atom(Type,K),!,lame_key(K).
  130lame_key(rtm).
  131lame_key(rtm_e).
  132lame_key(data).
  133lame_key(var).
  134
  135slack_key(Type,Key,NewType):- lame_key(Type),!,slack_key(Key,NewType).
  136slack_key(Key,Type,NewType):- lame_key(Type),!,slack_key(Key,NewType).
  137slack_key(_Type,Key,NewType):-slack_key(Key,NewType).
  138
  139slack_key(Type,var):-var(Type),!.
  140slack_key(Type,K):-string(Type),!,string_to_atom(Type,K).
  141slack_key(Key-Type,NewType):-!,slack_key(Key,Type,NewType).
  142slack_key(Key,Key).
  143
  144slack_start_listener:-
  145 call_cleanup((
  146  repeat,
  147  once(slack_get_websocket(WS)),
  148  once(ws_receive(WS,Data,[format(json)])),
  149  (Data==
  150    end_of_file->!;
  151  (once(slack_receive(rtm_e,Data)),fail))),
  152  slack_remove_websocket(WS)).
  153
  154
  155
  156undict(ID,IDO):- is_dict(ID),ID.IDK=IDV,IDK=id,IDO=IDV.
  157undict(ID,ID).
  158
  159
  160% ignored
  161slack_event(reconnect_url,_Dict):-!.
  162/*
  163  must((Dict.url=URL,
  164   dmsg(reconnect(URL)),!,
  165   dmsg(slack_open_websocket(URL,_)))).
  166*/
  167
  168% simplify the data objects
  169slack_event(Type,O):- is_dict(O),O.Key=Data,Key=data,!,slack_receive(Type,Data),!.
  170
  171% typify the data objects
  172slack_event(rtm_e,O):- is_dict(O),O.Key=Type,Key=type,!,slack_receive(Type,O),!.
  173
  174% Notice newly created IMs
  175slack_event(im_open,Dict):-
  176  Dict.channel=IDI,
  177  Dict.user=User,
  178  undict(IDI,ID),
  179  string_to_atom(ID,IDA),
  180  asserta(slack_info(ims, instance, IDA)),
  181  asserta(slack_info(IDA, id, ID)),
  182  asserta(slack_info(IDA, user, User)).
  183
  184slack_event(_,end_of_file):- throw(slack_event(rtm_e,end_of_file)).
  185slack_event(_,"end_of_file"):- throw(slack_event(rtm_e,end_of_file)).
  186
  187
  188
  189% slack_event(Type,Data):-add_slack_info(now,Type,Data).
  190
  191slack_unused(user_typing).
  192slack_unused(reconnect_url).
  193
  194slack_receive(Type,Data):- string(Data),(string_to_dict(Data,Dict)->true;string_to_atom(Data,Dict)),!,slack_receive(Type,Dict).
  195slack_receive(Type,Data):- slack_key(Type,NewType)-> Type\==NewType,!,slack_receive(NewType,Data).
  196slack_receive(C,Dict):-    type_to_url(K,C),!, slack_receive(K,Dict).
  197slack_receive(Type,Data):- slack_event(Type,Data),!.
  198slack_receive(Type,Data):- slack_info(Type,Data),!.
  199slack_receive(Type,Data):- slack_unused(Type), format(user_error,'~N % UNUSED ~w ~w ~n',[Type,Data]).
  200slack_receive(Type,Data):- format(user_error,'~N % ~q ~q ~n',[Type,Data]).
  201
  202
  203
  204slack_info(Type,Data):-is_dict(Data),Data.Key=ID,Key=id,!,string_to_atom(ID,Atom),
  205   add_slack_info(Type,Atom,Data).
  206slack_info(rtm,Data):- is_list(Data),!, maplist(slack_receive(rtm),Data).
  207slack_info(Type,Key-[A|Data]):-is_dict(A),is_list(Data),!,maplist(slack_receive(Type-Key),[A|Data]).
  208slack_info(Type,Key-Data):- atomic(Data),add_slack_info(Type,Key,Data).
  209slack_info(Type,Key-Data):- is_dict(Data),dict_pairs(Data,Tag,Pairs),maplist(slack_receive(Type-Key-Tag),Pairs).
  210
  211
  212add_slack_info(Type,ID,Data):- is_dict(Data),dict_pairs(Data,_Tag,Pairs),!, add_slack_info1(Type,instance,ID),
  213   maplist(add_slack_info1(Type,ID),Pairs).
  214
  215add_slack_info(Type,ID,Data):-add_slack_info1(Type,ID,Data).
  216
  217add_slack_info1(Type,ID,K-V):- atom(Type),!,add_slack_info1(ID,K,V).
  218add_slack_info1(Type,ID,Data):-assert(slack_info(Type,ID,Data)).
  219
  220
  221name_to_id(Name,ID):-text_to_string(Name,NameS),slack_info(ID,name,NameS),!.
  222name_to_id(Name,ID):-text_to_string(Name,NameS),slack_info(ID,real_name,NameS),!.
  223name_to_id(Name,ID):-text_to_string(Name,NameS),slack_info(_,instance,ID), slack_info(ID,_,NameS),!.
  224
  225same_ids(ID,IDS):-text_to_string(ID,IDA),text_to_string(IDS,IDB),IDA==IDB.
  226
  227slack_ensure_im2(To,IM):- name_to_id(To,ID), slack_info(IM,user,IDS),same_ids(ID,IDS),slack_info(ims,instance,IM),!.
  228slack_ensure_im(To,IM):- slack_ensure_im2(To,IM),!.
  229slack_ensure_im(To,IM):- name_to_id(To,ID), slack_send({type:'im_open',user:ID}),!,must(slack_ensure_im2(To,IM)),!.
  230
  231
  232slack_id_time(ID,TS):-flag(slack_id,OID,OID+1),ID is OID+1,get_time(Time),number_string(Time,TS).
  233
  234
  235slack_self(Self):- get_slack_info(self, id, Self).
  236
  237%  {"id":2,"type":"ping","time":1484999912}
  238slack_ping :- slack_id_time(ID,_),get_time(Time),TimeRnd is round( Time),slack_send({"id":ID,"type":"ping", "time":TimeRnd}).
  239
  240% {"id":3,"type":"message","channel":"D3U47CE4W","text":"hi there"}
  241slack_chat :- slack_chat(logicmoo,"hi there").
  242slack_chat2:- slack_chat(dmiles,"hi dmiles").
  243
  244
  245slack_chat(To,Msg):-  slack_ensure_im(To,IM),
  246	  slack_send({ type: "message", username:"@prologmud_connection",
  247	    channel: IM, text: Msg
  248	  }),!.
  249
  250slack_post(Cmd,Params):- slack_token(Token),
  251	  make_url_params(Params,URLParams),
  252	  format(string(S),'https://slack.com/api/~w?token=~w&~w',[Cmd,Token,URLParams]),
  253	  format(user_error,'~N SLACK-POST ~q ~n',[S]),!,
  254	  http_open(S,Out,[]),!,
  255	  json_read_dict(Out,Dict),
  256	  dict_append_curls(Dict,Params,NewDict),
  257	  slack_receive(Cmd,NewDict).
  258
  259dict_append_curls(Dict,Params,NewDict):-any_to_curls(Params,Curly),
  260	dict_append_curls3(Dict,Curly,NewDict).
  261
  262dict_append_curls3(Dict,{},Dict):-!.
  263dict_append_curls3(Dict,{Curly},NewDict):-!,dict_append_curls3(Dict,Curly,NewDict).
  264dict_append_curls3(Dict,(A,B),NewDict):-!,dict_append_curls3(Dict,A,NewDictM),dict_append_curls3(NewDictM,B,NewDict).
  265dict_append_curls3(Dict,KS:V,NewDict):- string_to_atom(KS,K), put_dict(K,Dict,V,NewDict).
  266
  267
  268string_to_dict:-
  269 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),
  270  dmsg(Dict).
  271
  272string_to_dict(String,Dict):-
  273  %text_to_string(String,Atom),string_to_atom(AtomS,Atom),atom_to_memory_file(Atom,Handle),open_memory_file(Handle,read,Stream),
  274   open_string(String,Stream),
  275   catch(json_read_dict(Stream,Dict),_,fail),!.
  276
  277
  278
  279type_to_url("message",'chat.postMessage').
  280type_to_url("im_open",'im.open').
  281
  282make_url_params({In},Out):-!,make_url_params(In,Out).
  283make_url_params((A,B),Out):-!,make_url_params(A,AA),make_url_params(B,BB),format(atom(Out),'~w&~w',[AA,BB]).
  284make_url_params([A],Out):-!,make_url_params(A,Out).
  285make_url_params([A|B],Out):-!,make_url_params(A,AA),make_url_params(B,BB),format(atom(Out),'~w&~w',[AA,BB]).
  286make_url_params(K:A,Out):-www_form_encode(A,AA),format(atom(Out),'~w=~w',[K,AA]).
  287make_url_params(K-A,Out):-www_form_encode(A,AA),format(atom(Out),'~w=~w',[K,AA]).
  288make_url_params(K=A,Out):-www_form_encode(A,AA),format(atom(Out),'~w=~w',[K,AA]).
  289
  290slack_send(DataI):- any_to_curls(DataI,Data),slack_send00(Data).
  291
  292slack_send00({"type":Type,Params}):-type_to_url(Type,Cmd),!,slack_post(Cmd,Params).
  293% @TODO comment the above and fix this next block
  294slack_send00(Data):-slack_get_websocket(WebSocket),
  295   slack_websocket(WebSocket, _WsInput, WsOutput),
  296   flush_output(WsOutput),
  297   slack_send(WsOutput,Data),
  298   flush_output(WsOutput).
  299
  300dict_to_curly(Dict,{type:Type,Data}):- del_dict(type,Dict,Type,DictOut),dict_pairs(DictOut,_,Pairs),any_to_curls(Pairs,Data).
  301dict_to_curly(Dict,{type:Type,Data}):- dict_pairs(Dict,Type,Pairs),nonvar(Type),any_to_curls(Pairs,Data).
  302dict_to_curly(Dict,{Data}):- dict_pairs(Dict,_,Pairs),any_to_curls(Pairs,Data).
  303
  304any_to_curls(Dict,Out):- is_dict(Dict),!,dict_to_curly(Dict,Data),any_to_curls(Data,Out).
  305any_to_curls(Var,"var"):- \+ must(\+ var(Var)),!.
  306any_to_curls({DataI},{Data}):-!,any_to_curls(DataI,Data).
  307any_to_curls((A,B),(AA,BB)):-!,any_to_curls(A,AA),any_to_curls(B,BB).
  308any_to_curls([A],AA):-!,any_to_curls(A,AA).
  309any_to_curls([A|B],(AA,BB)):-!,any_to_curls(A,AA),any_to_curls(B,BB).
  310any_to_curls(A:B,AA:BB):-!,any_to_curls(A,AA),any_to_curls(B,BB).
  311any_to_curls(A-B,AA:BB):-!,any_to_curls(A,AA),any_to_curls(B,BB).
  312any_to_curls(A,A):- (integer(A);string(A)),!.
  313any_to_curls(A,AA):- catch(text_to_string(A,AA),_,fail),!.
  314any_to_curls(A,A).
  315
  316slack_send(WsOutput,Data):- format(WsOutput,'~q',[Data]),format(user_error,'~N ~q ~n',[Data]),!.
  317
  318
  319% start slack listener in a thread
  320:- if(( \+ (is_thread_running(slack_start_listener)))).  321:- thread_create(slack_start_listener,_,[alias(slack_start_listener)]).  322:- endif.  323
  324% if the above fails .. run in debug mode
  325:- if(( \+ (is_thread_running(slack_start_listener)))).  326:- rtrace(slack_start_listener).  327:- endif.