Skip to content

Commit

Permalink
Refactor the code to follow a more modular design
Browse files Browse the repository at this point in the history
- Put all goodreads api related methods in a dedicated class
- Put all github api related methods in a dedicated class
- Put all output formatting in a dedicated class
- Prepare the ground for dependency injection and more increased
testability
- Didn't change any behavior

This commit is intended to prepare future work. Design choices made here
are also a matter of preference.
  • Loading branch information
AntoineDrouhin committed Nov 17, 2020
1 parent 4d7ad3d commit 8e24697
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 136 deletions.
58 changes: 0 additions & 58 deletions src/api.ts

This file was deleted.

18 changes: 0 additions & 18 deletions src/bar.ts

This file was deleted.

43 changes: 43 additions & 0 deletions src/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@


export default class Formatter {

public generateLines(
books: Book[],
MAX_LENGTH = 54,
MAX_LINES = 5): String[] {
const barWidth = Math.floor(MAX_LENGTH / 4)
const lines = books.slice(0, MAX_LINES).map(({ title, percent }) => {
const bar = this.generateBarChart(percent, barWidth)
const percentage = `${percent}%`.padStart(4, ' ')
const length = MAX_LENGTH - bar.length - percentage.length - 1
let text = title
if (title.length > length) {
text = title.substring(0, length - 3).concat('...')
} else {
text = title.padEnd(length, ' ')
}
return `${text} ${bar}${percentage}`
})
return lines
}

/**
* Copyright (c) 2019, Matan Kushner <[email protected]>
* https://github.com/matchai/waka-box/blob/master/index.js
*/
private generateBarChart(percent: number, size: number): string {
const syms = '░▏▎▍▌▋▊▉█';

const frac = Math.floor((size * 8 * percent) / 100);
const barsFull = Math.floor(frac / 8);
if (barsFull >= size) {
return syms.substring(8, 9).repeat(size);
}
const semi = frac % 8;

return [syms.substring(8, 9).repeat(barsFull), syms.substring(semi, semi + 1)]
.join('')
.padEnd(size, syms.substring(0, 1));
}
}
26 changes: 26 additions & 0 deletions src/github.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Octokit } from '@octokit/rest'


export default class Github {

private octokit = new Octokit({ auth: process.env.GH_TOKEN });

async updateGist(
title: string,
content: string
): Promise<string> {
const gist_id = process.env.GIST_ID || '';
const gist = await this.octokit.gists.get({ gist_id });
const filename = Object.keys(gist.data.files)[0];
await this.octokit.gists.update({
gist_id,
files: {
[filename]: {
filename: title,
content,
},
},
});
return gist.url;
}
}
70 changes: 70 additions & 0 deletions src/goodreads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import xml from 'fast-xml-parser'
import he from 'he'
import got from 'got'

export default class Goodreads {

private request = got.extend({
prefixUrl: 'https://www.goodreads.com',
searchParams: {
v: 2,
key: process.env.GOODREADS_API_KEY,
},
})

public async getBooks(): Promise<Book[]> {
let reviewList: Goodreads.Review[]
try {
reviewList = await this.getReviewList()
} catch (error) {
throw new Error("Error while fetching review list: " + error)
}

const reviews = await Promise.all(
reviewList.map(({ id }) => this.getReviewShow(id))
)

const books = reviews
.map(({ id, book, date_updated, user_statuses }) => {
const latestStatus = Array.isArray(user_statuses?.user_status)
? user_statuses?.user_status[0]
: user_statuses?.user_status
return {
id: id,
title: he.decode(book.title),
percent: latestStatus?.percent || 0,
date: new Date(latestStatus?.updated_at || date_updated),
}
})
.sort((a, b) => {
if (process.env.BOOKS_SORT_BY === 'percent') {
return b.percent - a.percent
}
return b.date.getTime() - a.date.getTime()
})
return books
}

private async getReviewList(
shelf = 'currently-reading'
): Promise<Goodreads.Review[]> {
const res = await this.request
.get('review/list', {
searchParams: {
id: process.env.GOODREADS_USER_ID,
shelf,
},
})
.text()
const reviewList: Goodreads.ReviewList = xml.parse(res)
const reviews = reviewList.GoodreadsResponse.reviews.review
return reviews
}

private async getReviewShow(id: number): Promise<Goodreads.Review> {
const res = await this.request.get('review/show', { searchParams: { id } }).text()
const reviewShow: Goodreads.ReviewShow = xml.parse(res)
const review = reviewShow.GoodreadsResponse.review
return review
}
}
82 changes: 22 additions & 60 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,31 @@
import dotenv from 'dotenv';
import he from 'he';
import dotenv from 'dotenv'
import Formatter from './formatter'
import Github from './github'
import Goodreads from './goodreads'

import * as api from './api';
import { generateBarChart } from './bar';
dotenv.config()

dotenv.config();

const MAX_LENGTH = 54;
const MAX_LINES = 5;
async function main() {
try {
const goodreads = new Goodreads()

async function getBooks(): Promise<Book[]> {
const reviewList = await api.getReviewList();
const reviews = await Promise.all(
reviewList.map(({ id }) => api.getReviewShow(id))
);
const books = reviews
.map(({ id, book, date_updated, user_statuses }) => {
const latestStatus = Array.isArray(user_statuses?.user_status)
? user_statuses?.user_status[0]
: user_statuses?.user_status;
return {
id: id,
title: he.decode(book.title),
percent: latestStatus?.percent || 0,
date: new Date(latestStatus?.updated_at || date_updated),
};
})
.sort((a, b) => {
if (process.env.BOOKS_SORT_BY === 'percent') {
return b.percent - a.percent;
}
return b.date.getTime() - a.date.getTime();
});
return books;
}
const books = await goodreads.getBooks()
console.log(`Found ${books.length} book(s)`)

function generateLines(books: Book[]) {
const barWidth = Math.floor(MAX_LENGTH / 4);
const lines = books.slice(0, MAX_LINES).map(({ title, percent }) => {
const bar = generateBarChart(percent, barWidth);
const percentage = `${percent}%`.padStart(4, ' ');
const length = MAX_LENGTH - bar.length - percentage.length - 1;
let text = title;
if (title.length > length) {
text = title.substring(0, length - 3).concat('...');
} else {
text = title.padEnd(length, ' ');
}
return `${text} ${bar}${percentage}`;
});
return lines;
}
const formatter = new Formatter()
const lines = formatter.generateLines(books)

(async () => {
try {
const books = await getBooks();
console.log(`Found ${books.length} book(s)`);
const lines = generateLines(books);
const url = await api.updateGist(
const github = new Github()
const url = await github.updateGist(
`📚 Currently reading books (${lines.length}${books.length})`,
lines.join('\n')
);
console.log(`Updated: ${url}`);
)

console.log(`Updated: ${url}`)
} catch (error) {
console.error(error);
process.exit(1);
console.error(error)
process.exit(1)
}
})();
}

main()

0 comments on commit 8e24697

Please sign in to comment.