brianli.com

A Little Subscription Purging 📝

Today, I went through my credit card statements from the past few months in search for unnecessary subscriptions to purge. As a technology lover who’s always keen to try the latest and greatest, I don’t always remember to cancel subscriptions before the end of their respective free trial periods.

After half an hour of purging, I was able to remove $80/month worth of subscription services I don’t really use anymore! Most of the subscriptions I got rid of were related to servers, server management, and server monitoring.

What a surprise…

With $80/month of savings achieved, I dug further to see what else I could cut. I found a few more server monitoring services that I actually use for monitoring my block production node on the ICON network. Between UptimeRobot and NodePing, I was paying close to $30/month for monitoring.

I’ve been on a serverless function kick recently, so I decided to try writing a script to replace the core functionalities of UptimeRobot and NodePing. I ended up with the Python script below that makes two requests to the node with a 30 second gap in between, and messages me on Telegram if the node is offline. I’m going to add some more code over the next few days for error and timeout handling, but it works pretty well for now.

import json
import requests
import time
from multiprocessing import Process

def node_check(t):
	time.sleep(t)
	node_url = "http://...:9000/api/v1/avail/peer"
	r = requests.get(node_url)
	json_data = json.loads(r.text)
	is_service_online = json_data['status']
	is_service_available = json_data['service_available']
	if is_service_available == False or "is online" not in is_service_online:
		send_tg_message()

def send_tg_message():
	tg_bot_url = f"https://api.telegram.org/bot.../"
	chat_id = "..."
	response_message_data = {
        "chat_id": chat_id,
        "text": "RHIZOME P-Rep node (...) is offline.",
    }
	requests.post(f'{tg_bot_url}sendMessage', json=response_message_data)

def main(request):
	p = Process(target=node_check(0))
	p.start()
	p2 = Process(target=node_check(30))
	p2.start()
	p.join()
	p2.join()

I deployed the script to Google Cloud Function, and scheduled it to run once per minute with Google Cloud Scheduler. While this solution obviously doesn’t cover all the features of paid monitoring services, it’s definitely good enough for my needs – and it’s completely free.

Google Cloud Platform’s free tier allows for 2 million Cloud Function invocations and three Cloud Scheduler jobs per month. This monitoring system will cost me ~45,000 function invocations per month with a single cron job, so I’m well within the free tier. Yay for saving money!

5 Reasons Why I Love Hugo for Blogging

Late last year, I migrated my WordPress-powered blog to Hugo, and I still think it’s the best decision I’ve ever made with regard to my blogging workflow. Switching to a static site generator like Hugo has expanded my knowledge and perspective on modern CMSs and their associated technologies.

Ironically, I feel that exploring non-WordPress tools like Hugo has actually made me a better employee at Kinsta – a managed WordPress hosting company. In this post, I’ll share five reasons why I love working with Hugo, and elaborate on how it has impacted my personal and professional development.

Continue Reading →

Developing a Telegram Bot for Image Uploads 📝

Last month, I wrote about moving my wife’s website from WordPress to Hugo. Overall, the pros of switching outweigh the cons. She’ll have to adapt to a new publishing workflow that involves pushing updates to GitHub and waiting a few minutes for her site to rebuild. At the same time, it’ll be much easier for me to add custom functionality to her site because I’m very familiar with Hugo.

My wife may want to get an iPad to replace her MacBook in the future, so I’ve been thinking of ways for her to handle image uploading on her new Hugo site. At the moment, the image uploading functionality on my site is handled by a custom Python script mapped to a macOS quick action – it sounds complex, but it’s very user-friendly. Unfortunately, this kind of setup isn’t possible on iPad.

After some hacking around, I settled on developing a Telegram bot to handle image uploads. I chose Telegram because the bot API is fairly easy to figure out, and the service itself is cross-platform. At the moment, the bot is able to take an uploaded image, download it to a temporary location, and upload it to a Google Cloud Storage bucket (where I store blog images).

The next step is to add Dropbox support as well because my current macOS quick action pushes images to Dropbox for backup purposes. After that, I just need to write some code to instruct the bot to send a Hugo shortcode after a sucessful upload. That’ll be especially useful for my wife because she can just copy and paste the shortcode straight into her post.

The Differences Between Audio Interfaces and Headphone Amplifiers

