1 #include "stdafx.h"
2 #include "InternetCommon.h"
3 #include "Resource.h"
4 #include "MusicPlayer2.h"
5
6
CInternetCommon()7 CInternetCommon::CInternetCommon()
8 {
9 }
10
11
~CInternetCommon()12 CInternetCommon::~CInternetCommon()
13 {
14 }
15
URLEncode(const wstring & wstr)16 wstring CInternetCommon::URLEncode(const wstring & wstr)
17 {
18 string str_utf8;
19 wstring result{};
20 wchar_t buff[4];
21 str_utf8 = CCommon::UnicodeToStr(wstr, CodeType::UTF8_NO_BOM);
22 for (const auto& ch : str_utf8)
23 {
24 if (ch == ' ')
25 result.push_back(L'+');
26 else if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))
27 result.push_back(static_cast<wchar_t>(ch));
28 else if (ch == '-' || ch == '_' || ch == '.' || ch == '!' || ch == '~' || ch == '*'/* || ch == '\''*/ || ch == '(' || ch == ')')
29 result.push_back(static_cast<wchar_t>(ch));
30 else
31 {
32 swprintf_s(buff, L"%%%x", static_cast<unsigned char>(ch));
33 result += buff;
34 }
35 }
36 return result;
37 }
38
GetURL(const wstring & str_url,wstring & result,bool custom_ua,bool allow_other_codes)39 bool CInternetCommon::GetURL(const wstring & str_url, wstring & result, bool custom_ua, bool allow_other_codes)
40 {
41 wstring log_info;
42 log_info = L"http get: " + str_url;
43 theApp.WriteLog(log_info, NonCategorizedSettingData::LT_NORMAL);
44
45 bool sucessed{ false };
46 CInternetSession session{};
47 if (custom_ua) {
48 session.SetOption(INTERNET_OPTION_USER_AGENT, (LPVOID)L"MuiscPlayer2" APP_VERSION, wcslen(L"MuiscPlayer2" APP_VERSION) * sizeof(wchar_t));
49 }
50 CHttpFile* pfile{};
51 try
52 {
53 pfile = (CHttpFile *)session.OpenURL(str_url.c_str());
54 DWORD dwStatusCode;
55 pfile->QueryInfoStatusCode(dwStatusCode);
56 if (allow_other_codes || dwStatusCode == HTTP_STATUS_OK)
57 {
58 CString content;
59 CString data;
60 while (pfile->ReadString(data))
61 {
62 content += data;
63 }
64 result = CCommon::StrToUnicode(string{ (const char*)content.GetString() }, CodeType::UTF8); //获取网页内容,并转换成Unicode编码
65 sucessed = true;
66 }
67 pfile->Close();
68 delete pfile;
69 session.Close();
70 }
71 catch (CInternetException* e)
72 {
73 wstring log_info = L"http get " + str_url + L"error. Error code: ";
74 log_info += std::to_wstring(e->m_dwError);
75 theApp.WriteLog(log_info, NonCategorizedSettingData::LT_ERROR);
76
77 if (pfile != nullptr)
78 {
79 pfile->Close();
80 delete pfile;
81 }
82 session.Close();
83 sucessed = false;
84 e->Delete();
85 }
86 return sucessed;
87 }
88
HttpPost(const wstring & str_url,wstring & result)89 int CInternetCommon::HttpPost(const wstring& str_url, wstring& result) {
90 string body;
91 wstring headers;
92 return HttpPost(str_url, result, body, headers);
93 }
94
HttpPost(const wstring & str_url,wstring & result,const wstring & body,wstring & headers,bool custom_ua)95 int CInternetCommon::HttpPost(const wstring& str_url, wstring& result, const wstring& body, wstring& headers, bool custom_ua) {
96 const auto& tmp = CCommon::UnicodeToStr(body, CodeType::UTF8_NO_BOM);
97 return HttpPost(str_url, result, tmp, headers, custom_ua);
98 }
99
HttpPost(const wstring & str_url,wstring & result,const string & body,wstring & headers,bool custom_ua)100 int CInternetCommon::HttpPost(const wstring & str_url, wstring & result, const string& body, wstring& headers, bool custom_ua)
101 {
102 wstring log_info;
103 log_info = L"http post: " + str_url;
104 theApp.WriteLog(log_info, NonCategorizedSettingData::LT_NORMAL);
105
106 CInternetSession session;
107 CHttpConnection* pConnection{};
108 CHttpFile* pFile{};
109 CString strServer;
110 CString strObject;
111 DWORD dwServiceType;
112 INTERNET_PORT nPort;
113
114 if (custom_ua) {
115 session.SetOption(INTERNET_OPTION_USER_AGENT, (LPVOID)L"MuiscPlayer2" APP_VERSION, wcslen(L"MuiscPlayer2" APP_VERSION) * sizeof(wchar_t));
116 }
117
118 AfxParseURL(str_url.c_str(), dwServiceType, strServer, strObject, nPort);
119
120 if (AFX_INET_SERVICE_HTTP != dwServiceType && AFX_INET_SERVICE_HTTPS != dwServiceType)
121 return FAILURE;
122
123 try
124 {
125 pConnection = session.GetHttpConnection(strServer,
126 dwServiceType == AFX_INET_SERVICE_HTTP ? NORMAL_CONNECT : SECURE_CONNECT,
127 nPort);
128 pFile = pConnection->OpenRequest(_T("POST"), strObject,
129 NULL, 1, NULL, NULL,
130 (dwServiceType == AFX_INET_SERVICE_HTTP ? NORMAL_REQUEST : SECURE_REQUEST));
131
132 pFile->SendRequest(headers.empty() ? NULL : headers.c_str(), headers.size(), body.empty() ? NULL : (LPVOID)body.c_str(), body.size());
133
134 CString content;
135 CString data;
136 while (pFile->ReadString(data))
137 {
138 content += data;
139 }
140 result = CCommon::StrToUnicode(string{ (const char*)content.GetString() }, CodeType::UTF8);
141
142
143 pFile->Close();
144 delete pFile;
145 pConnection->Close();
146 delete pConnection;
147 session.Close();
148 }
149 catch (CInternetException* e)
150 {
151 pFile->Close();
152 delete pFile;
153 pConnection->Close();
154 delete pConnection;
155 session.Close();
156 DWORD dwErrorCode = e->m_dwError;
157 e->Delete();
158 //DWORD dwError = GetLastError();
159 //PRINT_LOG("dwError = %d", dwError, 0);
160
161 wstring log_info = L"http post " + str_url + L"error. Error code: ";
162 log_info += std::to_wstring(dwErrorCode);
163 theApp.WriteLog(log_info, NonCategorizedSettingData::LT_ERROR);
164
165 if (ERROR_INTERNET_TIMEOUT == dwErrorCode)
166 return OUTTIME;
167 else
168 return FAILURE;
169 }
170 return SUCCESS;
171 }
172
173
DeleteStrSlash(wstring & str)174 void CInternetCommon::DeleteStrSlash(wstring & str)
175 {
176 for (int i{}; i < static_cast<int>(str.size() - 1); i++)
177 {
178 if (str[i] == '\\' && str[i + 1] == '\"')
179 {
180 str.erase(i, 1);
181 }
182 }
183 }
184
DisposeSearchResult(vector<ItemInfo> & down_list,const wstring & search_result,int result_count)185 void CInternetCommon::DisposeSearchResult(vector<ItemInfo>& down_list, const wstring& search_result, int result_count)
186 {
187 down_list.clear();
188 ItemInfo item;
189 int index1{}, index2{}, index3{}, index4{};
190 //while (true)
191 for (int i{}; i < result_count; i++)
192 {
193 //获取歌曲的ID
194 if (i == 0)
195 {
196 index1 = search_result.find(L"\"songs\":[{\"id\":", index1 + 1);
197 if (index1 == string::npos) break;
198 index2 = search_result.find(L',', index1);
199 item.id = search_result.substr(index1 + 15, index2 - index1 - 15);
200 }
201 else
202 {
203 index1 = search_result.find(L",{\"id\":", index1 + 1);
204 if (index1 == string::npos) break;
205 index2 = search_result.find(L',', index1 + 1);
206 item.id = search_result.substr(index1 + 7, index2 - index1 - 7);
207 }
208
209 //获取歌曲标题
210 index2 = search_result.find(L"name", index1);
211 if (index2 == string::npos) continue;
212 index3 = search_result.find(L"\",\"", index2);
213 wstring title = search_result.substr(index2 + 7, index3 - index2 - 7);
214 if (search_result.substr(index3 + 3, 6) == L"picUrl") //如果找到的“name”后面的字符串是“picUrl”,说明这项name的值不是
215 { //另一首歌的标题,而是上一首歌的艺术家,上一首歌有多个艺术家
216 if (!down_list.empty())
217 {
218 down_list.back().artist += L'/';
219 down_list.back().artist += title;
220 }
221 continue;
222 }
223 else
224 {
225 item.title = title;
226 }
227
228 //获取歌曲的艺术家
229 index2 = search_result.find(L"artists", index1);
230 if (index2 == string::npos) continue;
231 index3 = search_result.find(L"name", index2);
232 index4 = search_result.find(L"\",\"", index3);
233 item.artist = search_result.substr(index3 + 7, index4 - index3 - 7);
234
235 //获取歌曲的唱片集
236 index2 = search_result.find(L"\"album\"", index1);
237 if (index2 == string::npos) continue;
238 index3 = search_result.find(L"name", index2);
239 index4 = search_result.find(L"\",\"", index3);
240 item.album = search_result.substr(index3 + 7, index4 - index3 - 7);
241
242 //获取时长
243 index2 = search_result.find(L"\"duration\"", index1);
244 if (index2 != string::npos)
245 {
246 index3 = search_result.find(L',', index2);
247 wstring str_duration = search_result.substr(index2 + 11, index3 - index2 - 11);
248 item.duration = _wtoi(str_duration.c_str());
249 }
250
251 DeleteStrSlash(item.title);
252 DeleteStrSlash(item.artist);
253 DeleteStrSlash(item.album);
254 down_list.push_back(item);
255 }
256 }
257
CharacterSimilarDegree(wchar_t ch1,wchar_t ch2)258 double CInternetCommon::CharacterSimilarDegree(wchar_t ch1, wchar_t ch2)
259 {
260 if (ch1 == ch2)
261 return 1;
262 else if ((ch1 >= 'A' && ch1 <= 'Z' && ch2 == ch1 + 32) || (ch1 >= 'a' && ch1 <= 'z' && ch2 == ch1 - 32))
263 return 0.8;
264 else if ((ch1 == L'1' && ch2 == L'一') || (ch1 == L'一' && ch2 == L'1')
265 || (ch1 == L'2' && ch2 == L'二') || (ch1 == L'二' && ch2 == L'2')
266 || (ch1 == L'3' && ch2 == L'三') || (ch1 == L'三' && ch2 == L'3')
267 || (ch1 == L'4' && ch2 == L'四') || (ch1 == L'四' && ch2 == L'4')
268 || (ch1 == L'5' && ch2 == L'五') || (ch1 == L'五' && ch2 == L'5')
269 || (ch1 == L'6' && ch2 == L'六') || (ch1 == L'六' && ch2 == L'6')
270 || (ch1 == L'7' && ch2 == L'七') || (ch1 == L'七' && ch2 == L'7')
271 || (ch1 == L'8' && ch2 == L'八') || (ch1 == L'八' && ch2 == L'8')
272 || (ch1 == L'9' && ch2 == L'九') || (ch1 == L'九' && ch2 == L'9')
273 || (ch1 == L'0' && ch2 == L'零') || (ch1 == L'零' && ch2 == L'0')
274 )
275 return 0.7;
276 else
277 return 0.0;
278 }
279
StringSimilarDegree_LD(const wstring & srcString,const wstring & matchString)280 double CInternetCommon::StringSimilarDegree_LD(const wstring & srcString, const wstring & matchString)
281 {
282 /*
283 编辑距离算法,来自“编辑距离——百度百科”(https://baike.baidu.com/history/编辑距离/8010193/131513486)
284 比如要计算cafe和coffee的编辑距离。cafe→caffe→coffe→coffee
285 先创建一个6×8的表(cafe长度为4,coffee长度为6,各加2)
286 表1:
287 | | | c | o | f | f | e | e |
288 | | | | | | | | |
289 | c | | | | | | | |
290 | a | | | | | | | |
291 | f | | | | | | | |
292 | e | | | | | | | |
293
294 接着,在如下位置填入数字(表2):
295 表2:
296 | | | c | o | f | f | e | e |
297 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
298 | c | 1 | | | | | | |
299 | a | 2 | | | | | | |
300 | f | 3 | | | | | | |
301 | e | 4 | | | | | | |
302
303 从3,3格开始,开始计算。取以下三个值的最小值:
304 如果最上方的字符等于最左方的字符,则为左上方的数字。否则为左上方的数字+1。(对于3,3来说为0)
305 左方数字+1(对于3,3格来说为2)
306 上方数字+1(对于3,3格来说为2)
307
308 因此为格3,3为0(表3)
309 表3:
310 | | | c | o | f | f | e | e |
311 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
312 | c | 1 | 0 | | | | | |
313 | a | 2 | | | | | | |
314 | f | 3 | | | | | | |
315 | e | 4 | | | | | | |
316
317 从3,4(三行四列)格开始,开始计算。取以下三个值的最小值:
318 * 如果最上方的字符等于最左方的字符,则为左上方的数字。否则为左上方的数字+1。(对于3,4来说为2)
319 * 左方数字+1(对于3,4格来说为1)
320 * 上方数字+1(对于3,4格来说为3)
321 因此为格3,3为0(表4)
322 表4:
323 | | | c | o | f | f | e | e |
324 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
325 | c | 1 | 0 | 1 | | | | |
326 | a | 2 | | | | | | |
327 | f | 3 | | | | | | |
328 | e | 4 | | | | | | |
329
330 循环操作,推出下表
331 | | | c | o | f | f | e | e |
332 | | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
333 | c | 1 | 0 | 1 | 2 | 3 | 4 | 5 |
334 | a | 2 | 1 | 1 | 2 | 3 | 4 | 5 |
335 | f | 3 | 2 | 2 | 1 | 2 | 3 | 4 |
336 | e | 4 | 3 | 3 | 2 | 2 | 2 | 3 |
337 取右下角,得编辑距离为3。
338 */
339
340 int n = srcString.size();
341 int m = matchString.size();
342
343 const int MAX_LENGTH = 256;
344 if (n <= 0 || n > MAX_LENGTH || m <= 0 || m > MAX_LENGTH || std::abs(n - m) > MAX_LENGTH) //如果要比较的字符串过长,则不计算
345 return 0;
346
347 //创建表
348 vector<vector<double>> d(n + 1, vector<double>(m + 1));
349 double cost; // cost
350
351 //// Step 1(如果其中一个字符串长度为0,则相似度为1)?
352 //if (n == 0 || m == 0) return 0.0; //如果其中一个字符串长度为0,则相似度为0
353
354 // Step 2,给表的第1行和第1列填入数字
355 for (int i{}; i <= n; ++i) d[i][0] = i;
356 for (int j{}; j <= m; ++j) d[0][j] = j;
357 // Step 3
358 for (int i = 1; i <= n; i++)
359 {
360 //Step 4
361 for (int j = 1; j <= m; j++)
362 {
363 // Step 5,遍历表格剩下的格子计算每个格子的值
364 wchar_t ch1 = matchString[j - 1];
365 wchar_t ch2 = srcString[i - 1];
366
367 //比较最上方的字符和最左方的字符
368 cost = 1 - CharacterSimilarDegree(ch1, ch2);
369
370 // Step 6,取3个值中的最小值
371 d[i][j] = CCommon::Min3(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
372 }
373 }
374
375 // Step 7
376 double ds = 1 - (double)d[n][m] / max(srcString.size(), matchString.size());
377
378 return ds;
379 }
380
381
SelectMatchedItem(const vector<ItemInfo> & down_list,const wstring & title,const wstring & artist,const wstring & album,const wstring & filename,bool write_log)382 int CInternetCommon::SelectMatchedItem(const vector<ItemInfo>& down_list, const wstring & title, const wstring & artist, const wstring & album, const wstring & filename, bool write_log)
383 {
384 /*
385 匹配度计算:
386 通过计算以下的匹配度,并设置权值,将每一项的匹配度乘以权值再相加,得到的值最大的就是最匹配的一项
387 项目 权值
388 标题——标题 0.4
389 艺术家——艺术家 0.4
390 唱片集——唱片集 0.3
391 文件名——标题 0.3
392 文件名——艺术家 0.2
393 列表中的排序 0.05
394 时长 0.6
395 */
396 if (down_list.empty()) return -1;
397 vector<double> weights; //储存列表中每一项的权值
398
399 //计算每一项的权值
400 for (size_t i{}; i<down_list.size(); i++)
401 {
402 double weight;
403 weight = 0;
404 weight += (StringSimilarDegree_LD(title, down_list[i].title) * 0.4);
405 weight += (StringSimilarDegree_LD(artist, down_list[i].artist) * 0.4);
406 weight += (StringSimilarDegree_LD(album, down_list[i].album) * 0.3);
407 weight += (StringSimilarDegree_LD(filename, down_list[i].title) * 0.3);
408 weight += (StringSimilarDegree_LD(filename, down_list[i].artist) * 0.3);
409
410 weight += ((1 - i * 0.02) * 0.05); //列表中顺序的权值,一般来说,网易云音乐的搜索结果的返回结果中
411 //排在越前面的关联度就越高,这里取第一项为1,之后每一项减0.02,最后再乘以0.05
412 weights.push_back(weight);
413 }
414
415 //查找权值最大的项
416 double max_weight = weights[0];
417 int max_index{};
418 for (size_t i{ 1 }; i < weights.size(); i++)
419 {
420 if (weights[i] > max_weight)
421 {
422 max_weight = weights[i];
423 max_index = i;
424 }
425 }
426
427 //如果权值最大项的权值小于0.3,则判定没有匹配的项,返回-1
428 if (max_weight < 0.3)
429 max_index = -1;
430
431 #ifdef DEBUG
432 if (write_log)
433 {
434 CString out_info{ _T("\n==============================================================================\n") };
435 CString tmp;
436 out_info += _T("\n歌曲信息:\n");
437 out_info += _T("文件名:");
438 out_info += filename.c_str();
439 out_info += _T("\n标题:");
440 out_info += title.c_str();
441 out_info += _T("\n艺术家:");
442 out_info += artist.c_str();
443 out_info += _T("\n唱片集:");
444 out_info += album.c_str();
445
446 out_info += _T("\n搜索结果:\n");
447 out_info += _T("序号\t歌曲ID\t标题\t艺术家\t唱片集\n");
448 for (size_t i{}; i<down_list.size(); i++)
449 {
450 tmp.Format(_T("%d\t%s\t%s\t%s\t%s\n"), i + 1, down_list[i].id.c_str(), down_list[i].title.c_str(), down_list[i].artist.c_str(), down_list[i].album.c_str());
451 out_info += tmp;
452 }
453
454 out_info += _T("各项权值:\n");
455 for (size_t i{}; i < weights.size(); i++)
456 {
457 tmp.Format(_T("%d\t%f\n"), i + 1, weights[i]);
458 out_info += tmp;
459 }
460
461 tmp.Format(_T("最佳匹配项:%d\n\n"), max_index + 1);
462 out_info += tmp;
463
464 CCommon::WriteLog(L".\\search.log", wstring{ out_info });
465 }
466 #endif // DEBUG
467
468 return max_index;
469 }
470
SearchSongAndGetMatched(const wstring & title,const wstring & artist,const wstring & album,const wstring & file_name,bool message,DownloadResult * result)471 CInternetCommon::ItemInfo CInternetCommon::SearchSongAndGetMatched(const wstring & title, const wstring & artist, const wstring & album, const wstring & file_name, bool message, DownloadResult* result)
472 {
473 //设置搜索关键字
474 wstring search_result; //查找歌曲返回的结果
475 wstring keyword; //查找的关键字
476 if (title.empty() || theApp.m_str_table.LoadText(L"TXT_EMPTY_TITLE") == title) // 如果没有标题信息,就把文件名设为搜索关键字
477 {
478 keyword = file_name;
479 size_t index = keyword.rfind(L'.'); //查找最后一个点
480 keyword = keyword.substr(0, index); //去掉扩展名
481 }
482 else if (artist.empty() || theApp.m_str_table.LoadText(L"TXT_EMPTY_ARTIST") == artist) //如果有标题信息但是没有艺术家信息,就把标题设为搜索关键字
483 {
484 keyword = title;
485 }
486 else //否则将“艺术家 标题”设为搜索关键字
487 {
488 keyword = artist + L' ' + title;
489 }
490
491 //搜索歌曲
492 wstring keyword_url = CInternetCommon::URLEncode(keyword); //将搜索关键字转换成URL编码
493 CString url;
494 url.Format(L"http://music.163.com/api/search/get/?s=%s&limit=20&type=1&offset=0", keyword_url.c_str());
495 int rtn = CInternetCommon::HttpPost(wstring(url), search_result); //向网易云音乐的歌曲搜索API发送http的POST请求
496 if (rtn != 0)
497 {
498 if(message)
499 {
500 const wstring& info = theApp.m_str_table.LoadText(L"MSG_NETWORK_CONNECTION_FAILED");
501 AfxMessageBox(info.c_str(), NULL, MB_ICONWARNING);
502 }
503 if (result != nullptr)
504 *result = DR_NETWORK_ERROR;
505
506 return CInternetCommon::ItemInfo();
507 }
508
509 //处理返回结果
510 vector<CInternetCommon::ItemInfo> down_list;
511 CInternetCommon::DisposeSearchResult(down_list, search_result); //处理返回的查找结果,并将结果保存在down_list容器里
512 if (down_list.empty())
513 {
514 if (message)
515 {
516 const wstring& info = theApp.m_str_table.LoadText(L"MSG_NETWORK_CANNOT_FIND_THIS_SONG");
517 AfxMessageBox(info.c_str(), NULL, MB_ICONWARNING);
518 }
519 if (result != nullptr)
520 *result = DR_DOWNLOAD_ERROR;
521 return CInternetCommon::ItemInfo();
522 }
523
524 //计算最佳选择项
525 wstring _title = title;
526 wstring _artist = artist;
527 wstring _album = album;
528 if (theApp.m_str_table.LoadText(L"TXT_EMPTY_TITLE") == title) _title.clear();
529 if (theApp.m_str_table.LoadText(L"TXT_EMPTY_ARTIST") == artist) _artist.clear();
530 if (theApp.m_str_table.LoadText(L"TXT_EMPTY_ALBUM") == album) _album.clear();
531 if (_title.empty())
532 _title = keyword;
533 int best_matched = CInternetCommon::SelectMatchedItem(down_list, _title, _artist, _album, file_name, true);
534 if (best_matched < 0)
535 {
536 if (message)
537 {
538 const wstring& info = theApp.m_str_table.LoadText(L"MSG_NETWORK_CANNOT_FIND_THIS_SONG");
539 AfxMessageBox(info.c_str(), NULL, MB_ICONWARNING);
540 }
541 if (result != nullptr)
542 *result = DR_DOWNLOAD_ERROR;
543 return CInternetCommon::ItemInfo();
544 }
545
546 //获返回最佳匹配项
547 if (result != nullptr)
548 *result = DR_SUCCESS;
549 return down_list[best_matched];
550 }
551