]> git.sesse.net Git - skvidarsync/blob - bin/ws.pl
Communicate through NOTIFY/LISTEN instead of a file.
[skvidarsync] / bin / ws.pl
1 #! /usr/bin/perl
2 use strict;
3 use warnings;
4 no warnings qw(once);
5 use JSON::XS;
6 use LWP::UserAgent;
7 use DBI;
8 use POSIX;
9 use AnyEvent::WebSocket::Client;
10 use AnyEvent::Loop;
11 binmode STDOUT, ':utf8';
12 binmode STDERR, ':utf8';
13 use utf8;
14
15 require '../include/config.pm';
16
17 my $ua = LWP::UserAgent->new('SKVidarLang/1.0');
18 our $ws_disconnected;
19
20 sub db_connect {
21         my $dbh = DBI->connect("dbi:Pg:dbname=$config::dbname;host=127.0.0.1", $config::dbuser, $config::dbpass, {RaiseError => 1})
22                 or warn "Could not connect to Postgres: " . DBI->errstr;
23         return $dbh;
24 }
25
26 my $dbh;
27 while (1) {
28         if (!defined($dbh) || !$dbh->ping) {
29                 $dbh = db_connect();
30                 if (!defined($dbh)) {
31                         sleep 1;
32                         next;
33                 }
34         }
35         $ws_disconnected = AnyEvent->condvar;
36         my $response = $ua->post('https://slack.com/api/apps.connections.open',
37                 Authorization => 'Bearer ' . $config::slack_app_token,
38                 Content_type => 'application/x-www-form-urlencoded'
39         );
40         if (!$response->is_success) {
41                 warn "apps.connections.open: " . $response->status_line;
42                 sleep 1;
43                 next;   
44         }
45         my $msg_json = JSON::XS::decode_json($response->decoded_content);
46         if (!defined($msg_json) || !$msg_json->{'ok'}) {
47                 warn "Something went wrong: " . $response->decoded_content;
48                 sleep 1;
49                 next;
50         }
51
52         my $ws_url = $msg_json->{'url'};
53         my $ws = AnyEvent::WebSocket::Client->new;
54         $ws->connect($ws_url)->cb(\&ws_cb);
55         $ws_disconnected->recv;
56         print "Disconnected; trying to reconnect.\n\n";
57 };
58
59 sub ws_cb {
60         our $connection = eval { shift->recv };
61         if ($@) {
62                 warn $@;
63                 sleep 1;
64                 $ws_disconnected->send;
65                 return;
66         }
67
68         print "Connected to the Slack WebSocket.\n";
69
70         $connection->on(each_message => sub {
71                 my ($conn, $message) = @_;
72                 print "Message: $message->{'body'}\n";
73                 my $json = JSON::XS::decode_json($message->{'body'});
74                 eval {
75                         if (exists($json->{'payload'}{'event'})) {
76                                 handle_event($json->{'payload'}{'event'});
77                         }
78                 };
79                 if ($@) {
80                         print "Error during handling: $@";
81                         die;
82                 } elsif (exists($json->{'envelope_id'})) {
83                         my $ack = { envelope_id => $json->{'envelope_id'} };
84                         print "Ack: " . JSON::XS::encode_json($ack) . "\n";
85                         $conn->send(JSON::XS::encode_json($ack))
86                                 or die "Error sending ack: $!";
87                 }
88         });
89
90         $connection->on(finish => sub {
91                 my ($conn, $msg) = @_;
92                 $msg //= '(none)';
93                 print "Finished with message: $msg\n";
94                 $ws_disconnected->send;
95         });
96 }
97
98 sub mark {
99         print STDERR "Marking that a sync is needed.\n";
100         $dbh->do('NOTIFY skvupdate');
101 }
102
103 sub handle_event {
104         my $ev = shift;
105         if (!exists($ev->{'type'})) {
106                 print STDERR "Has no type; ignoring.\n";
107                 return;
108         }
109
110         my $type = $ev->{'type'};
111         my $user = $ev->{'user'};
112
113         if ($type eq 'message') {
114                 if ($ev->{'message'}{'text'} =~ /(20\d{2}-\d{2}-\d{2})/) {
115                         # TODO: What if edits happen out-of-order?
116                         my $date = $1;
117                         my $channel = $ev->{'channel'};
118                         my $ts = $ev->{'message'}{'ts'};
119                         print "Matching message {$channel, $ts} to date $date\n";
120                         $dbh->do('INSERT INTO message_sheet_link (channel, ts, sheet_title) VALUES (?,?,?)', undef,
121                                 $channel, $ts, $date);
122                 } else {
123                         print STDERR "No date found in message, ignoring\n";
124                 }
125                 return;
126         }
127
128         my $reaction = $ev->{'reaction'};
129         my $channel = $ev->{'item'}{'channel'};
130         my $ts = $ev->{'item'}{'ts'};
131         my $event_ts = $ev->{'event_ts'};
132
133         if (!defined($channel) || !defined($ts)) {
134                 print STDERR "Not reacting to a message; ignoring.\n";
135                 return;
136         }
137
138         if ($type eq 'reaction_added' || $type eq 'reaction_removed') {
139                 $dbh->do('INSERT INTO reaction_log (userid, channel, ts, reaction, event_type, event_ts) VALUES (?,?,?,?,?,?)', undef,
140                         $user, $channel, $ts, $reaction, $type, $event_ts);
141                 mark($dbh);
142         } else {
143                 print STDERR "Type is $type (not a reaction added or removed); ignoring.\n";
144         }
145 }