A question that I often get is about the differences between audio interfaces and headphone amplifiers. While audio interfaces and headphone amps share certain traits and similarities, they are two fundamentally different products. In this post, you’ll learn about the differences between audio interfaces and headphone amplifiers, and why you may want one over the other.

Continue Reading →

Cloudflare Stream vs. YouTube for Video Hosting and Streaming

I recently hosted a live webinar on how to host an online concert with Zoom and MainStage. After the webinar, I wanted to make the recording available for people to watch in case they missed the live event. My initial thought was to upload the video to YouTube and call it a day. Eventually, I decided to upload it to Cloudflare Stream instead. In this post, I’ll go over the two platforms, and share why I decided to use Cloudflare Stream over YouTube.

Continue Reading →

How to Layer Instruments in MainStage

In today’s live and studio productions, being able to quickly create complex patches with multiple layers is crucial. With its extensive library of built-in sound libraries, plugins, and performance-oriented features, MainStage allows you to easily create complex layered patches. In this post, you’ll learn how to layer instruments in MainStage.

Continue Reading →

What Apple Silicon Means for Musicians

Earlier this week at WWDC 2020, Apple announced its upcoming transition from Intel CPUs to its own “Apple Silicon” for Macs. The new line of Apple Silicon chips are of the ARM-based variety – similar to the A-series chips found in the iPhone and iPad. In this post, I’ll share a few thoughts about how the transition to Apple Silicon may affect musicians, especially those using Apple-developed apps like Logic Pro X and MainStage.

Continue Reading →

Adding Schema Markup to a Hugo Site 📝

For tonight’s Hugo project, I decided to add @BlogPosting schema markup to my Hugo-generated blog. Schema markup was something I avoided for a while because I assumed it would be too complicated to add to my blog. After some hacking around tonight, I was able to successfully add the markup.

<script type="application/ld+json">
{
	"@context":"https://schema.org",
	"@type": "BlogPosting",
	"image": {{ partial "seo/og_image.html" . }},
	"url": {{ .Permalink }},
	"dateCreated": "{{ .PublishDate.Format "2006-01-02" }}",
	"datePublished": "{{ .PublishDate.Format "2006-01-02" }}",
	"dateModified": "{{ .Page.Lastmod.Format "2006-01-02" }}",
	"inLanguage": "en-US",
	"isFamilyFriendly": "true",
	"copyrightYear": "{{ .PublishDate.Format "2006" }}",
	"copyrightHolder": "Brian Li",
	"accountablePerson": {
		"@type": "Person",
		"name": "Brian Li",
		"url": "https://brianli.com"
	},
	"author": {
		"@type": "Person",
		"name": "Brian Li",
		"url": "https://brianli.com"
	},
	"creator": {
		"@type": "Person",
		"name": "Brian Li",
		"url": "https://brianli.com"
	},
	"publisher": {
		"@type": "Organization",
		"name": "Brian Li LLC",
		"url": "https://brianli.com",
		"logo": {
			"@type": "ImageObject",
			"url": "https://brianli.com/assets/BRIAN-LI-FEATURED-IMAGE.png"
		}
	},
	"mainEntityOfPage": "True",
	"articleBody": {{ .Content | safeJS | htmlUnescape | plainify }}
}
</script>

A few interesting things I noticed with Hugo’s rendering engine.

  • For certain pairs like "url": {{ .Permalink }}, adding quotes around {{ .Permalink }} messes up the JSON rendering. If you’re trying to add schema markup on your Hugo site, you may need to omit the quotes if you see escaped characters. I had to use this syntax for "image" as well.
  • "articleBody" required quite the workaround. {{ .Content | safeJS | htmlUnescape | plainify }} ended up working for me – notice the lack of quotations once again.

Tasty Burger for Father's Day

My parents were gracious enough to take care of J tonight, so Ayaka and I got to sneak out for Father’s Day dinner. We couldn’t stay out for too long, so we decided to go to Tasty Burger in Boston for some quick burgers and fries. It’s been on our list of places to go since our previous visit to Boston, so it seemed like a good idea. I used to come here almost every week during my time at Northeastern University. It was nice to be able to come back, reflect on how much life has changed since college, and enjoy some greasy goodness.

Continue Reading →

Tokyo in Motion 📷

I took this photo in Tokyo during a night out with my friend Drew. I think it captures Tokyo’s nightlife vibe pretty well. I wish I could say this was a planned shot, but it wasn’t. It was taken by accident, and I didn’t even realize it until I browsed through the night’s images afterward.