Lucerne (named after the Swiss city) is a Twitter reader designed to amplify my personal Twitter workflows. Lucerne is built on my usual homebrew stack, with Ink on the backend and Torus on the browser. You can read more about Lucerne on my blog.
Lucerne is built around a new concept called channels. Channels are search filters over all of Twitter that can be pinned to the sidebar, so you can switch between them quickly and easily to "tune in" to different parts of the Twittersphere. Channels can filter to specific threads, tweeters, conversations, types of media, keywords, and engagement metrics, providing a powerful primitive that can be used to build a lot of higher level use cases for channels. Built-in actions like seeing another user's tweets or searching by hashtag also take advantage of the core filter pattern. (e.g. clicking on a profile searches for from:the_user
)
Twitter and Tweetdeck have built-in search and timeline features that are similar, but UX of experimenting with filters and immediately saving the good ones to a growing list is what really makes Lucerne fun to use, and that isn't possible in existing clients.
I think tools should start simple and grow with use. Lucerne is designed around a single idea, filtered channels, with other peripheral information I frequently check on Twitter displayed on the side for reference.
In addition to the headline channels feature, Lucerne has a few other niceties designed around the way I use Twitter, as a place for finding new ideas and sharing what I'm working on with the public.
Lucerne is designed as a better Twitter experience, but isn't designed as a complete replacement. Where Twitter.com can provide a better experience (for authoring tweets, interacting with threads, etc.) either due to API support or edge cases, Lucerne links out to Twitter's web client.
It goes without saying, because the product is designed to help me get the most out of Twitter rather than make me stay on Twitter to consume ads, it feels much cleaner, more focused, and pleasant to use.
Lucerne allows you tu search twitter with a pattern:filter
style syntax. Most of this borrows from Twitter's v1.1 search API queries, except a few that are custom and pre-processed on the backend, like re:tweet_id
for replies to a tweet or sort:top
to sort by top rather than recency.
I like a textual syntax for advanced searches over a bunch of toggles in the UI, since text queries can be copy-pasted, easily saved for later, and edited quickly on the keyboard. Using this particular syntax confers two advantages:
- The syntax is also mostly usable on Twitter's web client search page, which is nice when I'm using that instead of Lucerne.
- It keeps the implementation simple, since I pass the query (mostly) transparently through to Twitter's API endpoint.
If using Twitter's basic search API (i.e. on non-Premium, non-Enterprise developer accounts), note that their Search API is limited to the last 7 days or so. You can probably increase this limit by upgrading your developer account, but I haven't done so personally.
I usually share updates to projects I'm working on or my blog posts daily on Twitter, and like to keep track of how those tweets are doing with engagement. This use case gets a panel on the right side.
My own stats -- follower and following counts -- are there because I like keeping track of it, but I need to check it at most once a couple days, and it's not important. So it gets a small footnote on the left side below the channels.
Below the engagement metrics panel, there's a list of new followers in reverse-chronological order, so I can see if there's anyone interesting who recently found me on the platform. I can see if anyone has a big presence on Twitter and one-click switch to viewing their recent or popular tweets.
Lucerne's backend is written purely in Ink. Lucerne reuses server and routing libraries from other Ink projects, but implements custom implementations of the SHA1 HMAC algorithm and OAuth 1.1 signature algorithm, both available in lib/
. These are used to communicate with Twitter's API without relying on a third-party implementation.
(Is rolling your own crypto like this smart? Probably not. But this is a toy project, and I'm the only one who uses it.)
The frontend is written as a single page Torus application, with no other dependencies. This keeps the page fast and responsive, even on slower connections and devices.
At the moment, Lucerne is designed as a single-user application: a single server can only serve one logged-in Twitter account. To run your own Lucerne server, create a credentials.ink
file in the root of the repository, with the following keys. You should be able to find these credentials when you register a Twitter developer account.
UserID := '<Your Twitter account's user ID, which should be a number>'
ConsumerKey := '<Your developer account's consumer public key>'
ConsumerSecret := '<Your developer account's consumer secret key>'
BearerToken := '<Your developer account's Bearer Token>'
OAuthToken := '<Your account's OAuth token>'
OAuthSecret := '<Your account's OAuth secret>'
You'll also need to modify static/js/main.js
to replace const ME = 'thesephist';
with your own Twitter username.
With the file present, run ink main.ink
to start the server, running on localhost:7238/
. (To do this, you need to have Ink installed.)
Alternatively, if you don't already have Ink installed, you can run Lucerne from a Docker container. You will need to supply your twitter credentials (as above) but as environment variables, like so
.env
:
USER_ID=11223344
USER_NAME=your_twitter_username
CONSUMER_KEY=...
CONSUMER_SECRET=...
BEARER_TOKEN=...
OAUTH_TOKEN=...
OAUTH_SECRET=...
Then from the root of the repo you can build and run the container using these commands:
docker build . -t lucerne
docker run -it --env-file .env -p 7238:7238 lucerne
Lucerne uses GNU Make for development scripts.
make
ormake run
starts an auto-restarting development server that restarts when any relevant files are changed.make fmt
ormake f
auto-formats all Ink files with inkfmt. You need to have inkfmt installed on the system for this to work.
Lucerne runs on the hard-coded port 7238 when you run ink main.ink
in the root directory of the project. To do this in deployment, I define Lucerne as a systemd service with lucerne.service
and let systemd manage the server. If you want to try deploying Lucerne yourself, make sure you have a valid credentials.ink
from above and you're invoking ink from the correct working directory.