/*
Copyright (C) 2012-24 Miranda NG team (https://miranda-ng.org)
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation version 2
of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "stdafx.h"
CMStringW CTelegramProto::GetAvatarFilename(MCONTACT hContact)
{
CMStringW wszResult(GetAvatarPath());
const wchar_t *szFileType = ProtoGetAvatarExtension(getByte(hContact, "AvatarType", PA_FORMAT_JPEG));
wszResult.AppendFormat(L"\\%lld%s", GetId(hContact), szFileType);
return wszResult;
}
INT_PTR CTelegramProto::SvcGetAvatarCaps(WPARAM wParam, LPARAM lParam)
{
switch (wParam) {
case AF_MAXSIZE:
((POINT *)lParam)->x = 160;
((POINT *)lParam)->y = 160;
break;
case AF_MAXFILESIZE:
return 32000;
case AF_PROPORTION:
return PIP_SQUARE;
case AF_FORMATSUPPORTED:
case AF_ENABLED:
case AF_DONTNEEDDELAYS:
case AF_FETCHIFPROTONOTVISIBLE:
case AF_FETCHIFCONTACTOFFLINE:
return 1;
}
return 0;
}
INT_PTR CTelegramProto::SvcGetAvatarInfo(WPARAM, LPARAM lParam)
{
auto *pai = (PROTO_AVATAR_INFORMATION *)lParam;
CMStringW wszPath(GetAvatarFilename(pai->hContact));
pai->format = getByte(pai->hContact, DBKEY_AVATAR_TYPE, PA_FORMAT_JPEG);
wcsncpy_s(pai->filename, wszPath, _TRUNCATE);
if (::_waccess(pai->filename, 0) == 0)
return GAIR_SUCCESS;
debugLogA("No avatar");
return GAIR_NOAVATAR;
}
INT_PTR CTelegramProto::SvcGetMyAvatar(WPARAM wParam, LPARAM lParam)
{
auto wszFileName(GetAvatarFilename(0));
mir_wstrncpy((wchar_t *)wParam, wszFileName, lParam);
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
void CTelegramProto::OnAvatarSet(td::ClientManager::Response&, void *pUserInfo)
{
ptrW pwszFileName((wchar_t *)pUserInfo);
DeleteFileW(pwszFileName);
}
INT_PTR CTelegramProto::SvcSetMyAvatar(WPARAM, LPARAM lParam)
{
auto *pwszFileName = (const wchar_t *)lParam;
if (ProtoGetAvatarFileFormat(pwszFileName) != PA_FORMAT_JPEG) {
Popup(0, TranslateT("Avatar file must be a picture in JPEG format"), TranslateT("Error setting avatar"));
return 1;
}
TD::object_ptr localFile(new TD::inputFileLocal(T2Utf(pwszFileName).get()));
TD::object_ptr photo(new TD::inputChatPhotoStatic(std::move(localFile)));
SendQuery(new TD::setProfilePhoto(std::move(photo), true), &CTelegramProto::OnAvatarSet, mir_wstrdup(pwszFileName));
return -1;
}
/////////////////////////////////////////////////////////////////////////////////////////
// Cloud file downloader
void CTelegramProto::OnGetFileInfo(td::ClientManager::Response &response, void *pUserInfo)
{
if (!response.object)
return;
if (response.object->get_id() != TD::file::ID) {
debugLogA("Gotten class ID %d instead of %d, exiting", response.object->get_id(), TD::chats::ID);
return;
}
auto *pFile = (TD::file*)response.object.get();
auto *ft = (TG_FILE_REQUEST *)pUserInfo;
ft->m_fileId = pFile->id_;
ft->m_uniqueId = pFile->remote_->unique_id_.c_str();
SendQuery(new TD::downloadFile(pFile->id_, 10, 0, 0, false));
}
void CTelegramProto::OnGetFileLink(td::ClientManager::Response &response)
{
if (!response.object)
return;
}
void __cdecl CTelegramProto::OfflineFileThread(void *pParam)
{
auto *ofd = (OFDTHREAD *)pParam;
DB::EventInfo dbei(ofd->hDbEvent);
if (dbei && !strcmp(dbei.szModule, m_szModuleName) && dbei.eventType == EVENTTYPE_FILE) {
if (!ofd->bCopy) {
auto *ft = new TG_FILE_REQUEST(TG_FILE_REQUEST::FILE, 0, "");
ft->ofd = ofd;
m_arFiles.insert(ft);
DB::FILE_BLOB blob(dbei);
SendQuery(new TD::getRemoteFile(blob.getUrl(), 0), &CTelegramProto::OnGetFileInfo, ft);
}
}
else delete ofd;
}
INT_PTR __cdecl CTelegramProto::SvcOfflineFile(WPARAM param, LPARAM)
{
ForkThread((MyThreadFunc)&CTelegramProto::OfflineFileThread, (void *)param);
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
// Cloud file pre-creator
void CTelegramProto::OnReceiveOfflineFile(DB::EventInfo&, DB::FILE_BLOB &blob)
{
if (auto *ft = (TG_FILE_REQUEST *)blob.getUserInfo()) {
blob.setUrl(ft->m_uniqueId.GetBuffer());
blob.setSize(ft->m_fileSize);
delete ft;
}
}
void CTelegramProto::OnSendOfflineFile(DB::EventInfo &dbei, DB::FILE_BLOB &blob, void *hTransfer)
{
auto *ft = (TG_FILE_REQUEST *)hTransfer;
dbei.szId = ft->m_uniqueId;
if (!ft->m_szUserId.IsEmpty())
dbei.szUserId = ft->m_szUserId;
auto *p = wcsrchr(ft->m_fileName, '\\');
if (p == nullptr)
p = ft->m_fileName;
else
p++;
blob.setName(p);
blob.setUrl("boo");
blob.complete(ft->m_fileSize);
blob.setLocalName(ft->m_fileName);
}
/////////////////////////////////////////////////////////////////////////////////////////
TG_FILE_REQUEST* CTelegramProto::FindFile(const char *pszUniqueId)
{
mir_cslock lck(m_csFiles);
for (auto &it : m_arFiles)
if (it->m_uniqueId == pszUniqueId)
return it;
return nullptr;
}
/////////////////////////////////////////////////////////////////////////////////////////
// Extracts a photo/avatar to a file
void CTelegramProto::ProcessAvatar(const TD::file *pFile, TG_USER *pUser)
{
if (pUser->hContact == INVALID_CONTACT_ID)
return;
auto remoteId = pFile->remote_->unique_id_;
auto storedId = getMStringA(pUser->hContact, DBKEY_AVATAR_HASH);
auto wszFileName = GetAvatarFilename(pUser->hContact);
if (remoteId != storedId.c_str() || _waccess(wszFileName, 0)) {
if (!remoteId.empty()) {
pUser->szAvatarHash = remoteId.c_str();
setString(pUser->hContact, DBKEY_AVATAR_HASH, remoteId.c_str());
SendQuery(new TD::downloadFile(pFile->id_, 5, 0, 0, false));
}
else delSetting(pUser->hContact, DBKEY_AVATAR_HASH);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// handles file info updates
void CTelegramProto::ProcessFile(TD::updateFile *pObj)
{
auto *pFile = pObj->file_.get();
if (pFile == nullptr)
return;
auto *pRemote = pFile->remote_.get();
if (pRemote == nullptr)
return;
if (!pFile->local_->is_downloading_completed_) {
if (auto *F = FindFile(pRemote->unique_id_.c_str())) {
if (F->m_type != F->AVATAR && F->ofd) {
DBVARIANT dbv = { DBVT_DWORD };
dbv.dVal = pFile->local_->downloaded_size_;
db_event_setJson(F->ofd->hDbEvent, "ft", &dbv);
}
}
return;
}
Utf2T wszExistingFile(pFile->local_->path_.c_str());
if (auto *F = FindFile(pRemote->unique_id_.c_str())) {
if (F->m_type == F->AVATAR) {
CMStringW wszFullName = F->m_destPath;
if (!wszFullName.IsEmpty())
wszFullName += L"\\";
wszFullName += F->m_fileName;
if (F->m_fileName.Right(5).MakeLower() == L".webp") {
if (auto *pImage = FreeImage_LoadU(FIF_WEBP, wszExistingFile)) {
wszFullName.Truncate(wszFullName.GetLength() - 5);
wszFullName += L".png";
FreeImage_SaveU(FIF_PNG, pImage, wszFullName);
FreeImage_Unload(pImage);
}
}
else MoveFileW(wszExistingFile, wszFullName);
if (F->m_isSmiley)
SmileyAdd_LoadContactSmileys(SMADD_FILE, m_szModuleName, wszFullName);
else
NS_NotifyFileReady(wszFullName);
mir_cslock lck(m_csFiles);
m_arFiles.remove(F);
delete F;
}
else { // FILE, PICTURE, VIDEO, VOICE
if (F->ofd == nullptr)
return;
DBVARIANT dbv = { DBVT_DWORD };
dbv.dVal = pFile->local_->downloaded_size_;
db_event_setJson(F->ofd->hDbEvent, "ft", &dbv);
CMStringW wszFullName(F->ofd->wszPath);
int idxSlash = wszFullName.ReverseFind('\\') + 1;
if (wszFullName.Find('.', idxSlash) == -1) {
auto *pSlash = strrchr(pFile->local_->path_.c_str(), '\\');
if (!pSlash)
pSlash = pFile->local_->path_.c_str();
else
pSlash++;
if (strchr(pSlash, '.')) {
dbv.type = DBVT_UTF8;
dbv.pszVal = (char *)pSlash;
db_event_setJson(F->ofd->hDbEvent, "f", &dbv);
wszFullName.Truncate(idxSlash);
wszFullName.Append(Utf2T(pSlash));
F->ofd->ResetFileName(wszFullName); // resulting ofd->wszPath may differ from wszFullName
}
else {
int iFormat = ProtoGetAvatarFileFormat(wszExistingFile);
if (iFormat != PA_FORMAT_UNKNOWN) {
wszFullName.AppendChar('.');
wszFullName.Append(ProtoGetAvatarExtension(iFormat));
F->ofd->ResetFileName(wszFullName);
}
}
}
MoveFileW(wszExistingFile, F->ofd->wszPath);
F->ofd->Finish();
mir_cslock lck(m_csFiles);
m_arFiles.remove(F);
delete F;
}
return;
}
for (auto &it : m_arOwnMsg) {
if (it->tmpFileId == pFile->id_) {
if (!pRemote->id_.empty()) {
if (auto hDbEvent = db_event_getById(m_szModuleName, it->szMsgId)) {
DBVARIANT dbv = { DBVT_UTF8 };
dbv.pszVal = (char *)pRemote->id_.c_str();
db_event_setJson(hDbEvent, "u", &dbv);
// file is uploaded to the server
db_event_delivered(it->hContact, hDbEvent);
}
}
return;
}
}
for (auto &it : m_arUsers) {
if (it->szAvatarHash == pRemote->unique_id_.c_str()) {
PROTO_AVATAR_INFORMATION pai;
pai.hContact = it->hContact;
pai.format = ProtoGetAvatarFileFormat(wszExistingFile);
setByte(pai.hContact, DBKEY_AVATAR_TYPE, pai.format);
CMStringW wszAvatarPath(GetAvatarFilename(it->hContact));
wcsncpy_s(pai.filename, wszAvatarPath, _TRUNCATE);
MoveFileW(wszExistingFile, wszAvatarPath);
ProtoBroadcastAck(it->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &pai);
break;
}
}
}