2 * This file is part of Jenirok.
4 * Jenirok is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * Jenirok is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with Jenirok. If not, see <http://www.gnu.org/licenses/>.
19 #include <QtCore/QDebug>
24 static const QString SITE_URLS[Eniro::SITE_COUNT] =
26 "http://wap.eniro.fi/",
27 "http://wap.eniro.se/",
28 "http://wap.eniro.dk/"
31 static const QString SITE_NAMES[Eniro::SITE_COUNT] =
38 static const QString SITE_IDS[Eniro::SITE_COUNT] =
45 static const QString INVALID_LOGIN_STRING = "Invalid login details";
46 static const QString TIMEOUT_STRING = "Request timed out";
47 static const QString PERSON_REGEXP = "<td class=\"hTd2\">(.*)<b>(.*)</td>";
48 static const QString YELLOW_REGEXP = "<td class=\"hTd2\">(.*)<span class=\"gray\">(.*)</td>|<td class=\"hTd2\">(.*)<span class=\"bold\"\\}>(.*)</td>";
49 static const QString SINGLE_REGEXP = "<div class=\"header\">(.*)</div>(.*)<div class=\"callRow\">(.*)(<div class=\"block\">|</p>(.*)<br/>|</p>(.*)<br />)";
50 static const QString NUMBER_REGEXP = "<div class=\"callRow\">(.*)</div>";
51 static const QString LOGIN_CHECK = "<input class=\"inpTxt\" id=\"loginformUsername\"";
54 Eniro::Eniro(QObject *parent): Source(parent), site_(Eniro::FI),
55 loggedIn_(false), username_(""), password_(""),
56 timerId_(0), pendingSearches_(), pendingNumberRequests_()
68 for(searchMap::iterator sit = pendingSearches_.begin();
69 sit != pendingSearches_.end(); sit++)
78 pendingSearches_.clear();
80 for(numberMap::iterator nit = pendingNumberRequests_.begin();
81 nit != pendingNumberRequests_.end(); nit++)
90 pendingNumberRequests_.clear();
91 pendingLoginRequests_.clear();
94 void Eniro::setSite(Eniro::Site site)
99 void Eniro::timerEvent(QTimerEvent* t)
103 int currentId = http_.currentId();
107 searchMap::const_iterator it = pendingSearches_.find(currentId);
109 if(it != pendingSearches_.end())
111 QVector <Eniro::Result> results = it.value()->results;
112 SearchDetails details = it.value()->details;
116 setError(TIMEOUT, TIMEOUT_STRING);
118 emit requestFinished(results, details, true);
123 void Eniro::login(QString const& username,
124 QString const& password)
126 username_ = username;
127 password_ = password;
138 void Eniro::search(SearchDetails const& details)
142 SearchType type = details.type;
144 // Only logged in users can use other than person search
145 if(!loggedIn_ && site_ == FI)
150 QUrl url = createUrl(details.query, details.location);
153 // We must use full search instead of wap page because wap search is currently not
154 // working for persons
155 if(loggedIn_ && type == PERSONS && site_ == FI && getMaxResults() > 1)
159 else if(loggedIn_ || site_ != FI)
182 url.addQueryItem("what", what);
184 http_.setHost(url.host(), url.port(80));
185 int id = http_.get(url.encodedPath() + '?' + url.encodedQuery());
187 //qDebug() << "Url: " << url.host() << url.encodedPath() << "?" << url.encodedQuery();
189 QVector <Source::Result> results;
191 // Store search data for later identification
192 SearchData* newData = new SearchData;
193 newData->details = details;
194 newData->results = results;
195 newData->foundNumbers = 0;
196 newData->numbersTotal = 0;
198 // Store request id so that it can be identified later
199 pendingSearches_[id] = newData;
203 void Eniro::handleHttpData(int id, QByteArray const& data)
205 searchMap::const_iterator searchIt;
206 numberMap::const_iterator numberIt;
208 // Check if request is pending search request
209 if((searchIt = pendingSearches_.find(id)) !=
210 pendingSearches_.end())
214 setError(CONNECTION_FAILURE, "Server returned empty data");
215 emitRequestFinished(id, searchIt.value(), true);
219 // Load results from html data
220 loadResults(id, data);
223 // Check if request is pending number requests
224 else if((numberIt = pendingNumberRequests_.find(id)) !=
225 pendingNumberRequests_.end())
229 setError(CONNECTION_FAILURE, "Server returned empty data");
230 emitRequestFinished(id, searchIt.value(), true);
234 // Load number from html data
235 loadNumber(id, data);
238 // Check for login request
239 else if(pendingLoginRequests_.find(id) !=
240 pendingLoginRequests_.end())
244 // If html source contains LOGIN_CHECK, login failed
245 if(data.indexOf(LOGIN_CHECK) != -1)
250 emit loginStatus(success);
255 void Eniro::handleHttpError(int id)
257 searchMap::const_iterator searchIt;
258 numberMap::const_iterator numberIt;
260 // Check if request is pending search request
261 if((searchIt = pendingSearches_.find(id)) !=
262 pendingSearches_.end())
264 setError(CONNECTION_FAILURE, http_.errorString());
265 emitRequestFinished(id, searchIt.value(), true);
268 // Check if request is pending number requests
269 else if((numberIt = pendingNumberRequests_.find(id)) !=
270 pendingNumberRequests_.end())
272 setError(CONNECTION_FAILURE, http_.errorString());
273 delete pendingNumberRequests_[id];
274 pendingNumberRequests_.remove(id);
277 // Check for login request
278 else if(pendingLoginRequests_.find(id) !=
279 pendingLoginRequests_.end())
281 emit loginStatus(false);
285 // Loads results from html source code
286 void Eniro::loadResults(int id, QString const& httpData)
288 searchMap::iterator it = pendingSearches_.find(id);
290 // Finnish person search is not working in wap mode so we have to use different type of loading
291 if(getMaxResults() > 1 && loggedIn_ && site_ == FI && it.value()->details.type == PERSONS)
293 loadFinnishPersonResults(id, httpData);
297 QRegExp rx("((" + YELLOW_REGEXP + ")|(" + PERSON_REGEXP + ")|(" + SINGLE_REGEXP + "))");
300 bool requestsPending = false;
305 while((pos = rx.indexIn(httpData, pos)) != -1)
307 pos += rx.matchedLength();
311 data = stripTags(data);
313 QStringList rows = data.split('\n');
315 for(int i = 0; i < rows.size(); i++)
317 // Remove white spaces
318 QString trimmed = rows.at(i).trimmed().toLower();
320 // Remove empty strings
321 if(trimmed.isEmpty())
328 // Convert words to uppercase
329 rows[i] = ucFirst(trimmed);
338 result.country = "Finland";
341 result.country = "Sweden";
344 result.country = "Denmark";
348 int size = rows.size();
353 result.name = rows[0];
357 result.name = rows[0];
358 result.city = rows[1];
362 if(isPhoneNumber(rows[1]))
364 result.name = rows[0];
365 result.number = cleanUpNumber(rows[1]);
366 result.city = rows[2];
370 result.name = rows[0];
371 result.street = rows[1];
372 result.city = rows[2];
377 result.name = rows[0];
378 // Remove slashes and spaces from number
379 result.number = cleanUpNumber(rows[1]);
380 result.street = rows[2];
381 result.city = rows[3];
387 for(int a = 0; a < size && a < 8; a++)
389 if(isPhoneNumber(rows[a]))
391 result.name = rows[0];
392 result.number = cleanUpNumber(rows[a]);
394 for(int i = a + 1; i < size && i < 8; i++)
396 if(!isPhoneNumber(rows[i]) && size > i + 1 && isStreet(rows[i]))
398 result.street = rows[i];
399 result.city = rows[i+1];
418 it.value()->results.push_back(result);
420 unsigned int foundResults = ++(it.value()->numbersTotal);
422 // If phone number search is enabled, we have to make another
423 // request to find it out
424 if(getFindNumber() && size < 4 && (loggedIn_ || site_ != FI) &&
425 it.value()->details.type != YELLOW_PAGES)
427 requestsPending = true;
428 getNumberForResult(id, it.value()->results.size() - 1, it.value()->details);
430 // Otherwise result is ready
433 emit resultAvailable(result, it.value()->details);
436 unsigned int maxResults = getMaxResults();
438 // Stop searching if max results is reached
439 if(maxResults && (foundResults >= maxResults))
445 // If there were no results or no phone numbers needed to
446 // be fetched, the whole request is ready
447 if(it.value()->numbersTotal == 0 || !requestsPending)
451 if(httpData.indexOf(LOGIN_CHECK) != -1)
453 setError(INVALID_LOGIN, INVALID_LOGIN_STRING),
457 emitRequestFinished(it.key(), it.value(), error);
461 void Eniro::loadFinnishPersonResults(int id, QString const& httpData)
463 searchMap::iterator it = pendingSearches_.find(id);
465 static QRegExp rx("<div id=\"hit_(.*)<p class=\"adLinks\">");
466 static QRegExp name("<a class=\"fn expand\" href=\"#\">(.*)</a>");
467 static QRegExp number("<!-- sphoneid(.*)-->(.*)<!--");
468 static QRegExp street("<span class=\"street-address\">(.*)</span>");
469 static QRegExp zipCode("<span class=\"postal-code\">(.*)</span>");
470 static QRegExp city("<span class=\"locality\">(.*)</span>");
472 name.setMinimal(true);
473 number.setMinimal(true);
474 street.setMinimal(true);
475 zipCode.setMinimal(true);
476 city.setMinimal(true);
480 unsigned int maxResults = getMaxResults();
481 unsigned int foundResults = 0;
483 while((pos = rx.indexIn(httpData, pos)) != -1)
485 pos += rx.matchedLength();
487 QString data = rx.cap(0);
491 if(name.indexIn(data) != -1)
493 result.name = name.cap(1);
500 if(number.indexIn(data) != -1)
502 result.number = number.cap(2);
505 if(street.indexIn(data) != -1)
507 result.street = street.cap(1);
512 if(zipCode.indexIn(data) != -1)
514 cityStr = zipCode.cap(1) + " ";
517 if(city.indexIn(data) != -1)
519 cityStr += city.cap(1);
522 result.city = cityStr;
524 result.name = cleanUpString(result.name);
525 result.street = cleanUpString(result.street);
526 result.number = cleanUpNumber(result.number);
527 result.city = cleanUpString(result.city);
528 result.country = "Finland";
530 it.value()->results.push_back(result);
531 emit resultAvailable(result, it.value()->details);
535 if(foundResults >= maxResults)
542 emitRequestFinished(it.key(), it.value(), false);
546 QString& Eniro::cleanUpString(QString& str)
548 str = htmlEntityDecode(str);
551 static QRegExp cleaner("(\r\n|\n|\t| )+");
552 str = str.replace(cleaner, " ");
557 // Loads phone number from html source
558 void Eniro::loadNumber(int id, QString const& result)
560 numberMap::iterator numberIt = pendingNumberRequests_.find(id);
562 // Make sure that id exists in pending number requests
563 if(numberIt == pendingNumberRequests_.end() || numberIt.value() == 0)
568 searchMap::iterator searchIt = pendingSearches_.find(numberIt.value()->searchId);
570 if(searchIt == pendingSearches_.end() || searchIt.value() == 0)
575 QRegExp rx(NUMBER_REGEXP);
581 if((pos = rx.indexIn(result, pos)) != -1)
583 QString data = rx.cap(1);
584 data = stripTags(data);
586 QString trimmed = data.trimmed();
588 if(!trimmed.isEmpty())
590 // Remove whitespaces from number
591 searchIt.value()->results[numberIt.value()->index].number = cleanUpNumber(trimmed);
593 emit resultAvailable(searchIt.value()->results[numberIt.value()->index], searchIt.value()->details);
595 unsigned int found = ++searchIt.value()->foundNumbers;
597 // Check if all numbers have been found
598 if(found >= searchIt.value()->numbersTotal)
600 emitRequestFinished(searchIt.key(), searchIt.value(), false);
603 // If number was found, there was no error
610 setError(INVALID_LOGIN, INVALID_LOGIN_STRING);
611 emitRequestFinished(searchIt.key(), searchIt.value(), true);
614 // Remove number request
615 int key = numberIt.key();
617 delete pendingNumberRequests_[key];
618 pendingNumberRequests_[key] = 0;
619 pendingNumberRequests_.remove(key);
623 QUrl Eniro::createUrl(QString const& query, QString const& location)
625 QUrl url(SITE_URLS[site_] + "query");
629 url.addQueryItem("search_word", query);
632 if(!location.isEmpty())
634 url.addQueryItem("geo_area", location);
637 unsigned int maxResults = getMaxResults();
641 url.addQueryItem("hpp", QString::number(maxResults));
643 if(loggedIn_ && site_ == FI)
645 url.addQueryItem("login_name", username_);
646 url.addQueryItem("login_password", password_);
654 // Creates a new request for phone number retrieval
655 void Eniro::getNumberForResult(int id, int index, SearchDetails const& details)
657 QUrl url = createUrl(details.query, details.location);
658 url.addQueryItem("what", "mobwpinfo");
659 url.addQueryItem("search_number", QString::number(index + 1));
661 http_.setHost(url.host(), url.port(80));
662 int requestId = http_.get(url.encodedPath() + '?' + url.encodedQuery());
663 NumberData* number = new NumberData;
664 number->searchId = id;
665 number->index = index;
666 pendingNumberRequests_[requestId] = number;
670 void Eniro::emitRequestFinished(int key, SearchData* data, bool error)
672 emit requestFinished(data->results, data->details, error);
673 delete pendingSearches_[key];
674 pendingSearches_[key] = 0;
675 pendingSearches_.remove(key);
679 QMap <Eniro::Site, Eniro::SiteDetails> Eniro::getSites()
681 QMap <Site, SiteDetails> sites;
683 for(int i = 0; i < SITE_COUNT; i++)
686 details.name = SITE_NAMES[i];
687 details.id = SITE_IDS[i];
688 sites[static_cast<Site>(i)] = details;
694 Eniro::Site Eniro::stringToSite(QString const& str)
697 QString lower = str.toLower();
699 for(int i = 0; i < SITE_COUNT; i++)
701 if(lower == SITE_NAMES[i] || lower == SITE_IDS[i])
703 site = static_cast <Site> (i);
711 bool Eniro::isStreet(QString const& str)
713 static QRegExp number("([0-9]+)");
714 int a = number.indexIn(str);
715 int b = str.indexOf(" ");
717 if((a == -1 && b == -1) || (a != -1 && b != -1))