summaryrefslogtreecommitdiff
path: root/protocols/NewsAggregator/Src
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2019-03-02 12:32:44 +0300
committerGeorge Hazan <ghazan@miranda.im>2019-03-02 12:32:55 +0300
commit931a7dc1ac0dbc7e6c1083583ced915e572f5b47 (patch)
tree9fe9a6448d44030e26aa7107ce16044ed413e0d0 /protocols/NewsAggregator/Src
parentdd7d9954042254e66e3bbbec7195c6be8b1a0663 (diff)
all protocols (even virtual ones) moved to the Protocols folder
Diffstat (limited to 'protocols/NewsAggregator/Src')
-rw-r--r--protocols/NewsAggregator/Src/Authentication.cpp101
-rw-r--r--protocols/NewsAggregator/Src/CheckFeed.cpp473
-rw-r--r--protocols/NewsAggregator/Src/Icons.cpp53
-rw-r--r--protocols/NewsAggregator/Src/Menus.cpp83
-rw-r--r--protocols/NewsAggregator/Src/NewsAggregator.cpp106
-rw-r--r--protocols/NewsAggregator/Src/Options.cpp1016
-rw-r--r--protocols/NewsAggregator/Src/Options.h162
-rw-r--r--protocols/NewsAggregator/Src/Services.cpp261
-rw-r--r--protocols/NewsAggregator/Src/Update.cpp139
-rw-r--r--protocols/NewsAggregator/Src/Utils.cpp445
-rw-r--r--protocols/NewsAggregator/Src/resource.h57
-rw-r--r--protocols/NewsAggregator/Src/stdafx.cxx18
-rw-r--r--protocols/NewsAggregator/Src/stdafx.h167
-rw-r--r--protocols/NewsAggregator/Src/version.h13
14 files changed, 3094 insertions, 0 deletions
diff --git a/protocols/NewsAggregator/Src/Authentication.cpp b/protocols/NewsAggregator/Src/Authentication.cpp
new file mode 100644
index 0000000000..35c5cb36bd
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Authentication.cpp
@@ -0,0 +1,101 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+void CreateAuthString(char *auth, MCONTACT hContact, CFeedEditor *pDlg)
+{
+ wchar_t *tlogin = nullptr, *tpass = nullptr;
+ if (hContact && g_plugin.getByte(hContact, "UseAuth")) {
+ tlogin = g_plugin.getWStringA(hContact, "Login");
+ tpass = g_plugin.getWStringA(hContact, "Password");
+ }
+ else if (pDlg && pDlg->m_useauth.IsChecked()) {
+ tlogin = pDlg->m_login.GetText();
+ tpass = pDlg->m_password.GetText();
+ }
+ char *user = mir_u2a(tlogin), *pass = mir_u2a(tpass);
+
+ char str[MAX_PATH];
+ int len = mir_snprintf(str, "%s:%s", user, pass);
+ mir_free(user);
+ mir_free(pass);
+ mir_free(tlogin);
+ mir_free(tpass);
+
+ mir_snprintf(auth, 250, "Basic %s", ptrA(mir_base64_encode(str, (size_t)len)));
+}
+
+CAuthRequest::CAuthRequest(CFeedEditor *pDlg, MCONTACT hContact) :
+ CSuper(g_plugin, IDD_AUTHENTICATION),
+ m_feedname(this, IDC_FEEDNAME), m_username(this, IDC_FEEDUSERNAME),
+ m_password(this, IDC_FEEDPASSWORD), m_ok(this, IDOK)
+{
+ m_pDlg = pDlg;
+ m_hContact = hContact;
+ m_ok.OnClick = Callback(this, &CAuthRequest::OnOk);
+}
+
+bool CAuthRequest::OnInitDialog()
+{
+ if (m_pDlg) {
+ ptrW strfeedtitle(m_pDlg->m_feedtitle.GetText());
+
+ if (strfeedtitle)
+ m_feedname.SetText(strfeedtitle);
+ else {
+ ptrW strfeedurl(m_pDlg->m_feedurl.GetText());
+ m_feedname.SetText(strfeedurl);
+ }
+ }
+ else if (m_hContact) {
+ ptrW ptszNick(g_plugin.getWStringA(m_hContact, "Nick"));
+ if (!ptszNick)
+ ptszNick = g_plugin.getWStringA(m_hContact, "URL");
+ if (ptszNick)
+ m_feedname.SetText(ptszNick);
+ }
+ return true;
+}
+
+void CAuthRequest::OnOk(CCtrlBase*)
+{
+ ptrW strfeedusername(m_username.GetText());
+ if (!strfeedusername || mir_wstrcmp(strfeedusername, L"") == 0) {
+ MessageBox(m_hwnd, TranslateT("Enter your username"), TranslateT("Error"), MB_OK | MB_ICONERROR);
+ return;
+ }
+ ptrA strfeedpassword(m_password.GetTextA());
+ if (!strfeedpassword || mir_strcmp(strfeedpassword, "") == 0) {
+ MessageBox(m_hwnd, TranslateT("Enter your password"), TranslateT("Error"), MB_OK | MB_ICONERROR);
+ return;
+ }
+ if (m_pDlg) {
+ m_pDlg->m_useauth.SetState(1);
+ m_pDlg->m_login.Enable(1);
+ m_pDlg->m_password.Enable(1);
+ m_pDlg->m_login.SetText(strfeedusername);
+ m_pDlg->m_password.SetTextA(strfeedpassword);
+ }
+ else if (m_hContact) {
+ g_plugin.setByte(m_hContact, "UseAuth", 1);
+ g_plugin.setWString(m_hContact, "Login", strfeedusername);
+ g_plugin.setString(m_hContact, "Password", strfeedpassword);
+ }
+}
diff --git a/protocols/NewsAggregator/Src/CheckFeed.cpp b/protocols/NewsAggregator/Src/CheckFeed.cpp
new file mode 100644
index 0000000000..87282451f4
--- /dev/null
+++ b/protocols/NewsAggregator/Src/CheckFeed.cpp
@@ -0,0 +1,473 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+static CMStringA DetectEncoding(const TiXmlDocument &doc)
+{
+ auto *pChild = doc.FirstChild();
+ if (pChild)
+ if (auto *pDecl = pChild->ToDeclaration())
+ if (auto *pVal = pDecl->Value())
+ if (!memcmp(pVal, "xml", 3)) {
+ const char *p1 = strstr(pVal, "encoding=\""), *p2 = 0;
+ if (p1) {
+ p1 += 10;
+ p2 = strchr(p1, '\"');
+ }
+ if (p1 && p2)
+ return CMStringA(p1, int(p2-p1));
+ }
+
+ return CMStringA();
+}
+
+static wchar_t* EncodeResult(const char *szString, const CMStringA &szEncoding)
+{
+ if (szEncoding == "koi8-r")
+ return mir_a2u_cp(szString, 20866);
+ if (szEncoding == "windows-1251")
+ return mir_a2u_cp(szString, 1251);
+
+ return mir_utf8decodeW(szString);
+}
+
+static void SetAvatar(MCONTACT hContact, const char *pszValue)
+{
+ Utf2T url(pszValue);
+ g_plugin.setWString(hContact, "ImageURL", url);
+
+ PROTO_AVATAR_INFORMATION ai = { 0 };
+ ai.hContact = hContact;
+
+ ptrW szNick(g_plugin.getWStringA(hContact, "Nick"));
+ if (szNick) {
+ wchar_t *ext = wcsrchr((wchar_t *)url, '.') + 1;
+ ai.format = ProtoGetAvatarFormat(ext);
+
+ wchar_t *filename = szNick;
+ mir_snwprintf(ai.filename, L"%s\\%s.%s", tszRoot, filename, ext);
+ if (DownloadFile(url, ai.filename)) {
+ g_plugin.setWString(hContact, "ImagePath", ai.filename);
+ ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai, NULL);
+ }
+ else ProtoBroadcastAck(MODULENAME, hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, NULL);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// touches a feed to verify whether it exists
+
+LPCTSTR CheckFeed(wchar_t *tszURL, CFeedEditor *pEditDlg)
+{
+ Netlib_LogfW(hNetlibUser, L"Started validating feed %s.", tszURL);
+ char *szData = nullptr;
+ GetNewsData(tszURL, &szData, NULL, pEditDlg);
+ if (szData) {
+ TiXmlDocument doc;
+ int ret = doc.Parse(szData);
+ mir_free(szData);
+ if (ret == ERROR_SUCCESS) {
+ CMStringA codepage = DetectEncoding(doc);
+
+ for (auto *it : TiXmlEnum(&doc)) {
+ auto *szNodeName = it->Name();
+ const TiXmlElement *pNode;
+ if (!mir_strcmpi(szNodeName, "rss") || !mir_strcmpi(szNodeName, "rdf"))
+ pNode = it->FirstChildElement();
+ else if (!mir_strcmpi(szNodeName, "feed"))
+ pNode = it;
+ else continue;
+
+ for (auto *child : TiXmlFilter(pNode, "title")) {
+ wchar_t mes[MAX_PATH];
+ mir_snwprintf(mes, TranslateT("%s\nis a valid feed's address."), tszURL);
+ MessageBox(pEditDlg->GetHwnd(), mes, TranslateT("News Aggregator"), MB_OK | MB_ICONINFORMATION);
+ return EncodeResult(child->GetText(), codepage);
+ }
+ }
+ }
+ }
+
+ Netlib_LogfW(hNetlibUser, L"%s is not a valid feed's address.", tszURL);
+ wchar_t mes[MAX_PATH];
+ mir_snwprintf(mes, TranslateT("%s\nis not a valid feed's address."), tszURL);
+ MessageBox(pEditDlg->GetHwnd(), mes, TranslateT("News Aggregator"), MB_OK | MB_ICONERROR);
+ return nullptr;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// loads a feed completely, with messages
+
+static void XmlToMsg(MCONTACT hContact, CMStringW &title, CMStringW &link, CMStringW &descr, CMStringW &author, CMStringW &comments, CMStringW &guid, CMStringW &category, time_t stamp)
+{
+ CMStringW message = g_plugin.getWStringA(hContact, "MsgFormat");
+ if (!message)
+ message = TAGSDEFAULT;
+
+ if (title.IsEmpty())
+ message.Replace(L"#<title>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<title>#", title);
+
+ if (link.IsEmpty())
+ message.Replace(L"#<link>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<link>#", link);
+
+ if (descr.IsEmpty())
+ message.Replace(L"#<description>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<description>#", descr);
+
+ if (author.IsEmpty())
+ message.Replace(L"#<author>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<author>#", author);
+
+ if (comments.IsEmpty())
+ message.Replace(L"#<comments>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<comments>#", comments);
+
+ if (guid.IsEmpty())
+ message.Replace(L"#<guid>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<guid>#", guid);
+
+ if (category.IsEmpty())
+ message.Replace(L"#<category>#", TranslateT("empty"));
+ else
+ message.Replace(L"#<category>#", category);
+
+ DBEVENTINFO olddbei = {};
+ bool MesExist = false;
+ T2Utf pszTemp(message);
+ DWORD cbMemoLen = 10000, cbOrigLen = (DWORD)mir_strlen(pszTemp);
+ BYTE *pbBuffer = (BYTE*)mir_alloc(cbMemoLen);
+ for (MEVENT hDbEvent = db_event_last(hContact); hDbEvent; hDbEvent = db_event_prev(hContact, hDbEvent)) {
+ olddbei.cbBlob = db_event_getBlobSize(hDbEvent);
+ if (olddbei.cbBlob > cbMemoLen)
+ pbBuffer = (PBYTE)mir_realloc(pbBuffer, (size_t)(cbMemoLen = olddbei.cbBlob));
+ olddbei.pBlob = pbBuffer;
+ db_event_get(hDbEvent, &olddbei);
+
+ // there's no need to look for the elder events
+ if (stamp > 0 && olddbei.timestamp < (DWORD)stamp)
+ break;
+
+ if ((DWORD)mir_strlen((char*)olddbei.pBlob) == cbOrigLen && !mir_strcmp((char*)olddbei.pBlob, pszTemp)) {
+ MesExist = true;
+ break;
+ }
+ }
+ mir_free(pbBuffer);
+
+ if (!MesExist) {
+ if (stamp == 0)
+ stamp = time(0);
+
+ T2Utf pszMessage(message);
+
+ PROTORECVEVENT recv = { 0 };
+ recv.timestamp = (DWORD)stamp;
+ recv.szMessage = pszMessage;
+ ProtoChainRecvMsg(hContact, &recv);
+ }
+}
+
+void CheckCurrentFeed(MCONTACT hContact)
+{
+ // Check is disabled by the user?
+ if (!g_plugin.getByte(hContact, "CheckState", 1) != 0)
+ return;
+
+ wchar_t *szURL = g_plugin.getWStringA(hContact, "URL");
+ if (szURL == nullptr)
+ return;
+
+ Netlib_LogfW(hNetlibUser, L"Started checking feed %s.", szURL);
+
+ char *szData = nullptr;
+ GetNewsData(szURL, &szData, hContact, nullptr);
+ mir_free(szURL);
+
+ if (szData == nullptr)
+ return;
+
+ TiXmlDocument doc;
+ int ret = doc.Parse(szData);
+ mir_free(szData);
+ if (ret != ERROR_SUCCESS)
+ return;
+
+ CMStringA codepage = DetectEncoding(doc);
+
+ CMStringW szValue;
+
+ for (auto *it : TiXmlEnum(&doc)) {
+ auto *szNodeName = it->Name();
+ bool isRSS = !mir_strcmpi(szNodeName, "rss"), isAtom = !mir_strcmpi(szNodeName, "rdf");
+ if (isRSS || isAtom) {
+ if (isRSS) {
+ if (auto *pVersion = it->Attribute("version")) {
+ char ver[MAX_PATH];
+ mir_snprintf(ver, "RSS %s", pVersion);
+ g_plugin.setString(hContact, "MirVer", ver);
+ }
+ }
+ else if (isAtom)
+ g_plugin.setWString(hContact, "MirVer", L"RSS 1.0");
+
+ for (auto *child : TiXmlEnum(it->FirstChildElement())) {
+ auto *childName = child->Name();
+ if (!mir_strcmpi(childName, "title")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "FirstName", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(childName, "link")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "Homepage", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(childName, "description")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText) {
+ ClearText(szValue, szChildText);
+ g_plugin.setWString(hContact, "About", szValue);
+ db_set_ws(hContact, "CList", "StatusMsg", szValue);
+ }
+ }
+ else if (!mir_strcmpi(childName, "language")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "Language1", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(childName, "managingEditor")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "e-mail", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(childName, "category")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "Interest0Text", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(childName, "copyright")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ db_set_s(hContact, "UserInfo", "MyNotes", _T2A(ClearText(szValue, szChildText)));
+ }
+ else if (!mir_strcmpi(childName, "image")) {
+ for (auto *xmlImage : TiXmlFilter(child, "url"))
+ SetAvatar(hContact, xmlImage->GetText());
+ }
+ else if (!mir_strcmpi(childName, "lastBuildDate")) {
+ time_t stamp = DateToUnixTime(child->GetText(), 0);
+ double deltaupd = difftime(time(0), stamp);
+ double deltacheck = difftime(time(0), (time_t)g_plugin.getDword(hContact, "LastCheck"));
+ if (deltaupd - deltacheck >= 0) {
+ g_plugin.setDword(hContact, "LastCheck", (DWORD)time(0));
+ return;
+ }
+ }
+ else if (!mir_strcmpi(childName, "item")) {
+ CMStringW title, link, descr, author, comments, guid, category;
+ time_t stamp = 0;
+ for (auto *itemval : TiXmlEnum(child)) {
+ auto *itemName = itemval->Name();
+ ptrW value(EncodeResult(itemval->GetText(), codepage));
+
+ // We only use the first tag for now and ignore the rest.
+ if (!mir_strcmpi(itemName, "title"))
+ ClearText(title, value);
+
+ else if (!mir_strcmpi(itemName, "link"))
+ ClearText(link, value);
+
+ else if (!mir_strcmpi(itemName, "pubDate") || !mir_strcmpi(itemName, "date")) {
+ if (stamp == 0)
+ stamp = DateToUnixTime(itemval->GetText(), 0);
+ }
+ else if (!mir_strcmpi(itemName, "description") || !mir_strcmpi(itemName, "encoded"))
+ ClearText(descr, value);
+
+ else if (!mir_strcmpi(itemName, "author") || !mir_strcmpi(itemName, "creator"))
+ ClearText(author, value);
+
+ else if (!mir_strcmpi(itemName, "comments"))
+ ClearText(comments, value);
+
+ else if (!mir_strcmpi(itemName, "guid"))
+ ClearText(guid, value);
+
+ else if (!mir_strcmpi(itemName, "category"))
+ ClearText(category, value);
+ }
+
+ XmlToMsg(hContact, title, link, descr, author, comments, guid, category, stamp);
+ }
+ }
+ }
+ else if (!mir_strcmpi(szNodeName, "feed")) {
+ g_plugin.setWString(hContact, "MirVer", L"Atom 3");
+ for (auto *child : TiXmlEnum(it)) {
+ auto *szChildName = child->Name();
+ if (!mir_strcmpi(szChildName, "title")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "FirstName", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(szChildName, "link")) {
+ if (!child->Attribute("rel", "self"))
+ if (auto *pHref = child->Attribute("href"))
+ g_plugin.setWString(hContact, "Homepage", Utf2T(pHref));
+ }
+ else if (!mir_strcmpi(szChildName, "subtitle")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText) {
+ ClearText(szValue, szChildText);
+ g_plugin.setWString(hContact, "About", szValue);
+ db_set_ws(hContact, "CList", "StatusMsg", szValue);
+ }
+ }
+ else if (!mir_strcmpi(szChildName, "language")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "Language1", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(szChildName, "author")) {
+ for (auto *authorval : TiXmlFilter(child, "email")) {
+ g_plugin.setWString(hContact, "e-mail", ptrW(EncodeResult(authorval->GetText(), codepage)));
+ break;
+ }
+ }
+ else if (!mir_strcmpi(szChildName, "category")) {
+ ptrW szChildText(EncodeResult(child->GetText(), codepage));
+ if (szChildText)
+ g_plugin.setWString(hContact, "Interest0Text", ClearText(szValue, szChildText));
+ }
+ else if (!mir_strcmpi(szChildName, "icon")) {
+ for (auto *imageval : TiXmlFilter(child, "url"))
+ SetAvatar(hContact, imageval->GetText());
+ }
+ else if (!mir_strcmpi(szChildName, "updated")) {
+ time_t stamp = DateToUnixTime(child->GetText(), 1);
+ double deltaupd = difftime(time(0), stamp);
+ double deltacheck = difftime(time(0), (time_t)g_plugin.getDword(hContact, "LastCheck"));
+ if (deltaupd - deltacheck >= 0) {
+ g_plugin.setDword(hContact, "LastCheck", (DWORD)time(0));
+ return;
+ }
+ }
+ else if (!mir_strcmpi(szChildName, "entry")) {
+ CMStringW title, link, descr, author, comments, guid, category;
+ time_t stamp = 0;
+ for (auto *itemval : TiXmlEnum(child)) {
+ LPCSTR szItemName = itemval->Name();
+ if (!mir_strcmpi(szItemName, "title")) {
+ ptrW szItemText(EncodeResult(itemval->GetText(), codepage));
+ if (szItemText)
+ ClearText(title, szItemText);
+ }
+ else if (!mir_strcmpi(szItemName, "link")) {
+ if (auto *pszLink = itemval->Attribute("href"))
+ ClearText(link, ptrW(EncodeResult(pszLink, codepage)));
+ }
+ else if (!mir_strcmpi(szItemName, "updated")) {
+ if (stamp == 0)
+ stamp = DateToUnixTime(itemval->GetText(), 0);
+ }
+ else if (!mir_strcmpi(szItemName, "summary") || !mir_strcmpi(szItemName, "content")) {
+ ptrW szItemText(EncodeResult(itemval->GetText(), codepage));
+ if (szItemText)
+ ClearText(descr, szItemText);
+ }
+ else if (!mir_strcmpi(szItemName, "author")) {
+ for (auto *authorval : TiXmlFilter(itemval, "name")) {
+ ptrW szItemText(EncodeResult(authorval->GetText(), codepage));
+ if (szItemText)
+ ClearText(author, szItemText);
+ break;
+ }
+ }
+ else if (!mir_strcmpi(szItemName, "comments")) {
+ ptrW szItemText(EncodeResult(itemval->GetText(), codepage));
+ if (szItemText)
+ ClearText(comments, szItemText);
+ }
+ else if (!mir_strcmpi(szItemName, "id")) {
+ ptrW szItemText(EncodeResult(itemval->GetText(), codepage));
+ if (szItemText)
+ ClearText(guid, szItemText);
+ }
+ else if (!mir_strcmpi(szItemName, "category")) {
+ if (auto *p = itemval->Attribute("term"))
+ ClearText(link, ptrW(EncodeResult(p, codepage)));
+ }
+ }
+
+ XmlToMsg(hContact, title, link, descr, author, comments, guid, category, stamp);
+ }
+ }
+ }
+ }
+ g_plugin.setDword(hContact, "LastCheck", (DWORD)time(0));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// downloads avatars from a given feed
+
+void CheckCurrentFeedAvatar(MCONTACT hContact)
+{
+ if (!g_plugin.getByte(hContact, "CheckState", 1))
+ return;
+
+ wchar_t *szURL = g_plugin.getWStringA(hContact, "URL");
+ if (szURL == nullptr)
+ return;
+
+ char *szData = nullptr;
+ GetNewsData(szURL, &szData, hContact, nullptr);
+ mir_free(szURL);
+
+ if (szData == nullptr)
+ return;
+
+ TiXmlDocument doc;
+ int ret = doc.Parse(szData);
+ mir_free(szData);
+ if (ret != ERROR_SUCCESS)
+ return;
+
+ for (auto *it : TiXmlEnum(&doc)) {
+ auto *szNodeName = it->Name();
+ if (!mir_strcmpi(szNodeName, "rss") || !mir_strcmpi(szNodeName, "rdf")) {
+ for (auto *child : TiXmlFilter(it->FirstChildElement(), "image"))
+ for (auto *xmlImage : TiXmlFilter(child, "url"))
+ SetAvatar(hContact, xmlImage->GetText());
+ }
+ else if (!mir_strcmpi(szNodeName, "feed")) {
+ for (auto *child : TiXmlFilter(it, "icon"))
+ for (auto *xmlImage : TiXmlFilter(child, "url"))
+ SetAvatar(hContact, xmlImage->GetText());
+ }
+ }
+}
diff --git a/protocols/NewsAggregator/Src/Icons.cpp b/protocols/NewsAggregator/Src/Icons.cpp
new file mode 100644
index 0000000000..b9cdeb6166
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Icons.cpp
@@ -0,0 +1,53 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+static IconItem iconList[] =
+{
+ { LPGEN("Protocol icon"), "main", IDI_ICON},
+ { LPGEN("Check All Feeds"), "checkall", IDI_CHECKALL},
+ { LPGEN("Add Feed"), "addfeed", IDI_ADDFEED},
+ { LPGEN("Import Feeds"), "importfeeds", IDI_IMPORTFEEDS},
+ { LPGEN("Export Feeds"), "exportfeeds", IDI_EXPORTFEEDS},
+ { LPGEN("Check Feed"), "checkfeed", IDI_CHECKALL},
+ { LPGEN("Auto Update Enabled"), "enabled", IDI_ENABLED},
+ { LPGEN("Auto Update Disabled"), "disabled", IDI_DISABLED}
+};
+
+void InitIcons()
+{
+ g_plugin.registerIcon(LPGEN("News Aggregator"), iconList, MODULENAME);
+}
+
+HICON LoadIconEx(const char *name, bool big)
+{
+ char szSettingName[100];
+ mir_snprintf(szSettingName, "%s_%s", MODULENAME, name);
+ return IcoLib_GetIcon(szSettingName, big);
+}
+
+HANDLE GetIconHandle(const char *name)
+{
+ for (int i=0; i < _countof(iconList); i++)
+ if ( !mir_strcmp(iconList[i].szName, name))
+ return iconList[i].hIcolib;
+
+ return nullptr;
+}
diff --git a/protocols/NewsAggregator/Src/Menus.cpp b/protocols/NewsAggregator/Src/Menus.cpp
new file mode 100644
index 0000000000..7770e843eb
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Menus.cpp
@@ -0,0 +1,83 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+HGENMENU hService2[7];
+
+void InitMenu()
+{
+ CMenuItem mi(&g_plugin);
+ mi.flags = CMIF_UNICODE | CMIF_NOTOFFLINE;
+ mi.root = g_plugin.addRootMenu(MO_MAIN, LPGENW("News Aggregator"), 500099000);
+ Menu_ConfigureItem(mi.root, MCI_OPT_UID, "D9733E4F-1946-4390-8EB3-591E8687222E");
+
+ SET_UID(mi, 0x3ec91864, 0xefa7, 0x4994, 0xb7, 0x75, 0x6c, 0x96, 0xcb, 0x29, 0x2f, 0x93);
+ mi.position = 10100001;
+ if (g_plugin.getByte("AutoUpdate", 1))
+ mi.name.w = LPGENW("Auto Update Enabled");
+ else
+ mi.name.w = LPGENW("Auto Update Disabled");
+ mi.hIcolibItem = GetIconHandle("main");
+ mi.pszService = MS_NEWSAGGREGATOR_ENABLED;
+ hService2[0] = Menu_AddMainMenuItem(&mi);
+
+ SET_UID(mi, 0x8076bb4d, 0x1e44, 0x43af, 0x97, 0x1e, 0x31, 0xd8, 0xa4, 0xe9, 0xb8, 0x37);
+ mi.position = 20100001;
+ mi.name.w = LPGENW("Check All Feeds");
+ mi.pszService = MS_NEWSAGGREGATOR_CHECKALLFEEDS;
+ hService2[1] = Menu_AddMainMenuItem(&mi);
+
+ SET_UID(mi, 0xb876484d, 0x28aa, 0x4e03, 0x9e, 0x98, 0xed, 0xbc, 0xd1, 0xcf, 0x31, 0x80);
+ mi.position = 20100002;
+ mi.hIcolibItem = GetIconHandle("addfeed");
+ mi.name.w = LPGENW("Add Feed");
+ mi.pszService = MS_NEWSAGGREGATOR_ADDFEED;
+ hService2[2] = Menu_AddMainMenuItem(&mi);
+
+ SET_UID(mi, 0x600bf2c2, 0xa974, 0x44d3, 0x98, 0xf9, 0xe6, 0x65, 0x7c, 0x1f, 0x63, 0x37);
+ mi.position = 20100003;
+ mi.hIcolibItem = GetIconHandle("importfeeds");
+ mi.name.w = LPGENW("Import Feeds");
+ mi.pszService = MS_NEWSAGGREGATOR_IMPORTFEEDS;
+ hService2[3] = Menu_AddMainMenuItem(&mi);
+
+ SET_UID(mi, 0xc09c8119, 0x64c2, 0x49bd, 0x81, 0xf, 0x54, 0x20, 0x69, 0xd7, 0x30, 0xcf);
+ mi.position = 20100004;
+ mi.hIcolibItem = GetIconHandle("exportfeeds");
+ mi.name.w = LPGENW("Export Feeds");
+ mi.pszService = MS_NEWSAGGREGATOR_EXPORTFEEDS;
+ hService2[4] = Menu_AddMainMenuItem(&mi);
+
+ // adding contact menu items
+ SET_UID(mi, 0x92be499c, 0x928c, 0x4789, 0x8f, 0x36, 0x28, 0xa2, 0x9f, 0xb7, 0x1a, 0x97);
+ mi.root = nullptr;
+ mi.position = -0x7FFFFFFA;
+ mi.hIcolibItem = GetIconHandle("checkfeed");
+ mi.name.w = LPGENW("Check feed");
+ mi.pszService = MS_NEWSAGGREGATOR_CHECKFEED;
+ hService2[5] = Menu_AddContactMenuItem(&mi, MODULENAME);
+
+ SET_UID(mi, 0x41a70fbc, 0x9241, 0x44c0, 0x90, 0x90, 0x87, 0xd2, 0xc5, 0x9f, 0xc9, 0xac);
+ mi.name.w = LPGENW("Change feed");
+ mi.pszService = MS_NEWSAGGREGATOR_CHANGEFEED;
+ hService2[6] = Menu_AddContactMenuItem(&mi, MODULENAME);
+
+ Menu_ModifyItem(hService2[0], nullptr, GetIconHandle(g_plugin.getByte("AutoUpdate", 1) ? "enabled" : "disabled"));
+}
diff --git a/protocols/NewsAggregator/Src/NewsAggregator.cpp b/protocols/NewsAggregator/Src/NewsAggregator.cpp
new file mode 100644
index 0000000000..8ee17dd97a
--- /dev/null
+++ b/protocols/NewsAggregator/Src/NewsAggregator.cpp
@@ -0,0 +1,106 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+HANDLE hPrebuildMenuHook = nullptr;
+CDlgBase *pAddFeedDialog = nullptr, *pImportDialog = nullptr, *pExportDialog = nullptr;
+wchar_t tszRoot[MAX_PATH] = {0};
+HANDLE hUpdateMutex;
+
+LIST<CFeedEditor> g_arFeeds(1, PtrKeySortT);
+
+CMPlugin g_plugin;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+PLUGININFOEX pluginInfoEx = {
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {56CC3F29-CCBF-4546-A8BA-9856248A412A}
+ {0x56cc3f29, 0xccbf, 0x4546, {0xa8, 0xba, 0x98, 0x56, 0x24, 0x8a, 0x41, 0x2a}}
+};
+
+CMPlugin::CMPlugin() :
+ PLUGIN<CMPlugin>(MODULENAME, pluginInfoEx)
+{
+ RegisterProtocol(PROTOTYPE_VIRTUAL);
+ SetUniqueId("URL");
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPlugin::Load()
+{
+ // Add options hook
+ HookEvent(ME_OPT_INITIALISE, OptInit);
+ HookEvent(ME_SYSTEM_MODULESLOADED, NewsAggrInit);
+ HookEvent(ME_SYSTEM_PRESHUTDOWN, NewsAggrPreShutdown);
+
+ hUpdateMutex = CreateMutex(nullptr, FALSE, nullptr);
+
+ CreateProtoServiceFunction(MODULENAME, PS_GETNAME, NewsAggrGetName);
+ CreateProtoServiceFunction(MODULENAME, PS_GETCAPS, NewsAggrGetCaps);
+ CreateProtoServiceFunction(MODULENAME, PS_SETSTATUS, NewsAggrSetStatus);
+ CreateProtoServiceFunction(MODULENAME, PS_GETSTATUS, NewsAggrGetStatus);
+ CreateProtoServiceFunction(MODULENAME, PS_LOADICON, NewsAggrLoadIcon);
+ CreateProtoServiceFunction(MODULENAME, PSS_GETINFO, NewsAggrGetInfo);
+ CreateProtoServiceFunction(MODULENAME, PS_GETAVATARINFO, NewsAggrGetAvatarInfo);
+ CreateProtoServiceFunction(MODULENAME, PSR_MESSAGE, NewsAggrRecvMessage);
+
+ CreateServiceFunction(MS_NEWSAGGREGATOR_CHECKALLFEEDS, CheckAllFeeds);
+ CreateServiceFunction(MS_NEWSAGGREGATOR_ADDFEED, AddFeed);
+ CreateServiceFunction(MS_NEWSAGGREGATOR_IMPORTFEEDS, ImportFeeds);
+ CreateServiceFunction(MS_NEWSAGGREGATOR_EXPORTFEEDS, ExportFeeds);
+ CreateServiceFunction(MS_NEWSAGGREGATOR_CHECKFEED, CheckFeed);
+ CreateServiceFunction(MS_NEWSAGGREGATOR_CHANGEFEED, ChangeFeed);
+ CreateServiceFunction(MS_NEWSAGGREGATOR_ENABLED, EnableDisable);
+
+ HOTKEYDESC hkd = {};
+ hkd.dwFlags = HKD_UNICODE;
+ hkd.pszName = "NewsAggregator/CheckAllFeeds";
+ hkd.szDescription.w = LPGENW("Check All Feeds");
+ hkd.szSection.w = LPGENW("News Aggregator");
+ hkd.pszService = MS_NEWSAGGREGATOR_CHECKALLFEEDS;
+ hkd.DefHotKey = HOTKEYCODE(HOTKEYF_CONTROL+HKCOMB_A, 'O') | HKF_MIRANDA_LOCAL;
+ g_plugin.addHotkey(&hkd);
+
+ InitIcons();
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CMPlugin::Unload()
+{
+ DestroyUpdateList();
+ CloseHandle(hUpdateMutex);
+ return 0;
+}
diff --git a/protocols/NewsAggregator/Src/Options.cpp b/protocols/NewsAggregator/Src/Options.cpp
new file mode 100644
index 0000000000..6239ce15c1
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Options.cpp
@@ -0,0 +1,1016 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+CExportFeed::CExportFeed()
+ : CSuper(g_plugin, IDD_FEEDEXPORT),
+ m_feedslist(this, IDC_FEEDSLIST), m_feedsexportlist(this, IDC_FEEDSEXPORTLIST),
+ m_addfeed(this, IDC_ADDFEED), m_removefeed(this, IDC_REMOVEFEED),
+ m_addallfeeds(this, IDC_ADDALLFEEDS), m_removeallfeeds(this, IDC_REMOVEALLFEEDS),
+ m_ok(this, IDOK)
+{
+ m_addfeed.OnClick = Callback(this, &CExportFeed::OnAddFeed);
+ m_removefeed.OnClick = Callback(this, &CExportFeed::OnRemoveFeed);
+ m_addallfeeds.OnClick = Callback(this, &CExportFeed::OnAddAllFeeds);
+ m_removeallfeeds.OnClick = Callback(this, &CExportFeed::OnRemoveAllFeeds);
+ m_ok.OnClick = Callback(this, &CExportFeed::OnOk);
+
+ m_feedslist.OnDblClick = Callback(this, &CExportFeed::OnFeedsList);
+ m_feedsexportlist.OnDblClick = Callback(this, &CExportFeed::OnFeedsExportList);
+}
+
+bool CExportFeed::OnInitDialog()
+{
+ Utils_RestoreWindowPositionNoSize(m_hwnd, NULL, MODULENAME, "ExportDlg");
+ for (auto &hContact : Contacts(MODULENAME)) {
+ ptrW message(g_plugin.getWStringA(hContact, "Nick"));
+ if (message != nullptr)
+ m_feedslist.AddString(message);
+ }
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+ if (!m_feedslist.GetCount()) {
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+ }
+ return true;
+}
+
+void CExportFeed::OnAddFeed(CCtrlBase*)
+{
+ if (!m_removefeed.Enabled())
+ m_removefeed.Enable();
+ if (!m_removeallfeeds.Enabled())
+ m_removeallfeeds.Enable();
+ if (!m_ok.Enabled())
+ m_ok.Enable();
+ int cursel = m_feedslist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedslist.GetItemText(cursel, item, _countof(item));
+ m_feedsexportlist.AddString(item);
+ m_feedslist.DeleteString(cursel);
+ if (!m_feedslist.GetCount()) {
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+ }
+}
+
+void CExportFeed::OnRemoveFeed(CCtrlBase*)
+{
+ if (!m_addfeed.Enabled())
+ m_addfeed.Enable();
+ if (!m_addallfeeds.Enabled())
+ m_addallfeeds.Enable();
+ int cursel = m_feedsexportlist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedsexportlist.GetItemText(cursel, item, _countof(item));
+ m_feedslist.AddString(item);
+ m_feedsexportlist.DeleteString(cursel);
+ if (!m_feedsexportlist.GetCount()) {
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+ }
+}
+
+void CExportFeed::OnAddAllFeeds(CCtrlBase*)
+{
+ if (!m_removefeed.Enabled())
+ m_removefeed.Enable();
+ if (!m_removeallfeeds.Enabled())
+ m_removeallfeeds.Enable();
+ if (!m_ok.Enabled())
+ m_ok.Enable();
+ int count = m_feedslist.GetCount();
+ for (int i = 0; i < count; i++) {
+ wchar_t item[MAX_PATH];
+ m_feedslist.GetItemText(i, item, _countof(item));
+ m_feedsexportlist.AddString(item);
+ }
+ for (int i = count - 1; i > -1; i--)
+ m_feedslist.DeleteString(i);
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+}
+
+void CExportFeed::OnRemoveAllFeeds(CCtrlBase*)
+{
+ if (!m_addfeed.Enabled())
+ m_addfeed.Enable();
+ if (!m_addallfeeds.Enabled())
+ m_addallfeeds.Enable();
+ int count = m_feedsexportlist.GetCount();
+ for (int i = 0; i < count; i++) {
+ wchar_t item[MAX_PATH];
+ m_feedsexportlist.GetItemText(i, item, _countof(item));
+ m_feedslist.AddString(item);
+ }
+ for (int i = count - 1; i > -1; i--)
+ m_feedsexportlist.DeleteString(i);
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+}
+
+void CExportFeed::OnFeedsList(CCtrlBase*)
+{
+ if (!m_removefeed.Enabled())
+ m_removefeed.Enable();
+ if (!m_removeallfeeds.Enabled())
+ m_removeallfeeds.Enable();
+ if (!m_ok.Enabled())
+ m_ok.Enable();
+ int cursel = m_feedslist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedslist.GetItemText(cursel, item, _countof(item));
+ m_feedsexportlist.AddString(item);
+ m_feedslist.DeleteString(cursel);
+ if (!m_feedslist.GetCount()) {
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+ }
+}
+
+void CExportFeed::OnFeedsExportList(CCtrlBase*)
+{
+ if (!m_addfeed.Enabled())
+ m_addfeed.Enable();
+ if (!m_addallfeeds.Enabled())
+ m_addallfeeds.Enable();
+ int cursel = m_feedsexportlist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedsexportlist.GetItemText(cursel, item, _countof(item));
+ m_feedslist.AddString(item);
+ m_feedsexportlist.DeleteString(cursel);
+ if (!m_feedsexportlist.GetCount()) {
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+ }
+}
+
+static const TiXmlElement* AdviceNode(const TiXmlElement *node)
+{
+ auto *tmpnode = node;
+
+ // try to rest on the same level first
+ node = node->NextSiblingElement();
+ if (node)
+ return node;
+
+ do {
+ // go up one level
+ node = tmpnode->Parent()->ToElement();
+ tmpnode = node;
+ node = node->NextSiblingElement();
+ if (node)
+ return node;
+ }
+ while (mir_strcmpi(tmpnode->Name(), "body"));
+
+ // nothing found or we reached body
+ return nullptr;
+}
+
+void CExportFeed::OnOk(CCtrlBase*)
+{
+ wchar_t FileName[MAX_PATH];
+ VARSW tszMirDir(L"%miranda_path%");
+
+ OPENFILENAME ofn = { 0 };
+ ofn.lStructSize = sizeof(ofn);
+ wchar_t tmp[MAX_PATH];
+ mir_snwprintf(tmp, L"%s (*.opml)%c*.opml%c%c", TranslateT("OPML files"), 0, 0, 0);
+ ofn.lpstrFilter = tmp;
+ ofn.hwndOwner = nullptr;
+ ofn.lpstrFile = FileName;
+ ofn.nMaxFile = MAX_PATH;
+ ofn.nMaxFileTitle = MAX_PATH;
+ ofn.Flags = OFN_HIDEREADONLY | OFN_SHAREAWARE | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
+ ofn.lpstrInitialDir = tszMirDir;
+ *FileName = '\0';
+ ofn.lpstrDefExt = L"";
+ if (!GetSaveFileName(&ofn))
+ return;
+
+ TiXmlDocument doc;
+ auto *hXml = doc.NewElement("opml"); doc.InsertEndChild(hXml);
+ hXml->SetAttribute("version", "1.0");
+
+ auto *xmlHeader = doc.NewElement("head"); hXml->InsertEndChild(xmlHeader);
+ auto *xmlTitle = doc.NewElement("title"); xmlTitle->SetText("Miranda NG NewsAggregator plugin export"); xmlHeader->InsertEndChild(xmlTitle);
+
+ auto *xmlBody = doc.NewElement("body"); hXml->InsertEndChild(xmlBody);
+
+ int count = m_feedsexportlist.GetCount();
+ for (int i = 0; i < count; i++) {
+ wchar_t item[MAX_PATH];
+ m_feedsexportlist.GetItemText(i, item, _countof(item));
+ MCONTACT hContact = GetContactByNick(item);
+ wchar_t
+ *title = g_plugin.getWStringA(hContact, "Nick"),
+ *url = g_plugin.getWStringA(hContact, "URL"),
+ *siteurl = g_plugin.getWStringA(hContact, "Homepage"),
+ *group = db_get_wsa(hContact, "CList", "Group");
+
+ TiXmlElement *elem = xmlBody;
+ if (group) {
+ wchar_t *section = wcstok(group, L"\\");
+ while (section != nullptr) {
+ TiXmlElement *existgroup = 0;
+ for (auto *it : TiXmlFilter(elem, "outline")) {
+ if (it->Attribute("title", T2Utf(section))) {
+ existgroup = (TiXmlElement*)it;
+ break;
+ }
+ }
+
+ if (!existgroup) {
+ auto *pNew = doc.NewElement("outline");
+ pNew->SetAttribute("title", section); pNew->SetAttribute("text", section);
+ elem->InsertEndChild(pNew);
+ elem = pNew;
+ }
+ else elem = existgroup;
+
+ section = wcstok(nullptr, L"\\");
+ }
+ }
+
+ auto *pNew = doc.NewElement("outline"); elem->InsertEndChild(pNew);
+ pNew->SetAttribute("text", title);
+ pNew->SetAttribute("title", title);
+ pNew->SetAttribute("type", "rss");
+ pNew->SetAttribute("xmlUrl", url);
+ pNew->SetAttribute("htmlUrl", siteurl);
+
+ mir_free(title);
+ mir_free(url);
+ mir_free(siteurl);
+ mir_free(group);
+ }
+
+ FILE *out = _wfopen(FileName, L"wb");
+ if (out) {
+ tinyxml2::XMLPrinter printer(out);
+ doc.Print(&printer);
+ fclose(out);
+ }
+}
+
+bool CExportFeed::OnClose()
+{
+ Utils_SaveWindowPosition(m_hwnd, NULL, MODULENAME, "ExportDlg");
+ if (pExportDialog)
+ pExportDialog = nullptr;
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CImportFeed::CImportFeed(CCtrlListView *m_feeds)
+ : CSuper(g_plugin, IDD_FEEDIMPORT),
+ m_importfile(this, IDC_IMPORTFILEPATH), m_browsefile(this, IDC_BROWSEIMPORTFILE),
+ m_feedslist(this, IDC_FEEDSLIST), m_feedsimportlist(this, IDC_FEEDSIMPORTLIST),
+ m_addfeed(this, IDC_ADDFEED), m_removefeed(this, IDC_REMOVEFEED),
+ m_addallfeeds(this, IDC_ADDALLFEEDS), m_removeallfeeds(this, IDC_REMOVEALLFEEDS),
+ m_ok(this, IDOK)
+{
+ m_list = m_feeds;
+ m_browsefile.OnClick = Callback(this, &CImportFeed::OnBrowseFile);
+ m_addfeed.OnClick = Callback(this, &CImportFeed::OnAddFeed);
+ m_removefeed.OnClick = Callback(this, &CImportFeed::OnRemoveFeed);
+ m_addallfeeds.OnClick = Callback(this, &CImportFeed::OnAddAllFeeds);
+ m_removeallfeeds.OnClick = Callback(this, &CImportFeed::OnRemoveAllFeeds);
+ m_ok.OnClick = Callback(this, &CImportFeed::OnOk);
+
+ m_feedslist.OnDblClick = Callback(this, &CImportFeed::OnFeedsList);
+ m_feedsimportlist.OnDblClick = Callback(this, &CImportFeed::OnFeedsImportList);
+}
+
+bool CImportFeed::OnInitDialog()
+{
+ Utils_RestoreWindowPositionNoSize(m_hwnd, NULL, MODULENAME, "ImportDlg");
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+ return true;
+}
+
+void CImportFeed::OnBrowseFile(CCtrlBase*)
+{
+ wchar_t FileName[MAX_PATH];
+ VARSW tszMirDir(L"%miranda_path%");
+
+ OPENFILENAME ofn = { 0 };
+ ofn.lStructSize = sizeof(ofn);
+ wchar_t tmp[MAX_PATH];
+ mir_snwprintf(tmp, L"%s (*.opml, *.xml)%c*.opml;*.xml%c%c", TranslateT("OPML files"), 0, 0, 0);
+ ofn.lpstrFilter = tmp;
+ ofn.hwndOwner = nullptr;
+ ofn.lpstrFile = FileName;
+ ofn.nMaxFile = MAX_PATH;
+ ofn.nMaxFileTitle = MAX_PATH;
+ ofn.Flags = OFN_HIDEREADONLY;
+ ofn.lpstrInitialDir = tszMirDir;
+ *FileName = '\0';
+ ofn.lpstrDefExt = L"";
+ if (!GetOpenFileName(&ofn))
+ return;
+
+ FILE *in = _wfopen(FileName, L"rb");
+ if (in == nullptr)
+ return;
+
+ TiXmlDocument doc;
+ int res = doc.LoadFile(in);
+ fclose(in);
+ if (res != 0) {
+ MessageBox(m_hwnd, TranslateT("Not valid import file."), TranslateT("Error"), MB_OK | MB_ICONERROR);
+ return;
+ }
+
+ m_importfile.SetText(FileName);
+
+ auto *node = TiXmlConst(&doc)["opml"]["body"]["outline"].ToElement();
+ if (!node)
+ node = TiXmlConst(&doc)["body"]["outline"].ToElement();
+ if (node == nullptr) {
+ MessageBox(m_hwnd, TranslateT("Not valid import file."), TranslateT("Error"), MB_OK | MB_ICONERROR);
+ return;
+ }
+
+ while (node) {
+ auto *pszUrl = node->Attribute("xmlUrl");
+ if (!pszUrl && node->NoChildren())
+ node = AdviceNode(node);
+ else if (!pszUrl && !node->NoChildren())
+ node = node->FirstChildElement();
+ else if (pszUrl) {
+ if (auto *pszText = node->Attribute("text")) {
+ Utf2T text(pszText);
+ m_feedslist.AddString(text);
+ m_addfeed.Enable();
+ m_addallfeeds.Enable();
+ }
+
+ node = AdviceNode(node);
+ }
+ }
+}
+
+void CImportFeed::OnAddFeed(CCtrlBase*)
+{
+ if (!m_removefeed.Enabled())
+ m_removefeed.Enable();
+ if (!m_removeallfeeds.Enabled())
+ m_removeallfeeds.Enable();
+ if (!m_ok.Enabled())
+ m_ok.Enable();
+ int cursel = m_feedslist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedslist.GetItemText(cursel, item, _countof(item));
+ m_feedsimportlist.AddString(item);
+ m_feedslist.DeleteString(cursel);
+ if (!m_feedslist.GetCount()) {
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+ }
+}
+
+void CImportFeed::OnRemoveFeed(CCtrlBase*)
+{
+ if (!m_addfeed.Enabled())
+ m_addfeed.Enable();
+ if (!m_addallfeeds.Enabled())
+ m_addallfeeds.Enable();
+ int cursel = m_feedsimportlist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedsimportlist.GetItemText(cursel, item, _countof(item));
+ m_feedslist.AddString(item);
+ m_feedsimportlist.DeleteString(cursel);
+ if (!m_feedsimportlist.GetCount()) {
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+ }
+}
+
+void CImportFeed::OnAddAllFeeds(CCtrlBase*)
+{
+ if (!m_removefeed.Enabled())
+ m_removefeed.Enable();
+ if (!m_removeallfeeds.Enabled())
+ m_removeallfeeds.Enable();
+ if (!m_ok.Enabled())
+ m_ok.Enable();
+ int count = m_feedslist.GetCount();
+ for (int i = 0; i < count; i++) {
+ wchar_t item[MAX_PATH];
+ m_feedslist.GetItemText(i, item, _countof(item));
+ m_feedsimportlist.AddString(item);
+ }
+ for (int i = count - 1; i > -1; i--)
+ m_feedslist.DeleteString(i);
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+}
+
+void CImportFeed::OnRemoveAllFeeds(CCtrlBase*)
+{
+ if (!m_addfeed.Enabled())
+ m_addfeed.Enable();
+ if (!m_addallfeeds.Enabled())
+ m_addallfeeds.Enable();
+ int count = m_feedsimportlist.GetCount();
+ for (int i = 0; i < count; i++) {
+ wchar_t item[MAX_PATH];
+ m_feedsimportlist.GetItemText(i, item, _countof(item));
+ m_feedslist.AddString(item);
+ }
+ for (int i = count - 1; i > -1; i--)
+ m_feedsimportlist.DeleteString(i);
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+}
+
+void CImportFeed::OnFeedsList(CCtrlBase*)
+{
+ if (!m_removefeed.Enabled())
+ m_removefeed.Enable();
+ if (!m_removeallfeeds.Enabled())
+ m_removeallfeeds.Enable();
+ if (!m_ok.Enabled())
+ m_ok.Enable();
+ int cursel = m_feedslist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedslist.GetItemText(cursel, item, _countof(item));
+ m_feedsimportlist.AddString(item);
+ m_feedslist.DeleteString(cursel);
+ if (!m_feedslist.GetCount()) {
+ m_addfeed.Disable();
+ m_addallfeeds.Disable();
+ }
+}
+
+void CImportFeed::OnFeedsImportList(CCtrlBase*)
+{
+ if (!m_addfeed.Enabled())
+ m_addfeed.Enable();
+ if (!m_addallfeeds.Enabled())
+ m_addallfeeds.Enable();
+ int cursel = m_feedsimportlist.GetCurSel();
+ wchar_t item[MAX_PATH];
+ m_feedsimportlist.GetItemText(cursel, item, _countof(item));
+ m_feedslist.AddString(item);
+ m_feedsimportlist.DeleteString(cursel);
+ if (!m_feedsimportlist.GetCount()) {
+ m_removefeed.Disable();
+ m_removeallfeeds.Disable();
+ m_ok.Disable();
+ }
+}
+
+void CImportFeed::OnOk(CCtrlBase*)
+{
+ wchar_t FileName[MAX_PATH];
+ m_importfile.GetText(FileName, _countof(FileName));
+
+ FILE *in = _wfopen(FileName, L"rb");
+ if (in == nullptr)
+ return;
+
+ TiXmlDocument doc;
+ int res = doc.LoadFile(in);
+ fclose(in);
+ if (res != 0)
+ return;
+
+ auto *node = TiXmlConst(&doc)["opml"]["body"]["outline"].ToElement();
+ if (!node)
+ node = TiXmlConst(&doc)["body"]["outline"].ToElement();
+ if (node == nullptr)
+ return;
+
+ int count = m_feedsimportlist.GetCount();
+ int DUPES = 0;
+
+ while (node) {
+ auto *pszUrl = node->Attribute("xmlUrl");
+ if (!pszUrl && node->NoChildren())
+ node = AdviceNode(node);
+ else if (!pszUrl && !node->NoChildren())
+ node = node->FirstChildElement();
+ else if (pszUrl) {
+ wchar_t *text = nullptr, *url = nullptr, *siteurl = nullptr;
+ bool bNeedToImport = false;
+
+ if (auto *pszText = node->Attribute("text")) {
+ text = mir_utf8decodeW(pszText);
+
+ for (int j = 0; j < count; j++) {
+ wchar_t item[MAX_PATH];
+ m_feedsimportlist.GetItemText(j, item, _countof(item));
+ if (!mir_wstrcmpi(item, text)) {
+ bNeedToImport = true;
+ break;
+ }
+ }
+ }
+
+ if (auto *pszText = node->Attribute("xmlUrl")) {
+ url = mir_utf8decodeW(pszText);
+ if (GetContactByURL(url) && bNeedToImport) {
+ bNeedToImport = false;
+ DUPES++;
+ }
+ }
+
+ if (auto *pszText = node->Attribute("htmlUrl"))
+ siteurl = mir_utf8decodeW(pszText);
+
+ if (bNeedToImport && text && url && siteurl) {
+ CMStringW wszGroup;
+ auto *parent = node->Parent()->ToElement();
+ while (mir_strcmpi(parent->Name(), "body")) {
+ if (auto *pszText = parent->Attribute("text")) {
+ if (!wszGroup.IsEmpty())
+ wszGroup.Insert(0, L"\\");
+ wszGroup.Insert(0, Utf2T(pszText));
+ }
+ parent = parent->Parent()->ToElement();
+ }
+
+ MCONTACT hContact = db_add_contact();
+ Proto_AddToContact(hContact, MODULENAME);
+ g_plugin.setWString(hContact, "Nick", text);
+ g_plugin.setWString(hContact, "URL", url);
+ g_plugin.setWString(hContact, "Homepage", siteurl);
+ g_plugin.setByte(hContact, "CheckState", 1);
+ g_plugin.setDword(hContact, "UpdateTime", DEFAULT_UPDATE_TIME);
+ g_plugin.setWString(hContact, "MsgFormat", TAGSDEFAULT);
+ g_plugin.setWord(hContact, "Status", Proto_GetStatus(MODULENAME));
+
+ if (m_list != nullptr) {
+ int iItem = m_list->AddItem(text, -1);
+ m_list->SetItem(iItem, 1, url);
+ m_list->SetCheckState(iItem, 1);
+ }
+
+ if (!wszGroup.IsEmpty()) {
+ db_set_ws(hContact, "CList", "Group", wszGroup);
+ Clist_GroupCreate(0, wszGroup);
+ }
+ }
+ mir_free(text);
+ mir_free(url);
+ mir_free(siteurl);
+
+ node = AdviceNode(node);
+ }
+ }
+
+ wchar_t mes[MAX_PATH];
+ if (DUPES)
+ mir_snwprintf(mes, TranslateT("Imported %d feed(s)\r\nNot imported %d duplicate(s)."), count - DUPES, DUPES);
+ else
+ mir_snwprintf(mes, TranslateT("Imported %d feed(s)."), count);
+ MessageBox(m_hwnd, mes, TranslateT("News Aggregator"), MB_OK | MB_ICONINFORMATION);
+}
+
+bool CImportFeed::OnClose()
+{
+ Utils_SaveWindowPosition(m_hwnd, NULL, MODULENAME, "ImportDlg");
+ if (pImportDialog)
+ pImportDialog = nullptr;
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CFeedEditor::CFeedEditor(int iItem, CCtrlListView *m_feeds, MCONTACT Contact)
+ : CSuper(g_plugin, IDD_ADDFEED),
+ m_feedtitle(this, IDC_FEEDTITLE), m_feedurl(this, IDC_FEEDURL),
+ m_checktime(this, IDC_CHECKTIME), m_checktimespin(this, IDC_TIMEOUT_VALUE_SPIN, 999),
+ m_checkfeed(this, IDC_DISCOVERY), m_useauth(this, IDC_USEAUTH),
+ m_login(this, IDC_LOGIN), m_password(this, IDC_PASSWORD),
+ m_tagedit(this, IDC_TAGSEDIT), m_reset(this, IDC_RESET),
+ m_help(this, IDC_TAGHELP), m_ok(this, IDOK), m_iItem(iItem)
+{
+ m_list = m_feeds;
+ m_hContact = Contact;
+ m_checkfeed.OnClick = Callback(this, &CFeedEditor::OnCheckFeed);
+ m_useauth.OnChange = Callback(this, &CFeedEditor::OnUseAuth);
+ m_reset.OnClick = Callback(this, &CFeedEditor::OnReset);
+ m_help.OnClick = Callback(this, &CFeedEditor::OnHelp);
+ m_ok.OnClick = Callback(this, &CFeedEditor::OnOk);
+}
+
+bool CFeedEditor::OnInitDialog()
+{
+ if (m_iItem == -1 && m_hContact == NULL)
+ SetWindowText(m_hwnd, TranslateT("Add Feed"));
+ else
+ SetWindowText(m_hwnd, TranslateT("Change Feed"));
+ m_checktime.SetMaxLength(3);
+
+ if (m_iItem > -1 && m_hContact == 0) {
+ wchar_t SelNick[MAX_PATH], SelUrl[MAX_PACKAGE_NAME];
+ m_list->GetItemText(m_iItem, 0, SelNick, _countof(SelNick));
+ m_list->GetItemText(m_iItem, 1, SelUrl, _countof(SelNick));
+
+ for (auto &hContact : Contacts(MODULENAME)) {
+ ptrW dbNick(g_plugin.getWStringA(hContact, "Nick"));
+ if ((dbNick == NULL) || (mir_wstrcmp(dbNick, SelNick) != 0))
+ continue;
+
+ ptrW dbURL(g_plugin.getWStringA(hContact, "URL"));
+ if ((dbURL == NULL) || (mir_wstrcmp(dbURL, SelUrl) != 0))
+ continue;
+
+ m_hContact = hContact;
+ m_feedtitle.SetText(SelNick);
+ m_feedurl.SetText(SelUrl);
+ m_checktime.SetInt(g_plugin.getDword(hContact, "UpdateTime", DEFAULT_UPDATE_TIME));
+
+ ptrW szMsgFormat(g_plugin.getWStringA(hContact, "MsgFormat"));
+ if (szMsgFormat)
+ m_tagedit.SetText(szMsgFormat);
+
+ if (g_plugin.getByte(hContact, "UseAuth", 0)) {
+ m_useauth.SetState(1);
+ m_login.Enable();
+ m_password.Enable();
+
+ ptrW szLogin(g_plugin.getWStringA(hContact, "Login"));
+ if (szLogin)
+ m_login.SetText(szLogin);
+
+ pass_ptrA pwd(g_plugin.getStringA(hContact, "Password"));
+ m_password.SetTextA(pwd);
+ }
+ g_arFeeds.insert(this);
+ Utils_RestoreWindowPositionNoSize(m_hwnd, hContact, MODULENAME, "ChangeDlg");
+ break;
+ }
+ }
+ else if (m_iItem == -1 && m_hContact == NULL) {
+ m_feedurl.SetText(L"http://");
+ m_tagedit.SetText(TAGSDEFAULT);
+ m_checktime.SetInt(DEFAULT_UPDATE_TIME);
+ Utils_RestoreWindowPositionNoSize(m_hwnd, NULL, MODULENAME, "AddDlg");
+ }
+ else if (m_hContact != NULL) {
+ ptrW dbNick(g_plugin.getWStringA(m_hContact, "Nick"));
+ ptrW dbURL(g_plugin.getWStringA(m_hContact, "URL"));
+
+ m_feedtitle.SetText(dbNick);
+ m_feedurl.SetText(dbURL);
+ m_checktime.SetInt(g_plugin.getDword(m_hContact, "UpdateTime", DEFAULT_UPDATE_TIME));
+
+ ptrW szMsgFormat(g_plugin.getWStringA(m_hContact, "MsgFormat"));
+ if (szMsgFormat)
+ m_tagedit.SetText(szMsgFormat);
+
+ if (g_plugin.getByte(m_hContact, "UseAuth")) {
+ m_useauth.SetState(1);
+ m_login.Enable();
+ m_password.Enable();
+
+ ptrW szLogin(g_plugin.getWStringA(m_hContact, "Login"));
+ if (szLogin)
+ m_login.SetText(szLogin);
+
+ pass_ptrA pwd(g_plugin.getStringA(m_hContact, "Password"));
+ m_password.SetTextA(pwd);
+ }
+ g_arFeeds.insert(this);
+ Utils_RestoreWindowPositionNoSize(m_hwnd, m_hContact, MODULENAME, "ChangeDlg");
+ }
+ return true;
+}
+
+void CFeedEditor::OnCheckFeed(CCtrlBase*)
+{
+ m_checkfeed.Disable();
+ m_checkfeed.SetText(TranslateT("Wait..."));
+ wchar_t *tszTitle = nullptr;
+ ptrW strfeedurl(m_feedurl.GetText());
+ if (strfeedurl || mir_wstrcmp(strfeedurl, L"http://") != 0 || mir_wstrcmp(strfeedurl, L"") != 0)
+ tszTitle = (wchar_t*)CheckFeed(strfeedurl, this);
+ else
+ MessageBox(m_hwnd, TranslateT("Enter Feed URL"), TranslateT("Error"), MB_OK);
+ m_feedtitle.SetText(tszTitle);
+ mir_free(tszTitle);
+ m_checkfeed.Enable();
+ m_checkfeed.SetText(TranslateT("Check Feed"));
+}
+
+void CFeedEditor::OnReset(CCtrlBase*)
+{
+ if (MessageBox(m_hwnd, TranslateT("Are you sure?"), TranslateT("Tags Mask Reset"), MB_YESNO | MB_ICONWARNING) == IDYES)
+ m_tagedit.SetText(TAGSDEFAULT);
+}
+
+void CFeedEditor::OnHelp(CCtrlBase*)
+{
+ CMStringW wszTagHelp;
+ wszTagHelp.Format(L"%s - %s\n%s - %s\n%s - %s\n%s - %s\n%s - %s\n%s - %s\n%s - %s",
+ L"#<title>#", TranslateT("The title of the item."),
+ L"#<description>#", TranslateT("The item synopsis."),
+ L"#<link>#", TranslateT("The URL of the item."),
+ L"#<author>#", TranslateT("Email address of the author of the item."),
+ L"#<comments>#", TranslateT("URL of a page for comments relating to the item."),
+ L"#<guid>#", TranslateT("A string that uniquely identifies the item."),
+ L"#<category>#", TranslateT("Specify one or more categories that the item belongs to."));
+ MessageBox(m_hwnd, wszTagHelp, TranslateT("Feed Tag Help"), MB_OK);
+}
+
+void CFeedEditor::OnOk(CCtrlBase*)
+{
+ ptrW strfeedtitle(m_feedtitle.GetText());
+ if (!strfeedtitle || mir_wstrcmp(strfeedtitle, L"") == 0) {
+ MessageBox(m_hwnd, TranslateT("Enter Feed name"), TranslateT("Error"), MB_OK);
+ return;
+ }
+
+ ptrW strfeedurl(m_feedurl.GetText());
+ if (!strfeedurl || mir_wstrcmp(strfeedurl, L"http://") == 0 || mir_wstrcmp(strfeedurl, L"") == 0) {
+ MessageBox(m_hwnd, TranslateT("Enter Feed URL"), TranslateT("Error"), MB_OK);
+ return;
+ }
+
+ ptrW strtagedit(m_tagedit.GetText());
+ if (!strtagedit || mir_wstrcmp(strtagedit, L"") == 0) {
+ MessageBox(m_hwnd, TranslateT("Enter message format"), TranslateT("Error"), MB_OK);
+ return;
+ }
+
+ MCONTACT hContact;
+ if (m_iItem == -1 && m_hContact == NULL) {
+ hContact = db_add_contact();
+ Proto_AddToContact(hContact, MODULENAME);
+ g_plugin.setByte(hContact, "CheckState", 1);
+ }
+ else hContact = m_hContact;
+
+ g_plugin.setWString(hContact, "Nick", strfeedtitle);
+ g_plugin.setWString(hContact, "URL", strfeedurl);
+ g_plugin.setDword(hContact, "UpdateTime", m_checktime.GetInt());
+ g_plugin.setWString(hContact, "MsgFormat", strtagedit);
+ g_plugin.setWord(hContact, "Status", Proto_GetStatus(MODULENAME));
+ if (m_useauth.IsChecked()) {
+ g_plugin.setByte(hContact, "UseAuth", 1);
+ g_plugin.setWString(hContact, "Login", m_login.GetText());
+ g_plugin.setString(hContact, "Password", m_password.GetTextA());
+ }
+ else {
+ g_plugin.delSetting(hContact, "UseAuth");
+ g_plugin.delSetting(hContact, "Login");
+ g_plugin.delSetting(hContact, "Password");
+ }
+
+ if (m_iItem == -1 && m_list != nullptr && m_hContact == NULL) {
+ int iItem = m_list->AddItem(strfeedtitle, -1);
+ m_list->SetItem(iItem, 1, strfeedurl);
+ m_list->SetCheckState(iItem, 1);
+ }
+ else if (m_iItem > -1) {
+ m_list->SetItem(m_iItem, 0, strfeedtitle);
+ m_list->SetItem(m_iItem, 1, strfeedurl);
+ }
+}
+
+bool CFeedEditor::OnClose()
+{
+ g_arFeeds.remove(this);
+ Utils_SaveWindowPosition(m_hwnd, NULL, MODULENAME, m_iItem == -1 ? "AddDlg" : "ChangeDlg");
+ if (pAddFeedDialog == this)
+ pAddFeedDialog = nullptr;
+ return true;
+}
+
+void CFeedEditor::OnUseAuth(CCtrlBase*)
+{
+ m_login.Enable(m_useauth.GetState());
+ m_password.Enable(m_useauth.GetState());
+}
+
+void COptionsMain::UpdateList()
+{
+ for (auto &hContact : Contacts(MODULENAME)) {
+ UpdateListFlag = TRUE;
+ ptrW ptszNick(g_plugin.getWStringA(hContact, "Nick"));
+ if (ptszNick) {
+ int iItem = m_feeds.AddItem(ptszNick, -1);
+
+ ptrW ptszURL(g_plugin.getWStringA(hContact, "URL"));
+ if (ptszURL) {
+ m_feeds.SetItem(iItem, 1, ptszURL);
+ m_feeds.SetCheckState(iItem, g_plugin.getByte(hContact, "CheckState", 1));
+ }
+ }
+ }
+ UpdateListFlag = FALSE;
+}
+
+COptionsMain::COptionsMain() :
+ CDlgBase(g_plugin, IDD_OPTIONS),
+ m_feeds(this, IDC_FEEDLIST),
+ m_add(this, IDC_ADD),
+ m_change(this, IDC_CHANGE),
+ m_delete(this, IDC_REMOVE),
+ m_import(this, IDC_IMPORT),
+ m_export(this, IDC_EXPORT),
+ m_checkonstartup(this, IDC_STARTUPRETRIEVE)
+{
+ CreateLink(m_checkonstartup, "StartupRetrieve", DBVT_BYTE, 1);
+
+ m_add.OnClick = Callback(this, &COptionsMain::OnAddButtonClick);
+ m_change.OnClick = Callback(this, &COptionsMain::OnChangeButtonClick);
+ m_delete.OnClick = Callback(this, &COptionsMain::OnDeleteButtonClick);
+ m_import.OnClick = Callback(this, &COptionsMain::OnImportButtonClick);
+ m_export.OnClick = Callback(this, &COptionsMain::OnExportButtonClick);
+
+ m_feeds.OnItemChanged = Callback(this, &COptionsMain::OnFeedListItemChanged);
+ m_feeds.OnDoubleClick = Callback(this, &COptionsMain::OnFeedListDoubleClick);
+
+}
+
+bool COptionsMain::OnInitDialog()
+{
+ CDlgBase::OnInitDialog();
+ m_change.Disable();
+ m_delete.Disable();
+ m_feeds.SetExtendedListViewStyle(LVS_EX_FULLROWSELECT | LVS_EX_CHECKBOXES);
+ m_feeds.AddColumn(0, TranslateT("Feed"), 160);
+ m_feeds.AddColumn(1, TranslateT("URL"), 276);
+ UpdateList();
+ return true;
+}
+
+bool COptionsMain::OnApply()
+{
+ for (auto &hContact : Contacts(MODULENAME)) {
+ ptrW dbNick(g_plugin.getWStringA(hContact, "Nick"));
+ for (int i = 0; i < m_feeds.GetItemCount(); i++) {
+ wchar_t nick[MAX_PATH];
+ m_feeds.GetItemText(i, 0, nick, _countof(nick));
+ if (mir_wstrcmp(dbNick, nick) == 0) {
+ g_plugin.setByte(hContact, "CheckState", m_feeds.GetCheckState(i));
+ if (!m_feeds.GetCheckState(i))
+ db_set_b(hContact, "CList", "Hidden", 1);
+ else
+ db_unset(hContact, "CList", "Hidden");
+ }
+ }
+ }
+ return true;
+}
+
+void COptionsMain::OnAddButtonClick(CCtrlBase*)
+{
+ if (pAddFeedDialog == nullptr) {
+ pAddFeedDialog = new CFeedEditor(-1, &m_feeds, NULL);
+ pAddFeedDialog->SetParent(m_hwnd);
+ pAddFeedDialog->Show();
+ }
+ else {
+ SetForegroundWindow(pAddFeedDialog->GetHwnd());
+ SetFocus(pAddFeedDialog->GetHwnd());
+ }
+}
+
+void COptionsMain::OnChangeButtonClick(CCtrlBase*)
+{
+ int isel = m_feeds.GetSelectionMark();
+ CFeedEditor *pDlg = nullptr;
+ for (auto &it : g_arFeeds) {
+ wchar_t nick[MAX_PATH], url[MAX_PATH];
+ m_feeds.GetItemText(isel, 0, nick, _countof(nick));
+ m_feeds.GetItemText(isel, 1, url, _countof(url));
+
+ ptrW dbNick(g_plugin.getWStringA(it->getContact(), "Nick"));
+ if ((dbNick == NULL) || (mir_wstrcmp(dbNick, nick) != 0))
+ continue;
+
+ ptrW dbURL(g_plugin.getWStringA(it->getContact(), "URL"));
+ if ((dbURL == NULL) || (mir_wstrcmp(dbURL, url) != 0))
+ continue;
+
+ pDlg = it;
+ }
+
+ if (pDlg == nullptr) {
+ pDlg = new CFeedEditor(isel, &m_feeds, NULL);
+ pDlg->SetParent(m_hwnd);
+ pDlg->Show();
+ }
+ else {
+ SetForegroundWindow(pDlg->GetHwnd());
+ SetFocus(pDlg->GetHwnd());
+ }
+}
+
+void COptionsMain::OnDeleteButtonClick(CCtrlBase*)
+{
+ if (MessageBox(m_hwnd, TranslateT("Are you sure?"), TranslateT("Contact deleting"), MB_YESNO | MB_ICONWARNING) == IDYES) {
+ wchar_t nick[MAX_PATH], url[MAX_PATH];
+ int isel = m_feeds.GetSelectionMark();
+ m_feeds.GetItemText(isel, 0, nick, _countof(nick));
+ m_feeds.GetItemText(isel, 1, url, _countof(url));
+
+ for (auto &hContact : Contacts(MODULENAME)) {
+ ptrW dbNick(g_plugin.getWStringA(hContact, "Nick"));
+ if (dbNick == NULL)
+ break;
+ if (mir_wstrcmp(dbNick, nick))
+ continue;
+
+ ptrW dbURL(g_plugin.getWStringA(hContact, "URL"));
+ if (dbURL == NULL)
+ break;
+ if (mir_wstrcmp(dbURL, url))
+ continue;
+
+ db_delete_contact(hContact);
+ m_feeds.DeleteItem(isel);
+ break;
+ }
+ }
+}
+
+void COptionsMain::OnImportButtonClick(CCtrlBase*)
+{
+ if (pImportDialog == nullptr) {
+ pImportDialog = new CImportFeed(&m_feeds);
+ pImportDialog->Show();
+ pImportDialog->SetParent(m_hwnd);
+ }
+}
+
+void COptionsMain::OnExportButtonClick(CCtrlBase*)
+{
+ if (pExportDialog == nullptr) {
+ pExportDialog = new CExportFeed();
+ pExportDialog->Show();
+ pExportDialog->SetParent(m_hwnd);
+ }
+}
+
+void COptionsMain::OnFeedListItemChanged(CCtrlListView::TEventInfo *evt)
+{
+ int isel = m_feeds.GetSelectionMark();
+ if (isel == -1) {
+ m_change.Disable();
+ m_delete.Disable();
+ }
+ else {
+ m_change.Enable();
+ m_delete.Enable();
+ }
+ if (((evt->nmlv->uNewState ^ evt->nmlv->uOldState) & LVIS_STATEIMAGEMASK) && !UpdateListFlag)
+ NotifyChange();
+}
+
+void COptionsMain::OnFeedListDoubleClick(CCtrlBase*)
+{
+ int isel = m_feeds.GetHotItem();
+ if (isel != -1) {
+ CFeedEditor *pDlg = new CFeedEditor(isel, &m_feeds, 0);
+ pDlg->SetParent(m_hwnd);
+ pDlg->Show();
+ }
+}
+
+int OptInit(WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = {};
+ odp.flags = ODPF_BOLDGROUPS | ODPF_UNICODE;
+ odp.szGroup.w = LPGENW("Network");
+ odp.szTitle.w = LPGENW("News Aggregator");
+ odp.pDialog = new COptionsMain();
+ g_plugin.addOptions(wParam, &odp);
+ return 0;
+}
diff --git a/protocols/NewsAggregator/Src/Options.h b/protocols/NewsAggregator/Src/Options.h
new file mode 100644
index 0000000000..2389eb9bb9
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Options.h
@@ -0,0 +1,162 @@
+#ifndef _OPTIONS_H_
+#define _OPTIONS_H_
+
+class COptionsMain : public CDlgBase
+{
+private:
+ CCtrlListView m_feeds;
+ CCtrlButton m_add;
+ CCtrlButton m_change;
+ CCtrlButton m_delete;
+ CCtrlButton m_import;
+ CCtrlButton m_export;
+ CCtrlCheck m_checkonstartup;
+
+protected:
+ bool OnInitDialog() override;
+ bool OnApply() override;
+
+ void OnAddButtonClick(CCtrlBase*);
+ void OnChangeButtonClick(CCtrlBase*);
+ void OnDeleteButtonClick(CCtrlBase*);
+ void OnImportButtonClick(CCtrlBase*);
+ void OnExportButtonClick(CCtrlBase*);
+
+ void OnFeedListItemChanged(CCtrlListView::TEventInfo *evt);
+ void OnFeedListDoubleClick(CCtrlBase*);
+
+ void UpdateList();
+
+public:
+ COptionsMain();
+};
+
+class CFeedEditor : public CDlgBase
+{
+ friend class CAuthRequest;
+
+private:
+ typedef CDlgBase CSuper;
+
+ int m_iItem;
+ CCtrlListView *m_list;
+ MCONTACT m_hContact;
+
+ CCtrlEdit m_feedtitle;
+ CCtrlEdit m_feedurl;
+ CCtrlEdit m_checktime;
+ CCtrlSpin m_checktimespin;
+ CCtrlButton m_checkfeed;
+ CCtrlEdit m_tagedit;
+ CCtrlButton m_reset;
+ CCtrlButton m_help;
+ CCtrlButton m_ok;
+
+protected:
+ bool OnInitDialog() override;
+ bool OnClose() override;
+
+ void OnCheckFeed(CCtrlBase*);
+ void OnReset(CCtrlBase*);
+ void OnHelp(CCtrlBase*);
+ void OnOk(CCtrlBase*);
+ void OnUseAuth(CCtrlBase*);
+
+public:
+ CCtrlCheck m_useauth;
+ CCtrlEdit m_login;
+ CCtrlEdit m_password;
+
+ CFeedEditor(int iItem, CCtrlListView *m_list, MCONTACT Contact);
+
+ __inline MCONTACT getContact() const { return m_hContact; }
+};
+
+class CImportFeed : public CDlgBase
+{
+private:
+ typedef CDlgBase CSuper;
+
+ CCtrlListView *m_list;
+
+ CCtrlEdit m_importfile;
+ CCtrlButton m_browsefile;
+ CCtrlListBox m_feedslist;
+ CCtrlListBox m_feedsimportlist;
+ CCtrlButton m_addfeed;
+ CCtrlButton m_removefeed;
+ CCtrlButton m_addallfeeds;
+ CCtrlButton m_removeallfeeds;
+ CCtrlButton m_ok;
+
+protected:
+ bool OnInitDialog() override;
+ bool OnClose() override;
+
+ void OnBrowseFile(CCtrlBase*);
+ void OnAddFeed(CCtrlBase*);
+ void OnRemoveFeed(CCtrlBase*);
+ void OnAddAllFeeds(CCtrlBase*);
+ void OnRemoveAllFeeds(CCtrlBase*);
+ void OnOk(CCtrlBase*);
+
+ void OnFeedsList(CCtrlBase*);
+ void OnFeedsImportList(CCtrlBase*);
+
+public:
+ CImportFeed(CCtrlListView *m_list);
+};
+
+class CExportFeed : public CDlgBase
+{
+private:
+ typedef CDlgBase CSuper;
+
+ CCtrlListBox m_feedslist;
+ CCtrlListBox m_feedsexportlist;
+ CCtrlButton m_addfeed;
+ CCtrlButton m_removefeed;
+ CCtrlButton m_addallfeeds;
+ CCtrlButton m_removeallfeeds;
+ CCtrlButton m_ok;
+
+protected:
+ bool OnInitDialog() override;
+ bool OnClose() override;
+
+ void OnAddFeed(CCtrlBase*);
+ void OnRemoveFeed(CCtrlBase*);
+ void OnAddAllFeeds(CCtrlBase*);
+ void OnRemoveAllFeeds(CCtrlBase*);
+ void OnOk(CCtrlBase*);
+
+ void OnFeedsList(CCtrlBase*);
+ void OnFeedsExportList(CCtrlBase*);
+
+public:
+ CExportFeed();
+};
+
+class CAuthRequest : public CDlgBase
+{
+private:
+ typedef CDlgBase CSuper;
+
+ CFeedEditor *m_pDlg;
+ MCONTACT m_hContact;
+
+ CCtrlBase m_feedname;
+ CCtrlEdit m_username;
+ CCtrlEdit m_password;
+ CCtrlButton m_ok;
+
+protected:
+ bool OnInitDialog() override;
+
+ void OnOk(CCtrlBase*);
+
+public:
+ CAuthRequest(CFeedEditor *pDlg, MCONTACT hContact);
+};
+
+#endif //_OPTIONS_H_ \ No newline at end of file
diff --git a/protocols/NewsAggregator/Src/Services.cpp b/protocols/NewsAggregator/Src/Services.cpp
new file mode 100644
index 0000000000..b5b8c9f028
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Services.cpp
@@ -0,0 +1,261 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+int g_nStatus = ID_STATUS_ONLINE;
+UINT_PTR timerId = 0;
+HANDLE hTBButton = nullptr, hNewsAggregatorFolder = nullptr;
+
+int OnFoldersChanged(WPARAM, LPARAM)
+{
+ FoldersGetCustomPathT(hNewsAggregatorFolder, tszRoot, MAX_PATH, L"");
+ return 0;
+}
+
+int NewsAggrInit(WPARAM, LPARAM)
+{
+ if (hNewsAggregatorFolder = FoldersRegisterCustomPathT(LPGEN("Avatars"), LPGEN("News Aggregator"), MIRANDA_USERDATAT L"\\Avatars\\" _A2W(DEFAULT_AVATARS_FOLDER)))
+ FoldersGetCustomPathT(hNewsAggregatorFolder, tszRoot, MAX_PATH, L"");
+ else
+ mir_wstrncpy(tszRoot, VARSW(L"%miranda_userdata%\\Avatars\\" _A2W(DEFAULT_AVATARS_FOLDER)), _countof(tszRoot));
+
+ for (auto &hContact : Contacts(MODULENAME)) {
+ if (!g_plugin.getByte("StartupRetrieve", 1))
+ g_plugin.setDword(hContact, "LastCheck", (DWORD)time(0));
+ g_plugin.setWord(hContact, "Status", ID_STATUS_ONLINE);
+ }
+
+ NetlibInit();
+ InitMenu();
+
+ HookEvent(ME_TTB_MODULELOADED, OnToolbarLoaded);
+ HookEvent(ME_FOLDERS_PATH_CHANGED, OnFoldersChanged);
+
+ // timer for the first update
+ timerId = SetTimer(nullptr, 0, 10000, timerProc2); // first update is 10 sec after load
+
+ return 0;
+}
+
+int NewsAggrPreShutdown(WPARAM, LPARAM)
+{
+ KillTimer(nullptr, timerId);
+ NetlibUnInit();
+ return 0;
+}
+
+INT_PTR NewsAggrGetName(WPARAM wParam, LPARAM lParam)
+{
+ if(lParam) {
+ mir_strncpy((char *)lParam, MODULENAME, wParam);
+ return 0;
+ }
+
+ return 1;
+}
+
+INT_PTR NewsAggrGetCaps(WPARAM wp, LPARAM)
+{
+ switch(wp) {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_PEER2PEER;
+ case PFLAGNUM_3:
+ case PFLAGNUM_2:
+ return PF2_ONLINE;
+ case PFLAGNUM_4:
+ return PF4_AVATARS;
+ case PFLAG_UNIQUEIDTEXT:
+ return (INT_PTR) "News Feed";
+ default:
+ return 0;
+ }
+}
+
+INT_PTR NewsAggrSetStatus(WPARAM wp, LPARAM)
+{
+ int nStatus = (int)wp;
+ if ((ID_STATUS_ONLINE == nStatus) || (ID_STATUS_OFFLINE == nStatus)) {
+ int nOldStatus = g_nStatus;
+ if(nStatus != g_nStatus) {
+ g_nStatus = nStatus;
+
+ for (auto &hContact : Contacts(MODULENAME))
+ g_plugin.setWord(hContact, "Status", nStatus);
+
+ ProtoBroadcastAck(MODULENAME, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)nOldStatus, (LPARAM)g_nStatus);
+ }
+ }
+
+ return 0;
+}
+
+INT_PTR NewsAggrGetStatus(WPARAM, LPARAM)
+{
+ return g_nStatus;
+}
+
+INT_PTR NewsAggrLoadIcon(WPARAM wParam, LPARAM)
+{
+ return (LOWORD(wParam) == PLI_PROTOCOL) ? (INT_PTR)CopyIcon(LoadIconEx("main", FALSE)) : 0;
+}
+
+static void __cdecl AckThreadProc(void *param)
+{
+ Sleep(100);
+ ProtoBroadcastAck(MODULENAME, (MCONTACT)param, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1);
+}
+
+INT_PTR NewsAggrGetInfo(WPARAM, LPARAM lParam)
+{
+ CCSDATA *ccs = (CCSDATA *)lParam;
+ mir_forkthread(AckThreadProc, (void*)ccs->hContact);
+ return 0;
+}
+
+INT_PTR CheckAllFeeds(WPARAM, LPARAM lParam)
+{
+ for (auto &hContact : Contacts(MODULENAME)) {
+ if (lParam && g_plugin.getDword(hContact, "UpdateTime", DEFAULT_UPDATE_TIME))
+ UpdateListAdd(hContact);
+ else if (!lParam)
+ UpdateListAdd(hContact);
+ }
+ if (!ThreadRunning)
+ mir_forkthread(UpdateThreadProc);
+
+ return 0;
+}
+
+INT_PTR AddFeed(WPARAM, LPARAM)
+{
+ if (pAddFeedDialog == nullptr) {
+ pAddFeedDialog = new CFeedEditor(-1, nullptr, NULL);
+ pAddFeedDialog->Show();
+ }
+ else {
+ SetForegroundWindow(pAddFeedDialog->GetHwnd());
+ SetFocus(pAddFeedDialog->GetHwnd());
+ }
+ return 0;
+}
+
+INT_PTR ChangeFeed(WPARAM hContact, LPARAM)
+{
+ CFeedEditor *pDlg = nullptr;
+ for (auto &it : g_arFeeds)
+ if (it->getContact() == hContact)
+ pDlg = it;
+
+ if (pDlg == nullptr) {
+ pDlg = new CFeedEditor(-1, nullptr, (MCONTACT)hContact);
+ pDlg->Show();
+ }
+ else {
+ SetForegroundWindow(pDlg->GetHwnd());
+ SetFocus(pDlg->GetHwnd());
+ }
+ return 0;
+}
+
+INT_PTR ImportFeeds(WPARAM, LPARAM)
+{
+ if (pImportDialog == nullptr)
+ pImportDialog = new CImportFeed(nullptr);
+ pImportDialog->Show();
+ return 0;
+}
+
+INT_PTR ExportFeeds(WPARAM, LPARAM)
+{
+ if (pExportDialog == nullptr)
+ pExportDialog = new CExportFeed();
+ pExportDialog->Show();
+ return 0;
+}
+
+INT_PTR CheckFeed(WPARAM hContact, LPARAM)
+{
+ if(IsMyContact((MCONTACT)hContact))
+ UpdateListAdd((MCONTACT)hContact);
+ if ( !ThreadRunning)
+ mir_forkthread(UpdateThreadProc);
+ return 0;
+}
+
+INT_PTR NewsAggrGetAvatarInfo(WPARAM wParam, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATION *pai = (PROTO_AVATAR_INFORMATION *)lParam;
+ if (!IsMyContact(pai->hContact))
+ return GAIR_NOAVATAR;
+
+ // if GAIF_FORCE is set, we are updating the feed
+ // otherwise, cached avatar is used
+ if ((wParam & GAIF_FORCE) && g_plugin.getDword(pai->hContact, "UpdateTime", DEFAULT_UPDATE_TIME))
+ UpdateListAdd(pai->hContact);
+ if (g_plugin.getByte("AutoUpdate", 1) != 0 && !ThreadRunning)
+ mir_forkthread(UpdateThreadProc, (void *)TRUE);
+
+ ptrW ptszImageURL(g_plugin.getWStringA(pai->hContact, "ImageURL"));
+ return (ptszImageURL == nullptr) ? GAIR_NOAVATAR : GAIR_WAITFOR;
+}
+
+INT_PTR NewsAggrRecvMessage(WPARAM, LPARAM lParam)
+{
+ PROTOACCOUNT *pa = Proto_GetAccount(MODULENAME);
+ if (pa && pa->ppro) {
+ CCSDATA *ccs = (CCSDATA*)lParam;
+ pa->ppro->PROTO_INTERFACE::RecvMsg(ccs->hContact, (PROTORECVEVENT*)ccs->lParam);
+ }
+
+ return 0;
+}
+
+void UpdateMenu(bool State)
+{
+ if (!State) // to enable auto-update
+ Menu_ModifyItem(hService2[0], LPGENW("Auto Update Enabled"), GetIconHandle("enabled"));
+ else // to disable auto-update
+ Menu_ModifyItem(hService2[0], LPGENW("Auto Update Disabled"), GetIconHandle("disabled"));
+
+ CallService(MS_TTB_SETBUTTONSTATE, (WPARAM)hTBButton, State ? TTBST_PUSHED : 0);
+ g_plugin.setByte("AutoUpdate", !State);
+}
+
+// update the newsaggregator auto-update menu item when click on it
+INT_PTR EnableDisable(WPARAM, LPARAM)
+{
+ UpdateMenu(g_plugin.getByte("AutoUpdate", 1) != 0);
+ NewsAggrSetStatus(g_plugin.getByte("AutoUpdate", 1) ? ID_STATUS_ONLINE : ID_STATUS_OFFLINE, 0);
+ return 0;
+}
+
+int OnToolbarLoaded(WPARAM, LPARAM)
+{
+ TTBButton ttb = {};
+ ttb.name = LPGEN("Enable/disable auto update");
+ ttb.pszService = MS_NEWSAGGREGATOR_ENABLED;
+ ttb.pszTooltipUp = LPGEN("Auto Update Enabled");
+ ttb.pszTooltipDn = LPGEN("Auto Update Disabled");
+ ttb.hIconHandleUp = GetIconHandle("enabled");
+ ttb.hIconHandleDn = GetIconHandle("disabled");
+ ttb.dwFlags = (g_plugin.getByte("AutoUpdate", 1) ? 0 : TTBBF_PUSHED) | TTBBF_ASPUSHBUTTON | TTBBF_VISIBLE;
+ hTBButton = g_plugin.addTTB(&ttb);
+ return 0;
+}
diff --git a/protocols/NewsAggregator/Src/Update.cpp b/protocols/NewsAggregator/Src/Update.cpp
new file mode 100644
index 0000000000..96990dbcd2
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Update.cpp
@@ -0,0 +1,139 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+// check if Feed is currently updating
+bool ThreadRunning;
+UPDATELIST *UpdateListHead = nullptr;
+UPDATELIST *UpdateListTail = nullptr;
+
+// main auto-update timer
+void CALLBACK timerProc(HWND, UINT, UINT_PTR, DWORD)
+{
+ // only run if it is not current updating and the auto update option is enabled
+ if (!ThreadRunning && !Miranda_IsTerminated()) {
+ bool HaveUpdates = FALSE;
+ for (auto &hContact : Contacts(MODULENAME)) {
+ if (g_plugin.getDword(hContact, "UpdateTime", DEFAULT_UPDATE_TIME)) {
+ double diff = difftime(time(0), (time_t)g_plugin.getDword(hContact, "LastCheck", 0));
+ if (g_plugin.getByte("AutoUpdate", 1) != 0 && diff >= g_plugin.getDword(hContact, "UpdateTime", DEFAULT_UPDATE_TIME) * 60) {
+ UpdateListAdd(hContact);
+ HaveUpdates = TRUE;
+ }
+ }
+ }
+ if (!ThreadRunning && HaveUpdates)
+ mir_forkthread(UpdateThreadProc);
+ }
+}
+
+// temporary timer for first run
+// when this is run, it kill the old startup timer and create the permenant one above
+void CALLBACK timerProc2(HWND, UINT, UINT_PTR, DWORD)
+{
+ KillTimer(nullptr, timerId);
+ ThreadRunning = FALSE;
+
+ if (g_plugin.getByte("AutoUpdate", 1) && !Miranda_IsTerminated()) {
+ if (g_plugin.getByte("StartupRetrieve", 1))
+ CheckAllFeeds(0, 1);
+ timerId = SetTimer(nullptr, 0, 30000, (TIMERPROC)timerProc);
+ }
+}
+
+void UpdateListAdd(MCONTACT hContact)
+{
+ UPDATELIST *newItem = (UPDATELIST*)mir_alloc(sizeof(UPDATELIST));
+ newItem->hContact = hContact;
+ newItem->next = nullptr;
+
+ WaitForSingleObject(hUpdateMutex, INFINITE);
+
+ if (UpdateListTail == nullptr)
+ UpdateListHead = newItem;
+ else UpdateListTail->next = newItem;
+ UpdateListTail = newItem;
+
+ ReleaseMutex(hUpdateMutex);
+}
+
+MCONTACT UpdateGetFirst()
+{
+ MCONTACT hContact = NULL;
+
+ WaitForSingleObject(hUpdateMutex, INFINITE);
+
+ if (UpdateListHead != nullptr) {
+ UPDATELIST* Item = UpdateListHead;
+ hContact = Item->hContact;
+ UpdateListHead = Item->next;
+ mir_free(Item);
+
+ if (UpdateListHead == nullptr)
+ UpdateListTail = nullptr;
+ }
+
+ ReleaseMutex(hUpdateMutex);
+
+ return hContact;
+}
+
+void DestroyUpdateList(void)
+{
+ WaitForSingleObject(hUpdateMutex, INFINITE);
+
+ // free the list one by one
+ UPDATELIST *temp = UpdateListHead;
+ while (temp != nullptr) {
+ UpdateListHead = temp->next;
+ mir_free(temp);
+ temp = UpdateListHead;
+ }
+ // make sure the entire list is clear
+ UpdateListTail = nullptr;
+
+ ReleaseMutex(hUpdateMutex);
+}
+
+void UpdateThreadProc(void *AvatarCheck)
+{
+ WaitForSingleObject(hUpdateMutex, INFINITE);
+ if (ThreadRunning) {
+ ReleaseMutex(hUpdateMutex);
+ return;
+ }
+ ThreadRunning = TRUE; // prevent 2 instance of this thread running
+ ReleaseMutex(hUpdateMutex);
+
+ CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+ // update news by getting the first station from the queue until the queue is empty
+ while (UpdateListHead != nullptr && !Miranda_IsTerminated()) {
+ if (AvatarCheck != nullptr)
+ CheckCurrentFeedAvatar(UpdateGetFirst());
+ else
+ CheckCurrentFeed(UpdateGetFirst());
+ }
+
+ // exit the update thread
+ ThreadRunning = FALSE;
+
+ CoUninitialize();
+}
diff --git a/protocols/NewsAggregator/Src/Utils.cpp b/protocols/NewsAggregator/Src/Utils.cpp
new file mode 100644
index 0000000000..80248ba48f
--- /dev/null
+++ b/protocols/NewsAggregator/Src/Utils.cpp
@@ -0,0 +1,445 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#include "stdafx.h"
+
+HNETLIBUSER hNetlibUser = nullptr;
+HNETLIBCONN hNetlibHttp;
+bool UpdateListFlag = FALSE;
+
+bool IsMyContact(MCONTACT hContact)
+{
+ const char *szProto = GetContactProto(hContact);
+ return szProto != nullptr && mir_strcmp(MODULENAME, szProto) == 0;
+}
+
+void NetlibInit()
+{
+ NETLIBUSER nlu = {};
+ nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_UNICODE;
+ nlu.szDescriptiveName.w = TranslateT("NewsAggregator HTTP connections");
+ nlu.szSettingsModule = MODULENAME;
+ hNetlibUser = Netlib_RegisterUser(&nlu);
+}
+
+void NetlibUnInit()
+{
+ Netlib_CloseHandle(hNetlibUser);
+ hNetlibUser = nullptr;
+}
+
+void GetNewsData(wchar_t *tszUrl, char **szData, MCONTACT hContact, CFeedEditor *pEditDlg)
+{
+ Netlib_LogfW(hNetlibUser, L"Getting feed data %s.", tszUrl);
+ NETLIBHTTPREQUEST nlhr = { 0 };
+
+ // initialize the netlib request
+ nlhr.cbSize = sizeof(nlhr);
+ nlhr.requestType = REQUEST_GET;
+ nlhr.flags = NLHRF_DUMPASTEXT | NLHRF_HTTP11 | NLHRF_REDIRECT;
+ if (wcsstr(tszUrl, L"https://") != nullptr)
+ nlhr.flags |= NLHRF_SSL;
+ char *szUrl = mir_u2a(tszUrl);
+ nlhr.szUrl = szUrl;
+ nlhr.nlc = hNetlibHttp;
+
+ // change the header so the plugin is pretended to be IE 6 + WinXP
+ NETLIBHTTPHEADER headers[5];
+ nlhr.headersCount = 4;
+ nlhr.headers = headers;
+ nlhr.headers[0].szName = "User-Agent";
+ nlhr.headers[0].szValue = NETLIB_USER_AGENT;
+ nlhr.headers[1].szName = "Cache-Control";
+ nlhr.headers[1].szValue = "no-cache";
+ nlhr.headers[2].szName = "Pragma";
+ nlhr.headers[2].szValue = "no-cache";
+ nlhr.headers[3].szName = "Connection";
+ nlhr.headers[3].szValue = "close";
+ char auth[256];
+ if (g_plugin.getByte(hContact, "UseAuth", 0) || (pEditDlg && pEditDlg->m_useauth.IsChecked()) /*IsDlgButtonChecked(hwndDlg, IDC_USEAUTH)*/) {
+ nlhr.headersCount++;
+ nlhr.headers[4].szName = "Authorization";
+
+ CreateAuthString(auth, hContact, pEditDlg);
+ nlhr.headers[4].szValue = auth;
+ }
+
+ // download the page
+ NETLIBHTTPREQUEST *nlhrReply = Netlib_HttpTransaction(hNetlibUser, &nlhr);
+ if (nlhrReply) {
+ // if the recieved code is 200 OK
+ if (nlhrReply->resultCode == 200 && nlhrReply->dataLength > 0) {
+ Netlib_LogfW(hNetlibUser, L"Code 200: Succeeded getting feed data %s.", tszUrl);
+ // allocate memory and save the retrieved data
+ *szData = (char *)mir_alloc((size_t)(nlhrReply->dataLength + 2));
+ memcpy(*szData, nlhrReply->pData, (size_t)nlhrReply->dataLength);
+ (*szData)[nlhrReply->dataLength] = 0;
+ }
+ else if (nlhrReply->resultCode == 401) {
+ Netlib_LogfW(hNetlibUser, L"Code 401: feed %s needs auth data.", tszUrl);
+
+ if (CAuthRequest(pEditDlg, hContact).DoModal() == IDOK)
+ GetNewsData(tszUrl, szData, hContact, pEditDlg);
+ }
+ else Netlib_LogfW(hNetlibUser, L"Code %d: Failed getting feed data %s.", nlhrReply->resultCode, tszUrl);
+
+ Netlib_FreeHttpRequest(nlhrReply);
+ }
+ else Netlib_LogfW(hNetlibUser, L"Failed getting feed data %s, no response.", tszUrl);
+
+ mir_free(szUrl);
+}
+
+time_t DateToUnixTime(const char *stamp, bool FeedType)
+{
+ struct tm timestamp;
+ char date[9];
+ int i, y;
+ time_t t;
+
+ if (stamp == nullptr)
+ return 0;
+
+ char *p = NEWSTR_ALLOCA(stamp);
+
+ if (FeedType) {
+ // skip '-' chars
+ int si = 0, sj = 0;
+ while (true) {
+ if (p[si] == '-')
+ si++;
+ else if (!(p[sj++] = p[si++]))
+ break;
+ }
+ }
+ else {
+ char monthstr[4], timezonesign[2];
+ int day, month = 0, year, hour, min, sec, timezoneh, timezonem;
+ if (strchr(p, ',')) {
+ strtok(p, ",");
+ p = strtok(nullptr, ",");
+ sscanf(p + 1, "%d %3s %d %d:%d:%d %1s%02d%02d", &day, &monthstr, &year, &hour, &min, &sec, &timezonesign, &timezoneh, &timezonem);
+ if (!mir_strcmpi(monthstr, "Jan"))
+ month = 1;
+ if (!mir_strcmpi(monthstr, "Feb"))
+ month = 2;
+ if (!mir_strcmpi(monthstr, "Mar"))
+ month = 3;
+ if (!mir_strcmpi(monthstr, "Apr"))
+ month = 4;
+ if (!mir_strcmpi(monthstr, "May"))
+ month = 5;
+ if (!mir_strcmpi(monthstr, "Jun"))
+ month = 6;
+ if (!mir_strcmpi(monthstr, "Jul"))
+ month = 7;
+ if (!mir_strcmpi(monthstr, "Aug"))
+ month = 8;
+ if (!mir_strcmpi(monthstr, "Sep"))
+ month = 9;
+ if (!mir_strcmpi(monthstr, "Oct"))
+ month = 10;
+ if (!mir_strcmpi(monthstr, "Nov"))
+ month = 11;
+ if (!mir_strcmpi(monthstr, "Dec"))
+ month = 12;
+ if (year < 2000)
+ year += 2000;
+ if (!mir_strcmp(timezonesign, "+"))
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour - timezoneh, min - timezonem, sec);
+ else if (!mir_strcmp(timezonesign, "-"))
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour + timezoneh, min + timezonem, sec);
+ else
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour, min, sec);
+ }
+ else if (strchr(p, 'T')) {
+ sscanf(p, "%d-%d-%dT%d:%d:%d", &year, &month, &day, &hour, &min, &sec);
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour, min, sec);
+ }
+ else {
+ sscanf(p, "%d-%d-%d %d:%d:%d %1s%02d%02d", &year, &month, &day, &hour, &min, &sec, &timezonesign, &timezoneh, &timezonem);
+ if (!mir_strcmp(timezonesign, "+"))
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour - timezoneh, min - timezonem, sec);
+ else if (!mir_strcmp(timezonesign, "-"))
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour + timezoneh, min + timezonem, sec);
+ else
+ mir_snprintf(p, 4 + 2 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1, "%04d%02d%02dT%02d:%02d:%02d", year, month, day, hour, min, sec);
+ }
+ }
+ // Get the date part
+ for (i = 0; *p != '\0' && i < 8 && isdigit(*p); p++, i++)
+ date[i] = *p;
+
+ // Parse year
+ if (i == 6) {
+ // 2-digit year ( 1970-2069 )
+ y = (date[0] - '0') * 10 + (date[1] - '0');
+ if (y < 70)
+ y += 100;
+ }
+ else if (i == 8) {
+ // 4-digit year
+ y = (date[0] - '0') * 1000 + (date[1] - '0') * 100 + (date[2] - '0') * 10 + date[3] - '0';
+ y -= 1900;
+ }
+ else return 0;
+
+ timestamp.tm_year = y;
+ // Parse month
+ timestamp.tm_mon = (date[i - 4] - '0') * 10 + date[i - 3] - '0' - 1;
+ // Parse date
+ timestamp.tm_mday = (date[i - 2] - '0') * 10 + date[i - 1] - '0';
+
+ // Skip any date/time delimiter
+ for (; *p != '\0' && !isdigit(*p); p++);
+
+ // Parse time
+ if (sscanf(p, "%d:%d:%d", &timestamp.tm_hour, &timestamp.tm_min, &timestamp.tm_sec) != 3)
+ return 0;
+
+ timestamp.tm_isdst = 0; // DST is already present in _timezone below
+ t = mktime(&timestamp);
+
+ _tzset();
+ t -= (time_t)_timezone;
+ return (t >= 0) ? t : 0;
+}
+
+bool DownloadFile(LPCTSTR tszURL, LPCTSTR tszLocal)
+{
+ NETLIBHTTPREQUEST nlhr = { 0 };
+ nlhr.cbSize = sizeof(nlhr);
+ nlhr.requestType = REQUEST_GET;
+ nlhr.flags = NLHRF_DUMPASTEXT | NLHRF_HTTP11;
+ char *szUrl = mir_u2a(tszURL);
+ nlhr.szUrl = szUrl;
+ NETLIBHTTPHEADER headers[4];
+ nlhr.headersCount = 4;
+ nlhr.headers = headers;
+ nlhr.headers[0].szName = "User-Agent";
+ nlhr.headers[0].szValue = NETLIB_USER_AGENT;
+ nlhr.headers[1].szName = "Connection";
+ nlhr.headers[1].szValue = "close";
+ nlhr.headers[2].szName = "Cache-Control";
+ nlhr.headers[2].szValue = "no-cache";
+ nlhr.headers[3].szName = "Pragma";
+ nlhr.headers[3].szValue = "no-cache";
+
+ bool ret = false;
+ NETLIBHTTPREQUEST *pReply = Netlib_HttpTransaction(hNetlibUser, &nlhr);
+ if (pReply) {
+ if ((200 == pReply->resultCode) && (pReply->dataLength > 0)) {
+ char *date = nullptr, *size = nullptr;
+ for (int i = 0; i < pReply->headersCount; i++) {
+ if (!mir_strcmpi(pReply->headers[i].szName, "Last-Modified")) {
+ date = pReply->headers[i].szValue;
+ continue;
+ }
+ else if (!mir_strcmpi(pReply->headers[i].szName, "Content-Length")) {
+ size = pReply->headers[i].szValue;
+ continue;
+ }
+ }
+ if (date != nullptr && size != nullptr) {
+ wchar_t *tsize = mir_a2u(size);
+ struct _stat buf;
+
+ int fh = _wopen(tszLocal, _O_RDONLY);
+ if (fh != -1) {
+ _fstat(fh, &buf);
+ time_t modtime = DateToUnixTime(date, 0);
+ time_t filemodtime = mktime(localtime(&buf.st_atime));
+ if (modtime > filemodtime && buf.st_size != _wtoi(tsize)) {
+ DWORD dwBytes;
+ HANDLE hFile = CreateFile(tszLocal, GENERIC_READ | GENERIC_WRITE, NULL, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ WriteFile(hFile, pReply->pData, (DWORD)pReply->dataLength, &dwBytes, nullptr);
+ ret = true;
+ if (hFile)
+ CloseHandle(hFile);
+ }
+ _close(fh);
+ }
+ else {
+ DWORD dwBytes;
+ HANDLE hFile = CreateFile(tszLocal, GENERIC_READ | GENERIC_WRITE, NULL, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ WriteFile(hFile, pReply->pData, (DWORD)pReply->dataLength, &dwBytes, nullptr);
+ ret = true;
+ if (hFile)
+ CloseHandle(hFile);
+ }
+ mir_free(tsize);
+ }
+ else {
+ DWORD dwBytes;
+ HANDLE hFile = CreateFile(tszLocal, GENERIC_READ | GENERIC_WRITE, NULL, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
+ WriteFile(hFile, pReply->pData, (DWORD)pReply->dataLength, &dwBytes, nullptr);
+ ret = true;
+ if (hFile)
+ CloseHandle(hFile);
+ }
+ }
+ Netlib_FreeHttpRequest(pReply);
+ }
+
+ mir_free(szUrl);
+
+ return ret;
+}
+
+typedef HRESULT(MarkupCallback)(IHTMLDocument3 *, BSTR &message);
+
+HRESULT TestMarkupServices(BSTR bstrHtml, MarkupCallback *pCallback, BSTR &message)
+{
+ IHTMLDocument3 *pHtmlDocRoot = nullptr;
+
+ // Create the root document -- a "workspace" for parsing.
+ HRESULT hr = CoCreateInstance(CLSID_HTMLDocument, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pHtmlDocRoot));
+ if (SUCCEEDED(hr) && pHtmlDocRoot) {
+ IPersistStreamInit *pPersistStreamInit = nullptr;
+
+ hr = pHtmlDocRoot->QueryInterface(IID_PPV_ARGS(&pPersistStreamInit));
+ if (SUCCEEDED(hr)) {
+ // Initialize the root document to a default state -- ready for parsing.
+ pPersistStreamInit->InitNew();
+
+ IMarkupServices *pMarkupServices = nullptr;
+ hr = pHtmlDocRoot->QueryInterface(IID_PPV_ARGS(&pMarkupServices));
+ if (SUCCEEDED(hr)) {
+ IMarkupPointer *pMarkupBegin = nullptr;
+ IMarkupPointer *pMarkupEnd = nullptr;
+
+ // These markup pointers indicate the insertion point.
+ hr = pMarkupServices->CreateMarkupPointer(&pMarkupBegin);
+ if (SUCCEEDED(hr))
+ hr = pMarkupServices->CreateMarkupPointer(&pMarkupEnd);
+
+ if (SUCCEEDED(hr) && pMarkupBegin && pMarkupEnd) {
+ IMarkupContainer *pMarkupContainer = nullptr;
+
+ // Parse the string -- the markup container contains the parsed HTML.
+ // Markup pointers are updated to point to begining and end of new container.
+ hr = pMarkupServices->ParseString(bstrHtml, 0, &pMarkupContainer, pMarkupBegin, pMarkupEnd);
+ if (SUCCEEDED(hr) && pMarkupContainer) {
+ IHTMLDocument3 *pHtmlDoc = nullptr;
+
+ // Retrieve the document interface to the markup container.
+ hr = pMarkupContainer->QueryInterface(IID_PPV_ARGS(&pHtmlDoc));
+ if (SUCCEEDED(hr) && pHtmlDoc) {
+ // Invoke the user-defined action for this new fragment.
+ hr = pCallback(pHtmlDoc, message);
+
+ // Clean up.
+ pHtmlDoc->Release();
+ }
+ pMarkupContainer->Release();
+ }
+ pMarkupEnd->Release();
+ }
+ if (pMarkupBegin)
+ pMarkupBegin->Release();
+ pMarkupServices->Release();
+ }
+ pPersistStreamInit->Release();
+ }
+ pHtmlDocRoot->Release();
+ }
+ return hr;
+}
+
+HRESULT TestDocumentText(IHTMLDocument3 *pHtmlDoc, BSTR &message)
+{
+ IHTMLDocument2 *pDoc = nullptr;
+ IHTMLElement *pElem = nullptr;
+ BSTR bstrId = SysAllocString(L"test");
+
+ HRESULT hr = pHtmlDoc->QueryInterface(IID_PPV_ARGS(&pDoc));
+ if (SUCCEEDED(hr) && pDoc) {
+ hr = pDoc->get_body(&pElem);
+ if (SUCCEEDED(hr) && pElem) {
+ BSTR bstrText = nullptr;
+ pElem->get_innerText(&bstrText);
+ message = SysAllocString(bstrText);
+ SysFreeString(bstrText);
+ pElem->Release();
+ }
+
+ pDoc->Release();
+ }
+
+ SysFreeString(bstrId);
+ return hr;
+}
+
+LPCTSTR ClearText(CMStringW &result, const wchar_t *message)
+{
+ BSTR bstrHtml = SysAllocString(message), bstrRes = SysAllocString(L"");
+ HRESULT hr = TestMarkupServices(bstrHtml, &TestDocumentText, bstrRes);
+ if (SUCCEEDED(hr))
+ result = bstrRes;
+ else
+ result = message;
+ SysFreeString(bstrHtml);
+ SysFreeString(bstrRes);
+
+ result.Replace(L"&#163;", L"£"); //pound
+ result.Replace(L"&#178;", L"²"); //sup2
+ result.Replace(L"&#228;", L"ä"); //auml
+ result.Replace(L"&#233;", L"é"); //latin small letter e with acute
+ result.Replace(L"&#235;", L"ë"); //euml
+ result.Replace(L"&#246;", L"ö"); //ouml
+ result.Replace(L"&#382;", L"ž"); //Latin Small Letter Z With Caron
+ result.Replace(L"&#665;", L"ʙ"); //latin letter small capital b
+ result.Replace(L"&#774;", L"˘"); //Combining Breve
+ result.Replace(L"&#769;", L"´"); //Combining Acute Accent острое ударение
+ result.Replace(L"&#959;", L"ό"); // greek small letter omicron with tonos
+ result.Replace(L"&#1123;", L"ѣ"); //Cyrillic Small Letter Yat
+ result.Replace(L"&#1180;", L"Ҝ"); //cyrillic capital letter ka with vertical stroke
+ result.Replace(L"&#8203;", L"");
+ result.Replace(L"&#8206;", L""); //lrm
+ result.Replace(L"&#8207;", L""); //rlm
+ result.Replace(L"&#8209;", L"‑"); //Non-Breaking Hyphen
+ result.Replace(L"&#8227;", L"‣"); //Triangular Bullet
+ result.Replace(L"&#8722;", L"−"); //minus
+ result.Replace(L"&#9786;", L"☺"); //White Smiling Face
+ result.Replace(L"&#65279;", L"");
+
+ result.Trim();
+
+ return result;
+}
+
+MCONTACT GetContactByNick(const wchar_t *nick)
+{
+ for (auto &hContact : Contacts(MODULENAME)) {
+ ptrW contactNick(g_plugin.getWStringA(hContact, "Nick"));
+ if (!mir_wstrcmpi(contactNick, nick))
+ return hContact;
+ }
+ return 0;
+}
+
+MCONTACT GetContactByURL(const wchar_t *url)
+{
+ for (auto &hContact : Contacts(MODULENAME)) {
+ ptrW contactURL(g_plugin.getWStringA(hContact, "URL"));
+ if (!mir_wstrcmpi(contactURL, url))
+ return hContact;
+ }
+ return 0;
+}
diff --git a/protocols/NewsAggregator/Src/resource.h b/protocols/NewsAggregator/Src/resource.h
new file mode 100644
index 0000000000..131a0844ba
--- /dev/null
+++ b/protocols/NewsAggregator/Src/resource.h
@@ -0,0 +1,57 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by c:\Temp\Miranda NG\plugins\NewsAggregator\res\Resource.rc
+//
+#define IDD_OPTIONS 101
+#define IDD_AUTHENTICATION 102
+#define IDI_ICON 109
+#define IDD_ADDFEED 110
+#define IDI_CHECKALL 111
+#define IDI_ADDFEED 112
+#define IDI_IMPORTFEEDS 113
+#define IDI_EXPORTFEEDS 114
+#define IDI_ENABLED 115
+#define IDI_DISABLED 116
+#define IDD_FEEDEXPORT 140
+#define IDD_FEEDIMPORT 141
+#define IDC_TIMEOUT_VALUE_SPIN 1035
+#define IDC_FEEDLIST 1036
+#define IDC_ADD 1037
+#define IDC_CHANGE 1038
+#define IDC_REMOVE 1039
+#define IDC_IMPORT 1040
+#define IDC_EXPORT 1041
+#define IDC_FEEDTITLE 1042
+#define IDC_FEEDURL 1043
+#define IDC_CHECKTIME 1044
+#define IDC_DISCOVERY 1045
+#define IDC_USEAUTH 1046
+#define IDC_LOGIN 1047
+#define IDC_PASSWORD 1048
+#define IDC_TAGSEDIT 1049
+#define IDC_RESET 1050
+#define IDC_TAGHELP 1051
+#define IDC_STARTUPRETRIEVE 1052
+#define IDC_FEEDUSERNAME 1105
+#define IDC_FEEDPASSWORD 1106
+#define IDC_FEEDSLIST 1108
+#define IDC_FEEDSEXPORTLIST 1109
+#define IDC_ADDFEED 1110
+#define IDC_REMOVEFEED 1111
+#define IDC_ADDALLFEEDS 1112
+#define IDC_REMOVEALLFEEDS 1113
+#define IDC_IMPORTFILEPATH 1114
+#define IDC_BROWSEIMPORTFILE 1115
+#define IDC_FEEDSIMPORTLIST 1117
+#define IDC_FEEDNAME 1124
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 117
+#define _APS_NEXT_COMMAND_VALUE 40075
+#define _APS_NEXT_CONTROL_VALUE 1053
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/protocols/NewsAggregator/Src/stdafx.cxx b/protocols/NewsAggregator/Src/stdafx.cxx
new file mode 100644
index 0000000000..1b563fc866
--- /dev/null
+++ b/protocols/NewsAggregator/Src/stdafx.cxx
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2012-19 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" \ No newline at end of file
diff --git a/protocols/NewsAggregator/Src/stdafx.h b/protocols/NewsAggregator/Src/stdafx.h
new file mode 100644
index 0000000000..819e96d69f
--- /dev/null
+++ b/protocols/NewsAggregator/Src/stdafx.h
@@ -0,0 +1,167 @@
+/*
+Copyright (C) 2012 Mataes
+
+This is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public
+License as published by the Free Software Foundation; either
+version 2 of the License, or (at your option) any later version.
+
+This 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
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with this file; see the file license.txt. If
+not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+Boston, MA 02111-1307, USA.
+*/
+
+#pragma once
+
+// Windows Header Files:
+#include <windows.h>
+#include <commctrl.h>
+#include <time.h>
+#include <malloc.h>
+#include <fcntl.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <mshtml.h>
+
+// Miranda header files
+#include <newpluginapi.h>
+#include <m_clist.h>
+#include <m_langpack.h>
+#include <m_options.h>
+#include <m_protosvc.h>
+#include <m_protoint.h>
+#include <m_database.h>
+#include <m_netlib.h>
+#include <m_icolib.h>
+#include <m_message.h>
+#include <win2k.h>
+#include <m_xml.h>
+#include <m_avatars.h>
+#include <m_hotkeys.h>
+#include <m_gui.h>
+
+#include <m_folders.h>
+#include <m_toptoolbar.h>
+
+#include "Options.h"
+#include "version.h"
+#include "resource.h"
+
+#define MODULENAME "NewsAggregator"
+#define TAGSDEFAULT L"#<title>#\r\n#<link>#\r\n#<description>#"
+#define DEFAULT_AVATARS_FOLDER "NewsAggregator"
+#define DEFAULT_UPDATE_TIME 60
+
+extern CDlgBase *pAddFeedDialog, *pImportDialog, *pExportDialog;
+extern HNETLIBUSER hNetlibUser;
+extern UINT_PTR timerId;
+extern LIST<CFeedEditor> g_arFeeds;
+// check if Feeds is currently updating
+extern bool ThreadRunning;
+extern bool UpdateListFlag;
+extern wchar_t tszRoot[MAX_PATH];
+
+struct CMPlugin : public PLUGIN<CMPlugin>
+{
+ CMPlugin();
+
+ int Load() override;
+ int Unload() override;
+};
+
+//============ STRUCT USED TO MAKE AN UPDATE LIST ============
+
+struct NEWSCONTACTLIST {
+ MCONTACT hContact;
+ struct NEWSCONTACTLIST *next;
+};
+
+typedef struct NEWSCONTACTLIST UPDATELIST;
+
+extern UPDATELIST *UpdateListHead;
+extern UPDATELIST *UpdateListTail;
+
+void UpdateListAdd(MCONTACT hContact);
+void UpdateThreadProc(void*);
+void DestroyUpdateList(void);
+
+extern HANDLE hUpdateMutex;
+extern HGENMENU hService2[7];
+
+int NewsAggrInit(WPARAM wParam,LPARAM lParam);
+int OptInit(WPARAM wParam, LPARAM lParam);
+int NewsAggrPreShutdown(WPARAM wParam,LPARAM lParam);
+void NetlibInit();
+void NetlibUnInit();
+void InitMenu();
+void InitIcons();
+HICON LoadIconEx(const char* name, bool big);
+HANDLE GetIconHandle(const char* name);
+
+INT_PTR NewsAggrGetName(WPARAM wParam, LPARAM lParam);
+INT_PTR NewsAggrGetCaps(WPARAM wp, LPARAM lp);
+INT_PTR NewsAggrSetStatus(WPARAM wp, LPARAM /*lp*/);
+INT_PTR NewsAggrGetStatus(WPARAM/* wp*/, LPARAM/* lp*/);
+INT_PTR NewsAggrLoadIcon(WPARAM wParam, LPARAM lParam);
+INT_PTR NewsAggrGetInfo(WPARAM wParam, LPARAM lParam);
+INT_PTR NewsAggrGetAvatarInfo(WPARAM wParam, LPARAM lParam);
+INT_PTR NewsAggrRecvMessage(WPARAM wParam, LPARAM lParam);
+
+INT_PTR CheckAllFeeds(WPARAM wParam, LPARAM lParam);
+INT_PTR AddFeed(WPARAM wParam, LPARAM lParam);
+INT_PTR ChangeFeed(WPARAM wParam, LPARAM lParam);
+INT_PTR ImportFeeds(WPARAM wParam, LPARAM lParam);
+INT_PTR ExportFeeds(WPARAM wParam, LPARAM lParam);
+INT_PTR CheckFeed(WPARAM wParam, LPARAM lParam);
+INT_PTR EnableDisable(WPARAM wParam, LPARAM lParam);
+int OnToolbarLoaded(WPARAM wParam, LPARAM lParam);
+void CALLBACK timerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
+void CALLBACK timerProc2(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);
+
+bool IsMyContact(MCONTACT hContact);
+void GetNewsData(wchar_t *szUrl, char **szData, MCONTACT hContact, CFeedEditor *pEditDlg);
+time_t DateToUnixTime(const char *stamp, bool FeedType);
+void CheckCurrentFeed(MCONTACT hContact);
+void CheckCurrentFeedAvatar(MCONTACT hContact);
+LPCTSTR CheckFeed(wchar_t* tszURL, CFeedEditor *pEditDlg);
+void UpdateMenu(bool State);
+LPCTSTR ClearText(CMStringW &value, const wchar_t *message);
+bool DownloadFile(LPCTSTR tszURL, LPCTSTR tszLocal);
+void CreateAuthString(char *auth, MCONTACT hContact, CFeedEditor *pDlg);
+MCONTACT GetContactByNick(const wchar_t *nick);
+MCONTACT GetContactByURL(const wchar_t *url);
+
+// =============== NewsAggregator SERVICES ================
+// Check all Feeds info
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_CHECKALLFEEDS "NewsAggregator/CheckAllFeeds"
+
+// Add new Feed channel
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_ADDFEED "NewsAggregator/AddNewsFeed"
+
+// Change Feed channel
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_CHANGEFEED "NewsAggregator/ChangeNewsFeed"
+
+// Import Feed channels from file
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_IMPORTFEEDS "NewsAggregator/ImportFeeds"
+
+// Export Feed channels to file
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_EXPORTFEEDS "NewsAggregator/ExportFeeds"
+
+// Check Feed info
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_CHECKFEED "NewsAggregator/CheckFeed"
+
+// Enable/disable getting feed info
+// WPARAM = LPARAM = NULL
+#define MS_NEWSAGGREGATOR_ENABLED "NewsAggregator/Enabled"
diff --git a/protocols/NewsAggregator/Src/version.h b/protocols/NewsAggregator/Src/version.h
new file mode 100644
index 0000000000..a7d2126e30
--- /dev/null
+++ b/protocols/NewsAggregator/Src/version.h
@@ -0,0 +1,13 @@
+#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 1
+#define __RELEASE_NUM 0
+#define __BUILD_NUM 5
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "News aggregator"
+#define __FILENAME "NewsAggregator.dll"
+#define __DESCRIPTION "RSS/Atom news aggregator."
+#define __AUTHOR "Mataes, FREAK_THEMIGHTY"
+#define __AUTHORWEB "https://miranda-ng.org/p/NewsAggregator/"
+#define __COPYRIGHT "© 2012-19 Mataes, FREAK_THEMIGHTY"