From 9d3b0744ff2694f7d50981731d01ff3e0d86c3b1 Mon Sep 17 00:00:00 2001 From: Miguel Freitas Date: Sat, 29 Nov 2014 13:20:43 -0200 Subject: [PATCH] 0.9.27 new 'getmentions' api. return known mentions from users we follow. never miss a mention from your friends anymore! :-) (twister-html support pending) --- src/bitcoinrpc.cpp | 3 ++ src/bitcoinrpc.h | 1 + src/clientversion.h | 2 +- src/twister.cpp | 107 +++++++++++++++++++++++++++++++++++++++--- src/twister_utils.cpp | 30 ++++++++++++ src/twister_utils.h | 5 ++ 6 files changed, 141 insertions(+), 7 deletions(-) diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index d941d652..f23882e8 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -251,6 +251,7 @@ static const CRPCCommand vRPCCommands[] = { "newrtmsg", &newrtmsg, false, true, false }, { "getposts", &getposts, false, true, false }, { "getdirectmsgs", &getdirectmsgs, false, true, false }, + { "getmentions", &getmentions, false, true, false }, { "setspammsg", &setspammsg, false, false, false }, { "getspammsg", &getspammsg, false, false, false }, { "follow", &follow, false, true, false }, @@ -1308,6 +1309,8 @@ Array RPCConvertValues(const std::string &strMethod, const std::vector 2) ConvertTo(params[2]); if (strMethod == "getdirectmsgs" && n > 1) ConvertTo(params[1]); if (strMethod == "getdirectmsgs" && n > 2) ConvertTo(params[2]); + if (strMethod == "getmentions" && n > 1) ConvertTo(params[1]); + if (strMethod == "getmentions" && n > 2) ConvertTo(params[2]); if (strMethod == "follow" && n > 1) ConvertTo(params[1]); if (strMethod == "unfollow" && n > 1) ConvertTo(params[1]); if (strMethod == "listusernamespartial" && n > 1) ConvertTo(params[1]); diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index ed767cf6..0c438f17 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -204,6 +204,7 @@ extern json_spirit::Value newdirectmsg(const json_spirit::Array& params, bool fH extern json_spirit::Value newrtmsg(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getposts(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getdirectmsgs(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getmentions(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value setspammsg(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getspammsg(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value follow(const json_spirit::Array& params, bool fHelp); diff --git a/src/clientversion.h b/src/clientversion.h index a66f94f8..fde9e725 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -8,7 +8,7 @@ // These need to be macros, as version.cpp's and bitcoin-qt.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 9 -#define CLIENT_VERSION_REVISION 26 +#define CLIENT_VERSION_REVISION 27 #define CLIENT_VERSION_BUILD 0 // Set to true for release, false for prerelease or test build diff --git a/src/twister.cpp b/src/twister.cpp index 42797a65..e088b1ec 100644 --- a/src/twister.cpp +++ b/src/twister.cpp @@ -77,6 +77,8 @@ const int hashtagTimerInterval = 60; // Timer interval (sec) const double hashtagAgingFactor = pow(0.5, hashtagTimerInterval/hashtagHalfLife); const double hashtagCriticalValue = pow(0.5, hashtagExpiration/hashtagHalfLife); +const char* msgTokensDelimiter = " \n\t.,:/?!;'\"()[]{}*"; + class SimpleThreadCounter { public: SimpleThreadCounter(CCriticalSection *lock, int *counter, const char *name) : @@ -1053,6 +1055,7 @@ bool verifySignature(std::string const &strMessage, std::string const &strUserna return (pubkeyRec.GetID() == pubkey.GetID()); } +// try decrypting new DM received by any torrent we follow bool processReceivedDM(lazy_entry const* post) { bool result = false; @@ -1137,6 +1140,39 @@ bool processReceivedDM(lazy_entry const* post) return result; } +// check post received in a torrent we follow if they mention local users +void processReceivedPost(lazy_entry const &v, std::string &username, int64 time, std::string &msg) +{ + // split and look for mentions for local users + vector tokens; + boost::algorithm::split(tokens,msg,boost::algorithm::is_any_of(msgTokensDelimiter), + boost::algorithm::token_compress_on); + BOOST_FOREACH(string const& token, tokens) { + if( token.length() >= 2 ) { + char delim = token.at(0); + if( delim != '@' ) continue; + string mentionUser = token.substr(1); +#ifdef HAVE_BOOST_LOCALE + mentionUser = boost::locale::to_lower(mentionUser); +#else + boost::algorithm::to_lower(mentionUser); +#endif + + LOCK(cs_twister); + // mention of a local user && sent by someone we follow + if( m_users.count(mentionUser) && m_users[mentionUser].m_following.count(username) ) { + std::string postKey = username + ";" + boost::lexical_cast(time); + if( m_users[mentionUser].m_mentionsKeys.count(postKey) == 0 ) { + m_users[mentionUser].m_mentionsKeys.insert(postKey); + entry vEntry; + vEntry = v; + m_users[mentionUser].m_mentionsPosts.push_back(vEntry); + } + } + } + } +} + bool acceptSignedPost(char const *data, int data_size, std::string username, int seq, std::string &errmsg, boost::uint32_t *flags) { bool ret = false; @@ -1162,6 +1198,7 @@ bool acceptSignedPost(char const *data, int data_size, std::string username, int int msgUtf8Chars = utf8::num_characters(msg.begin(), msg.end()); int k = post->dict_find_int_value("k",-1); int height = post->dict_find_int_value("height",-1); + int64 time = post->dict_find_int_value("time",-1); if( n != username ) { sprintf(errbuf,"expected username '%s' got '%s'", @@ -1204,10 +1241,14 @@ bool acceptSignedPost(char const *data, int data_size, std::string username, int } } - lazy_entry const* dm = post->dict_find_dict("dm"); - if( dm && flags ) { - (*flags) |= USERPOST_FLAG_DM; - processReceivedDM(post); + if( flags ) { + lazy_entry const* dm = post->dict_find_dict("dm"); + if( dm ) { + (*flags) |= USERPOST_FLAG_DM; + processReceivedDM(post); + } else { + processReceivedPost(v, username, time, msg); + } } } } @@ -1444,7 +1485,7 @@ void updateSeenHashtags(std::string &message, int64_t msgTime) // split and look for hashtags vector tokens; set hashtags; - boost::algorithm::split(tokens,message,boost::algorithm::is_any_of(" \n\t.,:/?!;'\"()[]{}*"), + boost::algorithm::split(tokens,message,boost::algorithm::is_any_of(msgTokensDelimiter), boost::algorithm::token_compress_on); BOOST_FOREACH(string const& token, tokens) { if( token.length() >= 2 && token.at(0) == '#' ) { @@ -1793,7 +1834,7 @@ Value newpostmsg(const Array& params, bool fHelp) // split and look for mentions and hashtags vector tokens; - boost::algorithm::split(tokens,strMsg,boost::algorithm::is_any_of(" \n\t.,:/?!;'\"()[]{}*"), + boost::algorithm::split(tokens,strMsg,boost::algorithm::is_any_of(msgTokensDelimiter), boost::algorithm::token_compress_on); BOOST_FOREACH(string const& token, tokens) { if( token.length() >= 2 ) { @@ -2109,6 +2150,60 @@ Value getdirectmsgs(const Array& params, bool fHelp) return ret; } +Value getmentions(const Array& params, bool fHelp) +{ + if (fHelp || params.size() < 2 || params.size() > 3 ) + throw runtime_error( + "getmentions '{\"max_id\":max_id,\"since_id\":since_id}'\n" + "get (locally stored) mentions to user by users followed.\n" + "(use 'dhtget user mention m' for non-followed mentions)\n" + "max_id and since_id may be omited. up to posts are returned."); + + string strUsername = params[0].get_str(); + int count = params[1].get_int(); + + int max_id = std::numeric_limits::max(); + int since_id = -1; + + if( params.size() >= 3 ) { + Object optParms = params[2].get_obj(); + for (Object::const_iterator i = optParms.begin(); i != optParms.end(); ++i) { + if( i->name_ == "max_id" ) max_id = i->value_.get_int(); + if( i->name_ == "since_id" ) since_id = i->value_.get_int(); + } + } + + Array ret; + + LOCK(cs_twister); + if( strUsername.size() && m_users.count(strUsername) ) { + const std::vector &mentions = m_users[strUsername].m_mentionsPosts; + max_id = std::min( max_id, (int)mentions.size()-1); + since_id = std::max( since_id, max_id - count ); + + for( int i = std::max(since_id+1,0); i <= max_id; i++) { + const entry *post = mentions.at(i).find_key("userpost"); + if( post && post->type() == entry::dictionary_t ) { + const entry *ptime = post->find_key("time"); + if( ptime && ptime->type() == entry::int_t ) { + int64 time = ptime->integer(); + + if(time <= 0 || time > GetAdjustedTime() + MAX_TIME_IN_FUTURE ) { + printf("getmentions: ignoring far-future post\n"); + } else { + entry vEntry; + vEntry = mentions.at(i); + hexcapePost(vEntry); + vEntry["id"] = i; + ret.push_back(entryToJson(vEntry)); + } + } + } + } + } + + return ret; +} Value setspammsg(const Array& params, bool fHelp) { diff --git a/src/twister_utils.cpp b/src/twister_utils.cpp index a4a1bc56..96d913d4 100644 --- a/src/twister_utils.cpp +++ b/src/twister_utils.cpp @@ -6,6 +6,7 @@ #include #include +#include #include @@ -148,6 +149,14 @@ int saveUserData(std::string const& filename, std::map con } } + if( udata.m_mentionsPosts.size() ) { + entry &userData = userDict[i->first]; + entry &mentionsList = userData["mentions"]; + BOOST_FOREACH( libtorrent::entry const &mention, udata.m_mentionsPosts) { + mentionsList.list().push_back(mention); + } + } + if( udata.m_directmsg.size() ) { entry &userData = userDict[i->first]; entry &dmDict = userData["dm"]; @@ -200,6 +209,27 @@ int loadUserData(std::string const& filename, std::map &us } } + const lazy_entry *mentionsList = userData->dict_find("mentions"); + if( mentionsList ) { + if( mentionsList->type() != lazy_entry::list_t ) goto data_error; + + for( int j = 0; j < mentionsList->list_size(); j++ ) { + const lazy_entry *v = mentionsList->list_at(j); + if( v->type() != lazy_entry::dict_t ) goto data_error; + lazy_entry const* post = v->dict_find_dict("userpost"); + if( !post ) goto data_error; + + std::string username = post->dict_find_string_value("n"); + int64 time = post->dict_find_int_value("time",-1); + std::string postKey = username + ";" + boost::lexical_cast(time); + udata.m_mentionsKeys.insert(postKey); + + entry vEntry; + vEntry = *v; + udata.m_mentionsPosts.push_back( vEntry ); + } + } + const lazy_entry *dmDict = userData->dict_find("dm"); if( dmDict ) { if( dmDict->type() != lazy_entry::dict_t ) goto data_error; diff --git a/src/twister_utils.h b/src/twister_utils.h index 26c4c913..ce0faaab 100644 --- a/src/twister_utils.h +++ b/src/twister_utils.h @@ -18,9 +18,14 @@ struct StoredDirectMsg { // in-memory data per wallet user struct UserData { + // users we follow std::set m_following; // m_directmsg key is the other username std::map > m_directmsg; + // key for fast checking (log N) if a post is already stored on m_mentionsPosts + std::set m_mentionsKeys; + // known posts mentioning this user (by users in m_following) + std::vector m_mentionsPosts; };