Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor and fix handling of goodreads api when currently reading contains a single item #2

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Next Next commit
Refactor the code to follow a more modular design
- 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
commit 8e246979d97ca65be0af4a715d29520132b3a532
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()