diff options
Diffstat (limited to 'protocols/Teams/src/teams_utils.cpp')
-rw-r--r-- | protocols/Teams/src/teams_utils.cpp | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/protocols/Teams/src/teams_utils.cpp b/protocols/Teams/src/teams_utils.cpp new file mode 100644 index 0000000000..4fcc168a34 --- /dev/null +++ b/protocols/Teams/src/teams_utils.cpp @@ -0,0 +1,774 @@ +/* +Copyright (c) 2025 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 <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +#pragma warning(disable:4566) + +struct HtmlEntity +{ + const char *entity; + const char *symbol; +}; + +const HtmlEntity htmlEntities[] = +{ + { "AElig", "\u00C6" }, + { "Aacute", "\u00C1" }, + { "Acirc", "\u00C2" }, + { "Agrave", "\u00C0" }, + { "Alpha", "\u0391" }, + { "Aring", "\u00C5" }, + { "Atilde", "\u00C3" }, + { "Auml", "\u00C4" }, + { "Beta", "\u0392" }, + { "Ccedil", "\u00C7" }, + { "Chi", "\u03A7" }, + { "Dagger", "‡" }, + { "Delta", "\u0394" }, + { "ETH", "\u00D0" }, + { "Eacute", "\u00C9" }, + { "Ecirc", "\u00CA" }, + { "Egrave", "\u00C8" }, + { "Epsilon", "\u0395" }, + { "Eta", "\u0397" }, + { "Euml", "\u00CB" }, + { "Gamma", "\u0393" }, + { "Iacute", "\u00CD" }, + { "Icirc", "\u00CE" }, + { "Igrave", "\u00CC" }, + { "Iota", "\u0399" }, + { "Iuml", "\u00CF" }, + { "Kappa", "\u039A" }, + { "Lambda", "\u039B" }, + { "Mu", "\u039C" }, + { "Ntilde", "\u00D1" }, + { "Nu", "\u039D" }, + { "OElig", "\u0152" }, + { "Oacute", "\u00D3" }, + { "Ocirc", "\u00D4" }, + { "Ograve", "\u00D2" }, + { "Omega", "\u03A9" }, + { "Omicron", "\u039F" }, + { "Oslash", "\u00D8" }, + { "Otilde", "\u00D5" }, + { "Ouml", "\u00D6" }, + { "Phi", "\u03A6" }, + { "Pi", "\u03A0" }, + { "Prime", "\u2033" }, + { "Psi", "\u03A8" }, + { "Rho", "\u03A1" }, + { "Scaron", "Š" }, + { "Sigma", "Σ" }, + { "THORN", "Þ" }, + { "Tau", "Τ" }, + { "Theta", "Θ" }, + { "Uacute", "Ú" }, + { "Ucirc", "Û" }, + { "Ugrave", "Ù" }, + { "Upsilon", "Υ" }, + { "Uuml", "Ü" }, + { "Xi", "Ξ" }, + { "Yacute", "Ý" }, + { "Yuml", "Ÿ" }, + { "Zeta", "Ζ" }, + { "aacute", "á" }, + { "acirc", "â" }, + { "acute", "´" }, + { "aelig", "æ" }, + { "agrave", "à" }, + { "alefsym", "ℵ" }, + { "alpha", "α" }, + { "amp", "&" }, + { "and", "∧" }, + { "ang", "∠" }, + { "apos", "'" }, + { "aring", "å" }, + { "asymp", "≈" }, + { "atilde", "ã" }, + { "auml", "ä" }, + { "bdquo", "„" }, + { "beta", "β" }, + { "brvbar", "¦" }, + { "bull", "•" }, + { "cap", "∩" }, + { "ccedil", "ç" }, + { "cedil", "¸" }, + { "cent", "¢" }, + { "chi", "χ" }, + { "circ", "ˆ" }, + { "clubs", "♣" }, + { "cong", "≅" }, + { "copy", "©" }, + { "crarr", "↵" }, + { "cup", "∪" }, + { "curren", "¤" }, + { "dArr", "⇓" }, + { "dagger", "†" }, + { "darr", "↓" }, + { "deg", "°" }, + { "delta", "δ" }, + { "diams", "♦" }, + { "divide", "÷" }, + { "eacute", "é" }, + { "ecirc", "ê" }, + { "egrave", "è" }, + { "empty", "∅" }, + { "emsp", " " }, + { "ensp", " " }, + { "epsilon", "ε" }, + { "equiv", "≡" }, + { "eta", "η" }, + { "eth", "ð" }, + { "euml", "ë" }, + { "euro", "€" }, + { "exist", "∃" }, + { "fnof", "ƒ" }, + { "forall", "∀" }, + { "frac12", "½" }, + { "frac14", "¼" }, + { "frac34", "¾" }, + { "frasl", "⁄" }, + { "gamma", "γ" }, + { "ge", "≥" }, + { "gt", ">" }, + { "hArr", "⇔" }, + { "harr", "↔" }, + { "hearts", "♥" }, + { "hellip", "…" }, + { "iacute", "í" }, + { "icirc", "î" }, + { "iexcl", "¡" }, + { "igrave", "ì" }, + { "image", "ℑ" }, + { "infin", "∞" }, + { "int", "∫" }, + { "iota", "ι" }, + { "iquest", "¿" }, + { "isin", "∈" }, + { "iuml", "ï" }, + { "kappa", "κ" }, + { "lArr", "⇐" }, + { "lambda", "λ" }, + { "lang", "〈" }, + { "laquo", "«" }, + { "larr", "←" }, + { "lceil", "⌈" }, + { "ldquo", "“" }, + { "le", "≤" }, + { "lfloor", "⌊" }, + { "lowast", "∗" }, + { "loz", "◊" }, + { "lrm", "\xE2\x80\x8E" }, + { "lsaquo", "‹" }, + { "lsquo", "‘" }, + { "lt", "<" }, + { "macr", "¯" }, + { "mdash", "—" }, + { "micro", "µ" }, + { "middot", "·" }, + { "minus", "−" }, + { "mu", "μ" }, + { "nabla", "∇" }, + { "nbsp", " " }, + { "ndash", "–" }, + { "ne", "≠" }, + { "ni", "∋" }, + { "not", "¬" }, + { "notin", "∉" }, + { "nsub", "⊄" }, + { "ntilde", "ñ" }, + { "nu", "ν" }, + { "oacute", "ó" }, + { "ocirc", "ô" }, + { "oelig", "œ" }, + { "ograve", "ò" }, + { "oline", "‾" }, + { "omega", "ω" }, + { "omicron", "ο" }, + { "oplus", "⊕" }, + { "or", "∨" }, + { "ordf", "ª" }, + { "ordm", "º" }, + { "oslash", "ø" }, + { "otilde", "õ" }, + { "otimes", "⊗" }, + { "ouml", "ö" }, + { "para", "¶" }, + { "part", "∂" }, + { "permil", "‰" }, + { "perp", "⊥" }, + { "phi", "φ" }, + { "pi", "π" }, + { "piv", "ϖ" }, + { "plusmn", "±" }, + { "pound", "£" }, + { "prime", "′" }, + { "prod", "∏" }, + { "prop", "∝" }, + { "psi", "ψ" }, + { "quot", "\"" }, + { "rArr", "⇒" }, + { "radic", "√" }, + { "rang", "〉" }, + { "raquo", "»" }, + { "rarr", "→" }, + { "rceil", "⌉" }, + { "rdquo", "”" }, + { "real", "ℜ" }, + { "reg", "®" }, + { "rfloor", "⌋" }, + { "rho", "ρ" }, + { "rlm", "\xE2\x80\x8F" }, + { "rsaquo", "›" }, + { "rsquo", "’" }, + { "sbquo", "‚" }, + { "scaron", "š" }, + { "sdot", "⋅" }, + { "sect", "§" }, + { "shy", "\xC2\xAD" }, + { "sigma", "σ" }, + { "sigmaf", "ς" }, + { "sim", "∼" }, + { "spades", "♠" }, + { "sub", "⊂" }, + { "sube", "⊆" }, + { "sum", "∑" }, + { "sup", "⊃" }, + { "sup1", "¹" }, + { "sup2", "²" }, + { "sup3", "³" }, + { "supe", "⊇" }, + { "szlig", "ß" }, + { "tau", "τ" }, + { "there4", "∴" }, + { "theta", "θ" }, + { "thetasym", "ϑ" }, + { "thinsp", " " }, + { "thorn", "þ" }, + { "tilde", "˜" }, + { "times", "×" }, + { "trade", "™" }, + { "uArr", "⇑" }, + { "uacute", "ú" }, + { "uarr", "↑" }, + { "ucirc", "û" }, + { "ugrave", "ù" }, + { "uml", "¨" }, + { "upsih", "ϒ" }, + { "upsilon", "υ" }, + { "uuml", "ü" }, + { "weierp", "℘" }, + { "xi", "ξ" }, + { "yacute", "ý" }, + { "yen", "¥" }, + { "yuml", "ÿ" }, + { "zeta", "ζ" }, + { "zwj", "\xE2\x80\x8D" }, + { "zwnj", "\xE2\x80\x8C" } +}; + +static CMStringW getAttrText(const wchar_t *pwszText, const wchar_t *pwszAttrName) +{ + if (auto *p1 = mir_wstrstri(pwszText, pwszAttrName)) { + p1 += wcslen(pwszAttrName); + if (p1[0] == '=' && p1[1] == '\"') { + p1 += 2; + if (auto *p2 = wcschr(p1, '\"')) + return CMStringW(p1, p2 - p1); + } + } + return L""; +} + +CMStringW CTeamsProto::RemoveHtml(const CMStringW &data) +{ + CMStringW new_string; + + for (int i = 0; i < data.GetLength(); i++) { + wchar_t c = data[i]; + if (c == '<') { + if (m_bUseBBCodes) { + bool bEnable = true; + auto *p = data.c_str() + i + 1; + if (*p == '/') { + bEnable = false; + p++; + } + + if (!wcsncmp(p, L"b>", 2)) + new_string.Append(bEnable ? L"[b]" : L"[/b]"); + else if (!wcsncmp(p, L"i>", 2)) + new_string.Append(bEnable ? L"[i]" : L"[/i]"); + else if (!wcsncmp(p, L"u>", 2)) + new_string.Append(bEnable ? L"[u]" : L"[/u]"); + else if (!wcsncmp(p, L"s>", 2)) + new_string.Append(bEnable ? L"[s]" : L"[/s]"); + else if (!wcsncmp(p, L"pre ", 4)) + new_string.Append(L"[code]"); + else if (!wcsncmp(p, L"pre>", 4)) + new_string.Append(L"[/code]"); + else if (!wcsncmp(p, L"legacyquote>", 12)) { + if (bEnable) + i = data.Find(L"legacyquote>", i+13); + } + else if (!wcsncmp(p, L"quote ", 6)) { + CMStringW author(getAttrText(p, L"authorname")); + CMStringW timestamp(getAttrText(p, L"timestamp")); + + wchar_t wszTime[100]; + TimeZone_PrintTimeStamp(0, _wtoi(timestamp), L"D t", wszTime, _countof(wszTime), 0); + + new_string.AppendFormat(L"[quote]%s %s %s: \r\n", wszTime, author.c_str(), TranslateT("wrote")); + } + else if (!wcsncmp(p, L"quote>", 6)) { + new_string.Append(L"[/quote]"); + } + } + + i = data.Find('>', i); + if (i == -1) + break; + + continue; + } + + // special character + if (c == '&') { + int begin = i; + i = data.Find(';', i); + if (i == -1) + i = begin; + else { + CMStringW entity = data.Mid(begin + 1, i - begin - 1); + + bool found = false; + if (entity.GetLength() > 1 && entity[0] == '#') { + // Numeric replacement + bool hex = false; + if (entity[1] == 'x') { + hex = true; + entity.Delete(0, 2); + } + else entity.Delete(0, 1); + + if (!entity.IsEmpty()) { + found = true; + errno = 0; + unsigned long value = wcstoul(entity, nullptr, hex ? 16 : 10); + if (errno != 0) { // error with conversion in strtoul, ignore the result + found = false; + } + else if (value <= 127) { // U+0000 .. U+007F + new_string += (char)value; + } + else if (value >= 128 && value <= 2047) { // U+0080 .. U+07FF + new_string += (char)(192 + (value / 64)); + new_string += (char)(128 + (value % 64)); + } + else if (value >= 2048 && value <= 65535) { // U+0800 .. U+FFFF + new_string += (char)(224 + (value / 4096)); + new_string += (char)(128 + ((value / 64) % 64)); + new_string += (char)(128 + (value % 64)); + } + else { + new_string += (char)((value >> 24) & 0xFF); + new_string += (char)((value >> 16) & 0xFF); + new_string += (char)((value >> 8) & 0xFF); + new_string += (char)((value) & 0xFF); + } + } + } + else { + // Keyword replacement + CMStringA tmp = entity; + for (auto &it : htmlEntities) { + if (!mir_strcmpi(tmp, it.entity)) { + new_string += it.symbol; + found = true; + break; + } + } + } + + if (found) + continue; + else + i = begin; + } + } + + new_string.AppendChar(c); + } + + return new_string; +} + +bool AddBbcodes(CMStringA &str) +{ + bool bUsed = false; + CMStringA ret; + + for (const char *p = str; *p; p++) { + if (*p == '[') { + p++; + if (!strncmp(p, "b]", 2)) { + p++; + ret.Append("<b _pre=\"*\" _post=\"*\">"); + bUsed = true; + } + else if (!strncmp(p, "/b]", 3)) { + p += 2; + ret.Append("</b>"); + bUsed = true; + } + else if (!strncmp(p, "i]", 2)) { + p++; + ret.Append("<i _pre=\"_\" _post=\"_\">"); + bUsed = true; + } + else if (!strncmp(p, "/i]", 3)) { + p += 2; + ret.Append("</i>"); + bUsed = true; + } + else if (!strncmp(p, "s]", 2)) { + p++; + ret.Append("<s _pre=\"~\" _post=\"~\">"); + bUsed = true; + } + else if (!strncmp(p, "/s]", 3)) { + p += 2; + ret.Append("</s>"); + bUsed = true; + } + else if (!strncmp(p, "code]", 5)) { + p += 4; + ret.Append("<pre _pre=\"```\" _post=\"```\">"); + bUsed = true; + } + else if (!strncmp(p, "/code]", 6)) { + p += 5; + ret.Append("</pre>"); + bUsed = true; + } + } + else ret.AppendChar(*p); + } + + if (bUsed) + str = ret; + return bUsed; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +void Utf32toUtf16(uint32_t c, CMStringW &dest) +{ + if (c < 0x10000) + dest.AppendChar(c); + else { + unsigned int t = c - 0x10000; + dest.AppendChar((((t << 12) >> 22) + 0xD800)); + dest.AppendChar((((t << 22) >> 22) + 0xDC00)); + } +} + +bool is_surrogate(wchar_t uc) { return (uc - 0xd800u) < 2048u; } +bool is_high_surrogate(wchar_t uc) { return (uc & 0xfffffc00) == 0xd800; } +bool is_low_surrogate(wchar_t uc) { return (uc & 0xfffffc00) == 0xdc00; } + +uint32_t Utf16toUtf32(const wchar_t *str) +{ + if (!is_surrogate(str[0])) + return str[0]; + + if (is_high_surrogate(str[0]) && is_low_surrogate(str[1])) + return (str[0] << 10) + str[1] - 0x35fdc00; + + return 0; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +int TeamsToMirandaStatus(const char *status) +{ + if (!mir_strcmpi(status, "Available")) + return ID_STATUS_ONLINE; + if (!mir_strcmpi(status, "Away")) + return ID_STATUS_AWAY; + if (!mir_strcmpi(status, "BeRightBack")) + return ID_STATUS_NA; + if (!mir_strcmpi(status, "AvailableIdle")) + return ID_STATUS_IDLE; + if (!mir_strcmpi(status, "Busy")) + return ID_STATUS_OCCUPIED; + if (!mir_strcmpi(status, "DoNotDisturb")) + return ID_STATUS_DND; + return ID_STATUS_OFFLINE; +} + +////////////////////////////////////////////////////////////////////////////////////////// + +bool CTeamsProto::IsMe(const wchar_t *str) +{ + return (!mir_wstrcmpi(str, Utf2T(m_szMyname)) || !mir_wstrcmp(str, Utf2T(m_szOwnSkypeId))); +} + +bool CTeamsProto::IsMe(const char *str) +{ + return (!mir_strcmpi(str, m_szMyname) || !mir_strcmp(str, m_szOwnSkypeId)); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +int64_t CTeamsProto::getLastTime(MCONTACT hContact) +{ + ptrA szLastTime(getStringA(hContact, "LastMsgTime")); + return (szLastTime) ? _atoi64(szLastTime) : 0; +} + +void CTeamsProto::setLastTime(MCONTACT hContact, int64_t iValue) +{ + char buf[100]; + _i64toa(iValue, buf, 10); + setString(hContact, "LastMsgTime", buf); + + if (iValue > getLastTime(0)) + setString("LastMsgTime", buf); +} + +////////////////////////////////////////////////////////////////////////////////////////// + +bool CTeamsProto::IsFileExists(std::wstring path) +{ + return _waccess(path.c_str(), 0) == 0; +} + +int64_t getRandomId() +{ + int64_t ret; + Utils_GetRandom(&ret, sizeof(ret)); + if (ret < 0) + ret = -ret; + return ret; +} + +CMStringA getMessageId(const JSONNode &node) +{ + CMStringA ret(node["skypeeditedid"].as_mstring()); + if (ret.IsEmpty()) + ret = node["clientmessageid"].as_mstring(); + return ret; +} + +const char* GetSkypeNick(const char *szSkypeId) +{ + if (auto *p = strchr(szSkypeId, ':')) + return p + 1; + return szSkypeId; +} + +const wchar_t* GetSkypeNick(const wchar_t *szSkypeId) +{ + if (auto *p = wcsrchr(szSkypeId, ':')) + return p + 1; + return szSkypeId; +} + +void CTeamsProto::SetString(MCONTACT hContact, const char *pszSetting, const JSONNode &node) +{ + CMStringW str = node.as_mstring(); + if (str.IsEmpty() || str == L"null") + delSetting(hContact, pszSetting); + else + setWString(hContact, pszSetting, str); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// url parsing + +CMStringA ParseUrl(const char *url, const char *token) +{ + if (url == nullptr) + return CMStringA(); + + auto *start = strstr(url, token); + if (start == nullptr) + return CMStringA(); + + auto *end = strchr(++start, '/'); + if (end == nullptr) + return CMStringA(start); + return CMStringA(start, end - start); +} + +CMStringW ParseUrl(const wchar_t *url, const wchar_t *token) +{ + if (url == nullptr) + return CMStringW(); + + auto *start = wcsstr(url, token); + if (start == nullptr) + return CMStringW(); + + auto *end = wcschr(++start, '/'); + if (end == nullptr) + return CMStringW(start); + return CMStringW(start, end - start); +} + +///////////////////////////////////////////////////////////////////////////////////////// + +static int possibleTypes[] = { 1, 2, 4, 8, 19, 28 }; + +bool IsPossibleUserType(const char *pszUserId) +{ + int iType = atoi(pszUserId); + + // we don't process 28 and other shit + for (auto &it : possibleTypes) + if (it == iType) + return true; + + return false; +} + +CMStringA UrlToSkypeId(const char *url, int *pUserType) +{ + int userType = -1; + CMStringA szResult; + + if (url != nullptr) { + for (auto &it : possibleTypes) { + char tmp[10]; + sprintf_s(tmp, "/%d:", it); + if (strstr(url, tmp)) { + userType = it; + szResult = ParseUrl(url, tmp); + break; + } + } + } + + if (pUserType) + *pUserType = userType; + + return szResult; +} + +CMStringW UrlToSkypeId(const wchar_t *url, int *pUserType) +{ + int userType = -1; + CMStringW szResult; + + if (url != nullptr) { + for (auto &it : possibleTypes) { + wchar_t tmp[10]; + swprintf_s(tmp, L"/%d:", it); + if (wcsstr(url, tmp)) { + userType = it; + szResult = ParseUrl(url, tmp); + break; + } + } + } + + if (pUserType) + *pUserType = userType; + + return szResult; +} + +///////////////////////////////////////////////////////////////////////////////////////// + +INT_PTR CTeamsProto::ParseSkypeUriService(WPARAM, LPARAM lParam) +{ + wchar_t *arg = (wchar_t *)lParam; + if (arg == nullptr) + return 1; + + // skip leading prefix + wchar_t szUri[1024]; + wcsncpy_s(szUri, arg, _TRUNCATE); + wchar_t *szJid = wcschr(szUri, ':'); + if (szJid == nullptr) + return 1; + + // empty jid? + if (!*szJid) + return 1; + + // command code + wchar_t *szCommand = szJid; + szCommand = wcschr(szCommand, '?'); + if (szCommand) + *(szCommand++) = 0; + + // parameters + wchar_t *szSecondParam = szCommand ? wcschr(szCommand, '&') : nullptr; + if (szSecondParam) + *(szSecondParam++) = 0; + + // no command or message command + if (!szCommand || !mir_wstrcmpi(szCommand, L"chat")) { + if (szSecondParam) { + wchar_t *szChatId = wcsstr(szSecondParam, L"id="); + if (szChatId) { + szChatId += 5; + StartChatRoom(szChatId, szChatId); + return 0; + } + } + MCONTACT hContact = AddContact(_T2A(szJid), nullptr, true); + CallService(MS_MSG_SENDMESSAGE, (WPARAM)hContact, NULL); + return 0; + } + + if (!mir_wstrcmpi(szCommand, L"call")) { + MCONTACT hContact = AddContact(_T2A(szJid), nullptr, true); + NotifyEventHooks(g_hCallEvent, (WPARAM)hContact, (LPARAM)0); + return 0; + } + + if (!mir_wstrcmpi(szCommand, L"userinfo")) + return 0; + + if (!mir_wstrcmpi(szCommand, L"add")) { + MCONTACT hContact = FindContact(_T2A(szJid)); + if (hContact == NULL) { + PROTOSEARCHRESULT psr = { 0 }; + psr.cbSize = sizeof(psr); + psr.id.w = mir_wstrdup(szJid); + psr.nick.w = mir_wstrdup(szJid); + psr.flags = PSR_UNICODE; + Contact::AddBySearch(m_szModuleName, &psr); + } + return 0; + } + + if (!mir_wstrcmpi(szCommand, L"sendfile")) { + MCONTACT hContact = AddContact(_T2A(szJid), nullptr, true); + File::Send(hContact); + return 1; + } + + if (!mir_wstrcmpi(szCommand, L"voicemail")) + return 1; + + return 1; +} |