Skip to content

Commit

Permalink
perf: adds cache to reduce api requests
Browse files Browse the repository at this point in the history
Fixes #16, resolves #5
  • Loading branch information
JackCuthbert committed Dec 2, 2019
1 parent 6b5f0a8 commit 8448c68
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
dist
.track.json
17 changes: 11 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"@semantic-release/release-notes-generator": "^7.3.5",
"@types/chalk": "^2.2.0",
"@types/date-fns": "^2.6.0",
"@types/fs-extra": "^8.0.1",
"@types/hapi__joi": "^16.0.3",
"@types/jest": "^24.0.22",
"@types/node": "^12.12.14",
Expand All @@ -46,6 +47,7 @@
"@sentry/node": "^5.9.0",
"axios": "^0.19.0",
"chalk": "^2.4.2",
"date-fns": "^2.8.1"
"date-fns": "^2.8.1",
"fs-extra": "^8.1.0"
}
}
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Variable | Default | Description
`ACTIVE_HOURS_END` | `18` | The hour of the day to stop updating your Slack status
`UPDATE_INTERVAL` | `1` | The time in minutes to wait until updating your Slack Status
`UPDATE_WEEKENDS` | `undefined` | Provide any value to enable status updates during the weekend
`UPDATE_EXPIRATION` | `15` | The time in minutes to use as a default status expiration length
`UPDATE_EXPIRATION` | `10` | The time in minutes to use as a default status expiration length
`SENTRY_DSN` | `undefined` | Optionally provide a Sentry DSN to enable error reporting

## Hosting
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const {
ACTIVE_HOURS_START = '8',
ACTIVE_HOURS_END = '18',
UPDATE_INTERVAL = '1',
UPDATE_EXPIRATION = '10',
UPDATE_WEEKENDS,
SENTRY_DSN
} = process.env
Expand Down Expand Up @@ -38,4 +39,7 @@ export const sentryDsn = SENTRY_DSN
export const updateInterval = Number(UPDATE_INTERVAL)
export const updateWeekends = !!UPDATE_WEEKENDS

/** Time in minutes to use as a default expiration time */
export const updateExpiration = Number(UPDATE_EXPIRATION)

export const tz = TZ
54 changes: 42 additions & 12 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,57 @@ import {
import {
getLastFmTrack,
getNowPlaying,
getRecentLastFmTracks
getRecentLastFmTracks,
trackIsEqual
} from './utils/lastFm'
import {
deleteTrackJSON,
readTrackJSON,
writeTrackJSON
} from './utils/cache'
import { handleError, enableErrorTracking } from './utils/errors'
import { log } from './utils/log'
import { validateConfig } from './utils/validation'

/** Clears the slack status if the cached track has no duration */
async function clearSlackStatus (cached: LastFM.Track | undefined) {
if (cached && cached.duration === '0') {
log('Cached track has no duration, clearing Slack status', 'slack')
await setSlackStatus('')
}
}

async function main () {
const cachedTrack = await readTrackJSON()

// Time restrictions
const currentTime = getTime(new Date())
const currentHour = getHours(currentTime)

const { start, end } = config.activeHours
if (currentHour < start || currentHour >= end) {
log(`Outside active hours (${start}-${end}), skipping`)
log(`Outside active hours (${start}-${end})`)
await clearSlackStatus(cachedTrack)
return
}

if (!config.updateWeekends && isWeekend(currentTime)) {
log('Weekend updates not enabled, skipping')
await clearSlackStatus(cachedTrack)
return
}

// Status restrictions
const currentPresence = await getSlackPresence()
if (currentPresence === 'away') {
log('User presence is "away", skipping')
log('User presence is "away"')
await clearSlackStatus(cachedTrack)
return
}

const currentProfile = await getSlackProfile()
if (!shouldSetStatus(currentProfile)) {
log('Custom status detected, skipping')
log('Custom status detected')
return
}

Expand All @@ -49,23 +68,34 @@ async function main () {
const nowPlaying = getNowPlaying(recentTracks.track)

if (nowPlaying === undefined) {
log('Nothing playing, skipping')
log('Nothing playing')
await clearSlackStatus(cachedTrack)
await deleteTrackJSON()
return
}

// Equality restriction, don't update if it's not necessary
if (trackIsEqual(nowPlaying, cachedTrack)) {
log('Now playing track is cached, no update necessary')
return
}

const track = await getLastFmTrack(nowPlaying.name, nowPlaying.artist['#text'])
let duration = 60 * (config.updateExpiration * 1000)
let status = `${nowPlaying.name} ${config.slack.separator} ${nowPlaying.artist['#text']}`

if (track !== undefined) {
const status = `${track.name} ${config.slack.separator} ${track.artist.name}`
log(`Setting status to "${status}"`, 'slack')
await setSlackStatus(status, Number(track.duration))
return
await writeTrackJSON(track)

status = `${track.name} ${config.slack.separator} ${track.artist.name}`
duration = track.duration !== '0' ? Number(track.duration) : duration
} else {
log('No detailed track info found, falling back to recent track', 'lastfm')
await deleteTrackJSON()
}

log('Unable to find detailed track info, falling back to recent track', 'lastfm')
const status = `${nowPlaying.name} ${config.slack.separator} ${nowPlaying.artist['#text']}`
log(`Setting status to "${status}"`, 'slack')
await setSlackStatus(status)
await setSlackStatus(status, duration)
}

async function loop () {
Expand Down
27 changes: 27 additions & 0 deletions src/utils/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as path from 'path'
import * as fs from 'fs-extra'

const cachePath = path.join('.', '.track.json')

export async function writeTrackJSON (obj?: LastFM.Track) {
if (obj === undefined) return undefined
return fs.writeJSON(cachePath, obj)
}

export async function readTrackJSON (): Promise<LastFM.Track | undefined> {
try {
const track: LastFM.Track = await fs.readJSON(cachePath)
return track
} catch (error) {
// this is fine, I promise
return undefined
}
}

export async function deleteTrackJSON () {
try {
await fs.remove(cachePath)
} catch (error) {
// this is okay
}
}
6 changes: 6 additions & 0 deletions src/utils/lastFm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,9 @@ export async function getLastFmTrack (track: string, artist: string): Promise<La
export function getNowPlaying (tracks: LastFM.RecentTrack[]) {
return tracks.find(track => track['@attr']?.nowplaying === 'true')
}

/** Determines if the recent track is equal to the cached track */
export function trackIsEqual (recent: LastFM.RecentTrack, cached?: LastFM.Track) {
if (cached === undefined) return false
return recent.name === cached.name && recent.artist['#text'] === cached.artist.name
}
4 changes: 2 additions & 2 deletions src/utils/slack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export function calcExpiration (duration: number) {
* [API Doc](https://api.slack.com/methods/users.profile.set)
*
* @param status The status text to set
* @param duration The time in seconds to keep the state
* @param duration The time in milliseconds to keep the state
*
*/
export async function setSlackStatus (status: string, duration?: number) {
Expand All @@ -29,7 +29,7 @@ export async function setSlackStatus (status: string, duration?: number) {
profile: {
status_text: status,
status_emoji: status !== '' ? config.slack.emoji : '',
status_expiration: duration ? calcExpiration(duration) : 0
status_expiration: duration !== undefined ? calcExpiration(duration) : 0
}
}

Expand Down

0 comments on commit 8448c68

Please sign in to comment.