Minds, and tokens can be used to boost their posts or crowdfund other users. Minds has been described as more privacy-focused than mainstream social media networks. I looked for a way to get the most recent posts programmatically.
Introduction
I needed to fetch the most recent posts from a given Minds.com user ID. To do so, I wanted to use the Minds.com API.
Building Blocks
One of the key building blocks to such project, would be libCurl. I used it as a static
library. The .lib file is included in the article's source code, however you can read about using libCurl
as a static
library here.
Note: WriteLogFile()
is one of my old logging functions described in this article.
Getting the User Unique Number
Usually, we will need to supply our function a user ID, such as "minds". You can also check my own profile "haephrati" (https://www.minds.com/haephrati).
How Minds.com Requests API Works
Minds.com provides an API call which looks like that:
?sync=1&limit=2&as_activities=0&export_user_counts=0&from_timestamp=
If we break this into its ingredients, we would get the following attributes, and here they are, along with a suggested value:
int sync = 1;
int limit = 150;
int as_activities = 0;
int export_user_counts = 0;
string from_timestamp = "";
Initiating libCurl
First, we initiate the Curl object:
curl_global_init(CURL_GLOBAL_ALL); curl = curl_easy_init();
Reading Posts
I wanted a simple function which I can use to read all posts of a given user. So if the user is ottman, I will call:
SG_ReadMindsFeed("ottman");
I created a class and the SG_ReadMindsFeed
goes like this:
bool SG_ReadMindsFeed(string userID)
{
int count = 100;
MindsFeedReader reader(count);
return reader.read(userID);
}
Then the read
function is called:
bool MindsFeedReader::read(string userID)
{
userid = userID;
if (readGUID() == false)
{
return false;
}
total_read_count = 0;
while (total_read_count < count)
{
if (readNext() == false)
{
return false;
}
if (read_count <= 0)
{
break;
}
total_read_count += read_count;
}
return true;
}
The GUID
The actual part that differs in each user in the feed is a long number called the GUID
. It can be composed as follows:
bool MindsFeedReader::readGUID()
{
string url = CHANNEL + userid;
CURL* curl = curl_easy_init();
if (curl == NULL)
{
WriteLogFile(L"Cannot intialize the CURL request");
return false;
}
CURLcode result;
string response;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
result = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (result != CURLE_OK)
{
WriteLogFile(L"Fail to perform the CURL request");
return false;
}
JSONValue* jsonRoot = JSON::Parse(response.c_str());
if (response == "")
{
WriteLogFile(L"Response empty. CURL request failed");
return false;
}
JSONObject jsonRootObject = jsonRoot->AsObject();
JSONValue* jsonChannel = jsonRootObject[L"channel"];
JSONObject jsonChannelObject = jsonChannel->AsObject();
JSONValue* jsonGUID = jsonChannelObject[L"guid"];
if (jsonGUID == NULL || !jsonGUID->IsString())
{
WriteLogFile(L"Invalid response. CURL request failed");
return false;
}
wstring wguid = jsonGUID->AsString();
guid = string(wguid.begin(), wguid.end());
return true;
}
Reading Chunks of Data
As you can see in the source code below, here is how we read a batch of posts (up to 10 in this case), then move to the next batch.
bool MindsFeedReader::readNext()
{
string url_with_params = FEED + guid + ACTIVITIES;
url_with_params += string("?") + PARAM_SYNC + "=" + to_string(sync);
url_with_params += string("&") + PARAM_LIMIT + "=" + to_string(limit);
url_with_params += string("&") + PARAM_AS_ACTIVITIES + "=" + to_string(as_activities);
url_with_params += string("&") + PARAM_EXPORT_USER_COUNTS +
"=" + to_string(export_user_counts);
url_with_params += string("&") + PARAM_FROM_TIMESTAMP + "=" + from_timestamp;
CURL* curl = curl_easy_init();
if (curl == NULL)
{
WriteLogFile(L"Cannot intialize the CURL request");
return false;
}
CURLcode result;
string response;
curl_easy_setopt(curl, CURLOPT_URL, url_with_params.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_string);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
result = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (result != CURLE_OK)
{
WriteLogFile(L"Fail to perform the CURL request");
return false;
}
JSONValue* jsonRoot = JSON::Parse(response.c_str());
if (response == "")
{
WriteLogFile(L"Response empty. CURL request failed");
return false;
}
JSONObject jsonRootObject = jsonRoot->AsObject();
JSONValue* jsonLoadNext = jsonRootObject[L"load-next"];
wstring loadNext = jsonLoadNext->AsString();
from_timestamp = string(loadNext.begin(), loadNext.end());
JSONValue* jsonEntities = jsonRootObject[L"entities"];
JSONArray jsonEntitiesArray = jsonEntities->AsArray();
read_count = 0;
for (JSONValue* jsonEntitiesItem : jsonEntitiesArray)
{
++read_count;
WriteLogFile(L"Showing post %d", total_read_count + read_count);
JSONObject jsonEntitiesItemObject = jsonEntitiesItem->AsObject();
JSONValue* jsonEntity = jsonEntitiesItemObject[L"entity"];
if (jsonEntity->IsObject())
{
JSONObject jsonEntityObject = jsonEntity->AsObject();
JSONValue* jsonMessage = jsonEntityObject[L"message"];
if (jsonMessage != NULL && jsonMessage->IsString())
{
wstring message = jsonMessage->AsString();
WriteLogFile(L"\n\nMessage:\n%s\n\n", message.c_str());
}
JSONValue* jsonThumbnailSrc = jsonEntityObject[L"thumbnail_src"];
if (jsonThumbnailSrc != NULL && jsonThumbnailSrc->IsString())
{
wstring thumbnailSrc = jsonThumbnailSrc->AsString();
WriteLogFile(L"\n\nThumbnail Src:\n%s\n\n", thumbnailSrc.c_str());
}
JSONValue* jsonTimeCreated = jsonEntityObject[L"time_created"];
if (jsonTimeCreated != NULL && jsonTimeCreated->IsString())
{
wchar_t buffer[TIMESTAMP_BUFFER_SIZE];
wstring wstrTimeCreated = jsonTimeCreated->AsString();
string strTimeCreated = string(wstrTimeCreated.begin(), wstrTimeCreated.end());
long timestamp = atol(strTimeCreated.c_str());
time_t rawtime = (const time_t)timestamp;
struct tm* timeinfo;
timeinfo = localtime(&rawtime);
wcsftime(buffer, TIMESTAMP_BUFFER_SIZE, L"%Y-%m-%dT%H:%M:%S.%z%Z", timeinfo);
WriteLogFile(L"\n\nTime Created:\n%s\n\n", buffer);
}
JSONValue* jsonTimeUpdated = jsonEntityObject[L"time_updated"];
if (jsonTimeUpdated != NULL && jsonTimeUpdated->IsString())
{
wchar_t buffer[TIMESTAMP_BUFFER_SIZE];
wstring wstrTimeUpdated = jsonTimeUpdated->AsString();
string strTimeUpdated = string(wstrTimeUpdated.begin(), wstrTimeUpdated.end());
long timestamp = atol(strTimeUpdated.c_str());
time_t rawtime = (const time_t)timestamp;
struct tm* timeinfo;
timeinfo = localtime(&rawtime);
wcsftime(buffer, TIMESTAMP_BUFFER_SIZE, L"%Y-%m-%dT%H:%M:%S.%z%Z", timeinfo);
WriteLogFile(L"\n\nTime Updated:\n%s\n\n", buffer);
}
}
else
break;
}
return true;
}
History
- 26th February, 2021: Initial version