Hello community,
here is the log from the commit of package bitlbee-discord for openSUSE:Factory checked in at 2018-12-05 09:45:50
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/bitlbee-discord (Old)
and /work/SRC/openSUSE:Factory/.bitlbee-discord.new.19453 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "bitlbee-discord"
Wed Dec 5 09:45:50 2018 rev:2 rq:654098 version:0.4.2
Changes:
--------
--- /work/SRC/openSUSE:Factory/bitlbee-discord/bitlbee-discord.changes 2018-01-09 14:56:11.415372345 +0100
+++ /work/SRC/openSUSE:Factory/.bitlbee-discord.new.19453/bitlbee-discord.changes 2018-12-05 09:45:56.448538227 +0100
@@ -1,0 +2,8 @@
+Mon Dec 03 22:18:18 UTC 2018 - 9+suse@cirno.systems
+
+- Update to version 0.4.2
+ This release contains mostly fixes to catch up with recent
+ changes in the way discord operates plus a couple of new
+ convenience options.
+
+-------------------------------------------------------------------
Old:
----
bitlbee-discord-0.4.1.tar.gz
New:
----
bitlbee-discord-0.4.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ bitlbee-discord.spec ++++++
--- /var/tmp/diff_new_pack.G5IaOC/_old 2018-12-05 09:45:57.080537537 +0100
+++ /var/tmp/diff_new_pack.G5IaOC/_new 2018-12-05 09:45:57.080537537 +0100
@@ -12,20 +12,18 @@
# license that conforms to the Open Source Definition (Version 1.9)
# published by the Open Source Initiative.
-# Please submit bugfixes or comments via http://bugs.opensuse.org/
+# Please submit bugfixes or comments via https://bugs.opensuse.org/
#
-%define github_user sm00th
-%define short_name discord
-Name: bitlbee-%{short_name}
-Version: 0.4.1
+Name: bitlbee-discord
+Version: 0.4.2
Release: 0
Summary: Bitlbee plugin for Discord
-License: GPL-2.0
+License: GPL-2.0-only
Group: Productivity/Networking/IRC
-URL: https://github.com/%{github_user}/%{name}
-Source: https://github.com/%{github_user}/%{name}/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
+URL: https://github.com/sm00th/bitlbee-discord
+Source: https://github.com/sm00th/bitlbee-discord/archive/%{version}.tar.gz#/%{name}-%{version}.tar.gz
BuildRequires: autoconf
BuildRequires: automake
BuildRequires: libtool
@@ -45,12 +43,13 @@
%install
%make_install
-rm %{buildroot}%{_libdir}/bitlbee/%{short_name}.la
+rm %{buildroot}%{_libdir}/bitlbee/discord.la
%files
-%doc LICENSE README
+%license LICENSE
+%doc README
%dir %{_libdir}/bitlbee
-%{_libdir}/bitlbee/%{short_name}.so
-%{_datadir}/bitlbee/%{short_name}-help.txt
+%{_libdir}/bitlbee/discord.so
+%{_datadir}/bitlbee/discord-help.txt
%changelog
++++++ bitlbee-discord-0.4.1.tar.gz -> bitlbee-discord-0.4.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/README new/bitlbee-discord-0.4.2/README
--- old/bitlbee-discord-0.4.1/README 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/README 2018-12-04 18:45:11.000000000 +0100
@@ -49,6 +49,11 @@
If your bitlbee's plugindir is in non-standard location you can specify it by
calling ./configure with --with-plugindir=/path/to/plugindir option.
+You can also use the dockerfile from contrib/docker to build a docker container
+containing bitlbee + bitlbee-discord. Use this command to run the container:
+
+ $ docker run -d -v /bitlbee/config:/var/lib/bitlbee -p 6667:6667 --name bitlbee <image>
+
Usage
-----
Plugin adds 'discord' protocol to bitlbee, add your account as usual:
@@ -56,6 +61,22 @@
account add discord <email> <password>
account discord on
+On your first login you might need to authorize bitlbee's ip address
+(discord will send you an email with a link) or get a captcha-request. In
+latter case you will have to manually set discord login-token to log in:
+
+ > account off discord
+ > acc discord set token_cache xxxxxxxx
+
+To get your token you'll have to login with your browser and locate it in
+"local storage"
+
+Chrome: Developer Tools -> Application -> Local Storage -> https://discordapp.com -> token
+Firefox: Web Developer -> Storage Inspector -> Local Storage -> http://discordapp.com -> token
+
+For more info on captcha issue and any progress on making it less painful see
+https://github.com/sm00th/bitlbee-discord/issues/118
+
You also need to configure discord channels you would like to join/autojoin. To
do that use bitlbee's 'chat list' functionality (`help chat list` and `help
chat add`):
@@ -144,6 +165,25 @@
friendship relationship with a user in addition to their actual away
status, and other users are added to channels.
+ - always_afk (type: boolean; default: off)
+ When enabled bitlbee-discord would always report client's status as afk.
+ This feature is not properly documented in official docs, but it presumably
+ can force push notifications to other clients when bitlbee is connected.
+
+ - emoji_urls (type: boolean; default: on)
+ Controls whether bitlbee-discord would display an url to emoji image next
+ to it's text alias.
+
+ - auto_join (type: boolean; default: off)
+ Automatically join all of the server's channels so you don't have to add
+ them manually (no "chat add" needed).
+
+ - auto_join_exclude (type: string; default: "")
+ Comma-separated list of channel patterns to exclude when auto-joining
+ channels. * matches any text, ? matches a single character. For instance,
+ "Foo.*,Bar.A" will exclude all channels from server "Foo" and channel "A"
+ from server "Bar".
+
Debugging
---------
You can enable extra debug output for bitlbee-discord, by setting BITLBEE_DEBUG
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/configure.ac new/bitlbee-discord-0.4.2/configure.ac
--- old/bitlbee-discord-0.4.1/configure.ac 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/configure.ac 2018-12-04 18:45:11.000000000 +0100
@@ -15,7 +15,7 @@
AC_INIT(
[bitlbee-discord],
- [0.4.1],
+ [0.4.2],
[https://github.com/sm00th/bitlbee-discord/issues],
[bitlbee-discord],
[https://github.com/sm00th/bitlbee-discord],
@@ -29,7 +29,7 @@
AM_PROG_CC_C_O
AC_DISABLE_STATIC
-AC_PROG_LIBTOOL
+LT_INIT
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
m4_ifdef([AC_PROG_CC_C99], [AC_PROG_CC_C99])
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/contrib/docker/Dockerfile new/bitlbee-discord-0.4.2/contrib/docker/Dockerfile
--- old/bitlbee-discord-0.4.1/contrib/docker/Dockerfile 1970-01-01 01:00:00.000000000 +0100
+++ new/bitlbee-discord-0.4.2/contrib/docker/Dockerfile 2018-12-04 18:45:11.000000000 +0100
@@ -0,0 +1,17 @@
+FROM debian:buster
+MAINTAINER Daniel da Silva
+
+# Make & install
+RUN apt-get update
+RUN apt-get install bitlbee-dev bitlbee-libpurple bitlbee-plugin-otr git autoconf build-essential autoproject libtool glib2.0 glib2.0-dev -y
+RUN cd tmp && git clone https://github.com/sm00th/bitlbee-discord.git && cd bitlbee-discord && ./autogen.sh && ./configure && make && make install
+
+# Bitlbee config
+EXPOSE 6667
+VOLUME ["/var/lib/bitlbee"]
+COPY bitlbee.conf /etc/bitlbee/bitlbee.conf
+WORKDIR /
+RUN chown -R bitlbee /var/lib/bitlbee/
+
+ENTRYPOINT chown -R bitlbee /var/lib/bitlbee && /usr/sbin/bitlbee -n -c /etc/bitlbee/bitlbee.conf
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/contrib/docker/bitlbee.conf new/bitlbee-discord-0.4.2/contrib/docker/bitlbee.conf
--- old/bitlbee-discord-0.4.1/contrib/docker/bitlbee.conf 1970-01-01 01:00:00.000000000 +0100
+++ new/bitlbee-discord-0.4.2/contrib/docker/bitlbee.conf 2018-12-04 18:45:11.000000000 +0100
@@ -0,0 +1,154 @@
+## BitlBee default configuration file
+##
+## Comments are marked like this. The rest of the file is INI-style. The
+## comments should tell you enough about what all settings mean.
+##
+
+[settings]
+
+## RunMode:
+##
+## Inetd -- Run from inetd (default)
+## Daemon -- Run as a stand-alone daemon, serving all users from one process.
+## This saves memory if there are more users, the downside is that when one
+## user hits a crash-bug, all other users will also lose their connection.
+## ForkDaemon -- Run as a stand-alone daemon, but keep all clients in separate
+## child processes. This should be pretty safe and reliable to use instead
+## of inetd mode.
+##
+RunMode = ForkDaemon
+
+## User:
+##
+## If BitlBee is started by root as a daemon, it can drop root privileges,
+## and change to the specified user.
+##
+User = bitlbee
+
+## DaemonPort/DaemonInterface:
+##
+## For daemon mode, you can specify on what interface and port the daemon
+## should be listening for connections.
+##
+# DaemonInterface = 0.0.0.0
+# DaemonPort = 6667
+
+## ClientInterface:
+##
+## If for any reason, you want BitlBee to use a specific address/interface
+## for outgoing traffic (IM connections, HTTP(S), etc.), set it here.
+##
+# ClientInterface = 0.0.0.0
+
+## AuthMode
+##
+## Open -- Accept connections from anyone, use NickServ for user authentication.
+## (default)
+## Closed -- Require authorization (using the PASS command during login) before
+## allowing the user to connect at all.
+## Registered -- Only allow registered users to use this server; this disables
+## the register- and the account command until the user identifies itself.
+##
+# AuthMode = Open
+
+## AuthPassword
+##
+## Password the user should enter when logging into a closed BitlBee server.
+## You can also have a BitlBee-style MD5 hash here. Format: "md5:", followed
+## by a hash as generated by "bitlbee -x hash <password>".
+##
+# AuthPassword = ItllBeBitlBee ## Heh.. Our slogan. ;-)
+## or
+# AuthPassword = md5:gzkK0Ox/1xh+1XTsQjXxBJ571Vgl
+
+## OperPassword
+##
+## Password that unlocks access to special operator commands.
+##
+# OperPassword = ChangeMe!
+## or
+# OperPassword = md5:I0mnZbn1t4R731zzRdDN2/pK7lRX
+
+## HostName
+##
+## Normally, BitlBee gets a hostname using getsockname(). If you have a nicer
+## alias for your BitlBee daemon, you can set it here and BitlBee will identify
+## itself with that name instead.
+##
+# HostName = localhost
+
+## MotdFile
+##
+## Specify an alternative MOTD (Message Of The Day) file. Default value depends
+## on the --etcdir argument to configure.
+##
+# MotdFile = /etc/bitlbee/motd.txt
+
+## ConfigDir
+##
+## Specify an alternative directory to store all the per-user configuration
+## files. (.nicks/.accounts)
+##
+ConfigDir = /var/lib/bitlbee
+
+## Ping settings
+##
+## BitlBee can send PING requests to the client to check whether it's still
+## alive. This is not very useful on local servers, but it does make sense
+## when most clients connect to the server over a real network interface.
+## (Public servers) Pinging the client will make sure lost clients are
+## detected and cleaned up sooner.
+##
+## PING requests are sent every PingInterval seconds. If no PONG reply has
+## been received for PingTimeOut seconds, BitlBee aborts the connection.
+##
+## To disable the pinging, set at least one of these to 0.
+##
+# PingInterval = 180
+# PingTimeOut = 300
+
+## Using proxy servers for outgoing connections
+##
+## If you're running BitlBee on a host which is behind a restrictive firewall
+## and a proxy server, you can tell BitlBee to use that proxy server here.
+## The setting has to be a URL, formatted like one of these examples:
+##
+## (Obviously, the username and password are optional)
+##
+# Proxy = http://john:doe@proxy.localnet.com:8080
+# Proxy = socks4://socksproxy.localnet.com
+# Proxy = socks5://socksproxy.localnet.com
+
+## Protocols offered by bitlbee
+##
+## As recompiling may be quite unpractical for some people, this option
+## allows to remove the support of protocol, even if compiled in. If
+## nothing is given, there are no restrictions.
+##
+# Protocols = jabber yahoo
+
+## Trusted CAs
+##
+## Path to a file containing a list of trusted certificate authorities used in
+## the verification of server certificates.
+##
+## Uncomment this and make sure the file actually exists and contains all
+## certificate authorities you're willing to accept (default value should
+## work on at least Debian/Ubuntu systems with the "ca-certificates" package
+## installed). As long as the line is commented out, SSL certificate
+## verification is completely disabled.
+##
+## The location of this file may be different on other distros/OSes. For
+## example, try /etc/ssl/ca-bundle.pem on OpenSUSE.
+##
+# CAfile = /etc/ssl/certs/ca-certificates.crt
+
+[defaults]
+
+## Here you can override the defaults for some per-user settings. Users are
+## still able to override your defaults, so this is not a way to restrict
+## your users...
+
+## To enable private mode by default, for example:
+
+## private = 1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/doc/discord-help.txt new/bitlbee-discord-0.4.2/doc/discord-help.txt
--- old/bitlbee-discord-0.4.1/doc/discord-help.txt 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/doc/discord-help.txt 2018-12-04 18:45:11.000000000 +0100
@@ -5,7 +5,9 @@
<trac3r> chat add discord !1 #mydiscordchannel
<trac3r> chan #mydiscordchannel set auto_join true
<trac3r> /join #mydiscordchannel
-If you set auto_join to true, next time you reconnect there will be no need to join the channel manually.
+If you set auto_join to true, next time you reconnect there will be no need to join the channel manually.
+Alternatively, you may set the auto_join setting on the account itself (account discord set auto_join on) to automatically join all of the server's channels.
+You can exclude channels from auto-joining using the auto_join_exclude setting.
See help discord options for more.
%
?discord options
@@ -23,6 +25,8 @@
server_prefix_len (default: 3)
fetch_pinned (default: off)
friendship_mode (default: on)
+auto_join (default: off)
+auto_join_exclude (default: "")
%
?discord host
host (type: string; default: "discordapp.com")
@@ -78,3 +82,22 @@
?friendship_mode
friendship_mode (type: boolean; default: on)
With this option enabled, online/offline status is determined by the friendship relationship with a user in addition to their actual away status, and other users are added to channels.
+%
+?discord auto_join
+auto_join (type: boolean; default: off)
+Automatically join all of the server's channels so you don't have to add them manually (no "chat add" needed).
+%
+?discord auto_join_exclude
+auto_join_exclude (type: string; default: "")
+Comma-separated list of channel patterns to exclude when auto-joining channels.
+* matches any text, ? matches a single character.
+For instance, "Foo.*,Bar.A" will exclude all channels from server "Foo" and channel "A" from server "Bar".
+%
+?always_afk
+always_afk (type: boolean; default: off)
+When enabled bitlbee-discord would always report client's status as afk. This feature is not properly documented in official docs, but it presumably can force push notifications to other clients when bitlbee is connected.
+%
+?emoji_urls
+emoji_urls (type: boolean; default: on)
+Controls whether bitlbee-discord would display an url to emoji image next to it's text alias.
+%
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/Makefile.am new/bitlbee-discord-0.4.2/src/Makefile.am
--- old/bitlbee-discord-0.4.1/src/Makefile.am 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/Makefile.am 2018-12-04 18:45:11.000000000 +0100
@@ -19,7 +19,8 @@
discord_la_CFLAGS = \
$(BITLBEE_CFLAGS) \
$(GLIB_CFLAGS) \
- -Wall
+ -Wall \
+ -std=c99
discord_la_LDFLAGS = \
-module \
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-handlers.c new/bitlbee-discord-0.4.2/src/discord-handlers.c
--- old/bitlbee-discord-0.4.1/src/discord-handlers.c 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-handlers.c 2018-12-04 18:45:11.000000000 +0100
@@ -145,6 +145,7 @@
if (bu == NULL) {
imcb_add_buddy(ic, name, NULL);
+ imcb_rename_buddy(ic, name, json_o_str(uinfo, "username"));
if (set_getbool(&ic->acc->set, "never_offline") == TRUE) {
flags = BEE_USER_ONLINE | BEE_USER_AWAY;
if (set_getbool(&ic->acc->set, "friendship_mode") == FALSE) {
@@ -190,12 +191,16 @@
{
discord_data *dd = ic->proto_data;
relationship_type rtype = 0;
- json_value *uinfo = json_o_get(rinfo, "user");
+ char *name = NULL;
+ json_value *uinfo = NULL;
+ bee_user_t *bu = NULL;
+ user_info *uinf = NULL;
json_value *tjs = json_o_get(rinfo, "type");
- char *name = discord_canonize_name(json_o_str(uinfo, "username"));
- bee_user_t *bu = bee_user_by_handle(ic->bee, ic, name);
if (action == ACTION_CREATE) {
+ uinfo = json_o_get(rinfo, "user");
+ name = discord_canonize_name(json_o_str(uinfo, "username"));
+ bu = bee_user_by_handle(ic->bee, ic, name);
rtype = (tjs && tjs->type == json_integer) ? tjs->u.integer : 0;
if (rtype == RELATIONSHIP_FRIENDS) {
@@ -206,8 +211,8 @@
if (bu) {
bu->data = GINT_TO_POINTER(TRUE);
if (set_getbool(&ic->acc->set, "friendship_mode") == TRUE) {
- user_info *uinfo = get_user(dd, name, NULL, SEARCH_NAME);
- imcb_buddy_status(ic, name, uinfo->flags, NULL, NULL);
+ uinf = get_user(dd, name, NULL, SEARCH_NAME);
+ imcb_buddy_status(ic, name, uinf->flags, NULL, NULL);
}
}
} else if (rtype == RELATIONSHIP_REQUEST_RECEIVED) {
@@ -215,17 +220,46 @@
}
} else if (action == ACTION_DELETE) {
- if (bu) {
+ uinf = get_user(dd, json_o_str(rinfo, "id"), NULL, SEARCH_ID);
+
+ if (uinf && uinf->user) {
+ bu = uinf->user;
+ name = g_strdup(uinf->name);
bu->data = GINT_TO_POINTER(FALSE);
if (set_getbool(&ic->acc->set, "friendship_mode") == TRUE) {
imcb_buddy_status(ic, name, 0, NULL, NULL);
}
}
}
-
g_free(name);
}
+static void discord_channel_auto_join(struct im_connection *ic,
+ const char *room)
+{
+ if (!set_getbool(&ic->acc->set, "auto_join")) {
+ return;
+ }
+
+ char *exclude_str = set_getstr(&ic->acc->set, "auto_join_exclude");
+ gchar **exclude_list = g_strsplit(exclude_str, ",", 0);
+ gboolean excluded = FALSE;
+
+ for (int i = 0; !excluded && exclude_list[i] != NULL; i++) {
+ char *pattern = g_strstrip(g_strdup(exclude_list[i]));
+ if (strlen(pattern) > 0 && g_pattern_match_simple(pattern, room)) {
+ excluded = TRUE;
+ }
+ g_free(pattern);
+ }
+
+ g_strfreev(exclude_list);
+
+ if (!excluded) {
+ discord_chat_do_join(ic, room, TRUE);
+ }
+}
+
void discord_handle_channel(struct im_connection *ic, json_value *cinfo,
const char *server_id, handler_action action)
{
@@ -275,7 +309,7 @@
discord_http_get_backlog(ic, ci->id);
}
} else {
- g_print("Failed to get recepient for private channel.\n");
+ imcb_error(ic, "Failed to get recepient for private channel.");
free_channel_info(ci);
}
break;
@@ -326,6 +360,9 @@
}
sinfo->channels = g_slist_prepend(sinfo->channels, ci);
+
+ discord_channel_auto_join(ic, bci->title);
+
break;
}
case CHANNEL_GROUP_PRIVATE:
@@ -373,10 +410,12 @@
dd->pchannels = g_slist_prepend(dd->pchannels, ci);
} else {
- g_print("Failed to get recepients for private channel.\n");
+ imcb_error(ic, "Failed to get recepients for private channel.");
free_channel_info(ci);
}
+ discord_channel_auto_join(ic, bci->title);
+
break;
}
case CHANNEL_VOICE:
@@ -506,7 +545,7 @@
}
static gboolean discord_post_message(channel_info *cinfo, const gchar *author,
- gchar *msg, gboolean is_self)
+ gchar *msg, gboolean is_self, time_t tstamp)
{
int flags = 0;
@@ -519,13 +558,13 @@
}
if (cinfo->type == CHANNEL_PRIVATE) {
- imcb_buddy_msg(cinfo->to.handle.ic, author, msg, flags, 0);
+ imcb_buddy_msg(cinfo->to.handle.ic, author, msg, flags, tstamp);
return TRUE;
} else if (cinfo->type == CHANNEL_GROUP_PRIVATE && cinfo->to.group.gc != NULL) {
- imcb_chat_msg(cinfo->to.group.gc, author, msg, flags, 0);
+ imcb_chat_msg(cinfo->to.group.gc, author, msg, flags, tstamp);
return TRUE;
} else if (cinfo->type == CHANNEL_TEXT && cinfo->to.channel.gc != NULL) {
- imcb_chat_msg(cinfo->to.channel.gc, author, msg, flags, 0);
+ imcb_chat_msg(cinfo->to.channel.gc, author, msg, flags, tstamp);
return TRUE;
}
return FALSE;
@@ -559,7 +598,7 @@
static gboolean discord_prepare_message(struct im_connection *ic,
json_value *minfo,
- channel_info *cinfo, gboolean is_edit)
+ channel_info *cinfo, gboolean is_edit, gboolean use_tstamp)
{
discord_data *dd = ic->proto_data;
gboolean posted = FALSE;
@@ -573,8 +612,10 @@
const char *nonce = json_o_str(minfo, "nonce");
gboolean is_self = discord_is_self(ic, author);
+ time_t tstamp = use_tstamp ? parse_iso_8601(json_o_str(minfo, "timestamp")) : 0;
+
// Don't echo self messages that we sent in this session
- if (is_self && nonce != NULL && g_strcmp0(nonce, dd->nonce) == 0) {
+ if (is_self && nonce != NULL && g_hash_table_remove(dd->sent_message_ids, nonce)) {
g_free(author);
g_free(msg);
return FALSE;
@@ -619,51 +660,67 @@
g_string_free(tstr, FALSE);
}
- if (cinfo->type == CHANNEL_PRIVATE) {
- posted = discord_post_message(cinfo, cinfo->to.handle.name, msg, is_self);
- } else if (cinfo->type == CHANNEL_TEXT || cinfo->type == CHANNEL_GROUP_PRIVATE) {
- json_value *mentions = json_o_get(minfo, "mentions");
- if (mentions != NULL && mentions->type == json_array) {
- for (int midx = 0; midx < mentions->u.array.length; midx++) {
- json_value *uinfo = mentions->u.array.values[midx];
- gchar *uname = discord_canonize_name(json_o_str(uinfo, "username"));
- gchar *newmsg = NULL;
- gchar *idstr = g_strdup_printf("<@!?%s>", json_o_str(uinfo, "id"));
- gchar *unstr = g_strdup_printf("@%s", uname);
- GRegex *regex = g_regex_new(idstr, 0, 0, NULL);
- newmsg = g_regex_replace_literal(regex, msg, -1, 0,
- unstr, 0, NULL);
- g_free(msg);
- msg = newmsg;
- g_regex_unref(regex);
- g_free(idstr);
- g_free(unstr);
- g_free(uname);
- }
+ json_value *mentions = json_o_get(minfo, "mentions");
+ if (mentions != NULL && mentions->type == json_array) {
+ for (int midx = 0; midx < mentions->u.array.length; midx++) {
+ json_value *uinfo = mentions->u.array.values[midx];
+ gchar *uname = discord_canonize_name(json_o_str(uinfo, "username"));
+ gchar *newmsg = NULL;
+ gchar *idstr = g_strdup_printf("<@!?%s>", json_o_str(uinfo, "id"));
+ gchar *unstr = g_strdup_printf("@%s", uname);
+ GRegex *regex = g_regex_new(idstr, 0, 0, NULL);
+ newmsg = g_regex_replace_literal(regex, msg, -1, 0,
+ unstr, 0, NULL);
+ g_free(msg);
+ msg = newmsg;
+ g_regex_unref(regex);
+ g_free(idstr);
+ g_free(unstr);
+ g_free(uname);
}
+ }
- // Replace custom emoji with code and a URL
- GRegex *emoji_regex = g_regex_new("<(:[^:]+:)(\\d+)>", 0, 0, NULL);
- gchar *emoji_msg = g_regex_replace(emoji_regex, msg, -1, 0, "\\1 https://cdn.discordapp.com/emojis/\\2.png", 0, NULL);
- g_free(msg);
- msg = emoji_msg;
- g_regex_unref(emoji_regex);
-
- GRegex *cregex = g_regex_new("<#(\\d+)>", 0, 0, NULL);
- gchar *fmsg = g_regex_replace_eval(cregex, msg, -1, 0, 0,
- discord_replace_channel,
- ic->proto_data, NULL);
- g_regex_unref(cregex);
+ // Replace animated emoji with code and a URL
+ GRegex *emoji_regex = g_regex_new("", 0, 0, NULL);
+ gchar *emoji_msg;
+ if (set_getbool(&ic->acc->set, "emoji_urls")) {
+ emoji_msg = g_regex_replace(emoji_regex, msg, -1, 0, "\\1 https://cdn.discordapp.com/emojis/\\2.gif", 0, NULL);
+ } else {
+ emoji_msg = g_regex_replace(emoji_regex, msg, -1, 0, "\\1", 0, NULL);
+ }
+ g_free(msg);
+ msg = emoji_msg;
+ g_regex_unref(emoji_regex);
- posted = discord_post_message(cinfo, author, fmsg, is_self);
- g_free(fmsg);
+ // Replace custom emoji with code and a URL
+ emoji_regex = g_regex_new("<(:[^:]+:)(\\d+)>", 0, 0, NULL);
+ if (set_getbool(&ic->acc->set, "emoji_urls")) {
+ emoji_msg = g_regex_replace(emoji_regex, msg, -1, 0, "\\1 https://cdn.discordapp.com/emojis/\\2.png", 0, NULL);
+ } else {
+ emoji_msg = g_regex_replace(emoji_regex, msg, -1, 0, "\\1", 0, NULL);
}
+ g_free(msg);
+ msg = emoji_msg;
+ g_regex_unref(emoji_regex);
+
+ GRegex *cregex = g_regex_new("<#(\\d+)>", 0, 0, NULL);
+ gchar *fmsg = g_regex_replace_eval(cregex, msg, -1, 0, 0,
+ discord_replace_channel,
+ ic->proto_data, NULL);
+ g_regex_unref(cregex);
+
+ if (cinfo->type == CHANNEL_PRIVATE) {
+ posted = discord_post_message(cinfo, cinfo->to.handle.name, fmsg, is_self, tstamp);
+ } else if (cinfo->type == CHANNEL_TEXT || cinfo->type == CHANNEL_GROUP_PRIVATE) {
+ posted = discord_post_message(cinfo, author, fmsg, is_self, tstamp);
+ }
+ g_free(fmsg);
json_value *attachments = json_o_get(minfo, "attachments");
if (attachments != NULL && attachments->type == json_array) {
for (int aidx = 0; aidx < attachments->u.array.length; aidx++) {
const char *url = json_o_str(attachments->u.array.values[aidx], "url");
- posted = discord_post_message(cinfo, author, (char *)url, is_self);
+ posted = discord_post_message(cinfo, author, (char *)url, is_self, tstamp);
}
}
g_free(author);
@@ -672,7 +729,7 @@
}
void discord_handle_message(struct im_connection *ic, json_value *minfo,
- handler_action action)
+ handler_action action, gboolean use_tstamp)
{
discord_data *dd = ic->proto_data;
@@ -685,6 +742,8 @@
if (cinfo == NULL) {
return;
}
+
+ time_t tstamp = use_tstamp ? parse_iso_8601(json_o_str(minfo, "timestamp")) : 0;
if (action == ACTION_CREATE) {
guint64 msgid = g_ascii_strtoull(json_o_str(minfo, "id"), NULL, 10);
@@ -695,7 +754,7 @@
if ((msgid > cinfo->last_read) || (pinned &&
!g_slist_find_custom(cinfo->pinned, json_o_str(minfo, "id"),
(GCompareFunc)g_strcmp0))) {
- gboolean posted = discord_prepare_message(ic, minfo, cinfo, FALSE);
+ gboolean posted = discord_prepare_message(ic, minfo, cinfo, FALSE, use_tstamp);
if (posted) {
if (msgid > cinfo->last_read) {
cinfo->last_read = msgid;
@@ -710,7 +769,7 @@
}
} else if (action == ACTION_UPDATE) {
if (json_o_str(json_o_get(minfo, "author"), "username") != NULL) {
- discord_prepare_message(ic, minfo, cinfo, TRUE);
+ discord_prepare_message(ic, minfo, cinfo, TRUE, use_tstamp);
} else {
json_value *embeds = json_o_get(minfo, "embeds");
if (embeds != NULL && embeds->type == json_array) {
@@ -727,7 +786,7 @@
const char *title = json_o_str(embeds->u.array.values[eidx], "title");
if (title != NULL) {
msg = g_strconcat("title: ", title, NULL);
- discord_post_message(cinfo, author, msg, FALSE);
+ discord_post_message(cinfo, author, msg, FALSE, tstamp);
g_free(msg);
}
@@ -735,7 +794,7 @@
"description");
if (description != NULL) {
msg = g_strconcat("description: ", description, NULL);
- discord_post_message(cinfo, author, msg, FALSE);
+ discord_post_message(cinfo, author, msg, FALSE, tstamp);
g_free(msg);
}
}
@@ -753,7 +812,7 @@
discord_debug("<<< (%s) %s %"G_GUINT64_FORMAT"\n%s\n", dd->uname, __func__, size, buf);
if (!js || js->type != json_object) {
- imcb_error(ic, "Failed to parse json reply.");
+ imcb_error(ic, "Failed to parse json reply (%s)", __func__);
imc_logout(ic, TRUE);
disconnected = TRUE;
goto exit;
@@ -785,11 +844,13 @@
} else if (op == OPCODE_HEARTBEAT) {
discord_ws_keepalive_loop(ic, 0, 0);
} else if (op == OPCODE_HEARTBEAT_ACK) {
- // heartbeat ack
+ if (dd->heartbeat_timeout_id > 0) {
+ b_event_remove(dd->heartbeat_timeout_id);
+ dd->heartbeat_timeout_id = 0;
+ }
} else if (op == OPCODE_RECONNECT) {
imcb_log(ic, "Reconnect requested");
- imc_logout(ic, TRUE);
- disconnected = TRUE;
+ discord_soft_reconnect(ic);
} else if (op == OPCODE_INVALID_SESSION) {
imcb_error(ic, "Invalid session, reconnecting");
imc_logout(ic, TRUE);
@@ -807,6 +868,7 @@
dd->id = json_o_strdup(user, "id");
dd->uname = discord_canonize_name(json_o_str(user, "username"));
}
+ dd->session_id = json_o_strdup(data, "session_id");
discord_add_global_server(ic);
json_value *guilds = json_o_get(data, "guilds");
@@ -925,16 +987,19 @@
discord_handle_server(ic, sinfo, ACTION_DELETE);
} else if (g_strcmp0(event, "MESSAGE_CREATE") == 0) {
json_value *minfo = json_o_get(js, "d");
- discord_handle_message(ic, minfo, ACTION_CREATE);
+ discord_handle_message(ic, minfo, ACTION_CREATE, FALSE);
} else if (g_strcmp0(event, "MESSAGE_UPDATE") == 0) {
json_value *minfo = json_o_get(js, "d");
- discord_handle_message(ic, minfo, ACTION_UPDATE);
+ discord_handle_message(ic, minfo, ACTION_UPDATE, TRUE);
} else if (g_strcmp0(event, "RELATIONSHIP_ADD") == 0) {
json_value *rinfo = json_o_get(js, "d");
discord_handle_relationship(ic, rinfo, ACTION_CREATE);
} else if (g_strcmp0(event, "RELATIONSHIP_REMOVE") == 0) {
json_value *rinfo = json_o_get(js, "d");
discord_handle_relationship(ic, rinfo, ACTION_DELETE);
+ } else if (g_strcmp0(event, "RESUMED") == 0) {
+ dd->reconnecting = FALSE;
+ dd->state = WS_READY;
} else if (g_strcmp0(event, "TYPING_START") == 0) {
// Ignoring those for now
} else if (g_strcmp0(event, "USER_UPDATE") == 0) {
@@ -970,8 +1035,8 @@
} else if (g_strcmp0(event, "CHANNEL_PINS_UPDATE") == 0) {
// Ignoring those for now
} else {
- g_print("%s: unhandled event: %s\n", __func__, event);
- g_print("%s\n", buf);
+ discord_debug("(%s) %s: unhandled event: %s\n%s\n", dd->uname, __func__,
+ event, buf);
}
exit:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-handlers.h new/bitlbee-discord-0.4.2/src/discord-handlers.h
--- old/bitlbee-discord-0.4.1/src/discord-handlers.h 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-handlers.h 2018-12-04 18:45:11.000000000 +0100
@@ -24,7 +24,7 @@
} handler_action;
void discord_handle_message(struct im_connection *ic, json_value *minfo,
- handler_action action);
+ handler_action action, gboolean use_tstamp);
void discord_handle_channel(struct im_connection *ic, json_value *cinfo,
const char *server_id, handler_action action);
/* Returns TRUE if it called iwc_logout() */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-http.c new/bitlbee-discord-0.4.2/src/discord-http.c
--- old/bitlbee-discord-0.4.1/src/discord-http.c 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-http.c 2018-12-04 18:45:11.000000000 +0100
@@ -139,7 +139,7 @@
if (req->status_code == 200) {
json_value *js = json_parse(req->reply_body, req->body_size);
if (!js || js->type != json_object) {
- imcb_error(ic, "Failed to parse json reply.");
+ imcb_error(ic, "Failed to parse json reply (%s)", __func__);
imc_logout(ic, TRUE);
json_value_free(js);
return;
@@ -217,7 +217,7 @@
json_value *js = json_parse(req->reply_body, req->body_size);
if (!js || js->type != json_object) {
- imcb_error(ic, "Failed to parse json reply.");
+ imcb_error(ic, "Failed to parse json reply (%s)", __func__);
imc_logout(ic, TRUE);
json_value_free(js);
return;
@@ -249,7 +249,7 @@
json_value *js = json_parse(req->reply_body, req->body_size);
if (!js || js->type != json_object) {
- imcb_error(ic, "Failed to parse json reply.");
+ imcb_error(ic, "Failed to parse json reply (%s)", __func__);
imc_logout(ic, TRUE);
json_value_free(js);
return;
@@ -276,11 +276,14 @@
json_value *em = NULL;
json_value *email = json_o_get(js, "email");
json_value *password = json_o_get(js, "password");
+ json_value *captcha_key = json_o_get(js, "captcha_key");
if (email != NULL && email->type == json_array) {
em = email->u.array.values[0];
} else if (password != NULL && password->type == json_array) {
em = password->u.array.values[0];
+ } else if (captcha_key != NULL && captcha_key->type == json_array) {
+ em = captcha_key->u.array.values[0];
}
if (em != NULL && em->type == json_string) {
@@ -314,7 +317,12 @@
if (req->status_code != 200) {
if (discord_http_check_retry(req) == 0) {
- imcb_error(ic, "Failed to send message (%d).", req->status_code);
+ char *json_text = strstr(req->request, "{\"content\":\"");
+ json_value *js = json_parse(json_text, strlen(json_text));
+ const char *message = json_o_str(js, "content");
+
+ imcb_error(ic, "Failed to send message (%d; %s).", req->status_code, message);
+ json_value_free(js);
}
}
}
@@ -336,7 +344,7 @@
} else {
json_value *messages = json_parse(req->reply_body, req->body_size);
if (!messages || messages->type != json_array) {
- imcb_error(ic, "Failed to parse json reply for backlog.");
+ imcb_error(ic, "Failed to parse json reply (%s)", __func__);
imc_logout(ic, TRUE);
json_value_free(messages);
return;
@@ -344,7 +352,7 @@
for (int midx = messages->u.array.length - 1; midx >= 0; midx--) {
json_value *minfo = messages->u.array.values[midx];
- discord_handle_message(ic, minfo, ACTION_CREATE);
+ discord_handle_message(ic, minfo, ACTION_CREATE, TRUE);
}
json_value_free(messages);
@@ -380,7 +388,7 @@
} else {
json_value *messages = json_parse(req->reply_body, req->body_size);
if (!messages || messages->type != json_array) {
- imcb_error(ic, "Failed to parse json reply for pinned messages.");
+ imcb_error(ic, "Failed to parse json reply (%s)", __func__);
imc_logout(ic, TRUE);
json_value_free(messages);
return;
@@ -388,7 +396,7 @@
for (int midx = messages->u.array.length - 1; midx >= 0; midx--) {
json_value *minfo = messages->u.array.values[midx];
- discord_handle_message(ic, minfo, ACTION_CREATE);
+ discord_handle_message(ic, minfo, ACTION_CREATE, TRUE);
}
json_value_free(messages);
@@ -519,8 +527,15 @@
emsg = nmsg;
}
+ gchar *nonce;
+ guchar nonce_bytes[16];
+
+ random_bytes(nonce_bytes, sizeof(nonce_bytes));
+ nonce = g_base64_encode(nonce_bytes, sizeof(nonce_bytes));
+ g_hash_table_insert(dd->sent_message_ids, nonce,
+ GUINT_TO_POINTER((guint)time(NULL)));
g_string_printf(content, "{\"content\":\"%s\", \"nonce\":\"%s\"}",
- emsg, dd->nonce);
+ emsg, nonce);
g_free(emsg);
g_string_printf(request, "POST /api/channels/%s/messages HTTP/1.1\r\n"
"Host: %s\r\n"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-util.c new/bitlbee-discord-0.4.2/src/discord-util.c
--- old/bitlbee-discord-0.4.1/src/discord-util.c 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-util.c 2018-12-04 18:45:11.000000000 +0100
@@ -113,15 +113,16 @@
void free_discord_data(discord_data *dd)
{
+ g_hash_table_destroy(dd->sent_message_ids);
g_slist_free_full(dd->pending_events, (GDestroyNotify)free_pending_ev);
g_slist_free_full(dd->pending_reqs, (GDestroyNotify)free_pending_req);
g_slist_free_full(dd->pchannels, (GDestroyNotify)free_channel_info);
g_slist_free_full(dd->servers, (GDestroyNotify)free_server_info);
free_gw_data(dd->gateway);
- g_free(dd->nonce);
g_free(dd->token);
g_free(dd->uname);
+ g_free(dd->session_id);
g_free(dd->id);
g_free(dd);
@@ -328,11 +329,7 @@
char *discord_canonize_name(const char *name)
{
- GRegex *regex = g_regex_new("[@+ ]", 0, 0, NULL);
- char *cname = g_regex_replace_literal(regex, name, -1, 0, "_", 0, NULL);
-
- g_regex_unref(regex);
- return cname;
+ return str_reject_chars(g_strdup(name), "@+ ", '_');
}
static gboolean discord_escape(const GMatchInfo *match, GString *result,
@@ -381,3 +378,20 @@
return g_strndup(str, g_utf8_offset_to_pointer(str, n) - str);
}
+
+time_t parse_iso_8601(const char *timestamp)
+{
+#if GLIB_CHECK_VERSION(2,56,0)
+ if (!timestamp) return 0;
+ GDateTime *dt = g_date_time_new_from_iso8601(timestamp, NULL);
+ if (!dt) return 0;
+ gint64 unix = g_date_time_to_unix(dt);
+ g_date_time_unref(dt);
+ return unix;
+#else
+ GTimeVal gt;
+ if (!timestamp) return 0;
+ if (!g_time_val_from_iso8601(timestamp, >)) return 0;
+ return gt.tv_sec;
+#endif
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-util.h new/bitlbee-discord-0.4.2/src/discord-util.h
--- old/bitlbee-discord-0.4.1/src/discord-util.h 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-util.h 2018-12-04 18:45:11.000000000 +0100
@@ -17,6 +17,7 @@
#include "discord.h"
#include
#include
+#include
typedef enum {
SEARCH_UNKNOWN,
@@ -43,3 +44,7 @@
char *discord_escape_string(const char *msg);
void discord_debug(char *format, ...);
char *discord_utf8_strndup(const char *str, size_t n);
+
+/* input: 2018-05-24T19:06:42.190000+00:00 */
+/* output: 1527188802 (the .19 and timezone are discarded) */
+time_t parse_iso_8601(const char *timestamp);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-websockets.c new/bitlbee-discord-0.4.2/src/discord-websockets.c
--- old/bitlbee-discord-0.4.1/src/discord-websockets.c 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-websockets.c 2018-12-04 18:45:11.000000000 +0100
@@ -23,6 +23,14 @@
#include "discord-util.h"
#include "discord.h"
+#define DISCORD_STATUS_TIMEOUT 500
+
+typedef struct {
+ struct im_connection *ic;
+ gchar *status;
+ gchar *msg;
+} status_data;
+
static gchar *discord_ws_mask(guchar key[4], const char *pload,
guint64 psize)
{
@@ -90,53 +98,81 @@
g_string_free(buf, TRUE);
}
+static gboolean discord_ws_heartbeat_timeout(gpointer data, gint fd,
+ b_input_condition cond)
+{
+ struct im_connection *ic = data;
+ imcb_log(ic, "Heartbeat timed out, reconnecting...");
+ discord_soft_reconnect(ic);
+ return FALSE;
+}
+
static gboolean discord_ws_writable(gpointer data, int source,
b_input_condition cond)
{
- discord_data *dd = (discord_data*)data;
+ struct im_connection *ic = data;
+ discord_data *dd = ic->proto_data;
if (dd->state == WS_CONNECTED) {
GString *buf = g_string_new("");
- g_string_printf(buf, "{\"d\":{\"token\":\"%s\",\"properties\":{\"$referring_domain\":\"\",\"$browser\":\"bitlbee-discord\",\"$device\":\"bitlbee\",\"$referrer\":\"\",\"$os\":\"linux\"},\"compress\":false,\"large_threshold\":250,\"synced_guilds\":[]},\"op\":%d}", dd->token, OPCODE_IDENTIFY);
-
- discord_ws_send_payload(dd, buf->str, buf->len);
- g_string_free(buf, TRUE);
- } else if (dd->state == WS_READY) {
- GString *buf = g_string_new("");
-
- if (dd->seq == 0) {
- g_string_printf(buf, "{\"op\":%d,\"d\":null}", OPCODE_HEARTBEAT);
+ if (dd->reconnecting == TRUE) {
+ g_string_printf(buf, "{\"d\":{\"token\":\"%s\",\"session_id\":\"%s\",\"seq\":%"G_GUINT64_FORMAT"},\"op\":%d}", dd->token, dd->session_id, dd->seq, OPCODE_RESUME);
} else {
- g_string_printf(buf, "{\"op\":%d,\"d\":%"G_GUINT64_FORMAT"}", OPCODE_HEARTBEAT,
- dd->seq);
+ g_string_printf(buf, "{\"d\":{\"token\":\"%s\",\"properties\":{\"$referring_domain\":\"\",\"$browser\":\"bitlbee-discord\",\"$device\":\"bitlbee\",\"$referrer\":\"\",\"$os\":\"linux\"},\"compress\":false,\"large_threshold\":250,\"synced_guilds\":[]},\"op\":%d}", dd->token, OPCODE_IDENTIFY);
}
+
discord_ws_send_payload(dd, buf->str, buf->len);
g_string_free(buf, TRUE);
} else {
- g_print("%s: Unhandled writable callback\n", __func__);
+ imcb_error(ic, "Unhandled writable callback.");
}
dd->wsid = 0;
return FALSE;
}
-static void discord_ws_callback_on_writable(discord_data *dd)
+static void discord_ws_callback_on_writable(struct im_connection *ic)
{
- dd->wsid = b_input_add(dd->sslfd, B_EV_IO_WRITE, discord_ws_writable, dd);
+ discord_data *dd = ic->proto_data;
+ dd->wsid = b_input_add(dd->sslfd, B_EV_IO_WRITE, discord_ws_writable, ic);
}
-
gboolean discord_ws_keepalive_loop(gpointer data, gint fd,
b_input_condition cond)
{
struct im_connection *ic = data;
discord_data *dd = ic->proto_data;
- if (dd->state == WS_READY) {
- discord_ws_callback_on_writable(dd);
+ if (dd->state > WS_CONNECTED && dd->state < WS_CLOSING) {
+ GString *buf = g_string_new("");
+
+ if (dd->seq == 0) {
+ g_string_printf(buf, "{\"op\":%d,\"d\":null}", OPCODE_HEARTBEAT);
+ } else {
+ g_string_printf(buf, "{\"op\":%d,\"d\":%"G_GUINT64_FORMAT"}", OPCODE_HEARTBEAT,
+ dd->seq);
+ }
+ discord_ws_send_payload(dd, buf->str, buf->len);
+ dd->heartbeat_timeout_id = b_timeout_add((dd->keepalive_interval - 100),
+ discord_ws_heartbeat_timeout, ic);
+ g_string_free(buf, TRUE);
+ } else {
+ discord_debug("=== (%s) %s tried to send keepalive in a wrong state: %d\n",
+ dd->uname, __func__, dd->state);
}
return TRUE;
}
+static void discord_ws_reconnect(struct im_connection *ic)
+{
+ discord_data *dd = ic->proto_data;
+
+ if (dd->state == WS_READY) {
+ discord_soft_reconnect(ic);
+ } else {
+ imc_logout(ic, TRUE);
+ }
+}
+
static gboolean discord_ws_in_cb(gpointer data, int source,
b_input_condition cond)
{
@@ -145,12 +181,19 @@
if (dd->state == WS_CONNECTING) {
gchar buf[4096] = "";
- ssl_read(dd->ssl, buf, sizeof(buf));
+ if (ssl_read(dd->ssl, buf, sizeof(buf)) < 1) {
+ if (ssl_errno == SSL_AGAIN)
+ return TRUE;
+ imcb_error(ic, "Failed to do ssl_read while switching to websocket mode: %d", ssl_errno);
+ imc_logout(ic, TRUE);
+ return FALSE;
+ }
if (g_strrstr_len(buf, 25, "101 Switching") != NULL && \
g_str_has_suffix(buf, "\r\n\r\n")) {
dd->state = WS_CONNECTED;
- discord_ws_callback_on_writable(dd);
+ discord_ws_callback_on_writable(ic);
} else {
+ discord_debug("<<< (%s) %s switching failure. buf:\n%s\n", dd->uname, __func__, buf);
imcb_error(ic, "Failed to switch to websocket mode");
imc_logout(ic, TRUE);
return FALSE;
@@ -165,14 +208,16 @@
gboolean disconnected;
if (ssl_read(dd->ssl, &buf, 1) < 1) {
- imcb_error(ic, "Failed to read data.");
- imc_logout(ic, TRUE);
+ if (ssl_errno == SSL_AGAIN)
+ return TRUE;
+ imcb_error(ic, "Failed to read ws header.");
+ discord_ws_reconnect(ic);
return FALSE;
}
if ((buf & 0xf0) != 0x80) {
imcb_error(ic, "Unexpected websockets header [0x%x], exiting", buf);
- imc_logout(ic, TRUE);
+ discord_ws_reconnect(ic);
return FALSE;
}
@@ -181,14 +226,16 @@
if (dd->state == WS_CONNECTED) {
imcb_log(ic, "Token expired, cleaning up");
set_setstr(&ic->acc->set, "token_cache", NULL);
+ imc_logout(ic, TRUE);
+ } else {
+ discord_ws_reconnect(ic);
}
- imc_logout(ic, TRUE);
return FALSE;
}
if (ssl_read(dd->ssl, &buf, 1) < 1) {
- imcb_error(ic, "Failed to read data.");
- imc_logout(ic, TRUE);
+ imcb_error(ic, "Failed to read first length byte.");
+ discord_ws_reconnect(ic);
return FALSE;
}
len = buf & 0x7f;
@@ -197,16 +244,16 @@
if (len == 126) {
guint16 lbuf;
if (ssl_read(dd->ssl, (gchar*)&lbuf, 2) < 2) {
- imcb_error(ic, "Failed to read data.");
- imc_logout(ic, TRUE);
+ imcb_error(ic, "Failed to read the rest of length (small).");
+ discord_ws_reconnect(ic);
return FALSE;
}
len = GUINT16_FROM_BE(lbuf);
} else if (len == 127) {
guint64 lbuf;
if (ssl_read(dd->ssl, (gchar*)&lbuf, 8) < 8) {
- imcb_error(ic, "Failed to read data.");
- imc_logout(ic, TRUE);
+ imcb_error(ic, "Failed to read the rest of length (big).");
+ discord_ws_reconnect(ic);
return FALSE;
}
len = GUINT64_FROM_BE(lbuf);
@@ -214,8 +261,8 @@
if (mask) {
if (ssl_read(dd->ssl, (gchar*)mkey, 4) < 4) {
- imcb_error(ic, "Failed to read data.");
- imc_logout(ic, TRUE);
+ imcb_error(ic, "Failed to read ws data.");
+ discord_ws_reconnect(ic);
return FALSE;
}
}
@@ -230,8 +277,8 @@
}
if (read != len) {
- imcb_error(ic, "Failed to read data.");
- imc_logout(ic, TRUE);
+ imcb_error(ic, "Short-read on ws data.");
+ discord_ws_reconnect(ic);
g_free(rdata);
return FALSE;
}
@@ -311,22 +358,21 @@
return 0;
}
-void discord_ws_cleanup(discord_data *dd)
+static void discord_ws_remove_event(gint *event)
{
- if (dd->keepalive_loop_id > 0) {
- b_event_remove(dd->keepalive_loop_id);
- dd->keepalive_loop_id = 0;
- }
-
- if (dd->wsid > 0) {
- b_event_remove(dd->wsid);
- dd->wsid = 0;
+ if (*event > 0) {
+ b_event_remove(*event);
+ *event = 0;
}
+}
- if (dd->inpa > 0) {
- b_event_remove(dd->inpa);
- dd->inpa = 0;
- }
+void discord_ws_cleanup(discord_data *dd)
+{
+ discord_ws_remove_event(&dd->keepalive_loop_id);
+ discord_ws_remove_event(&dd->heartbeat_timeout_id);
+ discord_ws_remove_event(&dd->status_timeout_id);
+ discord_ws_remove_event(&dd->wsid);
+ discord_ws_remove_event(&dd->inpa);
if (dd->ssl != NULL) {
ssl_disconnect(dd->ssl);
@@ -334,23 +380,73 @@
}
}
-void discord_ws_set_status(discord_data *dd, gboolean idle, gchar *message)
+static gboolean discord_ws_status_postponed(status_data *sd, gint fd,
+ b_input_condition cond)
+{
+ discord_data *dd = sd->ic->proto_data;
+ if (dd->state != WS_READY) {
+ return TRUE;
+ }
+
+ discord_ws_set_status(sd->ic, sd->status, sd->msg);
+
+ g_free(sd->msg);
+ g_free(sd->status);
+ g_free(sd);
+ dd->status_timeout_id = 0;
+
+ return FALSE;
+}
+
+void discord_ws_set_status(struct im_connection *ic, gchar *status,
+ gchar *message)
{
+ discord_data *dd = ic->proto_data;
GString *buf = g_string_new("");
gchar *msg = NULL;
+ gchar *stat = NULL;
+
+ if (dd->state != WS_READY) {
+ if (dd->status_timeout_id == 0) {
+ status_data *sdata = g_new0(status_data, 1);
+ sdata->ic = ic;
+ sdata->status = g_strdup(status);
+ sdata->msg = g_strdup(message);
+ dd->status_timeout_id = b_timeout_add(DISCORD_STATUS_TIMEOUT,
+ (b_event_handler)discord_ws_status_postponed, sdata);
+ }
+ return;
+ }
if (message != NULL) {
msg = discord_escape_string(message);
}
+ if (status != NULL) {
+ stat = discord_escape_string(status);
+ }
- if (idle == TRUE) {
- g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":%tu,\"game\":{\"name\":\"%s\",\"type\":0},\"afk\":true,\"status\":\"idle\"}}", OPCODE_STATUS_UPDATE, time(NULL)*1000, msg);
- } else if (message != NULL) {
- g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":null,\"game\":{\"name\":\"%s\",\"type\":0},\"afk\":false,\"status\":\"online\"}}", OPCODE_STATUS_UPDATE, msg);
+ if (status != NULL) {
+ if (message != NULL) { // game and away
+ g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":%llu,\"game\":{\"name\":\"%s\",\"type\":0},\"afk\":true,\"status\":\"%s\"}}", OPCODE_STATUS_UPDATE, ((unsigned long long)time(NULL))*1000, msg, stat);
+ } else { // away
+ g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":%llu,\"game\":null,\"afk\":true,\"status\":\"%s\"}}", OPCODE_STATUS_UPDATE, ((unsigned long long)time(NULL))*1000, stat);
+ }
} else {
- g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":null,\"game\":null,\"afk\":false,\"status\":\"online\"}}", OPCODE_STATUS_UPDATE);
+ char *afk;
+ if (set_getbool(&ic->acc->set, "always_afk")) {
+ afk = "true";
+ } else {
+ afk = "false";
+ }
+ if (message != NULL) { // game
+ g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":null,\"game\":{\"name\":\"%s\",\"type\":0},\"afk\":%s,\"status\":\"online\"}}", OPCODE_STATUS_UPDATE, msg, afk);
+ } else { // default
+ g_string_printf(buf, "{\"op\":%d,\"d\":{\"since\":null,\"game\":null,\"afk\":%s,\"status\":\"online\"}}", OPCODE_STATUS_UPDATE, afk);
+ }
}
+
discord_ws_send_payload(dd, buf->str, buf->len);
g_string_free(buf, TRUE);
g_free(msg);
+ g_free(stat);
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord-websockets.h new/bitlbee-discord-0.4.2/src/discord-websockets.h
--- old/bitlbee-discord-0.4.1/src/discord-websockets.h 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord-websockets.h 2018-12-04 18:45:11.000000000 +0100
@@ -37,5 +37,6 @@
int discord_ws_init(struct im_connection *ic, discord_data *dd);
void discord_ws_cleanup(discord_data *dd);
-void discord_ws_set_status(discord_data *dd, gboolean idle, gchar *message);
+void discord_ws_set_status(struct im_connection *ic, gchar *status,
+ gchar *message);
void discord_ws_sync_server(discord_data *dd, const char *id);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord.c new/bitlbee-discord-0.4.2/src/discord.c
--- old/bitlbee-discord-0.4.1/src/discord.c 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord.c 2018-12-04 18:45:11.000000000 +0100
@@ -89,6 +89,14 @@
s = set_add(&acc->set, "mention_ignorecase", "off", set_eval_bool, acc);
s = set_add(&acc->set, "incoming_me_translation", "on", set_eval_bool, acc);
s = set_add(&acc->set, "fetch_pinned", "off", set_eval_bool, acc);
+ s = set_add(&acc->set, "always_afk", "off", set_eval_bool, acc);
+ s = set_add(&acc->set, "emoji_urls", "on", set_eval_bool, acc);
+
+ s = set_add(&acc->set, "auto_join", "off", set_eval_bool, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
+
+ s = set_add(&acc->set, "auto_join_exclude", "", NULL, acc);
+ s->flags |= ACC_SET_OFFLINE_ONLY;
s = set_add(&acc->set, "max_backlog", "50", set_eval_int, acc);
s->flags |= ACC_SET_OFFLINE_ONLY;
@@ -111,23 +119,26 @@
discord_help_init();
}
+static void discord_do_login(struct im_connection *ic)
+{
+ if (set_getstr(&ic->acc->set,"token_cache")) {
+ discord_http_get_gateway(ic, set_getstr(&ic->acc->set,"token_cache"));
+ } else {
+ discord_http_login(ic->acc);
+ }
+}
+
static void discord_login(account_t *acc)
{
struct im_connection *ic = imcb_new(acc);
discord_data *dd = g_new0(discord_data, 1);
+ dd->sent_message_ids = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, NULL);
dd->keepalive_interval = DEFAULT_KEEPALIVE_INTERVAL;
ic->proto_data = dd;
- guchar nonce_bytes[16];
- random_bytes(nonce_bytes, sizeof(nonce_bytes));
- dd->nonce = g_base64_encode(nonce_bytes, sizeof(nonce_bytes));
-
- if (set_getstr(&ic->acc->set,"token_cache")) {
- discord_http_get_gateway(ic, set_getstr(&ic->acc->set,"token_cache"));
- } else {
- discord_http_login(acc);
- }
+ discord_do_login(ic);
}
static void discord_logout(struct im_connection *ic)
@@ -140,6 +151,16 @@
g_slist_free(ic->chatlist);
}
+void discord_soft_reconnect(struct im_connection *ic)
+{
+ discord_data *dd = ic->proto_data;
+
+ imcb_log(ic, "Performing soft-reconnect");
+ discord_ws_cleanup(dd);
+ dd->reconnecting = TRUE;
+ discord_do_login(ic);
+}
+
static void discord_chat_msg(struct groupchat *gc, char *msg, int flags)
{
channel_info *cinfo = gc->data;
@@ -157,6 +178,14 @@
const char *nick,
const char *password,
set_t **sets)
+
+{
+ return discord_chat_do_join(ic, room, FALSE);
+}
+
+struct groupchat *discord_chat_do_join(struct im_connection *ic,
+ const char *room,
+ gboolean is_auto_join)
{
discord_data *dd = ic->proto_data;
struct groupchat *gc = NULL;
@@ -166,6 +195,11 @@
if (cinfo != NULL && cinfo->type == CHANNEL_TEXT) {
sinfo = cinfo->to.channel.sinfo;
gc = imcb_chat_new(ic, cinfo->to.channel.name);
+
+ if (is_auto_join) {
+ imcb_chat_name_hint(gc, room);
+ }
+
if (cinfo->to.channel.bci->topic != NULL) {
imcb_chat_topic(gc, "root", cinfo->to.channel.bci->topic, 0);
}
@@ -182,6 +216,10 @@
} else if (cinfo != NULL && cinfo->type == CHANNEL_GROUP_PRIVATE) {
gc = imcb_chat_new(ic, cinfo->to.group.name);
+ if (is_auto_join) {
+ imcb_chat_name_hint(gc, room);
+ }
+
for (GSList *ul = cinfo->to.group.users; ul; ul = g_slist_next(ul)) {
user_info *uinfo = ul->data;
imcb_chat_add_buddy(gc, uinfo->user->handle);
@@ -206,6 +244,13 @@
return gc;
}
+static void discord_chat_leave(struct groupchat *gc)
+{
+ channel_info *cinfo = gc->data;
+ imcb_chat_free(cinfo->to.channel.gc);
+ cinfo->to.channel.gc = NULL;
+}
+
static int discord_buddy_msg(struct im_connection *ic, char *to, char *msg,
int flags)
{
@@ -241,7 +286,10 @@
{
static GList *m = NULL;
- m = g_list_append(m, "Idle");
+ m = g_list_prepend(m, "invisible");
+ m = g_list_prepend(m, "dnd");
+ m = g_list_prepend(m, "online");
+ m = g_list_prepend(m, "idle");
return m;
}
@@ -249,9 +297,7 @@
static void discord_set_away(struct im_connection *ic, char *state,
char *message)
{
- discord_data *dd = ic->proto_data;
-
- discord_ws_set_status(dd, state != NULL, message);
+ discord_ws_set_status(ic, state, message);
}
G_MODULE_EXPORT void init_plugin(void)
@@ -266,6 +312,7 @@
.chat_msg = discord_chat_msg,
.chat_list = discord_chat_list,
.chat_join = discord_chat_join,
+ .chat_leave = discord_chat_leave,
.buddy_msg = discord_buddy_msg,
.handle_cmp = g_strcmp0,
.handle_is_self = discord_is_self,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/bitlbee-discord-0.4.1/src/discord.h new/bitlbee-discord-0.4.2/src/discord.h
--- old/bitlbee-discord-0.4.1/src/discord.h 2018-01-06 22:26:56.000000000 +0100
+++ new/bitlbee-discord-0.4.2/src/discord.h 2018-12-04 18:45:11.000000000 +0100
@@ -54,26 +54,30 @@
} gw_data;
typedef struct _discord_data {
- char *token;
- char *id;
- char *uname;
- char *nonce;
- gw_data *gateway;
- GSList *servers;
- GSList *pchannels;
- gint main_loop_id;
- GString *ws_buf;
- ws_state state;
- gint keepalive_interval;
- gint keepalive_loop_id;
- void *ssl;
- int sslfd;
- int inpa;
- guint wsid;
- guint64 seq;
- guint pending_sync;
- GSList *pending_reqs;
- GSList *pending_events;
+ char *token;
+ char *id;
+ char *session_id;
+ char *uname;
+ gw_data *gateway;
+ GSList *servers;
+ GSList *pchannels;
+ gint main_loop_id;
+ GString *ws_buf;
+ ws_state state;
+ gint keepalive_interval;
+ gint keepalive_loop_id;
+ gint heartbeat_timeout_id;
+ gint status_timeout_id;
+ void *ssl;
+ int sslfd;
+ gint inpa;
+ gint wsid;
+ guint64 seq;
+ guint pending_sync;
+ GSList *pending_reqs;
+ GSList *pending_events;
+ gboolean reconnecting;
+ GHashTable *sent_message_ids;
} discord_data;
typedef struct _server_info {
@@ -121,4 +125,9 @@
gboolean discord_is_self(struct im_connection *ic, const char *who);
+struct groupchat *discord_chat_do_join(struct im_connection *ic,
+ const char *name,
+ gboolean is_auto_join);
+void discord_soft_reconnect(struct im_connection *ic);
+
#endif //__DISCORD_H