Notion Book Quotes Widget for iPhone

A few years ago, I switched from paper books to Kindle but found my way back to books in print. I felt that the Kindle left me disconnected from my reading content. You can’t simply thumb through a Kindle book.

I missed the ability to display my Kindle highlights as a widget on my phone’s home screen. I was using Readwise for that. Readwise makes it easy to view your Kindle highlights in the app and display random highlights in a widget on your phone’s home screen. Readwise allows for manual highlights entry, but if I have to manually enter them, I might as well use Notion.

After a few search requests, I landed on a script written by Mystery123SK. However, the script did not work with the Notion database format that I had created, so I began making changes to the code. I used to manually write code but have not had to exercise that part of my brain much in the last decade. This allowed me to use the latest version of Claude.AI from Anthropic.

I have seen many examples of using AI to generate code, but this was my first attempt. I asked Claude.ai to write a simple script that would bring in database content from Notion and display it in a widget that Scriptable (an iOS app) would generate from the code. It took me a few hours working with Claud to get it right, but I finally got the code to run correctly and display in a Scriptable widget.

Through my years of using Notion, I have always wanted my data to be formatted to make it easy to export into something else. That means using the Name field for the quote itself. I also wanted to use Select fields to sort and filter within Notion.

Here is what you need to create your own Notion Book Quotes Widget

  1. Notion – Get a free Notion account
  2. Quotes Database – Copy my quotes database to your Notion account.
  3. Scriptable App for iOS
  4. The Code – See below
  5. Your unique Database ID – See below
  6. Your unique Secret Integration API Token – See below

The Code

Copy this code into a new script in the scriptable app. It is best to view this post from a mobile browser so you can copy the code and paste it into the app.

// Notion settings
const databaseID = 'YOUR_DATABASE_ID';
const token = 'YOUR_SECRET_TOKEN';
const notionVersion = '2022-06-28';
const notionApi = `https://api.notion.com/v1/databases/${databaseID}/query`;

// Main function
(async () => {
  let quote = await readNotionQuote();
  if (!quote) {
    console.log('No quote found');
    return;
  }
  let widget = await createWidget(quote.text, quote.author, quote.book, quote.tags);
  if (config.runsInWidget) {
    Script.setWidget(widget);
  } else {
    await widget.presentMedium();
  }
  Script.complete();
})();

async function readNotionQuote() {
  let req = new Request(notionApi);
  req.method = 'POST';
  req.headers = {
    "Authorization": `Bearer ${token}`,
    "Notion-Version": notionVersion,
    "Content-Type": "application/json"
  };
  req.body = JSON.stringify({
    page_size: 100
  });
  let res = await req.loadJSON();
  
  if (!res.results || res.results.length === 0) {
    return null;
  }
  let quotes = res.results.map(page => {
    let quoteText = '';
    if (page.properties.title && Array.isArray(page.properties.title.title)) {
      quoteText = page.properties.title.title.map(t => t.plain_text).join('');
    } else if (page.properties.Quote && Array.isArray(page.properties.Quote.title)) {
      quoteText = page.properties.Quote.title.map(t => t.plain_text).join('');
    }
    
    let authorName = page.properties.Author?.select?.name || '';
    let bookTitle = page.properties.Book?.select?.name || '';
    let rating = (page.properties.Rating?.select?.name || '').length; // Count stars in the rating
    let tags = page.properties.Tags?.multi_select?.map(tag => tag.name) || [];
    return {
      text: quoteText,
      author: authorName,
      book: bookTitle,
      rating: rating,
      tags: tags
    };
  }).filter(quote => quote.text.trim() !== '' && quote.author.trim() !== '');
  
  if (quotes.length === 0) {
    return null;
  }
  
  // Weighted random selection based on rating
  let totalWeight = quotes.reduce((sum, quote) => sum + Math.pow(2, quote.rating), 0);
  let randomWeight = Math.random() * totalWeight;
  let weightSum = 0;
  
  for (let quote of quotes) {
    weightSum += Math.pow(2, quote.rating);
    if (weightSum > randomWeight) {
      return quote;
    }
  }
  
  // Fallback to random selection if something goes wrong
  return quotes[Math.floor(Math.random() * quotes.length)];
}

async function createWidget(text, author, book, tags) {
  let widget = new ListWidget();
  widget.backgroundColor = new Color("#1C1C1E");

  let quoteText = widget.addText(`"${text}"`);
  quoteText.centerAlignText();
  quoteText.font = Font.boldSystemFont(16);
  quoteText.textColor = new Color("#FFFFFF");
  quoteText.minimumScaleFactor = 0.5;

  widget.addSpacer(8);

  let authorText = widget.addText(`- ${author}`);
  authorText.centerAlignText();
  authorText.font = Font.boldSystemFont(14);
  authorText.textColor = new Color("#AAAAAA");
  authorText.minimumScaleFactor = 0.5;

  if (book) {
    widget.addSpacer(4);
    let bookText = widget.addText(book);
    bookText.centerAlignText();
    bookText.font = Font.italicSystemFont(12);
    bookText.textColor = new Color("#AAAAAA");
    bookText.minimumScaleFactor = 0.5;
  }

  if (tags.length > 0) {
    widget.addSpacer(4);
    let tagsText = widget.addText(`Tags: ${tags.join(', ')}`);
    tagsText.centerAlignText();
    tagsText.font = Font.systemFont(12);
    tagsText.textColor = new Color("#888888");
    tagsText.minimumScaleFactor = 0.5;
  }

  widget.refreshAfterDate = new Date(Date.now() + 1000 * 60 * 60);
  return widget;
}

When I have time, I will likely commit this code to Github, a better platform for sharing code than this post.

Feel free to copy the code into Scriptable to create your own widget. Replace “YOUR_DATABASE_ID” and “YOUR_SECRET_TOKEN” with your database and secret token.

How to find your Notion Database ID

How to create a Notion API Token

You must create a specific Notion API Token for the script to connect to.

Connect the Notion API Token to your Quotes Database

After you create the integration, you must connect the database to it.

Your Own Custom Book Quotes Widget

Congrats, you now have your custom book quotes widget based on quotes saved in a Notion database.

Troubleshooting

The first time I ran my code, nothing happened. I had Claude add in error reporting. That allowed me to identify that the script was looking for an incorrect field name. The error received was: “No Quote Found.” That is the default error, which can mean anything. I went down the rabbit hole of asking Claude to add in more detailed error reporting but that simply added a ton of bloat to the code and didn’t help, so I started over.

  1. Ensure your Database ID, Secret Token, and Database Field Names are correct.
  2. Check that your database is connected to the correct integration.

I will update this post as I have more time. I will also produce a setup tutorial soon.