2
0
mirror of https://github.com/ars3niy/tdlib-purple synced 2025-08-22 01:49:29 +00:00
tdlib-purple/call.cpp

304 lines
14 KiB
C++

#include "call.h"
#include "client-utils.h"
#include "receiving.h"
#include "config.h"
#include "buildopt.h"
#include "format.h"
#include "purple-info.h"
static td::td_api::object_ptr<td::td_api::callProtocol> getCallProtocol()
{
auto protocol = td::td_api::make_object<td::td_api::callProtocol>();
protocol->udp_p2p_ = true;
protocol->udp_reflector_ = true;
protocol->min_layer_ = 65;
protocol->max_layer_ = 92;
return protocol;
}
bool initiateCall(int32_t userId, TdAccountData &account, TdTransceiver &transceiver)
{
#ifndef NoVoip
if (!account.hasActiveCall()) {
td::td_api::object_ptr<td::td_api::createCall> callRequest = td::td_api::make_object<td::td_api::createCall>();
callRequest->user_id_ = userId;
callRequest->protocol_ = getCallProtocol();
transceiver.sendQuery(std::move(callRequest), nullptr);
} else
// TRANSLATOR: Dialog title of an error message.
purple_notify_warning(account.purpleAccount, _("Voice call"),
// TRANSLATOR: Dialog content of an error message.
_("Cannot start new call, already in another call"), NULL);
#endif
return false;
}
static void discardCall(int32_t callId, TdTransceiver &transceiver)
{
td::td_api::object_ptr<td::td_api::discardCall> discardReq = td::td_api::make_object<td::td_api::discardCall>();
discardReq->call_id_ = callId;
discardReq->is_disconnected_ = true;
discardReq->duration_ = 0;
discardReq->connection_id_ = 0;
transceiver.sendQuery(std::move(discardReq), nullptr);
}
struct CallRequestData {
int callId;
TdTransceiver *transceiver;
TdAccountData *account;
};
static void acceptCallCb(CallRequestData *data, int action)
{
std::unique_ptr<CallRequestData> request(data);
td::td_api::object_ptr<td::td_api::acceptCall> acceptReq = td::td_api::make_object<td::td_api::acceptCall>();
acceptReq->call_id_ = request->callId;
acceptReq->protocol_ = getCallProtocol();
request->transceiver->sendQuery(std::move(acceptReq), nullptr);
}
static void discardCallCb(CallRequestData *data, int action)
{
std::unique_ptr<CallRequestData> request(data);
discardCall(request->callId, *request->transceiver);
request->account->removeActiveCall();
}
static std::string getPurpleUserName(UserId userId, TdAccountData &account)
{
const td::td_api::user *user = account.getUser(userId);
if (user) {
const td::td_api::chat *privateChat = account.getPrivateChatByUserId(userId);
if (privateChat && isChatInContactList(*privateChat, user))
return getPurpleBuddyName(*user);
else
return account.getDisplayName(*user);
} else
return std::string();
}
static bool activateCall(const td::td_api::call &call, const std::string &buddyName,
TdAccountData &account, TdTransceiver &transceiver)
{
#ifndef NoVoip
if (call.state_->get_id() != td::td_api::callStateReady::ID)
return false;
const td::td_api::callStateReady &state = static_cast<const td::td_api::callStateReady &>(*call.state_);
if (state.protocol_->max_layer_ < 74) {
// libtgvoip crashes with openssl assertion failure if it goes into if(!useMTProto2) branch
// of VoIPController::ProcessIncomingPacket
// Unlikely error message not worth translating
if (!buddyName.empty())
showMessageTextIm(account, buddyName.c_str(), NULL,
"Discarding call due to low protocol layer",
time(NULL), PURPLE_MESSAGE_SYSTEM);
return false;
}
tgvoip::VoIPController *voip = account.getCallData();
if (!voip)
return false;
static tgvoip::VoIPController::Config config;
config.enableAEC = true;
config.enableNS = true;
config.enableAGC = true;
voip->SetConfig(config);
std::vector<tgvoip::Endpoint> endpoints;
for (const auto &pServer: state.servers_)
if (pServer && pServer->type_ && (pServer->type_->get_id() == td::td_api::callServerTypeTelegramReflector::ID)) {
const td::td_api::callServerTypeTelegramReflector &reflectorInfo =
static_cast<const td::td_api::callServerTypeTelegramReflector &>(*pServer->type_);
std::vector<unsigned char> tag(16);
memmove(tag.data(), reflectorInfo.peer_tag_.c_str(), std::min(reflectorInfo.peer_tag_.length(), tag.size()));
endpoints.push_back(tgvoip::Endpoint(pServer->id_, pServer->port_,
tgvoip::IPv4Address(pServer->ip_address_),
tgvoip::IPv6Address(pServer->ipv6_address_),
tgvoip::Endpoint::UDP_RELAY,
tag.data()));
}
voip->SetRemoteEndpoints(endpoints, state.allow_p2p_ && state.protocol_->udp_p2p_,
state.protocol_->max_layer_);
std::vector<char> key(state.encryption_key_.length()+1);
memmove(key.data(), state.encryption_key_.c_str(), key.size());
voip->SetEncryptionKey(key.data(), call.is_outgoing_);
voip->Start();
voip->Connect();
if (!buddyName.empty()) {
// For an outgoing call, "type /hangup to terminate" has already been shown when the call
// was initiated
// TRANSLATOR: In-chat status message
const char *message = call.is_outgoing_ ? _("Call active") :
// TRANSLATOR: In-chat status message. Please keep '/hangup' verbatim!
_("Call active, type /hangup to terminate");
showMessageTextIm(account, buddyName.c_str(), NULL, message,
time(NULL), PURPLE_MESSAGE_SYSTEM);
}
#endif
return true;
}
static void deactivateCall(TdAccountData &account)
{
#ifndef NoVoip
tgvoip::VoIPController *voip = account.getCallData();
if (voip)
voip->Stop();
#endif
}
static void notifyCallError(const td::td_api::callStateError &error, const std::string &buddyName,
TdAccountData &account)
{
std::string message;
if (error.error_)
message = formatMessage(errorCodeMessage(), {std::to_string(error.error_->code_),
error.error_->message_});
else
// Unlikely message not worth translating
message = "unknown error";
// TRANSLATOR: In-chat error message, argument is text
message = formatMessage(_("Call failed: {}"), message);
if (!buddyName.empty())
showMessageTextIm(account, buddyName.c_str(), NULL, message.c_str(),
time(NULL), PURPLE_MESSAGE_SYSTEM);
}
void updateCall(const td::td_api::call &call, TdAccountData &account, TdTransceiver &transceiver)
{
std::string buddyName = getPurpleUserName(getUserId(call), account);
#ifndef NoVoip
PurpleMediaManager *mediaManager = purple_media_manager_get();
PurpleMediaCaps capabilities = purple_media_manager_get_ui_caps(mediaManager);
if (!(capabilities & PURPLE_MEDIA_CAPS_AUDIO) && !(capabilities & PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION) &&
(strcasecmp(getUiName(), "pidgin") != 0)) {
#else
if (true) {
#endif
purple_debug_misc(config::pluginId, "Ignoring incoming call: no audio capability\n");
if (call.state_ && (call.state_->get_id() == td::td_api::callStatePending::ID)) {
if (!buddyName.empty())
showMessageTextIm(account, buddyName.c_str(), NULL,
// TRANSLATOR: In-chat error message
_("Received incoming call, but calls are not supported"),
time(NULL), PURPLE_MESSAGE_SYSTEM);
discardCall(call.id_, transceiver);
}
return;
}
if (!call.state_) return; // just in case
if (!call.is_outgoing_ && (call.state_->get_id() == td::td_api::callStatePending::ID)) {
if (!account.hasActiveCall()) {
account.setActiveCall(call.id_);
// TRANSLATOR: Dialog content, user will have the options "_OK" and "_Cancel".
std::string message = formatMessage(_("{} wishes to start a call with you."),
account.getDisplayName(getUserId(call)));
CallRequestData *request = new CallRequestData;
request->callId = call.id_;
request->transceiver = &transceiver;
request->account = &account;
purple_request_action(purple_account_get_connection(account.purpleAccount),
// TRANSLATOR: Dialog title, asking about an incoming telephone call (OK/Cancel)
_("Voice call"), message.c_str(), NULL,
PURPLE_DEFAULT_ACTION_NONE,
account.purpleAccount, !buddyName.empty() ? buddyName.c_str() : NULL, NULL,
// TRANSLATOR: Dialog option, regarding a phone call; the alternative is "_Cancel". The underscore marks accelerator keys, they must be different!
request, 2, _("_OK"), acceptCallCb,
// TRANSLATOR: Dialog option, regarding a phone call; the alternative is "_OK". The underscore marks accelerator keys, they must be different!
_("_Cancel"), discardCallCb);
} else if (call.id_ != account.getActiveCallId()) {
if (!buddyName.empty())
showMessageTextIm(account, buddyName.c_str(), NULL,
// TRANSLATOR: In-chat error message
_("Received incoming call while already in another call"),
time(NULL), PURPLE_MESSAGE_SYSTEM);
discardCall(call.id_, transceiver);
}
} else if (call.is_outgoing_ && (call.state_->get_id() == td::td_api::callStatePending::ID)) {
if (!account.hasActiveCall()) {
account.setActiveCall(call.id_);
if (!buddyName.empty())
showMessageTextIm(account, buddyName.c_str(), NULL,
// TRANSLATOR: In-chat status message. Please keep '/hangup' verbatim!
_("Call pending, type /hangup to terminate"),
time(NULL), PURPLE_MESSAGE_SYSTEM);
} else if (call.id_ != account.getActiveCallId()) {
// This would happen if there was no active call when sending createCall, but there is one
// a millisecond later when asynchronous response is received. Possible if two calls are
// started at the same time, or one is started an another received at the same time.
discardCall(call.id_, transceiver);
}
} else if (call.state_->get_id() == td::td_api::callStateReady::ID) {
if (! activateCall(call, buddyName, account, transceiver)) {
discardCall(call.id_, transceiver);
account.removeActiveCall();
}
}
else if ( ((call.state_->get_id() == td::td_api::callStateHangingUp::ID) ||
(call.state_->get_id() == td::td_api::callStateDiscarded::ID) ||
(call.state_->get_id() == td::td_api::callStateError::ID)) &&
account.hasActiveCall() && account.getActiveCallId() == call.id_)
{
if (call.state_->get_id() == td::td_api::callStateError::ID) {
const td::td_api::callStateError &error = static_cast<const td::td_api::callStateError &>(*call.state_);
notifyCallError(error, buddyName, account);
}
deactivateCall(account);
account.removeActiveCall();
}
}
void discardCurrentCall(TdAccountData &account, TdTransceiver &transceiver)
{
if (account.hasActiveCall())
discardCall(account.getActiveCallId(), transceiver);
}
void showCallMessage(const td::td_api::chat &chat, const TgMessageInfo &message,
const td::td_api::messageCall &callEnded, TdAccountData &account)
{
std::string notification;
if (callEnded.discard_reason_)
switch (callEnded.discard_reason_->get_id()) {
case td::td_api::callDiscardReasonMissed::ID:
// TRANSLATOR: In-line reason for an ended call; appears after a colon (':')
notification = _("call missed");
break;
case td::td_api::callDiscardReasonDeclined::ID:
// TRANSLATOR: In-line reason for an ended call; appears after a colon (':')
notification = _("declined by peer");
break;
case td::td_api::callDiscardReasonDisconnected::ID:
// TRANSLATOR: In-line reason for an ended call; appears after a colon (':')
notification = _("users disconnected");
break;
case td::td_api::callDiscardReasonHungUp::ID:
// TRANSLATOR: In-line reason for an ended call; appears after a colon (':')
notification = _("hung up");
break;
}
if (notification.empty()) {
// TRANSLATOR: In-line reason for an ended call; appears after a colon (':')
notification = _("reason unknown");
}
// TRANSLATOR: In-chat message, arguments will be a duration and a few words (like "hung up")
notification = formatMessage(_("Call ended ({0}): {1}"), {formatDuration(callEnded.duration_), notification});
showMessageText(account, chat, message, NULL, notification.c_str());
}