Templater Treats for Obsidian October
Tags: javascript, python
Published: 2021-10-09 23:40:48

I wrote two small [Templater](https://silentvoid13.github.io/Templater/) scripts for [Obsidian October](https://publish.obsidian.md/hub/11+-+Events/Obsidian+October+2021)*, both with their own Python micro-APIs. One transcribes YouTube videos and one fetches the day's quote from [DailyZen](https://www.dailyzen.com/). Neither are anywhere as complicated as the plugins people are submitting, but it feels good to participate all the same.

\*[Obsidian](https://obsidian.md) is a note-taking app that has lots of novel ways to link your notes together and visualize them. It's totally free, so if it sounds intriguing to you you should try it. 

## Daily Zen Quote in Your Journal

First comes the API, hosted on Replit and written in Python and Flask. I find Beautiful Soup, the Python library for web scraping, to be easier to work with than the web scrapers popular for JavaScript.

```python
import requests
from bs4 import BeautifulSoup
from markdownify import markdownify as markdownify
from flask import Flask
from flask_cors import CORS
import re
import prettierfier

app = Flask("app")
CORS(app)

@app.route("/")
def output():
  URL = "https://dailyzen.com"
  page = requests.get(URL)
  soup = BeautifulSoup(page.content, "html.parser")
  html = f"{soup('blockquote')[0]}{soup('cite')[0]}"
  pretty_html = prettierfier.prettify_html(html)
  markdown = markdownify(pretty_html, convert=['blockquote', 'cite'])
  quote = re.sub(">", "", markdown)
  print(quote) 
  return f">  {quote.lstrip().rstrip()}"

app.run(host="0.0.0.0", port=8080)
```

I pay a couple bucks a month to have the Replit hacker plan, which keeps certain servers "always on" without having to configure anything.

Since nothing needs to be passed into this one, it's pretty simple. 

```javascript
async function dailyzen() {
  const result = await fetch("https://DailyZen.bathrobe.repl.co");
  const reader = result.body.getReader();
  const { value } = await reader.read();
  const quote = new TextDecoder().decode(value);
  return quote;
}
module.exports = dailyzen;
```
Replit sends stuff over in `ReadableStream`, so you usually need to import it into your file in bite-size chunks. The quotes are so short that they come in entirely on the first chunk.

Then in Templater just call it with `tp.user.dailyzen()`. Voila.

![dailyzen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dplhxnl9hjjzri5bk3p.gif)
 

## Fetch YouTube Transcripts in Your Notes

This one's a little trickier.
First, a Templater system prompt gets a YouTube URL. Then the URL is verified, we make sure it's actually a link to a video, and the ID of the video is sent to the server.

Behind the scenes, Replit sends a big ReadableStream back with the transcript in chunks. Those chunks are gathered up until the stream completes, then they're concatenated.

```javascript
async function transcript(str) {
  let url = new URL(str);
  let videoID;
  //test and make sure it's a youtube URL
  if (url.host == "www.youtube.com" && url.pathname == "/watch") {
    videoID = url.search.slice(3);
    //send videoID as POST request to replit
    const res = await fetch("https://YTTranscript.bathrobe.repl.co", {
      method: "post",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        ID: videoID,
      }),
    });
    //replit returns a readablestream
    const reader = res.body.getReader();
    let result = "";
    let transcriptOutput = "";
    await reader.read().then(function processText({ done, value }) {
      if (done) {
        transcriptOutput = result;
        return transcriptOutput;
      }
      let stringValue = new TextDecoder("utf-8").decode(value);
      result += stringValue;
      return reader.read().then(processText);
    });
    return transcriptOutput;
  } else {
    console.log("nope");
    return "I can't recognize this as a YouTube link, sorry!";
  }
  //get the markdown transcript back and return it
}
module.exports = transcript;
```
Behind the scenes, here's Replit getting us the transcript. There's a `pip` package available that makes it really easy:

```python
from flask import Flask
from flask import request
from flask_cors import CORS
from youtube_transcript_api import YouTubeTranscriptApi

app = Flask("app")
CORS(app)

@app.route("/", methods=["POST"])
def output():
  ytID = request.json.get("ID")
  transcriptData = YouTubeTranscriptApi.get_transcript(ytID)
  transcript = ""
  for dict in transcriptData:
    transcript += dict["text"] + " \n"
  return transcript

app.run(host="0.0.0.0", port=8080)
```

Notice how there's a line break at the end of every string returned by the transcript package.

There are several ways to do this, but I found it easiest to prompt the user for a URL within the Templater markdown file itself.

`<% await tp.user.transcript(tp.system.prompt("YouTube URL Here"))%>`.
![youtube transcript](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oa670ent728v5laeba79.gif)
 
## Try them out!
If you want to use my servers, help yourself! Just code the JavaScript portions of this tutorial. Of course, they may disappear at any time--these were some just for fun projects.