weechat-xmpp/channel.cpp
2022-03-28 23:18:17 +01:00

1558 lines
54 KiB
C++

// This Source Code Form is subject to the terms of the Mozilla Public
// License, version 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <regex>
#include <fmt/core.h>
#include <strophe.h>
#include <weechat/weechat-plugin.h>
#include "plugin.hh"
#include "account.hh"
#include "omemo.hh"
#include "user.hh"
#include "channel.hh"
#include "input.hh"
#include "buffer.hh"
#include "pgp.hh"
#include "util.hh"
#include "xmpp/node.hh"
const char *channel__transport_name(enum t_channel_transport transport)
{
switch (transport)
{
case CHANNEL_TRANSPORT_PLAIN:
return "PLAINTEXT";
case CHANNEL_TRANSPORT_OMEMO:
return "OMEMO";
case CHANNEL_TRANSPORT_PGP:
return "PGP";
case CHANNEL_TRANSPORT_OTR:
return "OTR";
default:
return NULL;
}
}
void channel__set_transport(struct t_channel *channel, enum t_channel_transport transport, int force)
{
if (force)
switch (transport)
{
case CHANNEL_TRANSPORT_PLAIN:
channel->omemo.enabled = 0;
channel->pgp.enabled = 0;
break;
case CHANNEL_TRANSPORT_OMEMO:
channel->omemo.enabled = 1;
channel->pgp.enabled = 0;
break;
case CHANNEL_TRANSPORT_PGP:
channel->omemo.enabled = 0;
channel->pgp.enabled = 1;
break;
default:
break;
}
if (channel->transport != transport)
{
channel->transport = transport;
weechat_printf_date_tags(channel->buffer, 0, NULL, "%s%sTransport: %s",
weechat_prefix("network"), weechat_color("gray"),
channel__transport_name(channel->transport));
}
}
struct t_account *channel__account(struct t_channel *channel)
{
struct t_account *ptr_account;
struct t_channel *ptr_channel;
if (!channel)
return NULL;
for (ptr_account = accounts; ptr_account;
ptr_account = ptr_account->next_account)
{
for (ptr_channel = ptr_account->channels; ptr_channel;
ptr_channel = ptr_channel->next_channel)
{
if (ptr_channel == channel)
return ptr_account;
}
}
/* account not found */
return NULL;
}
struct t_channel *channel__search(struct t_account *account,
const char *id)
{
struct t_channel *ptr_channel;
if (!account || !id)
return NULL;
for (ptr_channel = account->channels; ptr_channel;
ptr_channel = ptr_channel->next_channel)
{
if (weechat_strcasecmp(ptr_channel->id, id) == 0)
return ptr_channel;
}
return NULL;
}
struct t_gui_buffer *channel__search_buffer(struct t_account *account,
enum t_channel_type type,
const char *name)
{
struct t_hdata *hdata_buffer;
struct t_gui_buffer *ptr_buffer;
const char *ptr_type, *ptr_account_name, *ptr_remote_jid;
hdata_buffer = weechat_hdata_get("buffer");
ptr_buffer = (struct t_gui_buffer*)weechat_hdata_get_list(hdata_buffer, "gui_buffers");
while (ptr_buffer)
{
if (weechat_buffer_get_pointer(ptr_buffer, "plugin") == weechat_plugin)
{
ptr_type = weechat_buffer_get_string(ptr_buffer, "localvar_type");
ptr_account_name = weechat_buffer_get_string(ptr_buffer,
"localvar_account");
ptr_remote_jid = weechat_buffer_get_string(ptr_buffer,
"localvar_remote_jid");
if (ptr_type && ptr_type[0]
&& ptr_account_name && ptr_account_name[0]
&& ptr_remote_jid && ptr_remote_jid[0]
&& ( (( (type == CHANNEL_TYPE_MUC))
&& (strcmp(ptr_type, "room") == 0))
|| (( (type == CHANNEL_TYPE_PM))
&& (strcmp(ptr_type, "private") == 0)))
&& (strcmp(ptr_account_name, account->name) == 0)
&& (weechat_strcasecmp(ptr_remote_jid, name) == 0))
{
return ptr_buffer;
}
}
ptr_buffer = (struct t_gui_buffer*)weechat_hdata_move(hdata_buffer, ptr_buffer, 1);
}
return NULL;
}
struct t_gui_buffer *channel__create_buffer(struct t_account *account,
enum t_channel_type type,
const char *name)
{
struct t_gui_buffer *ptr_buffer;
int buffer_created;
const char *short_name = NULL, *localvar_remote_jid = NULL;
char buffer_name[1024] = {0};
buffer_created = 0;
snprintf(buffer_name, sizeof(buffer_name),
"%s.%s", account->name, name);
ptr_buffer = channel__search_buffer(account, type, name);
if (ptr_buffer)
{
weechat_nicklist_remove_all(ptr_buffer);
}
else
{
ptr_buffer = weechat_buffer_new(buffer_name,
&input__data_cb, NULL, NULL,
&buffer__close_cb, NULL, NULL);
if (!ptr_buffer)
return NULL;
buffer_created = 1;
}
if (buffer_created)
{
char *res = (char*)strrchr(name, '/');
if (!weechat_buffer_get_integer(ptr_buffer, "short_name_is_set"))
weechat_buffer_set(ptr_buffer, "short_name",
res ? res + 1 : name);
}
else
{
short_name = weechat_buffer_get_string(ptr_buffer, "short_name");
localvar_remote_jid = weechat_buffer_get_string(ptr_buffer,
"localvar_remote_jid");
if (!short_name ||
(localvar_remote_jid && (strcmp(localvar_remote_jid, short_name) == 0)))
{
weechat_buffer_set(ptr_buffer, "short_name",
xmpp_jid_node(account->context, name));
}
}
if(!(account_nickname(account) && strlen(account_nickname(account))))
account_option_set(account, ACCOUNT_OPTION_NICKNAME,
xmpp_jid_node(account->context, account_jid(account)));
// Set notify level for buffer: "0" = never add to hotlist
// "1" = add for highlights only
// "2" = add for highlights and messages
// "3" = add for all messages.
weechat_buffer_set(ptr_buffer, "notify",
(type == CHANNEL_TYPE_PM) ? "3" : "2");
weechat_buffer_set(ptr_buffer, "localvar_set_type",
(type == CHANNEL_TYPE_PM) ? "private" : "channel");
weechat_buffer_set(ptr_buffer, "localvar_set_nick",
account_nickname(account));
weechat_buffer_set(ptr_buffer, "localvar_set_account", account->name);
weechat_buffer_set(ptr_buffer, "localvar_set_remote_jid", name);
weechat_buffer_set(ptr_buffer, "input_multiline", "1");
if (buffer_created)
{
(void) weechat_hook_signal_send("logger_backlog",
WEECHAT_HOOK_SIGNAL_POINTER,
ptr_buffer);
weechat_buffer_set(ptr_buffer, "input_get_unknown_commands", "1");
if (type != CHANNEL_TYPE_PM)
{
weechat_buffer_set(ptr_buffer, "nicklist", "1");
weechat_buffer_set(ptr_buffer, "nicklist_display_groups", "0");
weechat_buffer_set_pointer(ptr_buffer, "nicklist_callback",
(void*)&buffer__nickcmp_cb);
weechat_buffer_set_pointer(ptr_buffer, "nicklist_callback_pointer",
account);
}
weechat_buffer_set(ptr_buffer, "highlight_words_add",
account_nickname(account));
weechat_buffer_set(ptr_buffer, "highlight_tags_restrict",
"message");
}
return ptr_buffer;
}
void channel__add_nicklist_groups(struct t_account *account,
struct t_channel *channel)
{
struct t_gui_buffer *ptr_buffer;
char str_group[32];
if (channel && channel->type == CHANNEL_TYPE_PM)
return;
ptr_buffer = channel ? channel->buffer : account->buffer;
snprintf(str_group, sizeof(str_group), "%03d|%s", 000, "~");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 001, "&");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 002, "@");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 003, "%");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 004, "+");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 005, "?");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 006, "!");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
snprintf(str_group, sizeof(str_group), "%03d|%s", 999, "...");
weechat_nicklist_add_group(ptr_buffer, NULL, str_group,
"weechat.color.nicklist_group", 1);
}
struct t_channel *channel__new(struct t_account *account,
enum t_channel_type type,
const char *id, const char *name)
{
struct t_channel *new_channel, *ptr_channel, *muc_channel;
struct t_gui_buffer *ptr_buffer;
struct t_hook *typing_timer, *self_typing_timer;
if (!account || !id || !name || !name[0])
return NULL;
ptr_channel = channel__search(account, id);
if (ptr_channel)
{
return ptr_channel;
}
ptr_buffer = channel__create_buffer(account, type, name);
if (!ptr_buffer)
return NULL;
else if (type == CHANNEL_TYPE_PM)
{
muc_channel = channel__search(account, jid(account->context, id).bare.data());
if (muc_channel)
{
weechat_buffer_merge(ptr_buffer, muc_channel->buffer);
}
}
if ((new_channel = (struct t_channel*)malloc(sizeof(*new_channel))) == NULL)
return NULL;
typing_timer = weechat_hook_timer(1 * 1000, 0, 0,
&channel__typing_cb,
new_channel, NULL);
self_typing_timer = weechat_hook_timer(1 * 1000, 0, 0,
&channel__self_typing_cb,
new_channel, NULL);
new_channel->type = type;
new_channel->id = strdup(id);
new_channel->name = strdup(name);
new_channel->transport = CHANNEL_TRANSPORT_PLAIN;
new_channel->omemo.enabled = type == CHANNEL_TYPE_PM ? 1 : 0;
new_channel->omemo.devicelist_requests = weechat_hashtable_new(64,
WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_POINTER, NULL, NULL);
new_channel->omemo.bundle_requests = weechat_hashtable_new(64,
WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_POINTER, NULL, NULL);
new_channel->pgp.enabled = 1;
new_channel->pgp.ids = new std::remove_pointer<decltype(new_channel->pgp.ids)>::type;
new_channel->otr.enabled = 0;
new_channel->topic.value = NULL;
new_channel->topic.creator = NULL;
new_channel->topic.last_set = 0;
new_channel->creator = NULL;
new_channel->last_read = 0.0;
new_channel->unread_count = 0;
new_channel->unread_count_display = 0;
new_channel->typing_hook_timer = typing_timer;
new_channel->self_typing_hook_timer = self_typing_timer;
new_channel->members_speaking[0] = NULL;
new_channel->members_speaking[1] = NULL;
new_channel->unreads = NULL;
new_channel->self_typings = NULL;
new_channel->last_self_typing = NULL;
new_channel->typings = NULL;
new_channel->last_typing = NULL;
new_channel->members = NULL;
new_channel->last_member = NULL;
new_channel->buffer = ptr_buffer;
new_channel->buffer_as_string = NULL;
new_channel->prev_channel = account->last_channel;
new_channel->next_channel = NULL;
if (account->last_channel)
(account->last_channel)->next_channel = new_channel;
else
account->channels = new_channel;
account->last_channel = new_channel;
channel__add_nicklist_groups(account, new_channel);
if (type != CHANNEL_TYPE_MUC)
{
time_t start = time(NULL);
struct tm *ago = gmtime(&start);
ago->tm_mday -= 7;
start = mktime(ago);
channel__fetch_mam(account, new_channel, NULL, &start, NULL, NULL);
}
return new_channel;
}
void channel__member_speaking_add_to_list(struct t_channel *channel,
const char *nick,
int highlight)
{
int size, to_remove, i;
struct t_weelist_item *ptr_item;
/* create list if it does not exist */
if (!channel->members_speaking[highlight])
channel->members_speaking[highlight] = weechat_list_new();
/* remove item if it was already in list */
ptr_item = weechat_list_casesearch(channel->members_speaking[highlight], nick);
if (ptr_item)
weechat_list_remove(channel->members_speaking[highlight], ptr_item);
/* add nick in list */
weechat_list_add(channel->members_speaking[highlight], nick,
WEECHAT_LIST_POS_END, NULL);
/* reduce list size if it's too big */
size = weechat_list_size(channel->members_speaking[highlight]);
if (size > CHANNEL_MEMBERS_SPEAKING_LIMIT)
{
to_remove = size - CHANNEL_MEMBERS_SPEAKING_LIMIT;
for (i = 0; i < to_remove; i++)
{
weechat_list_remove(
channel->members_speaking[highlight],
weechat_list_get(channel->members_speaking[highlight], 0));
}
}
}
void channel__member_speaking_add(struct t_channel *channel,
const char *nick, int highlight)
{
if (highlight < 0)
highlight = 0;
if (highlight > 1)
highlight = 1;
if (highlight)
channel__member_speaking_add_to_list(channel, nick, 1);
channel__member_speaking_add_to_list(channel, nick, 0);
}
void channel__member_speaking_rename(struct t_channel *channel,
const char *old_nick,
const char *new_nick)
{
struct t_weelist_item *ptr_item;
int i;
for (i = 0; i < 2; i++)
{
if (channel->members_speaking[i])
{
ptr_item = weechat_list_search(channel->members_speaking[i], old_nick);
if (ptr_item)
weechat_list_set(ptr_item, new_nick);
}
}
}
void channel__member_speaking_rename_if_present(struct t_account *account,
struct t_channel *channel,
const char *nick)
{
struct t_weelist_item *ptr_item;
int i, j, list_size;
(void) account;
for (i = 0; i < 2; i++)
{
if (channel->members_speaking[i])
{
list_size = weechat_list_size(channel->members_speaking[i]);
for (j = 0; j < list_size; j++)
{
ptr_item = weechat_list_get(channel->members_speaking[i], j);
if (ptr_item && (weechat_strcasecmp(weechat_list_string(ptr_item),
nick) == 0))
weechat_list_set(ptr_item, nick);
}
}
}
}
void channel__typing_free(struct t_channel *channel,
struct t_channel_typing *typing)
{
struct t_channel_typing *new_typings;
if (!channel || !typing)
return;
/* remove typing from typings list */
if (channel->last_typing == typing)
channel->last_typing = typing->prev_typing;
if (typing->prev_typing)
{
(typing->prev_typing)->next_typing = typing->next_typing;
new_typings = channel->typings;
}
else
new_typings = typing->next_typing;
if (typing->next_typing)
(typing->next_typing)->prev_typing = typing->prev_typing;
/* free typing data */
if (typing->id)
free(typing->id);
if (typing->name)
free(typing->name);
free(typing);
channel->typings = new_typings;
}
void channel__typing_free_all(struct t_channel *channel)
{
while (channel->typings)
channel__typing_free(channel, channel->typings);
}
int channel__typing_cb(const void *pointer,
void *data,
int remaining_calls)
{
struct t_channel_typing *ptr_typing, *next_typing;
struct t_channel *channel;
const char *localvar;
unsigned typecount;
time_t now;
(void) data;
(void) remaining_calls;
if (!pointer)
return WEECHAT_RC_ERROR;
channel = (struct t_channel *)pointer;
now = time(NULL);
typecount = 0;
for (ptr_typing = channel->typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
next_typing = ptr_typing->next_typing;
while (ptr_typing && now - ptr_typing->ts > 5)
{
channel__typing_free(channel, ptr_typing);
ptr_typing = next_typing;
if (ptr_typing)
next_typing = ptr_typing->next_typing;
}
if (!ptr_typing)
break;
typecount++;
}
localvar = weechat_buffer_get_string(channel->buffer, "localvar_typing");
if (!localvar || strncmp(localvar, typecount > 0 ? "1" : "0", 1) != 0)
weechat_buffer_set(channel->buffer,
"localvar_set_typing",
typecount > 0 ? "1" : "0");
weechat_bar_item_update("typing");
return WEECHAT_RC_OK;
}
struct t_channel_typing *channel__typing_search(struct t_channel *channel,
const char *id)
{
struct t_channel_typing *ptr_typing;
if (!channel || !id)
return NULL;
for (ptr_typing = channel->typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
if (weechat_strcasecmp(ptr_typing->id, id) == 0)
return ptr_typing;
}
return NULL;
}
int channel__add_typing(struct t_channel *channel,
struct t_user *user)
{
struct t_channel_typing *new_typing;
int ret = 0;
new_typing = channel__typing_search(channel, user->id);
if (!new_typing)
{
new_typing = (struct t_channel_typing*)malloc(sizeof(*new_typing));
new_typing->id = strdup(user->id);
new_typing->name = strdup(user->profile.display_name);
new_typing->prev_typing = channel->last_typing;
new_typing->next_typing = NULL;
if (channel->last_typing)
(channel->last_typing)->next_typing = new_typing;
else
channel->typings = new_typing;
channel->last_typing = new_typing;
ret = 1;
}
new_typing->ts = time(NULL);
channel__typing_cb(channel, NULL, 0);
return ret;
}
void channel__self_typing_free(struct t_channel *channel,
struct t_channel_typing *typing)
{
struct t_channel_typing *new_typings;
if (!channel || !typing)
return;
/* remove typing from typings list */
if (channel->last_self_typing == typing)
channel->last_self_typing = typing->prev_typing;
if (typing->prev_typing)
{
(typing->prev_typing)->next_typing = typing->next_typing;
new_typings = channel->self_typings;
}
else
new_typings = typing->next_typing;
if (typing->next_typing)
(typing->next_typing)->prev_typing = typing->prev_typing;
/* free typing data */
if (typing->name)
free(typing->name);
free(typing);
channel->self_typings = new_typings;
}
void channel__self_typing_free_all(struct t_channel *channel)
{
while (channel->self_typings)
channel__self_typing_free(channel, channel->self_typings);
}
int channel__self_typing_cb(const void *pointer,
void *data,
int remaining_calls)
{
struct t_channel_typing *ptr_typing, *next_typing;
struct t_account *account;
struct t_channel *channel;
time_t now;
(void) data;
(void) remaining_calls;
if (!pointer)
return WEECHAT_RC_ERROR;
channel = (struct t_channel *)pointer;
account = channel__account(channel);
now = time(NULL);
for (ptr_typing = channel->self_typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
next_typing = ptr_typing->next_typing;
while (ptr_typing && now - ptr_typing->ts > 10)
{
channel__send_paused(account, channel, ptr_typing->user);
channel__self_typing_free(channel, ptr_typing);
ptr_typing = next_typing;
if (ptr_typing)
next_typing = ptr_typing->next_typing;
}
if (!ptr_typing)
break;
}
return WEECHAT_RC_OK;
}
struct t_channel_typing *channel__self_typing_search(struct t_channel *channel,
struct t_user *user)
{
struct t_channel_typing *ptr_typing;
if (!channel)
return NULL;
for (ptr_typing = channel->self_typings; ptr_typing;
ptr_typing = ptr_typing->next_typing)
{
if (user == ptr_typing->user)
return ptr_typing;
}
return NULL;
}
int channel__add_self_typing(struct t_channel *channel,
struct t_user *user)
{
struct t_channel_typing *new_typing;
int ret = 0;
new_typing = channel__self_typing_search(channel, user);
if (!new_typing)
{
new_typing = (struct t_channel_typing*)malloc(sizeof(*new_typing));
new_typing->user = user;
new_typing->name = user ? strdup(user->profile.display_name) : NULL;
new_typing->prev_typing = channel->last_self_typing;
new_typing->next_typing = NULL;
new_typing->ts = time(NULL);
if (channel->last_self_typing)
(channel->last_self_typing)->next_typing = new_typing;
else
channel->self_typings = new_typing;
channel->last_self_typing = new_typing;
ret = 1;
}
channel__self_typing_cb(channel, NULL, 0);
return ret;
}
void channel__unread_free(struct t_channel_unread *unread)
{
if (!unread)
return;
if (unread->id)
free(unread->id);
if (unread->thread)
free(unread->thread);
free(unread);
}
void channel__unread_free_all(struct t_channel *channel)
{
if (channel->unreads)
{
int list_size = weechat_list_size(channel->unreads);
for (int i = 0; i < list_size; i++)
{
struct t_weelist_item *ptr_item = weechat_list_get(channel->unreads, i);
if (ptr_item)
{
struct t_channel_unread *unread = (struct t_channel_unread *)weechat_list_user_data(ptr_item);
channel__unread_free(unread);
weechat_list_remove(channel->unreads, ptr_item);
}
}
weechat_list_free(channel->unreads);
}
}
void channel__member_free(struct t_channel *channel,
struct t_channel_member *member)
{
struct t_channel_member *new_members;
if (!channel || !member)
return;
/* remove member from members list */
if (channel->last_member == member)
channel->last_member = member->prev_member;
if (member->prev_member)
{
(member->prev_member)->next_member = member->next_member;
new_members = channel->members;
}
else
new_members = member->next_member;
if (member->next_member)
(member->next_member)->prev_member = member->prev_member;
/* free member data */
if (member->id)
free(member->id);
if (member->role)
free(member->role);
if (member->affiliation)
free(member->affiliation);
free(member);
channel->members = new_members;
}
void channel__member_free_all(struct t_channel *channel)
{
while (channel->members)
channel__member_free(channel, channel->members);
}
void channel__free(struct t_account *account,
struct t_channel *channel)
{
struct t_channel *new_channels;
if (!account || !channel)
return;
/* remove channel from channels list */
if (account->last_channel == channel)
account->last_channel = channel->prev_channel;
if (channel->prev_channel)
{
(channel->prev_channel)->next_channel = channel->next_channel;
new_channels = account->channels;
}
else
new_channels = channel->next_channel;
if (channel->next_channel)
(channel->next_channel)->prev_channel = channel->prev_channel;
/* free hooks */
if (channel->typing_hook_timer)
weechat_unhook(channel->typing_hook_timer);
if (channel->self_typing_hook_timer)
weechat_unhook(channel->self_typing_hook_timer);
/* free linked lists */
channel__self_typing_free_all(channel);
channel__typing_free_all(channel);
channel__member_free_all(channel);
channel__unread_free_all(channel);
/* free channel data */
if (channel->id)
free(channel->id);
if (channel->name)
free(channel->name);
if (channel->pgp.ids)
{
delete channel->pgp.ids;
channel->pgp.ids = nullptr;
}
if (channel->topic.value)
free(channel->topic.value);
if (channel->topic.creator)
free(channel->topic.creator);
if (channel->creator)
free(channel->creator);
if (channel->members_speaking[0])
weechat_list_free(channel->members_speaking[0]);
if (channel->members_speaking[1])
weechat_list_free(channel->members_speaking[1]);
if (channel->buffer_as_string)
free(channel->buffer_as_string);
free(channel);
account->channels = new_channels;
}
void channel__free_all(struct t_account *account)
{
while (account->channels)
channel__free(account, account->channels);
}
void channel__update_topic(struct t_channel *channel,
const char* topic,
const char* creator,
int last_set)
{
if (channel->topic.value)
free(channel->topic.value);
if (channel->topic.creator)
free(channel->topic.creator);
channel->topic.value = (topic) ? strdup(topic) : NULL;
channel->topic.creator = (creator) ? strdup(creator) : NULL;
channel->topic.last_set = last_set;
if (channel->topic.value)
weechat_buffer_set(channel->buffer, "title", topic);
else
weechat_buffer_set(channel->buffer, "title", "");
}
void channel__update_name(struct t_channel *channel,
const char* name)
{
if (name)
weechat_buffer_set(channel->buffer, "short_name", name);
else
weechat_buffer_set(channel->buffer, "short_name", "");
}
struct t_channel_member *channel__add_member(struct t_account *account,
struct t_channel *channel,
const char *id, const char *client)
{
struct t_channel_member *member;
struct t_user *user;
user = user__search(account, id);
if (user && weechat_strcasecmp(user->id, channel->id) == 0
&& channel->type == CHANNEL_TYPE_MUC)
{
weechat_printf_date_tags(channel->buffer, 0, "log2", "%sMUC: %s",
weechat_prefix("network"),
id);
return NULL;
}
if (!(member = channel__member_search(channel, id)))
{
member = (struct t_channel_member*)malloc(sizeof(struct t_channel_member));
member->id = strdup(id);
member->role = NULL;
member->affiliation = NULL;
member->prev_member = channel->last_member;
member->next_member = NULL;
if (channel->last_member)
(channel->last_member)->next_member = member;
else
channel->members = member;
channel->last_member = member;
}
else if (user)
user__nicklist_remove(account, channel, user);
if (user)
user__nicklist_add(account, channel, user);
char *jid_bare = xmpp_jid_bare(account->context, user->id);
char *jid_resource = xmpp_jid_resource(account->context, user->id);
if (weechat_strcasecmp(jid_bare, channel->id) == 0
&& channel->type == CHANNEL_TYPE_MUC)
weechat_printf_date_tags(channel->buffer, 0, "xmpp_presence,enter,log4", "%s%s%s%s%s %s%s%s%s %s%s%s%s%s%s%s%s%s%s%s%s%s%s",
weechat_prefix("join"),
user__as_prefix_raw(account, jid_resource),
client ? " (" : "",
client ? client : "",
client ? ")" : "",
user->profile.status ? "is " : "",
weechat_color("irc.color.message_join"),
user->profile.status ? user->profile.status : (user->profile.idle ? "idle" : "entered"),
weechat_color("reset"),
channel->id,
user->profile.status_text ? " [" : "",
user->profile.status_text ? user->profile.status_text : "",
user->profile.status_text ? "]" : "",
weechat_color("yellow"), " as ", weechat_color("reset"),
user->profile.affiliation ? user->profile.affiliation : "",
user->profile.affiliation ? " " : "",
user->profile.role,
user->profile.pgp_id ? weechat_color("gray") : "",
user->profile.pgp_id ? " with PGP:" : "",
user->profile.pgp_id ? user->profile.pgp_id : "",
user->profile.pgp_id ? weechat_color("reset") : "");
else
weechat_printf_date_tags(channel->buffer, 0, "xmpp_presence,enter,log4", "%s%s (%s) %s%s%s%s %s%s%s%s%s%s%s%s%s",
weechat_prefix("join"),
jid_resource ? user__as_prefix_raw(account, jid_bare) : "You",
jid_resource ? jid_resource : user__as_prefix_raw(account, jid_bare),
user->profile.status ? "is " : "",
weechat_color("irc.color.message_join"),
user->profile.status ? user->profile.status : (user->profile.idle ? "idle" : "entered"),
weechat_color("reset"),
user->profile.idle ? "since " : "",
user->profile.idle ? user->profile.idle->data() : "",
user->profile.status_text ? " [" : "",
user->profile.status_text ? user->profile.status_text : "",
user->profile.status_text ? "]" : "",
user->profile.pgp_id || user->profile.omemo ? weechat_color("gray") : "",
user->profile.pgp_id || user->profile.omemo ? " with " : "",
user->profile.pgp_id ? "PGP:" : "",
user->profile.pgp_id ? user->profile.pgp_id : "",
user->profile.omemo && user->profile.pgp_id ? " and " : "",
user->profile.omemo ? "OMEMO" : "",
user->profile.pgp_id || user->profile.omemo ? weechat_color("reset") : "");
return member;
}
struct t_channel_member *channel__member_search(struct t_channel *channel,
const char *id)
{
struct t_channel_member *ptr_member;
if (!channel || !id)
return NULL;
for (ptr_member = channel->members; ptr_member;
ptr_member = ptr_member->next_member)
{
if (weechat_strcasecmp(ptr_member->id, id) == 0)
return ptr_member;
}
return NULL;
}
struct t_channel_member *channel__remove_member(struct t_account *account,
struct t_channel *channel,
const char *id, const char *reason)
{
struct t_channel_member *member;
struct t_user *user;
user = user__search(account, id);
if (user)
user__nicklist_remove(account, channel, user);
member = channel__member_search(channel, id);
if (member)
channel__member_free(channel, member);
char *jid_bare = xmpp_jid_bare(account->context, user->id);
char *jid_resource = xmpp_jid_resource(account->context, user->id);
if (weechat_strcasecmp(jid_bare, channel->id) == 0
&& channel->type == CHANNEL_TYPE_MUC)
weechat_printf_date_tags(channel->buffer, 0, "xmpp_presence,leave,log4", "%s%s %sleft%s %s %s%s%s",
weechat_prefix("quit"),
jid_resource,
weechat_color("irc.color.message_quit"),
weechat_color("reset"),
channel->id,
reason ? "[" : "",
reason ? reason : "",
reason ? "]" : "");
else
weechat_printf_date_tags(channel->buffer, 0, "xmpp_presence,leave,log4", "%s%s (%s) %sleft%s %s %s%s%s",
weechat_prefix("quit"),
xmpp_jid_bare(account->context, user->id),
xmpp_jid_resource(account->context, user->id),
weechat_color("irc.color.message_quit"),
weechat_color("reset"),
channel->id,
reason ? "[" : "",
reason ? reason : "",
reason ? "]" : "");
return member;
}
int channel__send_message(struct t_account *account, struct t_channel *channel,
std::string to, std::string body,
std::optional<std::string> oob = {})
{
xmpp_stanza_t *message = xmpp_message_new(account->context,
channel->type == CHANNEL_TYPE_MUC
? "groupchat" : "chat",
to.data(), NULL);
char *id = xmpp_uuid_gen(account->context);
xmpp_stanza_set_id(message, id);
xmpp_free(account->context, id);
xmpp_message_set_body(message, body.data());
if (oob)
{
xmpp_stanza_t *message__x = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__x, "x");
xmpp_stanza_set_ns(message__x, "jabber:x:oob");
xmpp_stanza_t *message__x__url = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__x__url, "url");
xmpp_stanza_t *message__x__url__text = xmpp_stanza_new(account->context);
xmpp_stanza_set_text(message__x__url__text, oob->data());
xmpp_stanza_add_child(message__x__url, message__x__url__text);
xmpp_stanza_release(message__x__url__text);
xmpp_stanza_add_child(message__x, message__x__url);
xmpp_stanza_release(message__x__url);
xmpp_stanza_add_child(message, message__x);
xmpp_stanza_release(message__x);
}
xmpp_stanza_t *message__active = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__active, "active");
xmpp_stanza_set_ns(message__active, "http://jabber.org/protocol/chatstates");
xmpp_stanza_add_child(message, message__active);
xmpp_stanza_release(message__active);
xmpp_stanza_t *message__request = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__request, "request");
xmpp_stanza_set_ns(message__request, "urn:xmpp:receipts");
xmpp_stanza_add_child(message, message__request);
xmpp_stanza_release(message__request);
xmpp_stanza_t *message__markable = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__markable, "markable");
xmpp_stanza_set_ns(message__markable, "urn:xmpp:chat-markers:0");
xmpp_stanza_add_child(message, message__markable);
xmpp_stanza_release(message__markable);
xmpp_stanza_t *message__store = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__store, "store");
xmpp_stanza_set_ns(message__store, "urn:xmpp:hints");
xmpp_stanza_add_child(message, message__store);
xmpp_stanza_release(message__store);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
if (channel->type != CHANNEL_TYPE_MUC)
weechat_printf_date_tags(channel->buffer, 0,
"xmpp_message,message,private,notify_none,self_msg,log1",
"%s\t%s",
user__as_prefix_raw(account, account_jid(account)),
body);
return WEECHAT_RC_OK;
}
int channel__send_message(struct t_account *account, struct t_channel *channel,
const char *to, const char *body)
{
channel__send_reads(account, channel);
xmpp_stanza_t *message = xmpp_message_new(account->context,
channel->type == CHANNEL_TYPE_MUC
? "groupchat" : "chat",
to, NULL);
char *id = xmpp_uuid_gen(account->context);
xmpp_stanza_set_id(message, id);
xmpp_free(account->context, id);
if (account->omemo && channel->omemo.enabled)
{
xmpp_stanza_t *encrypted = account->omemo.encode(account, to, body);
if (!encrypted)
{
weechat_printf_date_tags(channel->buffer, 0, "notify_none", "%s%s",
weechat_prefix("error"), "OMEMO Encryption Error");
channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 1);
xmpp_stanza_release(message);
return WEECHAT_RC_ERROR;
}
xmpp_stanza_add_child(message, encrypted);
xmpp_stanza_release(encrypted);
xmpp_stanza_t *message__encryption = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__encryption, "encryption");
xmpp_stanza_set_ns(message__encryption, "urn:xmpp:eme:0");
xmpp_stanza_set_attribute(message__encryption, "namespace",
"eu.siacs.conversations.axolotl");
xmpp_stanza_set_attribute(message__encryption, "name", "OMEMO");
xmpp_stanza_add_child(message, message__encryption);
xmpp_stanza_release(message__encryption);
xmpp_message_set_body(message, OMEMO_ADVICE);
channel__set_transport(channel, CHANNEL_TRANSPORT_OMEMO, 0);
}
else if (channel->pgp.enabled && channel->pgp.ids)
{
xmpp_stanza_t *message__x = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__x, "x");
xmpp_stanza_set_ns(message__x, "jabber:x:encrypted");
xmpp_stanza_t *message__x__text = xmpp_stanza_new(account->context);
char *ciphertext = pgp__encrypt(channel->buffer, account->pgp, account_pgp_keyid(account), std::vector(channel->pgp.ids->begin(), channel->pgp.ids->end()), body);
if (ciphertext)
xmpp_stanza_set_text(message__x__text, ciphertext);
else
{
weechat_printf_date_tags(channel->buffer, 0, "notify_none", "%s%s",
weechat_prefix("error"), "PGP Error");
channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 1);
xmpp_stanza_release(message);
return WEECHAT_RC_ERROR;
}
free(ciphertext);
xmpp_stanza_add_child(message__x, message__x__text);
xmpp_stanza_release(message__x__text);
xmpp_stanza_add_child(message, message__x);
xmpp_stanza_release(message__x);
xmpp_stanza_t *message__encryption = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__encryption, "encryption");
xmpp_stanza_set_ns(message__encryption, "urn:xmpp:eme:0");
xmpp_stanza_set_attribute(message__encryption, "namespace", "jabber:x:encryption");
xmpp_stanza_add_child(message, message__encryption);
xmpp_stanza_release(message__encryption);
xmpp_message_set_body(message, PGP_ADVICE);
channel__set_transport(channel, CHANNEL_TRANSPORT_PGP, 0);
}
else
{
xmpp_message_set_body(message, body);
channel__set_transport(channel, CHANNEL_TRANSPORT_PLAIN, 0);
}
static const std::regex pattern("https?:[^ ]*");
std::cmatch match;
if (channel->transport == CHANNEL_TRANSPORT_PLAIN &&
std::regex_search(body, match, pattern)
&& match[0].matched && !match.prefix().length())
{
std::string url { &*match[0].first, static_cast<size_t>(match[0].length()) };
do {
struct t_hashtable *options = weechat_hashtable_new(8,
WEECHAT_HASHTABLE_STRING, WEECHAT_HASHTABLE_STRING,
NULL, NULL);
if (!options) { return WEECHAT_RC_ERROR; };
weechat_hashtable_set(options, "header", "1");
weechat_hashtable_set(options, "nobody", "1");
auto command = "url:" + url;
const int timeout = 30000;
struct message_task {
struct t_account *account;
struct t_channel *channel;
std::string to;
std::string body;
std::string url;
};
auto *task = new message_task { account, channel, to, body, url };
auto callback = [](const void *pointer, void *,
const char *, int ret, const char *out, const char *err) {
auto task = static_cast<const message_task*>(pointer);
if (!task) return WEECHAT_RC_ERROR;
if (ret == 0)
{
const std::string_view prefix = "content-type: ";
std::istringstream ss(out ? out : "");
std::string line, mime;
while (std::getline(ss, line)) {
std::transform(line.begin(), line.end(), line.begin(),
[](char c) -> char { return std::tolower(c); });
if (line.starts_with(prefix)) {
mime = line.substr(prefix.size());
break;
}
}
if (mime.starts_with("image") || mime.starts_with("video"))
{
weechat_printf_date_tags(task->channel->buffer, 0,
"notify_none,no_log", "[oob]\t%s%s",
weechat_color("gray"), mime.data());
channel__send_message(task->account, task->channel,
task->to, task->body, { task->url });
}
else
{
weechat_printf_date_tags(task->channel->buffer, 0,
"notify_none,no_log", "[curl]\t%s%s",
weechat_color("red"), err);
channel__send_message(task->account, task->channel,
task->to, task->body);
}
}
else
{
channel__send_message(task->account, task->channel,
task->to, task->body);
}
delete task;
return WEECHAT_RC_OK;
};
struct t_hook *process_hook =
weechat_hook_process_hashtable(command.data(), options, timeout,
callback, task, nullptr);
weechat_hashtable_free(options);
(void) process_hook;
return WEECHAT_RC_OK;
} while(0);
}
xmpp_stanza_t *message__active = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__active, "active");
xmpp_stanza_set_ns(message__active, "http://jabber.org/protocol/chatstates");
xmpp_stanza_add_child(message, message__active);
xmpp_stanza_release(message__active);
xmpp_stanza_t *message__request = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__request, "request");
xmpp_stanza_set_ns(message__request, "urn:xmpp:receipts");
xmpp_stanza_add_child(message, message__request);
xmpp_stanza_release(message__request);
xmpp_stanza_t *message__markable = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__markable, "markable");
xmpp_stanza_set_ns(message__markable, "urn:xmpp:chat-markers:0");
xmpp_stanza_add_child(message, message__markable);
xmpp_stanza_release(message__markable);
xmpp_stanza_t *message__store = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__store, "store");
xmpp_stanza_set_ns(message__store, "urn:xmpp:hints");
xmpp_stanza_add_child(message, message__store);
xmpp_stanza_release(message__store);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
if (channel->type != CHANNEL_TYPE_MUC)
weechat_printf_date_tags(channel->buffer, 0,
"xmpp_message,message,private,notify_none,self_msg,log1",
"%s\t%s",
user__as_prefix_raw(account, account_jid(account)),
body);
return WEECHAT_RC_OK;
}
void channel__send_reads(struct t_account *account, struct t_channel *channel)
{
if (channel && channel->unreads)
{
int list_size = weechat_list_size(channel->unreads);
for (int i = 0; i < list_size; i++)
{
struct t_weelist_item *ptr_item = weechat_list_get(channel->unreads, 0);
if (ptr_item)
{
const char *unread_id = weechat_list_string(ptr_item);
struct t_channel_unread *unread = (struct t_channel_unread*)weechat_list_user_data(ptr_item);
xmpp_stanza_t *message = xmpp_message_new(account->context, NULL,
channel->id, NULL);
xmpp_stanza_t *message__displayed = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__displayed, "displayed");
xmpp_stanza_set_ns(message__displayed, "urn:xmpp:chat-markers:0");
xmpp_stanza_set_id(message__displayed, unread->id ? unread->id : unread_id);
if (unread->thread)
{
xmpp_stanza_t *message__thread = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__thread, "thread");
xmpp_stanza_t *message__thread__text = xmpp_stanza_new(account->context);
xmpp_stanza_set_text(message__thread__text, unread->thread);
xmpp_stanza_add_child(message__thread, message__thread__text);
xmpp_stanza_release(message__thread__text);
xmpp_stanza_add_child(message, message__thread);
xmpp_stanza_release(message__thread);
}
xmpp_stanza_add_child(message, message__displayed);
xmpp_stanza_release(message__displayed);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
channel__unread_free(unread);
weechat_list_remove(channel->unreads, ptr_item);
}
}
}
}
void channel__send_typing(struct t_account *account, struct t_channel *channel,
struct t_user *user)
{
if (channel__add_self_typing(channel, user))
{
xmpp_stanza_t *message = xmpp_message_new(account->context,
channel->type == CHANNEL_TYPE_MUC
? "groupchat" : "chat",
user ? user->id : channel->id, NULL);
xmpp_stanza_t *message__composing = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__composing, "composing");
xmpp_stanza_set_ns(message__composing, "http://jabber.org/protocol/chatstates");
xmpp_stanza_add_child(message, message__composing);
xmpp_stanza_release(message__composing);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
}
}
void channel__send_paused(struct t_account *account, struct t_channel *channel,
struct t_user *user)
{
xmpp_stanza_t *message = xmpp_message_new(account->context,
channel->type == CHANNEL_TYPE_MUC
? "groupchat" : "chat",
user ? user->id : channel->id, NULL);
xmpp_stanza_t *message__paused = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(message__paused, "paused");
xmpp_stanza_set_ns(message__paused, "http://jabber.org/protocol/chatstates");
xmpp_stanza_add_child(message, message__paused);
xmpp_stanza_release(message__paused);
xmpp_send(account->connection, message);
xmpp_stanza_release(message);
}
void channel__fetch_mam(struct t_account *account, struct t_channel *channel,
const char *id, time_t *start, time_t *end, const char* after)
{
xmpp_stanza_t *iq = xmpp_iq_new(account->context, "set", "juliet1");
xmpp_stanza_set_id(iq, id ? id : xmpp_uuid_gen(account->context));
xmpp_stanza_t *query = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(query, "query");
xmpp_stanza_set_ns(query, "urn:xmpp:mam:2");
xmpp_stanza_t *x = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(x, "x");
xmpp_stanza_set_ns(x, "jabber:x:data");
xmpp_stanza_set_attribute(x, "type", "result");
xmpp_stanza_t *field, *value, *text;
{
field = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(field, "field");
xmpp_stanza_set_attribute(field, "var", "FORM_TYPE");
xmpp_stanza_set_attribute(field, "type", "hidden");
value = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(value, "value");
text = xmpp_stanza_new(account->context);
xmpp_stanza_set_text(text, "urn:xmpp:mam:2");
xmpp_stanza_add_child(value, text);
xmpp_stanza_release(text);
xmpp_stanza_add_child(field, value);
xmpp_stanza_release(value);
xmpp_stanza_add_child(x, field);
xmpp_stanza_release(field);
}
{
field = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(field, "field");
xmpp_stanza_set_attribute(field, "var", "with");
value = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(value, "value");
text = xmpp_stanza_new(account->context);
xmpp_stanza_set_text(text, channel->id);
xmpp_stanza_add_child(value, text);
xmpp_stanza_release(text);
xmpp_stanza_add_child(field, value);
xmpp_stanza_release(value);
xmpp_stanza_add_child(x, field);
xmpp_stanza_release(field);
}
if (start)
{
field = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(field, "field");
xmpp_stanza_set_attribute(field, "var", "start");
value = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(value, "value");
text = xmpp_stanza_new(account->context);
char time[256] = {0};
strftime(time, sizeof(time), "%Y-%m-%dT%H:%M:%SZ", gmtime(start));
xmpp_stanza_set_text(text, time);
xmpp_stanza_add_child(value, text);
xmpp_stanza_release(text);
xmpp_stanza_add_child(field, value);
xmpp_stanza_release(value);
xmpp_stanza_add_child(x, field);
xmpp_stanza_release(field);
}
if (end)
{
field = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(field, "field");
xmpp_stanza_set_attribute(field, "var", "end");
value = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(value, "value");
text = xmpp_stanza_new(account->context);
char time[256] = {0};
strftime(time, sizeof(time), "%Y-%m-%dT%H:%M:%SZ", gmtime(end));
xmpp_stanza_set_text(text, time);
xmpp_stanza_add_child(value, text);
xmpp_stanza_release(text);
xmpp_stanza_add_child(field, value);
xmpp_stanza_release(value);
xmpp_stanza_add_child(x, field);
xmpp_stanza_release(field);
}
xmpp_stanza_add_child(query, x);
xmpp_stanza_release(x);
if (after)
{
xmpp_stanza_t *set, *set__after, *set__after__text;
set = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(set, "set");
xmpp_stanza_set_ns(set, "http://jabber.org/protocol/rsm");
set__after = xmpp_stanza_new(account->context);
xmpp_stanza_set_name(set__after, "after");
set__after__text = xmpp_stanza_new(account->context);
xmpp_stanza_set_text(set__after__text, after);
xmpp_stanza_add_child(set__after, set__after__text);
xmpp_stanza_release(set__after__text);
xmpp_stanza_add_child(set, set__after);
xmpp_stanza_release(set__after);
xmpp_stanza_add_child(query, set);
xmpp_stanza_release(set);
}
else
account__add_mam_query(account, channel,
xmpp_stanza_get_id(iq), start, end);
xmpp_stanza_add_child(iq, query);
xmpp_stanza_release(query);
xmpp_send(account->connection, iq);
xmpp_stanza_release(iq);
}