mirror of
https://github.com/ArthurDanjou/artdanj-api.git
synced 2026-01-28 02:20:27 +01:00
Connect Athena to Spotify
This commit is contained in:
44
app/Controllers/Http/SongsController.ts
Normal file
44
app/Controllers/Http/SongsController.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||
import {
|
||||
fetchTopArtist,
|
||||
fetchTopTrack,
|
||||
getAuthorizationURI,
|
||||
getCurrentPlayingFromCache,
|
||||
getHistory,
|
||||
setupSpotify,
|
||||
} from 'App/Utils/SongUtils'
|
||||
import SongHistoryValidator from 'App/Validators/song/SongHistoryValidator'
|
||||
|
||||
export default class SongsController {
|
||||
public async getCurrentSong({ response }: HttpContextContract) {
|
||||
return response.status(200).send(getCurrentPlayingFromCache())
|
||||
}
|
||||
|
||||
public async getHistory({ request, response }: HttpContextContract) {
|
||||
const { range } = await request.validate(SongHistoryValidator)
|
||||
const history = await getHistory(range)
|
||||
return response.status(200).send({
|
||||
history,
|
||||
})
|
||||
}
|
||||
|
||||
public async getTopTrack({ response }: HttpContextContract) {
|
||||
return response.status(200).send({
|
||||
tracks: await fetchTopTrack(),
|
||||
})
|
||||
}
|
||||
|
||||
public async getTopArtist({ response }: HttpContextContract) {
|
||||
return response.status(200).send({
|
||||
tracks: await fetchTopArtist(),
|
||||
})
|
||||
}
|
||||
|
||||
public async authorize({ response }: HttpContextContract) {
|
||||
return response.status(200).redirect(getAuthorizationURI())
|
||||
}
|
||||
|
||||
public async callback({ request }: HttpContextContract) {
|
||||
await setupSpotify(request.param('code'))
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||
import StateSleepingValidator from 'App/Validators/states/StateSleepingValidator'
|
||||
import { getCurrentPlayingFromCache } from 'App/Utils/SongUtils'
|
||||
|
||||
export default class StatesController {
|
||||
// Listening Music
|
||||
|
||||
public async index({ response }: HttpContextContract) {
|
||||
const sleeping = this.formatValue(await Redis.get('states:sleeping'))
|
||||
const developing = this.formatValue(await Redis.get('states:developing'))
|
||||
return response.status(200).send({
|
||||
sleeping,
|
||||
developing,
|
||||
listening_music: 'Soon',
|
||||
listening_music: await getCurrentPlayingFromCache(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
27
app/Models/Song.ts
Normal file
27
app/Models/Song.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
|
||||
|
||||
export default class Song extends BaseModel {
|
||||
@column({ isPrimary: true })
|
||||
public date: number
|
||||
|
||||
@column()
|
||||
public device_name: string
|
||||
|
||||
@column()
|
||||
public device_type: string
|
||||
|
||||
@column()
|
||||
public item_name: string
|
||||
|
||||
@column()
|
||||
public item_type: string
|
||||
|
||||
@column()
|
||||
public author: string
|
||||
|
||||
@column()
|
||||
public image: string
|
||||
|
||||
@column()
|
||||
public duration: number
|
||||
}
|
||||
16
app/Tasks/CurrentSongTask.ts
Normal file
16
app/Tasks/CurrentSongTask.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import Logger from '@ioc:Adonis/Core/Logger'
|
||||
import { getCurrentPlayingFromSpotify } from 'App/Utils/SongUtils'
|
||||
|
||||
const MS = 1000
|
||||
let taskId
|
||||
|
||||
export async function Activate(): Promise<void> {
|
||||
Logger.info(`Starting task runner for watching spotify current playing [${MS} ms]`)
|
||||
await getCurrentPlayingFromSpotify()
|
||||
taskId = setInterval(getCurrentPlayingFromSpotify, MS)
|
||||
}
|
||||
|
||||
export function ShutDown(): void {
|
||||
clearInterval(taskId)
|
||||
Logger.info('Shutdown task runner for getting current developing state')
|
||||
}
|
||||
40
app/Tasks/HistorySongsTask.ts
Normal file
40
app/Tasks/HistorySongsTask.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import Logger from '@ioc:Adonis/Core/Logger'
|
||||
import { getCurrentPlayingFromCache } from 'App/Utils/SongUtils'
|
||||
import Song from 'App/Models/Song'
|
||||
|
||||
const MS = 10000
|
||||
let taskId
|
||||
|
||||
async function LogSpotifyHistory(): Promise<void> {
|
||||
const current = await getCurrentPlayingFromCache()
|
||||
|
||||
if (!current.is_playing) return
|
||||
|
||||
if (current.progress && current.progress < 1000) return
|
||||
|
||||
const last_entry = await Song.query().where('id', current.id!).orderBy('date', 'desc').first()
|
||||
|
||||
if (last_entry && new Date().getTime() - last_entry.duration <= new Date(last_entry.date).getTime()) return
|
||||
|
||||
await Song.create({
|
||||
date: current.started_at,
|
||||
duration: current.duration,
|
||||
item_name: current.name,
|
||||
item_type: current.type,
|
||||
author: current.author,
|
||||
device_name: current.device_name,
|
||||
device_type: current.device_type,
|
||||
image: current.image?.url,
|
||||
})
|
||||
}
|
||||
|
||||
export async function Activate(): Promise<void> {
|
||||
Logger.info(`Starting task runner for tracking spotify listen history [${MS} ms]`)
|
||||
await LogSpotifyHistory()
|
||||
taskId = setInterval(LogSpotifyHistory, MS)
|
||||
}
|
||||
|
||||
export function ShutDown(): void {
|
||||
clearInterval(taskId)
|
||||
Logger.info('Shutdown task runner for getting current developing state')
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import Logger from '@ioc:Adonis/Core/Logger'
|
||||
|
||||
const MS = 1000
|
||||
|
||||
export async function getCurrentPlayingMusic(): Promise<void> {
|
||||
// Fetch from deezer
|
||||
}
|
||||
|
||||
export async function Activate(): Promise<void> {
|
||||
Logger.info(`Starting task runner for watching deezer current playing [${MS} ms]`)
|
||||
await getCurrentPlayingMusic()
|
||||
setInterval(getCurrentPlayingMusic, MS)
|
||||
}
|
||||
@@ -1,51 +1,13 @@
|
||||
import { btoa } from 'buffer'
|
||||
import axios from 'axios'
|
||||
import Env from '@ioc:Adonis/Core/Env'
|
||||
import Logger from '@ioc:Adonis/Core/Logger'
|
||||
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||
import { fetchDevelopingState } from 'App/Utils/StatesTask'
|
||||
|
||||
const MS = 1000 * 2 * 60 // 2 min
|
||||
let taskId
|
||||
|
||||
interface StatesResponse {
|
||||
time: number
|
||||
}
|
||||
|
||||
async function getCurrentTime(): Promise<void> {
|
||||
try {
|
||||
const response = await axios.get<{ data: StatesResponse[]}>(`https://wakatime.com/api/v1/users/${Env.get('WAKATIME_USER')}/heartbeats`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${btoa(Env.get('WAKATIME_KEY'))}`,
|
||||
},
|
||||
params: {
|
||||
date: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
if (response.status === 200) {
|
||||
const heartbeat = response.data.data[response.data.data.length - 1]
|
||||
const current_time = new Date(Date.now()).getTime() / 1000
|
||||
|
||||
if (heartbeat && heartbeat.time!) {
|
||||
const active = current_time - heartbeat.time <= 60 * 5 // Less than 5 min.
|
||||
const redis_state = await Redis.get('states:developing') === 'true'
|
||||
|
||||
if (redis_state !== active) {
|
||||
await Redis.set('states:developing', String(active))
|
||||
if (redis_state) await Redis.set('states:sleeping', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
Logger.error('Error while getting the states')
|
||||
}
|
||||
}
|
||||
|
||||
export async function Activate(): Promise<void> {
|
||||
Logger.info(`Starting task runner for getting current developing state [every ${MS} ms]`)
|
||||
await getCurrentTime()
|
||||
taskId = setInterval(getCurrentTime, MS)
|
||||
await fetchDevelopingState()
|
||||
taskId = setInterval(fetchDevelopingState, MS)
|
||||
}
|
||||
|
||||
export function ShutDown(): void {
|
||||
|
||||
22
app/Types/ILocalSpotify.ts
Normal file
22
app/Types/ILocalSpotify.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export interface SpotifyArtist {
|
||||
id: string
|
||||
name: string
|
||||
image: string
|
||||
genres: string[]
|
||||
popularity: number
|
||||
followers: number
|
||||
}
|
||||
|
||||
export interface SpotifyTrack {
|
||||
item: {
|
||||
name: string
|
||||
type: string
|
||||
}
|
||||
device: {
|
||||
name: string
|
||||
type: string
|
||||
}
|
||||
author: string
|
||||
duration: number
|
||||
image: string
|
||||
}
|
||||
110
app/Types/ISpotify.ts
Normal file
110
app/Types/ISpotify.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
export interface SpotifyToken {
|
||||
access_token: string
|
||||
refresh_token: string
|
||||
}
|
||||
|
||||
interface Device {
|
||||
name: string
|
||||
type: 'computer' | 'smartphone' | 'speaker'
|
||||
}
|
||||
|
||||
interface ExternalUrl {
|
||||
spotify: string
|
||||
}
|
||||
|
||||
interface ExternalId {
|
||||
isrc: string
|
||||
ean: string
|
||||
upc: string
|
||||
}
|
||||
|
||||
interface Image {
|
||||
url: string
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
interface Restriction {
|
||||
reason: 'market' | 'explicit' | 'product'
|
||||
}
|
||||
|
||||
interface Follower {
|
||||
href: string
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface Artist {
|
||||
external_urls: ExternalUrl
|
||||
followers: Follower
|
||||
genres: string[]
|
||||
href: string
|
||||
id: string
|
||||
images: Image[]
|
||||
name: string
|
||||
popularity: number
|
||||
type: string
|
||||
uri: string
|
||||
}
|
||||
|
||||
interface Album {
|
||||
album_type: 'single' | 'album' | 'compilation'
|
||||
total_tracks: number
|
||||
available_markets: string[]
|
||||
external_urls: ExternalUrl
|
||||
href: string
|
||||
id: string
|
||||
images: Image[]
|
||||
name: string
|
||||
release_date: string
|
||||
release_date_precision: 'day' | 'month' | 'year'
|
||||
restrictions: Restriction
|
||||
type: string
|
||||
uri: string
|
||||
}
|
||||
|
||||
interface Item {
|
||||
album: Album & { album_group: 'album' | 'single' | 'compilation' | 'appears_on' ; artists: Artist[] }
|
||||
artists: Artist[]
|
||||
available_markets: string[]
|
||||
disc_number: number
|
||||
duration_ms: number
|
||||
explicit: boolean
|
||||
external_ids: ExternalId
|
||||
external_urls: ExternalUrl
|
||||
href: string
|
||||
id: string
|
||||
is_playable: boolean
|
||||
restrictions: Restriction
|
||||
name: string
|
||||
popularity: number
|
||||
preview_url: string
|
||||
track_number: number
|
||||
type: string
|
||||
uri: string
|
||||
is_local: boolean
|
||||
}
|
||||
|
||||
export interface PlayerResponse {
|
||||
device: Device
|
||||
timestamp: number
|
||||
is_playing: boolean
|
||||
item: Item
|
||||
progress_ms: number
|
||||
shuffle_state: string
|
||||
repeat_state: string
|
||||
currently_playing_type: 'track' | 'episode' | 'ad' | 'unknown'
|
||||
}
|
||||
|
||||
export interface InternalPlayerResponse {
|
||||
is_playing: boolean
|
||||
device_name?: string
|
||||
device_type?: string
|
||||
name?: string
|
||||
type?: string
|
||||
author?: string
|
||||
id?: string
|
||||
image?: Image
|
||||
progress?: number
|
||||
duration?: number
|
||||
started_at?: number
|
||||
}
|
||||
16
app/Types/IStats.ts
Normal file
16
app/Types/IStats.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface Time {
|
||||
hours: number
|
||||
minutes: number
|
||||
seconds: number
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
range: {
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
development_time: Time
|
||||
commands_ran: number
|
||||
builds_ran: number
|
||||
}
|
||||
@@ -1,11 +1,237 @@
|
||||
export async function getHistory(range: 'day' | 'week' | 'month') {
|
||||
return range
|
||||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'
|
||||
import Env from '@ioc:Adonis/Core/Env'
|
||||
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||
import { SpotifyArtist, SpotifyTrack } from 'App/Types/ILocalSpotify'
|
||||
import { Artist, InternalPlayerResponse, PlayerResponse, SpotifyToken } from 'App/Types/ISpotify'
|
||||
import Song from 'App/Models/Song'
|
||||
|
||||
export function getSpotifyAccount(): { access: string; refresh: string } {
|
||||
return JSON.parse(readFileSync('.config/.spotify').toString())
|
||||
}
|
||||
|
||||
export async function getTopTrack() {
|
||||
return 0
|
||||
export function getAuthorizationURI(): string {
|
||||
const query = JSON.stringify({
|
||||
response_type: 'code',
|
||||
client_id: Env.get('SPOTIFY_ID'),
|
||||
scope: encodeURIComponent('user-read-playback-state user-read-currently-playing'),
|
||||
redirect_uri: `${Env.get('BASE_URL')}/spotify/callback`,
|
||||
})
|
||||
|
||||
return `https://accounts.spotify.com/authorize?${query}`
|
||||
}
|
||||
|
||||
export async function GetCurrentPlaying() {
|
||||
return null
|
||||
export async function setupSpotify(code: string): Promise<void> {
|
||||
const authorization_tokens: AxiosResponse<SpotifyToken> = await axios.post(
|
||||
'https://accounts.spotify.com/api/token',
|
||||
{
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: `${Env.get('BASE_URL')}/spotify/callback`,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Basic ${Buffer.from(`${Env.get('SPOTIFY_ID')}:${Env.get('SPOTIFY_SECRET')}`).toString('base64')}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (authorization_tokens.status === 200) {
|
||||
writeFileSync(
|
||||
'.config/.spotify',
|
||||
JSON.stringify({
|
||||
access: authorization_tokens.data.access_token,
|
||||
refresh: authorization_tokens.data.refresh_token,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function regenerateTokens(): Promise<void> {
|
||||
const refresh_token = getSpotifyAccount().refresh
|
||||
|
||||
const authorization_tokens: AxiosResponse<SpotifyToken> = await axios.post(
|
||||
'https://accounts.spotify.com/api/token',
|
||||
{
|
||||
grant_type: 'refresh_token',
|
||||
refresh_token,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Basic ${Buffer.from(`${Env.get('SPOTIFY_ID')}:${Env.get('SPOTIFY_SECRET')}`).toString('base64')}`,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
if (authorization_tokens.status === 200) {
|
||||
writeFileSync(
|
||||
'.config/.spotify',
|
||||
JSON.stringify({
|
||||
access: authorization_tokens.data.access_token,
|
||||
refresh: authorization_tokens.data.refresh_token,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function RequestWrapper<T = never>(url: string): Promise<AxiosResponse<T>> {
|
||||
let request
|
||||
const options: AxiosRequestConfig = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${getSpotifyAccount().access}`,
|
||||
},
|
||||
}
|
||||
request = await axios.get<T>(url, options)
|
||||
|
||||
if (request.status === 401) {
|
||||
await regenerateTokens()
|
||||
request = await axios.get<T>(url, options)
|
||||
}
|
||||
return request
|
||||
}
|
||||
|
||||
export async function getCurrentPlayingFromCache(): Promise<InternalPlayerResponse> {
|
||||
return JSON.parse(await Redis.get('spotify:current') || '') || { is_playing: false }
|
||||
}
|
||||
|
||||
export async function getCurrentPlayingFromSpotify(): Promise<InternalPlayerResponse> {
|
||||
const current_track = await RequestWrapper<PlayerResponse>('https://api.spotify.com/v1/me/player?additional_types=track,episode')
|
||||
|
||||
let current: InternalPlayerResponse
|
||||
|
||||
if (current_track.data && !['track', 'episode'].includes(current_track.data.currently_playing_type))
|
||||
current = { is_playing: false }
|
||||
|
||||
if (current_track.data && current_track.data.is_playing) {
|
||||
current = {
|
||||
is_playing: true,
|
||||
device_name: current_track.data.device.name,
|
||||
device_type: current_track.data.device.type,
|
||||
name: current_track.data.item.name,
|
||||
type: current_track.data.item.type,
|
||||
author: current_track.data.item.artists.map(artist => artist.name).join(', '),
|
||||
id: current_track.data.item.id,
|
||||
image: current_track.data.item.album.images[0],
|
||||
progress: current_track.data.progress_ms,
|
||||
duration: current_track.data.item.duration_ms,
|
||||
started_at: current_track.data.timestamp,
|
||||
}
|
||||
}
|
||||
else {
|
||||
current = { is_playing: false }
|
||||
}
|
||||
|
||||
if ((await Redis.get('spotify:current') as string) !== JSON.stringify(current))
|
||||
await updateCurrentSong(current)
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
export async function updateCurrentSong(song: InternalPlayerResponse): Promise<void> {
|
||||
// const current = JSON.parse(await Redis.get('spotify/current') as string)
|
||||
await Redis.set('spotify:current', JSON.stringify(song))
|
||||
|
||||
// const changed = diff(current, song)
|
||||
// todo send message to Rabbit
|
||||
}
|
||||
|
||||
export async function getHistory(range: 'day' | 'week' | 'month' | 'total') {
|
||||
if (await Redis.exists(`spotify:history:range:${range || 'day'}`))
|
||||
return JSON.parse(await Redis.get(`spotify:history:range:${range || 'day'}`) || '')
|
||||
|
||||
let startDate = new Date(new Date().getTime() - 24 * 60 * 60 * 1000)
|
||||
if (range === 'week') startDate = new Date(new Date().getTime() - 7 * 24 * 60 * 60 * 1000)
|
||||
else if (range === 'month') startDate = new Date(new Date().setMonth(new Date().getMonth() - 1))
|
||||
|
||||
const endDate = new Date()
|
||||
|
||||
const songs = await Song
|
||||
.query()
|
||||
.where('date', '<=', endDate)
|
||||
.where('date', '>=', startDate)
|
||||
.orderBy('date', 'desc')
|
||||
|
||||
if (songs.length <= 0)
|
||||
return { history: 'no_tracks_in_that_range' }
|
||||
|
||||
await Redis.set(`spotify:history:range:${range || 'day'}`, JSON.stringify({
|
||||
cached: new Date().toUTCString(),
|
||||
history: songs,
|
||||
}), 'ex', 300)
|
||||
|
||||
return { history: songs }
|
||||
}
|
||||
|
||||
export async function fetchTopArtist(): Promise<SpotifyArtist[]> {
|
||||
if (await Redis.exists('spotify:top:artists'))
|
||||
return JSON.parse(await Redis.get('spotify:top:artists') || '')
|
||||
|
||||
const fetched_artists = await RequestWrapper<{ items: Artist[] }>('https://api.spotify.com/v1/me/top/type/artists?limit=5')
|
||||
|
||||
const artists: SpotifyArtist[] = []
|
||||
|
||||
if (fetched_artists.data) {
|
||||
for (const artist of fetched_artists.data.items) {
|
||||
artists.push({
|
||||
id: artist.id,
|
||||
image: artist.images[0].url,
|
||||
name: artist.name,
|
||||
followers: artist.followers.total,
|
||||
genres: artist.genres,
|
||||
popularity: artist.popularity,
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
await Redis.set('spotify:top:artists', JSON.stringify({
|
||||
cached: new Date().toUTCString(),
|
||||
top: artists,
|
||||
}), 'ex', 600)
|
||||
|
||||
return artists
|
||||
}
|
||||
|
||||
export async function fetchTopTrack(): Promise<SpotifyTrack[]> {
|
||||
if (await Redis.exists('spotify:top:tracks'))
|
||||
return JSON.parse(await Redis.get('spotify:top:tracks') || '')
|
||||
|
||||
const fetched_tracks = await Song
|
||||
.query()
|
||||
.orderBy('date', 'desc')
|
||||
.limit(5)
|
||||
|
||||
const tracks: SpotifyTrack[] = []
|
||||
|
||||
if (fetched_tracks.length >= 0) {
|
||||
for (const track of fetched_tracks) {
|
||||
tracks.push({
|
||||
item: {
|
||||
name: track.item_name,
|
||||
type: track.item_type,
|
||||
},
|
||||
device: {
|
||||
name: track.device_name,
|
||||
type: track.device_type,
|
||||
},
|
||||
duration: track.duration,
|
||||
author: track.author,
|
||||
image: track.image,
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
return []
|
||||
}
|
||||
|
||||
await Redis.set('spotify:top:tracks', JSON.stringify({
|
||||
cached: new Date().toUTCString(),
|
||||
top: tracks,
|
||||
}), 'ex', 300)
|
||||
|
||||
return tracks
|
||||
}
|
||||
|
||||
40
app/Utils/StatesTask.ts
Normal file
40
app/Utils/StatesTask.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { btoa } from 'buffer'
|
||||
import axios from 'axios'
|
||||
import Env from '@ioc:Adonis/Core/Env'
|
||||
import Redis from '@ioc:Adonis/Addons/Redis'
|
||||
import Logger from '@ioc:Adonis/Core/Logger'
|
||||
|
||||
interface StatesResponse {
|
||||
time: number
|
||||
}
|
||||
|
||||
export async function fetchDevelopingState(): Promise<void> {
|
||||
try {
|
||||
const response = await axios.get<{ data: StatesResponse[]}>(`https://wakatime.com/api/v1/users/${Env.get('WAKATIME_USER')}/heartbeats`, {
|
||||
headers: {
|
||||
Authorization: `Basic ${btoa(Env.get('WAKATIME_KEY'))}`,
|
||||
},
|
||||
params: {
|
||||
date: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
if (response.status === 200) {
|
||||
const heartbeat = response.data.data[response.data.data.length - 1]
|
||||
const current_time = new Date(Date.now()).getTime() / 1000
|
||||
|
||||
if (heartbeat && heartbeat.time!) {
|
||||
const active = current_time - heartbeat.time <= 60 * 5 // Less than 5 min.
|
||||
const redis_state = await Redis.get('states:developing') === 'true'
|
||||
|
||||
if (redis_state !== active) {
|
||||
await Redis.set('states:developing', String(active))
|
||||
if (redis_state) await Redis.set('states:sleeping', 'false')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
Logger.error('Error while getting the states')
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,7 @@
|
||||
import DevelopmentHour from 'App/Models/DevelopmentHour'
|
||||
import CommandsRun from 'App/Models/CommandsRun'
|
||||
import BuildsRun from 'App/Models/BuildsRun'
|
||||
|
||||
interface Time {
|
||||
hours: number
|
||||
minutes: number
|
||||
seconds: number
|
||||
}
|
||||
|
||||
export interface Stats {
|
||||
range: {
|
||||
start: string
|
||||
end: string
|
||||
}
|
||||
|
||||
development_time: Time
|
||||
commands_ran: number
|
||||
builds_ran: number
|
||||
}
|
||||
import { Stats, Time } from 'App/Types/IStats'
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
return date.toISOString().split('T')[0]
|
||||
|
||||
15
app/Validators/song/SongHistoryValidator.ts
Normal file
15
app/Validators/song/SongHistoryValidator.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { schema } from '@ioc:Adonis/Core/Validator'
|
||||
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
|
||||
|
||||
export default class SongHistoryValidator {
|
||||
constructor(protected ctx: HttpContextContract) {
|
||||
}
|
||||
|
||||
public schema = schema.create({
|
||||
range: schema.enum(['day', 'week', 'month', 'total'] as const),
|
||||
})
|
||||
|
||||
public messages = {
|
||||
required: 'The field {{field}} is required',
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user