Static site (micro)blogging with Telegram and DigitalOcean Functions

I was coquetting with the idea of using Telegram for (micro)blogging in the past. I almost took it seriously and released msgWP to the public; it allowed the creation of WordPress posts by sending messages to a Telegram bot.

Today, I prefer Jamstack websites over WordPress for personal projects. But I still think (micro)blogging with Telegram is a good idea.

Here's how it could work with a static site.

Taking a step back

Creating a post in the static site generator (SSG) world typically means creating a Markdown file that contains the post's content with maybe meta information, like date, tags, and title.

Publishing means running the build process and deploying the output to the hosting. If you are using a platform like Netlify or similar, publishing and deploying is something you don't even have to think of. All you have to do is commit a file and push it to the repo.

From all this, it follows that we have to answer this question: how will we create a (Markdown) file, commit it, and push it when we send a Telegram message (to a bot).

Create a file with API

If you are using GitHub, they offer an API endpoint to create or update file contents. If you use GitLab, they have something very similar. Probably, all Git repository hosting services offer this.

If by any chance you don't want to use the API, or it is not offered, there are alternative ways to this workflow. Maybe in a subsequent article, we can go over them.

From Telegram to Git repo API

I choose to use DigitalOcean (DO) Functions because it's something I have wanted to try for a while.

Besides the other serverless options, a different route would be using a low code integration platform like Pipedream.

There are two ways to create a DO Function. Whatever route you take, the important part is to have a main function.

The main function is the entry point with the $args parameter.

The $args is special. It contains, besides other things, the data sent to it. There's no need or way to use a superglobal variable like $_REQUEST or php://input to access the data.

function main(array $args): array
{
// 1. access incoming data
// 2. prepare file
// 3. create the file with the API
}

To keep out sensitive information from the code and make it more reusable, at least the following environment variables should be set: GH_PERSONAL_ACCESS_TOKEN, COMMIT_AUTHOR_NAME, COMMIT_AUTHOR_EMAIL, REPO_OWNER, REPO_NAME.

A Telegram bot receives many types of updates. The focus will be on a simple text message to keep the article short.

Because the $args already contains the data relayed by Telegram, we can use array destructuring and get the relevant information:

[
'update_id' => $updateId,
'message' => [
'text' => $text,
'date' => $timestamp,
],
] = $args;

Depending on the SSG you prefer, the content of the (post) file will vary. But most likely, you will have some metadata (for ex.: the date) as front matter and the content.

$formattedPostDate = date('c', (int)$timestamp); // ISO 8601
$markdownFileContent = <<<CONTENT
---
date: $formattedPostDate
---
$text
CONTENT;

Without any library, we can just use curl to make the request to the API:

$fileToCreate = "{$updateId}.md";
$ghApiUrl = sprintf(
'https://api.github.com/repos/%s/%s/contents/%s',
getenv('REPO_OWNER'),
getenv('REPO_NAME'),
$fileToCreate
);
 
$curlHandle = curl_init();
curl_setopt_array(
$curlHandle,
[
CURLOPT_URL => $ghApiUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_USERAGENT => 'DigitalOcean Functions',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('GH_PERSONAL_ACCESS_TOKEN'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'message' => "Create {$fileToCreate}",
'committer' => [
'name' => getenv('COMMIT_AUTHOR_NAME'),
'email' => getenv('COMMIT_AUTHOR_EMAIL'),
],
'content' => base64_encode($markdownFileContent),
]),
]
);
curl_exec($curlHandle);
curl_close($curlHandle);

curl is verbose, but we are just satisfying here the required information for the GitHub API request.

One thing that might not be obvious is that we have to set the user agent, and we must base64 encode the file content.

There's one more thing; we have to create a response to successfully terminate the DO Function:

return [
'body' => curl_info($curlHandle, CURLINFO_RESPONSE_CODE),
];

At this point, if you create a Telegram Bot and set the webhook to the DO Function URL things should work.

possible video demo here


This could serve as a basis but is not even close to have a flawless (micro)blogging experience. For example, in msgWP, I had the message (post) editing and image publishing implemented.