1:- module(slack_client, [
2 slack_start_listener/0,
3 slack_chat/2,
4 slack_send/1,
5 slack_ping/0,
6 is_thread_running/1,
7 slack_ensure_im/2,
8 name_to_id/2
9 ]).
14:- if(exists_source(library(dicts))). 15 :- use_module(library(dicts)). 16:- endif. 17
18:- use_module(library(http/http_open)). 19:- use_module(library(http/http_client)). 20:- use_module(library(http/http_json)). 21:- use_module(library(url)). 22:- use_module(library(http/json)). 23:- use_module(library(http/json_convert)). 24:- use_module(library(http/websocket)). 25
26:- if(exists_source(library(logicmoo_common))). 27 :- use_module(library(logicmoo_common)). 28:- endif. 29
30:- if(exists_source(library(dictoo))). 31 :- use_module(library(dictoo)). 32:- endif. 33
34:- if(exists_source(library(udt))). 36:- endif. 37
39dbgM(O):- dbgM('~N% ~p.~n',[O]).
40dbgM(F,O):- format(user_error,F,O),flush_output(user_error).
41
42:- use_module(library(eggdrop)). 43:- egg_go. 44
45is_thread_running(ID):-
46 is_thread(ID), thread_property(ID,status(What)),!,
47 (What==running->true;(thread_join(ID,_ ),!,fail)).
48
49
50:- dynamic(slack_token/1). 51
55
59slack_token('xoxb-1030744384576-1077790097232-HYegGh0O4y8SUnuiF6FSCrDC').
60
62:- if(( \+ slack_token(_) , exists_file('.slack_auth.pl'))). 63:- include('.slack_auth.pl'). 64:- endif. 65
68:- if(( \+ slack_token(_))). 69:- getenv('SLACK_API_TOKEN',Was)->asserta(slack_token(Was));true. 70:- endif. 71
73:- if(( \+ slack_token(_) , exists_file('~/.slack_auth.pl'))). 74:- include('~/.slack_auth.pl'). 75:- endif. 76
77:- if(( \+ slack_token(_))). 78:- throw(missing(slack_token(_))). 79:- endif. 80
81
82
83
84:- oo_class_begin(slack_client). 85
87:- oo_class_field(url). 88
90:- oo_inner_class_begin(clients). 91
92slack_client:clients:new(Ref):- throw(clients:new(Ref)).
93
94:- oo_inner_class_end(clients). 95
96
97
99:- oo_inner_class_begin(self). 100:- oo_inner_class_end(self). 101
103:- oo_inner_class_begin(self). 104:- oo_inner_class_end(self). 105
106
108:- oo_inner_class_begin(team). 109:- oo_inner_class_end(team). 110
112:- oo_inner_class_begin(users). 113:- oo_inner_class_end(users). 114
115
117:- oo_inner_class_begin(channels). 118:- oo_inner_class_end(channels). 119
121:- oo_inner_class_begin(self). 122:- oo_inner_class_end(self). 123
125:- oo_inner_class_begin(groups). 126:- oo_inner_class_end(groups). 127
129:- oo_inner_class_begin(bots). 130:- oo_inner_class_end(bots). 131
133:- oo_inner_class_begin(text). 134:- oo_inner_class_end(text). 135
137:- oo_inner_class_begin(self). 138:- oo_inner_class_end(self). 139
141:- oo_inner_class_begin(events). 142:- oo_inner_class_end(events). 143
145:- oo_inner_class_begin(files). 146:- oo_inner_class_end(files). 147
148:- oo_class_end(slack_client). 149
150:- dynamic(tmpd:slack_info/3). 151
155
156slack_token_string(S):-slack_token(T),atom_string(T,S).
157
158slack_get_websocket_url(URL):-
159 slack_token(Token),
160 format(atom(GetURL),'https://slack.com/api/rtm.start?token=~w',[Token]),
161 http_open(GetURL, In, []),
162 json_read_dict(In,Term),
163 dict_pairs(Term,_,Pairs),
164 must(maplist(slack_receive(rtm),Pairs)),
165 URL=Term.url,
166 167 close(In).
168
169:- dynamic(slack_websocket/3). 170
171slack_get_websocket(WS):- slack_websocket(WS,_,_),!.
172slack_get_websocket(WS):-
173 slack_get_websocket_url(URL),!,
174 slack_open_websocket(URL,WS),!.
175
176slack_open_websocket(URL,WS):-
177 ignore(slack_websocket(OLD_WS,_,_)),
178 http_open_websocket(URL, WS, []),
179 stream_pair(WS,I,O),
180 asserta(slack_websocket(WS,I,O)),
181 (nonvar(OLD_WS)->slack_remove_websocket(OLD_WS);true).
182
183slack_remove_websocket(OLD_WS):-
184 ignore(retract(slack_websocket(OLD_WS,_,_))),
185 ignore(catch(ws_close(OLD_WS,1000,''),_,true)).
186
190skip_propname(K):- var(K),!.
191skip_propname(_-_):-!,fail.
192skip_propname(Type):-string(Type),!,string_to_atom(Type,K),!,skip_propname(K).
193skip_propname(rtm).
194skip_propname(rtm_e).
195skip_propname(data).
196skip_propname(var).
197
198slack_propname(Type,var):-var(Type),!.
199slack_propname(Type,K):-string(Type),!,string_to_atom(Type,K).
200slack_propname(Key-Type,NewType):-!,slack_propname(Key,Type,NewType).
201slack_propname(Key.Type,NewType):-!,slack_propname(Key,Type,NewType).
202slack_propname(Key,Key).
203
204slack_propname(Key,Type,NewType):- skip_propname(Type),!,slack_propname(Key,NewType).
205slack_propname(Type,Key,NewType):- skip_propname(Type),!,slack_propname(Key,NewType).
206slack_propname(_Type,Key,NewType):-slack_propname(Key,NewType).
207
208
209slack_start_listener:-
210 call_cleanup((
211 repeat,
212 once(slack_get_websocket(WS)),
213 flush_output_safe,
214 once(ws_receive(WS,Data,[format(json)])),
215 flush_output_safe,
216 (Data==
217 end_of_file->!;
218 (once(slack_receive_now(rtm_e,Data)),fail))),
219 slack_remove_websocket(WS)).
220
221
222
223undict(ID,IDO):- is_dict(ID),ID.IDK=IDV,IDK=id,IDO=IDV.
224undict(ID,ID).
225
226
228slack_event(reconnect_url,Dict):-
229 must((Dict.url=URL,
230 dbgM(reconnect(URL)),!,
231 dbgM(slack_open_websocket(URL,_)))),
232 nop(slack_open_websocket(URL,_)).
233
235slack_event(rtm_e,O):- is_dict(O),O.Key=Type,Key=type,!,slack_receive(Type,O),!.
236
238slack_event(Type,O):- is_dict(O),O.Key=Data,Key=data,!,slack_receive(Type,Data),!.
239
241slack_event(im_open,Dict):-
242 Dict.channel=IDI,
243 Dict.user=User,
244 undict(IDI,ID),
245 string_to_atom(ID,IDA),
246 asserta(tmpd:slack_info(ims, instance, IDA)),
247 asserta(tmpd:slack_info(IDA, id, ID)),
248 asserta(tmpd:slack_info(IDA, user, User)).
249
250slack_event(_,end_of_file):- throw(slack_event(rtm_e,end_of_file)).
251
252
254
255slack_unused(user_typing).
256slack_unused(reconnect_url).
257
259slack_receive_now(Type,Data):-
260 nb_setval(websocket_in,Data),
261 slack_receive(Type,Data),!.
262
263slack_receive( Var-Type, Data) :- Var==(var),!,slack_receive(Type,Data).
264
266slack_receive(Type,Data):- string(Data),(string_to_dict(Data,Dict)->true;string_to_atom(Data,Dict)),!,slack_receive(Type,Dict).
267slack_receive(Type,Data):- slack_propname(Type,NewType)-> Type\==NewType,!,slack_receive(NewType,Data).
268slack_receive(Type,Dict):- type_to_url(K,Type)-> K \== Type,!,slack_receive(K,Dict).
269slack_receive(Type,Data):- slack_event(Type,Data),!.
270slack_receive(Type,Data):- slack_inform(Type,Data),!.
271slack_receive(Type,Data):- slack_unused(Type), dbgM(unused(slack_receive(Type,Data))),!.
272slack_receive(Type,Data):- (nb_current(websocket_in,Data2)->true;Data2=[]),dbgM(unknown(slack_receive(Type,Data):-Data2)).
273
274
275
277
278slack_inform(Type,Data):-is_dict(Data),Data.Key=ID,Key=id,!,string_to_atom(ID,Atom), add_slack_info(Type,Atom,Data).
279slack_inform(Type,Data):-is_dict(Data),dict_pairs(Data,_Tag,Pairs),!,slack_inform(Type,Pairs).
280
281slack_inform(rtm,Data):- is_list(Data),!, maplist(slack_receive(rtm),Data).
282slack_inform(Type,Key-[A|Data]):-is_dict(A),is_list(Data),!,maplist(slack_receive(Type-Key),[A|Data]).
283slack_inform(Type,Key-Data):- atomic(Data),add_slack_info(Type,Key,Data).
284slack_inform(Type,Key-Data):- is_dict(Data),dict_pairs(Data,Tag,Pairs),maplist(slack_receive(Type-Key-Tag),Pairs).
285
286
287
288add_slack_info(Type,ID,Data):- is_dict(Data),dict_pairs(Data,_Tag,Pairs),!,
289 add_slack_info1(Type,instance,ID),
290 maplist(add_slack_info1(Type,ID),Pairs).
291
292add_slack_info(Type,ID,Data):-add_slack_info1(Type,ID,Data).
293
294add_slack_info1(Type,Profile,Data):- is_dict(Data),dict_pairs(Data,_Tag,Pairs),!,add_slack_info1(Profile,Type,Pairs).
295add_slack_info1(Type,ID,K-V):- Type==var, !,add_slack_info1(ID,K,V).
296add_slack_info1(Type,ID,K-V):- Type==profile, !,add_slack_info1(ID,K,V).
297add_slack_info1(Type,ID,Data):- is_list(Data),!,maplist(add_slack_info1(Type,ID),Data).
298add_slack_info1(Type,ID,Data):- dbgM(add_slack_info1(Type,ID,Data)),fail.
299add_slack_info1(Type,ID,K-V):- atom(Type),!,add_slack_info1(ID,K,V).
300add_slack_info1(Type,ID,Data):-assert(tmpd:slack_info(Type,ID,Data)).
301
302get_slack_info(Object, Prop, Value):- tmpd:slack_info(Object, Prop, Value).
303
304name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(ID,name,NameS),ID\==var,!.
305name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(ID,real_name,NameS),ID\==var,!.
306name_to_id(Name,ID):-text_to_string(Name,NameS),get_slack_info(_,instance,ID), get_slack_info(ID,_,NameS),ID\==var,!.
307
308same_ids(ID,IDS):-text_to_string(ID,IDA),text_to_string(IDS,IDB),IDA==IDB.
309
310slack_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),!.
311slack_ensure_im(To,IM):- slack_ensure_im2(To,IM),!.
312slack_ensure_im(To,IM):- name_to_id(To,ID), slack_send({type:'im_open',user:ID}),!,must(slack_ensure_im2(To,IM)),!.
313
314
315slack_id_time(ID,TS):-flag(slack_id,OID,OID+1),ID is OID+1,get_time(Time),number_string(Time,TS).
316
317
318slack_self(Self):- get_slack_info(Self, real_name, "prolog_bot"),!.
319
321slack_ping :- slack_id_time(ID,_),get_time(Time),TimeRnd is round(Time),slack_send({"id":ID,"type":"ping", "time":TimeRnd}).
322
324slack_chat :- slack_chat(logicmoo,"hi there").
325slack_chat2:- slack_chat(dmiles,"hi dmiles").
326
327
328slack_chat(To,Msg):- slack_ensure_im(To,IM),
329 slack_send({
330 type: "message",
331 username:"@prologmud_connection",
332 channel: IM,
333 text: Msg
334 }),!.
335
336slack_post(Cmd,Params):- slack_token(Token),
337 make_url_params(Params,URLParams),
338 format(string(S),'https://slack.com/api/~w?token=~w&~w',[Cmd,Token,URLParams]),
339 dbgM('~N SLACK-POST ~q ~n',[S]),!,
340 http_open(S,Out,[]),!,
341 json_read_dict(Out,Dict),
342 dict_append_curls(Dict,Params,NewDict),
343 slack_receive(Cmd,NewDict).
344
345dict_append_curls(Dict,Params,NewDict):-any_to_curls(Params,Curly),
346 dict_append_curls3(Dict,Curly,NewDict).
347
348dict_append_curls3(Dict,{},Dict):-!.
349dict_append_curls3(Dict,{Curly},NewDict):-!,dict_append_curls3(Dict,Curly,NewDict).
350dict_append_curls3(Dict,(A,B),NewDict):-!,dict_append_curls3(Dict,A,NewDictM),dict_append_curls3(NewDictM,B,NewDict).
351dict_append_curls3(Dict,KS:V,NewDict):- string_to_atom(KS,K), put_dict(K,Dict,V,NewDict).
352
353
354string_to_dict:-
355 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),
356 dbgM(Dict).
357
358string_to_dict(String,Dict):-
359 open_string(String,Stream),
360 catch(json_read_dict(Stream,Dict),_,fail),!.
361
362
363
364type_to_url("message",'chat.postMessage').
365type_to_url("im_open",'im.open').
366
367make_url_params({In},Out):-!,make_url_params(In,Out).
368make_url_params((A,B),Out):-!,make_url_params(A,AA),make_url_params(B,BB),format(atom(Out),'~w&~w',[AA,BB]).
369make_url_params([A|B],Out):-!,make_url_params(A,AA),make_url_params(B,BB),format(atom(Out),'~w&~w',[AA,BB]).
370make_url_params([A],Out):-!,make_url_params(A,Out).
371make_url_params(KV,Out):-get_kv_local(KV,K,A),www_form_encode(A,AA),format(atom(Out),'~w=~w',[K,AA]).
372
373get_kv_local(K:V,K,V):- must(nonvar(K);throw(get_kv_local(K:V,K,V))).
374get_kv_local(K-V,K,V).
375get_kv_local(K=V,K,V).
376
377slack_send(DataI):- any_to_curls(DataI,Data),slack_send00(Data).
378
379slack_send00({"type":Type,Params}):-type_to_url(Type,Cmd),!,slack_post(Cmd,Params).
381slack_send00(Data):-slack_get_websocket(WebSocket),
382 slack_websocket(WebSocket, _WsInput, WsOutput),
383 flush_output(WsOutput),
384 slack_send(WsOutput,Data),
385 flush_output(WsOutput).
386
387dict_to_curly(Dict,{type:Type,Data}):- del_dict(type,Dict,Type,DictOut),dict_pairs(DictOut,_,Pairs),any_to_curls(Pairs,Data).
388dict_to_curly(Dict,{type:Type,Data}):- dict_pairs(Dict,Type,Pairs),nonvar(Type),any_to_curls(Pairs,Data).
389dict_to_curly(Dict,{Data}):- dict_pairs(Dict,_,Pairs),any_to_curls(Pairs,Data).
390
391any_to_curls(Dict,Out):- is_dict(Dict),!,dict_to_curly(Dict,Data),any_to_curls(Data,Out).
392any_to_curls(Var,"var"):- \+ must(\+ var(Var)),!.
393any_to_curls({DataI},{Data}):-!,any_to_curls(DataI,Data).
394any_to_curls((A,B),(AA,BB)):-!,any_to_curls(A,AA),any_to_curls(B,BB).
395any_to_curls([A|B],(AA,BB)):-!,any_to_curls(A,AA),any_to_curls(B,BB).
396any_to_curls([A],AA):-!,any_to_curls(A,AA).
397any_to_curls(KV,AA:BB):-get_kv_local(KV,A,B),!,any_to_curls(A,AA),any_to_curls(B,BB).
398any_to_curls(A,AA):- catch(text_to_string(A,AA),_,fail),!.
399any_to_curls(A,A).
400
401slack_send(WsOutput,Data):- format(WsOutput,'~q',[Data]),dbgM(slack_sent(Data)).
402
403
405:- if(( \+ (is_thread_running(slack_start_listener)))). 406:- thread_create(slack_start_listener,_,[alias(slack_start_listener)]). 407:- endif. 408
410:- if(( \+ (is_thread_running(slack_start_listener)))). 411:- slack_start_listener. 412:- endif. 413
414
416:- if(( \+ (is_thread_running(slack_start_listener)))). 417:- rtrace(slack_start_listener). 418:- endif.
slack_client - Provides a websocket API to write slack clients and bots
*/