DEV Community

Cover image for Sadghuru quotes from my puppeteer api
Caner Demirci
Caner Demirci

Posted on

Sadghuru quotes from my puppeteer api

I have made a simple web app that finds and shows and enabling you to print a random Sadghuru quote from goodreads.com with Node.js, Express, Puppeteer and Vue.js. App includes some basic CSS animations also print-friendly. I don't know very well Sadghuru but I see he is a wise person. I love to read his quotes.

Here is my server (Index.js) (Packages: dotenv, cors, express, puppeteer)

require('dotenv').config();

// in .env file (PORT=5000)
const PORT = process.env.PORT || 3000;

const express = require('express');
const cors = require('cors');
const puppeteer = require('puppeteer');

const app = express();

app.use(cors());
app.use(express.json());

app.get('/', async (req, res) => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    const rndPage = Math.floor(Math.random() * 26) + 1;
    await page.goto(`https://www.goodreads.com/author/quotes/30378.Sadhguru?page=${rndPage}`);

    const quotes = await page.$$('.quoteText');
    const rndQuote = Math.floor(Math.random() * (quotes.length - 1)) + 1;
    const quote = await quotes.find((el, index) => index === rndQuote).evaluate(el => el.innerText);

    await browser.close();

    return res.json({ quote });
});

app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

const browser = await puppeteer.launch();
Try this launch({ headless: false }); and browser will show.

There are 26 pages for Sadghuru quotes. page parameter can be random 1 to 26. goodreads.com/author/quotes/30378.Sadhguru?..{rndPage}

// Get all quote div elements.
const quotes = await page.$$('.quoteText');
// Random number 1-26
const rndQuote = Math.floor(Math.random() * (quotes.length - 1)) + 1;
// Find a random quote from all quotes in the random page then grab text in the div.
const quote = await quotes.find((el, index) => index === rndQuote).evaluate(el => el.innerText);
Enter fullscreen mode Exit fullscreen mode

Then we close puppeteer browser. (await browser.close()) Then we send the quote as json. (res.json({quote}))

And Frontend

App.vue

<template>
  <div id="app">
    <h1 class="header-text">Sadghuru Quotes</h1>
    <QuoteCard :quote="quote" :loadingAnim="loading" @onclick="findQuote()" />
    <button @click="printQuote()" id="printBtn"><span class="material-icons">print</span></button>
  </div>
</template>

<script>
import axios from 'axios';
import QuoteCard from './components/QuoteCard.vue';

export default {
  name: 'App',
  components: {
    QuoteCard
  },
  data() {
    return {
      quote: null,
      loading: true,
    }
  },
  mounted() {
    this.fetchQuote();
  },
  methods: {
    async fetchQuote() {
      this.loading = true;
      const result = await axios.get('http://localhost:5000/');
      this.quote = result.data.quote;
      this.loading = false;
    },
    findQuote: function() {
      if (!this.loading) this.fetchQuote();
    },
    printQuote() {
      window.print();
    }
  }
}
</script>

<style>
.header-text {
  text-align: center;
  margin: 2rem 0 2rem 0;
}

#printBtn {
  position: fixed;
  right: 2rem;
  bottom: 2rem;
  outline: none;
  border: 0;
  padding: 1rem;
  border-radius: 50%;
  cursor: pointer;
}

#printBtn:hover {
  animation-name: printbtnanim;
  animation-duration: 1s;
  animation-iteration-count: infinite;
}

#printBtn span {
  font-size: 48px;
}

@keyframes printbtnanim {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}

@media print {
  html, body { color: #000; }

  #printBtn {
    display: none;
  }

  .header-text {
    display: none;
  }
}
</style>
Enter fullscreen mode Exit fullscreen mode

QuoteCard.vue

<template>
    <div class="quotecard" :class="{'quotecard-anim': loadingAnim, 'fadein-anim': !loadingAnim }" @click="onClick()">
      <p>{{loadingAnim ? 'Loading...' : quote}}</p>
    </div>
</template>

<script>
export default {
    name: 'QuoteCard',
    props: {
        quote: String,
        loadingAnim: Boolean
    },
    methods: {
        onClick() {
            this.$emit('onclick');
        }
    }
}
</script>

<style scoped>
.quotecard {
  cursor: pointer;
  width: 70vw;
  max-height: 50vh;
  overflow: auto;
  padding: 2rem;
  margin: 4rem auto 0 auto;
  border-radius: 10px;
  box-shadow: 0 0 55px 1px rgba(0, 250, 250, 0.3);
  transition: all .5s;
  text-align: center;
  font-size: 1.4rem;
}

.quotecard:hover {
  box-shadow: 0 0 55px 1px rgba(0, 250, 250, 0.8);
}

.quotecard-anim {
  animation-name: quotecardanim;
  animation-duration: 1s;
  animation-iteration-count: infinite;
}

.fadein-anim {
    animation-name: fadeinanim;
    animation-duration: 2s;
}

@keyframes quotecardanim {
  0% {
    box-shadow: 0 0 55px 1px rgba(0, 250, 250, .1);
  }
  50% {
    box-shadow: 0 0 55px 1px rgba(0, 250, 250, 1);
  }
  100% {
    box-shadow: 0 0 55px 1px rgba(0, 250, 250, .1);
  }
}

@keyframes fadeinanim {
    from { opacity: 0; }
    to { opacity: 1; }
}

::-webkit-scrollbar{
width: 4px;
height: 4px;
}
::-webkit-scrollbar-thumb{
background: #ADFFFF;
border-radius: 15px;
}
::-webkit-scrollbar-thumb:hover{
background: #D4D4D4;
}
::-webkit-scrollbar-track{
background: #F0F0F0;
border-radius: 0px;
box-shadow: inset 0px 0px 0px 0px #F0F0F0;
}

@media print {
    .quotecard {
        box-shadow: none;
        width: 100%;
        max-height: none;
        padding: 1cm;
        margin: 0;
        border: 3px dashed #000;
        border-radius: 0;
    }

    .quotecard::after {
      content: 'Sad Ghuru';
      display: block;
      margin-top: 1cm;
      text-align: right;
      font-weight: bold;
    }
}
</style>
Enter fullscreen mode Exit fullscreen mode

style.css

@import url('https://fonts.googleapis.com/css2?family=Architects+Daughter&family=Italianno&display=swap');

* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

html, body {
    font-family: 'Architects Daughter', cursive;
    background: rgb(41, 41, 41) linear-gradient(rgb(41, 41, 41), rgb(0, 0, 0)) no-repeat top left;
    background-size: cover;
    color: #fff;
    height: 100%;
    width: 100%;
    overflow: hidden;
}

h1 {
    font-family: 'Architects Daughter', cursive;
}
Enter fullscreen mode Exit fullscreen mode

index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <link rel="stylesheet" href="style.css">
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons"
      rel="stylesheet">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)