2012-02-20 21:47:14 +00:00
|
|
|
/* This file is (c) 2008-2012 Konstantin Isakov <ikm@goldendict.org>
|
2010-06-12 20:16:35 +00:00
|
|
|
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
|
|
|
|
|
|
|
#include "forvo.hh"
|
|
|
|
#include "wstring_qt.hh"
|
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QtXml>
|
|
|
|
#include <list>
|
|
|
|
#include "audiolink.hh"
|
|
|
|
#include "htmlescape.hh"
|
2010-06-30 10:44:49 +00:00
|
|
|
#include "utf8.hh"
|
2013-11-16 18:34:09 +00:00
|
|
|
#include "gddebug.hh"
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
namespace Forvo {
|
|
|
|
|
|
|
|
using namespace Dictionary;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
class ForvoDictionary: public Dictionary::Class
|
|
|
|
{
|
|
|
|
string name;
|
|
|
|
QString apiKey, languageCode;
|
|
|
|
QNetworkAccessManager & netMgr;
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
ForvoDictionary( string const & id,
|
|
|
|
string const & name_,
|
|
|
|
QString const & apiKey_,
|
|
|
|
QString const & languageCode_,
|
|
|
|
QNetworkAccessManager & netMgr_ ):
|
|
|
|
Dictionary::Class( id, vector< string >() ),
|
|
|
|
name( name_ ),
|
|
|
|
apiKey( apiKey_ ),
|
|
|
|
languageCode( languageCode_ ),
|
|
|
|
netMgr( netMgr_ )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
string getName() noexcept override
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
map< Property, string > getProperties() noexcept override
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
|
|
|
return map< Property, string >();
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
unsigned long getArticleCount() noexcept override
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
unsigned long getWordCount() noexcept override
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
sptr< WordSearchRequest > prefixMatch( wstring const & /*word*/, unsigned long /*maxResults*/ ) override
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
2022-11-29 03:54:31 +00:00
|
|
|
sptr< WordSearchRequestInstant > sr = std::make_shared< WordSearchRequestInstant >();
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2010-06-22 12:43:11 +00:00
|
|
|
sr->setUncertain( true );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
return sr;
|
|
|
|
}
|
|
|
|
|
2023-04-22 09:04:03 +00:00
|
|
|
sptr< DataRequest > getArticle( wstring const &, vector< wstring > const & alts, wstring const &, bool ) override;
|
2012-12-03 12:47:43 +00:00
|
|
|
|
|
|
|
protected:
|
|
|
|
|
2022-12-29 07:07:40 +00:00
|
|
|
void loadIcon() noexcept override;
|
2010-06-12 20:16:35 +00:00
|
|
|
};
|
|
|
|
|
2023-12-12 02:20:34 +00:00
|
|
|
class ForvoArticleRequest: public Dictionary::DataRequest
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
struct NetReply
|
|
|
|
{
|
|
|
|
sptr< QNetworkReply > reply;
|
|
|
|
string word;
|
|
|
|
bool finished;
|
|
|
|
|
|
|
|
NetReply( sptr< QNetworkReply > const & reply_, string const & word_ ):
|
|
|
|
reply( reply_ ),
|
|
|
|
word( word_ ),
|
|
|
|
finished( false )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
using NetReplies = std::list< NetReply >;
|
|
|
|
NetReplies netReplies;
|
|
|
|
QString apiKey, languageCode;
|
|
|
|
string dictionaryId;
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
|
|
|
ForvoArticleRequest( wstring const & word,
|
|
|
|
vector< wstring > const & alts,
|
|
|
|
QString const & apiKey_,
|
|
|
|
QString const & languageCode_,
|
|
|
|
string const & dictionaryId_,
|
|
|
|
QNetworkAccessManager & mgr );
|
|
|
|
|
2024-11-05 22:17:54 +00:00
|
|
|
void cancel() override;
|
2023-12-12 02:20:34 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
|
|
|
|
void addQuery( QNetworkAccessManager & mgr, wstring const & word );
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
virtual void requestFinished( QNetworkReply * );
|
|
|
|
};
|
|
|
|
|
2010-11-22 23:50:30 +00:00
|
|
|
sptr< DataRequest >
|
|
|
|
ForvoDictionary::getArticle( wstring const & word, vector< wstring > const & alts, wstring const &, bool )
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
2022-11-18 12:13:24 +00:00
|
|
|
if ( word.size() > 80 || apiKey.isEmpty() ) {
|
2010-11-22 23:50:30 +00:00
|
|
|
// Don't make excessively large queries -- they're fruitless anyway
|
2010-06-30 10:44:49 +00:00
|
|
|
|
2022-11-29 03:54:31 +00:00
|
|
|
return std::make_shared< DataRequestInstant >( false );
|
2010-11-22 23:50:30 +00:00
|
|
|
}
|
|
|
|
else {
|
2022-11-29 03:54:31 +00:00
|
|
|
return std::make_shared< ForvoArticleRequest >( word, alts, apiKey, languageCode, getId(), netMgr );
|
2022-11-18 12:13:24 +00:00
|
|
|
}
|
2010-11-22 23:50:30 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2022-06-03 13:28:41 +00:00
|
|
|
void ForvoDictionary::loadIcon() noexcept
|
2010-11-22 23:50:30 +00:00
|
|
|
{
|
2013-01-31 22:30:11 +00:00
|
|
|
if ( dictionaryIconLoaded ) {
|
|
|
|
return;
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2013-01-31 22:30:11 +00:00
|
|
|
|
2023-06-19 02:34:08 +00:00
|
|
|
dictionaryIcon = QIcon( ":/icons/forvo.png" );
|
2012-12-03 12:47:43 +00:00
|
|
|
dictionaryIconLoaded = true;
|
2010-11-22 23:50:30 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2010-11-22 23:50:30 +00:00
|
|
|
} // namespace
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
void ForvoArticleRequest::cancel()
|
|
|
|
{
|
|
|
|
finish();
|
|
|
|
}
|
|
|
|
|
|
|
|
ForvoArticleRequest::ForvoArticleRequest( wstring const & str,
|
2010-06-30 10:44:49 +00:00
|
|
|
vector< wstring > const & alts,
|
2010-06-12 20:16:35 +00:00
|
|
|
QString const & apiKey_,
|
|
|
|
QString const & languageCode_,
|
|
|
|
string const & dictionaryId_,
|
|
|
|
QNetworkAccessManager & mgr ):
|
2010-06-30 10:44:49 +00:00
|
|
|
apiKey( apiKey_ ),
|
|
|
|
languageCode( languageCode_ ),
|
|
|
|
dictionaryId( dictionaryId_ )
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
2022-12-26 02:08:17 +00:00
|
|
|
connect( &mgr, &QNetworkAccessManager::finished, this, &ForvoArticleRequest::requestFinished, Qt::QueuedConnection );
|
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
addQuery( mgr, str );
|
|
|
|
|
2023-07-29 16:50:03 +00:00
|
|
|
for ( const auto & alt : alts ) {
|
|
|
|
addQuery( mgr, alt );
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ForvoArticleRequest::addQuery( QNetworkAccessManager & mgr, wstring const & str )
|
|
|
|
{
|
2023-04-16 09:07:07 +00:00
|
|
|
gdDebug( "Forvo: requesting article %s\n", QString::fromStdU32String( str ).toUtf8().data() );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2022-11-18 12:13:24 +00:00
|
|
|
QString key = apiKey;
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2013-05-31 04:20:25 +00:00
|
|
|
QUrl reqUrl =
|
2020-01-09 13:43:07 +00:00
|
|
|
QUrl::fromEncoded( QString( "https://apifree.forvo.com"
|
2017-09-01 09:30:29 +00:00
|
|
|
"/key/"
|
|
|
|
+ key
|
|
|
|
+ "/action/word-pronunciations"
|
|
|
|
"/format/xml"
|
2023-04-16 09:07:07 +00:00
|
|
|
"/word/"
|
|
|
|
+ QLatin1String( QUrl::toPercentEncoding( QString::fromStdU32String( str ) ) )
|
2017-09-01 09:30:29 +00:00
|
|
|
+ "/language/" + languageCode + "/order/rate-desc" )
|
2010-08-31 13:19:53 +00:00
|
|
|
.toUtf8() );
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-08-31 13:19:53 +00:00
|
|
|
// GD_DPRINTF( "req: %s\n", reqUrl.toEncoded().data() );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2022-01-15 07:29:20 +00:00
|
|
|
sptr< QNetworkReply > netReply = std::shared_ptr< QNetworkReply >( mgr.get( QNetworkRequest( reqUrl ) ) );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
2010-06-30 10:44:49 +00:00
|
|
|
netReplies.push_back( NetReply( netReply, Utf8::encode( str ) ) );
|
2010-06-12 20:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ForvoArticleRequest::requestFinished( QNetworkReply * r )
|
|
|
|
{
|
2014-05-10 21:02:31 +00:00
|
|
|
GD_DPRINTF( "Finished.\n" );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
if ( isFinished() ) { // Was cancelled
|
|
|
|
return;
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
// Find this reply
|
|
|
|
|
|
|
|
bool found = false;
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2023-07-29 16:50:03 +00:00
|
|
|
for ( auto & netReplie : netReplies ) {
|
|
|
|
if ( netReplie.reply.get() == r ) {
|
|
|
|
netReplie.finished = true; // Mark as finished
|
2010-06-12 20:16:35 +00:00
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !found ) {
|
|
|
|
// Well, that's not our reply, don't do anything
|
|
|
|
return;
|
|
|
|
}
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
bool updated = false;
|
|
|
|
|
2010-06-30 10:44:49 +00:00
|
|
|
for ( ; netReplies.size() && netReplies.front().finished; netReplies.pop_front() ) {
|
|
|
|
sptr< QNetworkReply > netReply = netReplies.front().reply;
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
if ( netReply->error() == QNetworkReply::NoError ) {
|
|
|
|
QDomDocument dd;
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
QString errorStr;
|
|
|
|
int errorLine, errorColumn;
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
if ( !dd.setContent( netReply.get(), false, &errorStr, &errorLine, &errorColumn ) ) {
|
|
|
|
setErrorString(
|
|
|
|
QString( tr( "XML parse error: %1 at %2,%3" ).arg( errorStr ).arg( errorLine ).arg( errorColumn ) ) );
|
|
|
|
}
|
|
|
|
else {
|
2022-01-15 07:29:20 +00:00
|
|
|
// GD_DPRINTF( "%s\n", dd.toByteArray().data() );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
QDomNode items = dd.namedItem( "items" );
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
if ( !items.isNull() ) {
|
|
|
|
QDomNodeList nl = items.toElement().elementsByTagName( "item" );
|
|
|
|
|
|
|
|
if ( nl.count() ) {
|
|
|
|
string articleBody;
|
|
|
|
|
|
|
|
articleBody += "<div class='forvo_headword'>";
|
2010-06-30 10:44:49 +00:00
|
|
|
articleBody += Html::escape( netReplies.front().word );
|
2010-06-12 20:16:35 +00:00
|
|
|
articleBody += "</div>";
|
|
|
|
|
|
|
|
articleBody += "<table class=\"forvo_play\">";
|
|
|
|
|
2022-04-05 12:53:25 +00:00
|
|
|
for ( int x = 0; x < nl.length(); ++x ) {
|
2010-06-12 20:16:35 +00:00
|
|
|
QDomElement item = nl.item( x ).toElement();
|
|
|
|
|
|
|
|
QDomNode mp3 = item.namedItem( "pathmp3" );
|
|
|
|
|
|
|
|
if ( !mp3.isNull() ) {
|
|
|
|
articleBody += "<tr>";
|
|
|
|
|
2013-05-31 04:20:25 +00:00
|
|
|
QUrl url( mp3.toElement().text() );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
string ref = string( "\"" ) + url.toEncoded().data() + "\"";
|
|
|
|
|
2024-10-24 13:32:21 +00:00
|
|
|
articleBody += addAudioLink( url.toEncoded(), dictionaryId ).c_str();
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
bool isMale = ( item.namedItem( "sex" ).toElement().text().toLower() != "f" );
|
|
|
|
|
|
|
|
QString user = item.namedItem( "username" ).toElement().text();
|
|
|
|
QString country = item.namedItem( "country" ).toElement().text();
|
|
|
|
|
|
|
|
string userProfile =
|
|
|
|
string( "http://www.forvo.com/user/" ) + QUrl::toPercentEncoding( user ).data() + "/";
|
|
|
|
|
|
|
|
int totalVotes = item.namedItem( "num_votes" ).toElement().text().toInt();
|
|
|
|
int positiveVotes = item.namedItem( "num_positive_votes" ).toElement().text().toInt();
|
|
|
|
int negativeVotes = totalVotes - positiveVotes;
|
|
|
|
|
|
|
|
string votes;
|
|
|
|
|
|
|
|
if ( positiveVotes || negativeVotes ) {
|
|
|
|
votes += " ";
|
|
|
|
|
|
|
|
if ( positiveVotes ) {
|
|
|
|
votes += "<span class='forvo_positive_votes'>+";
|
|
|
|
votes += QByteArray::number( positiveVotes ).data();
|
|
|
|
votes += "</span>";
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( negativeVotes ) {
|
|
|
|
if ( positiveVotes ) {
|
|
|
|
votes += " ";
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
votes += "<span class='forvo_negative_votes'>-";
|
|
|
|
votes += QByteArray::number( negativeVotes ).data();
|
|
|
|
votes += "</span>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
string addTime = tr( "Added %1" ).arg( item.namedItem( "addtime" ).toElement().text() ).toUtf8().data();
|
|
|
|
|
2023-03-05 20:20:05 +00:00
|
|
|
articleBody += "<td><a href=" + ref + " title=\"" + Html::escape( addTime )
|
|
|
|
+ R"("><img src="qrc:///icons/playsound.png" border="0" alt="Play"/></a></td>)";
|
2010-06-12 20:16:35 +00:00
|
|
|
articleBody += string( "<td>" ) + tr( "by" ).toUtf8().data() + " <a class='forvo_user' href='"
|
|
|
|
+ userProfile + "'>" + Html::escape( user.toUtf8().data() ) + "</a> <span class='forvo_location'>("
|
|
|
|
+ ( isMale ? tr( "Male" ) : tr( "Female" ) ).toUtf8().data() + " " + tr( "from" ).toUtf8().data()
|
|
|
|
+ " " + Html::escape( country.toUtf8().data() ) + ")</span>" + votes + "</td>";
|
|
|
|
articleBody += "</tr>";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
articleBody += "</table>";
|
|
|
|
|
2023-06-23 15:09:31 +00:00
|
|
|
appendString( articleBody );
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
hasAnyData = true;
|
|
|
|
|
|
|
|
updated = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QDomNode errors = dd.namedItem( "errors" );
|
|
|
|
|
|
|
|
if ( !errors.isNull() ) {
|
2010-08-31 19:24:34 +00:00
|
|
|
QString text( errors.namedItem( "error" ).toElement().text() );
|
|
|
|
|
|
|
|
if ( text == "Limit/day reached." && apiKey.simplified().isEmpty() ) {
|
|
|
|
// Give a hint that the user should apply for his own key.
|
|
|
|
|
|
|
|
text +=
|
|
|
|
"\n"
|
|
|
|
+ tr(
|
|
|
|
"Go to Edit|Dictionaries|Sources|Forvo and apply for our own API key to make this error disappear." );
|
|
|
|
}
|
|
|
|
|
|
|
|
setErrorString( text );
|
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
}
|
2014-05-10 21:02:31 +00:00
|
|
|
GD_DPRINTF( "done.\n" );
|
2010-06-12 20:16:35 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
setErrorString( netReply->errorString() );
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( netReplies.empty() ) {
|
|
|
|
finish();
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
else if ( updated ) {
|
|
|
|
update();
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
vector< sptr< Dictionary::Class > >
|
|
|
|
makeDictionaries( Dictionary::Initializing &, Config::Forvo const & forvo, QNetworkAccessManager & mgr )
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2010-06-12 20:16:35 +00:00
|
|
|
{
|
|
|
|
vector< sptr< Dictionary::Class > > result;
|
|
|
|
|
2022-11-18 12:13:24 +00:00
|
|
|
if ( forvo.enable && !forvo.apiKey.isEmpty() ) {
|
2021-12-29 14:29:06 +00:00
|
|
|
QStringList codes = forvo.languageCodes.split( ',', Qt::SkipEmptyParts );
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
QSet< QString > usedCodes;
|
|
|
|
|
2023-07-29 16:50:03 +00:00
|
|
|
for ( const auto & x : codes ) {
|
|
|
|
QString code = x.simplified();
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
if ( code.size() && !usedCodes.contains( code ) ) {
|
|
|
|
// Generate id
|
|
|
|
|
|
|
|
QCryptographicHash hash( QCryptographicHash::Md5 );
|
|
|
|
|
|
|
|
hash.addData( "Forvo source version 1.0" );
|
|
|
|
hash.addData( code.toUtf8() );
|
|
|
|
|
|
|
|
QString displayedCode( code.toLower() );
|
|
|
|
|
|
|
|
if ( displayedCode.size() ) {
|
|
|
|
displayedCode[ 0 ] = displayedCode[ 0 ].toUpper();
|
2024-10-10 07:13:23 +00:00
|
|
|
}
|
2010-06-12 20:16:35 +00:00
|
|
|
|
|
|
|
result.push_back(
|
2022-11-29 03:54:31 +00:00
|
|
|
std::make_shared< ForvoDictionary >( hash.result().toHex().data(),
|
2010-06-12 20:16:35 +00:00
|
|
|
QString( "Forvo (%1)" ).arg( displayedCode ).toUtf8().data(),
|
|
|
|
forvo.apiKey,
|
|
|
|
code,
|
|
|
|
mgr ) );
|
|
|
|
|
|
|
|
usedCodes.insert( code );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2023-12-12 02:20:34 +00:00
|
|
|
#include "forvo.moc"
|
2010-06-12 20:16:35 +00:00
|
|
|
} // namespace Forvo
|