chore: simpler charts

This commit is contained in:
Cory Dransfeldt 2023-07-22 07:59:49 -07:00
parent e8b8bb24b5
commit eaa43c41af
No known key found for this signature in database
7 changed files with 59 additions and 1092 deletions

3
.env
View file

@ -1,6 +1,3 @@
ACCESS_KEY_WASABI=
SECRET_KEY_WASABI=
BUCKET_WASABI=
API_KEY_PLAUSIBLE=
API_KEY_TRAKT=
API_KEY_WEBMENTIONS_CORYD_DEV=

View file

@ -1,915 +0,0 @@
{
"Punk": [
"+44",
"wire",
"sex-pistols",
"rkl",
"refused",
"propagandhi",
"the-pogues",
"nomeansno",
"nofx",
"misfits",
"minutemen",
"minor-threat",
"joyce-manor",
"jello-biafra-with-nomeansno",
"j-church",
"hüsker-dü",
"green-day",
"fugazi",
"fake-names",
"descendents",
"deep-turtle",
"death-from-above-1979",
"dead-kennedys",
"cock-sparrer",
"coachwhips",
"cloud-nothings",
"the-clash",
"box-car-racer",
"blink-182",
"black-flag",
"bad-religion",
"bad-brains",
"afi"
],
"Black Metal": [
"zhrine",
"yellow-eyes",
"wormlust",
"woods-of-ypres",
"woods-of-desolation",
"wolves-in-the-throne-room",
"white-ward",
"weakling",
"vital-spirit",
"ved-buens-ende...",
"vaura",
"unanimated",
"ultha",
"ultar",
"trna",
"tómarúm",
"tod-huetet-uebel",
"thou,-mizmor-&-emma-ruth-rundle",
"thantifaxath",
"tardigrada",
"svartidauði",
"sunken",
"stormkeep",
"skáphe",
"sinmara",
"the-silver",
"show-me-a-dinosaur",
"sacramentum",
"rebirth-of-nefast",
"pillorian",
"panopticon",
"outre",
"ossuaire",
"örmagna",
"olhava",
"odraza",
"oathbreaker",
"numenorean",
"nordicwinter",
"noltem",
"moonsorrow",
"møl",
"mizmor-&-thou",
"mizmor-&-andrew-black",
"mizmor",
"misþyrming",
"mgła",
"mare-cognitum",
"mannveira",
"lunar-aurora",
"lantlôs",
"kriegsmaschine",
"krallice",
"kostnatění",
"karg",
"hræ",
"helfró",
"harakiri-for-the-sky",
"grey-waters",
"gravenchalice",
"furia",
"fen",
"falls-of-rauros",
"enslaved",
"emperor",
"embrace-of-thorns",
"drudkh",
"drought",
"deathspell-omega",
"deafheaven",
"the-clearing-path",
"celeste",
"carpe-noctem",
"bosse-de-nage",
"blut-aus-nord",
"behemoth",
"an-autumn-for-crippled-children",
"au-dessus",
"ash-borer",
"arcturus",
"aosoth",
"aoratos",
"andavald",
"amesoeurs",
"altar-of-plagues",
"akhlys",
"agalloch",
"afsky"
],
"Death Metal": [
"zealotry",
"xysma",
"xoth",
"xenomorph",
"wretched-fate",
"worthless",
"worm",
"wombbath",
"witherscape",
"witch-vomit",
"weeping-sores",
"warp-chamber",
"vrenth",
"vorum",
"voimaton",
"voidceremony",
"void-terror",
"void-rot",
"vircolac",
"vertebra-atlantis",
"venomous-skeleton",
"vengeful",
"venenum",
"vasaeleth",
"vader",
"vacuous",
"vacivus",
"unleashed",
"universally-estranged",
"undersave",
"undergang",
"under-the-church",
"undeath",
"unbounded-terror",
"unaussprechlichen-kulten",
"unanimated",
"ulthar",
"úlfúð",
"ulcerate",
"turris-eburnea",
"tumba-de-carne",
"tribulation",
"trenchrot",
"transgressor",
"toughness",
"totten-korps",
"torchure",
"tomb-mold",
"timeghoul",
"tiamat",
"therion",
"thergothon",
"thanatos",
"temisto",
"teleport",
"teitanblood",
"teeth",
"taphos",
"sxuperion",
"sweven",
"swallowed",
"suppression",
"superstition",
"sunless",
"sulphurous",
"sulphur-aeon",
"suffocation",
"suffering-hour",
"stench",
"stargazer",
"spectral-voice",
"spawn-of-possession",
"spasme",
"sororicide",
"sorcery",
"sonne-adam",
"solothus",
"snorlax",
"sněť",
"slugathor",
"skullcrush",
"skeletal-remains",
"skeletal",
"sinister",
"siderean",
"shub-niggurath",
"shards-of-humanity",
"setentia",
"sermon-of-flames",
"seputus",
"sepulcher",
"sentient-horror",
"sentenced",
"sedimentum",
"seance",
"scythian",
"scorched",
"sarmat",
"runemagick",
"rune",
"rottrevore",
"rotted",
"rothadás",
"ritual-necromancy",
"ripping-corpse",
"ripped-to-shreds",
"resurgency",
"reptilian",
"replicant",
"reeking-aura",
"ravenous-death",
"rapture",
"question",
"qrixkuor",
"pyrrhon",
"pyrexia",
"pyre",
"putrescine",
"purtenance",
"pungent-stench",
"psycroptic",
"possessed",
"portal",
"poisonous",
"plague-bearer",
"phrenelith",
"phobophilic",
"pestilength",
"perilaxe-occlusion",
"pan.thy.monium",
"outre-tombe",
"outer-heaven",
"ossuarium",
"orthodoxy",
"oksennus",
"of-feather-and-bone",
"obscure-infinity",
"obscura",
"obscene",
"obliveon",
"obliteration",
"obituary",
"nucleus",
"nothingness",
"noisem",
"nocturnus",
"nithing",
"nihilist",
"nex-carnis",
"neuraxis",
"nekrovault",
"nekrofilth",
"negativa",
"necrovorous",
"necrovore",
"necrovation",
"necrot",
"necros-christos",
"necrophobic",
"necrophiliac",
"napalm-death",
"moss-upon-the-skull",
"mortuous",
"mortuary",
"mortiferum",
"mortem",
"mortal-incarnation",
"morta-skuld",
"morpheus-descends",
"morgue",
"mordicus",
"morbus-chron",
"morbid-angel",
"monstrosity",
"molested",
"mitochondrion",
"mithridatum",
"mithras",
"miscreance",
"miasma",
"merciless",
"megaslaughter",
"mefitis",
"master",
"massacre",
"martyr",
"malignant-altar",
"malevolent-creation",
"lykathea-aflame",
"lurid-panacea",
"lunar-chamber",
"luciferion",
"lost-harvest",
"last-sacrament",
"lantern",
"krypts",
"kronos",
"kralizec",
"krabathor",
"killing-addiction",
"khthoniik-cerviiks",
"kever",
"karmacipher",
"jumpin'-jesus",
"jordablod",
"intellect-devourer",
"innumerable-forms",
"infester",
"infernal-coil",
"inculter",
"incantation",
"inanna",
"imprecation",
"imperial-triumphant",
"impaler",
"immortalis",
"immolation",
"ignivomous",
"hypocrisy",
"hyperdontia",
"howls-of-ebb",
"howls-of-ebb",
"hour-of-penance",
"horror-god",
"hooded-menace",
"hissing",
"hideous-divinity",
"heaving-earth",
"haunter",
"hail-of-bullets",
"gutvoid",
"gutter-instinct",
"grenadier",
"graveyard",
"graveside",
"grave-ritual",
"grave-miasma",
"grave-infestation",
"grave",
"gosudar",
"gorguts",
"gorephilia",
"gorefest",
"golgothan-remains",
"gold-spire",
"god-macabre",
"glacial-tomb",
"gigan",
"ghoulgotha",
"ghastly",
"gateway",
"garroted",
"galvanizer",
"funeral-leech",
"funebre",
"funebrarum",
"fuming-mouth",
"frozen-soul",
"flourishing",
"fleshgod-apocalypse",
"fleshcrawl",
"fetid",
"faceless-burial",
"exlimitir",
"execration",
"excruciate",
"evilyn",
"eucharist",
"eternal-dirge",
"epitaph",
"entombed",
"engulfed",
"encoffination",
"encenathrakh",
"emptiness",
"electrocution",
"egregore",
"edge-of-sanity",
"dream-unending",
"dragged-into-sunlight",
"dormant-ordeal",
"dominus-xul",
"domains",
"dismember",
"disma",
"diskord",
"disincarnate",
"disharmonic-orchestra",
"disembarkation",
"disciples-of-mockery",
"disastrous-murmur",
"dig-that-body-up,-it's-alive!",
"devoid-of-thought",
"deteriorate",
"desultory",
"desolation-realm",
"desecresy",
"depravity",
"demilich",
"demigod",
"demented-ted",
"deiquisitor",
"deicide",
"degial",
"defeated-sanity",
"defacement",
"deceased",
"decaying-purity",
"death",
"dead-congregation",
"darkthrone",
"cryptworm",
"cryptopsy",
"cryptic-shift",
"cryptborn",
"crypt-of-kerberos",
"cruciamentum",
"cosmovore",
"cosmic-putrefaction",
"cosmic-atrophy",
"coscradh",
"corpsessed",
"convulsing",
"convulse",
"contaminated",
"concrete-winds",
"comecon",
"coffincraft",
"coffin-texts",
"coffin-curse",
"cianide",
"chthe'ilist",
"the-chasm",
"chapel-of-disease",
"chaotian",
"chainsword",
"cerebral-rot",
"centinex",
"cenotaph",
"cemetery-filth",
"cemetary",
"carnal-tomb",
"carnage",
"carcinoma",
"carcass",
"carbonized",
"cancer",
"cambion",
"cadaveric-fumes",
"cadaver",
"burial-invocation",
"brutality",
"broken-hope",
"borgia",
"bolt-thrower",
"bog-body",
"bloodsoaked-necrovoid",
"bloodbath",
"blood-spore",
"blood-incantation",
"black-curse",
"benediction",
"behemoth",
"bedsore",
"baphomet",
"backyard-mortuary",
"azath",
"autopsy",
"autophagy",
"authorize",
"augury",
"atrocity",
"atheist",
"atemporal",
"ataraxy",
"astriferous",
"asphyx",
"appalling-spawn",
"anatomia",
"amputory",
"amorphis",
"altered-dead",
"altars-ablaze",
"altars",
"altarage",
"agony",
"afflicted",
"aeviterne",
"adversarial",
"adramelech",
"ad-vitam-infernal",
"ad-nauseam",
"acrostichon",
"acephalix",
"accidental-suicide",
"abhorrence"
],
"Classic Rock": [
"yes",
"traveling-wilburys",
"tom-petty-and-the-heartbreakers",
"tom-petty",
"rush",
"pink-floyd",
"king-crimson",
"grave",
"bruce-springsteen"
],
"Alternative": ["year-of-the-rabbit", "failure", "the-cranberries", "bob-mould"],
"Sludge Metal": [
"yautja",
"yashira",
"thou-&-the-body",
"thou-&-mizmor",
"thou-&-emma-ruth-rundle",
"thou",
"sumac",
"primitive-man",
"neurosis",
"kowloon-walled-city",
"keiji-haino-&-sumac",
"inter-arma",
"full-of-hell-&-primitive-man",
"chat-pile",
"acid-bath"
],
"Indie": [
"the-xx",
"will-sheff",
"sufjan-stevens",
"the-stone-roses",
"the-postal-service",
"p.-wolf-and-avi",
"okkervil-river",
"neutral-milk-hotel",
"the-japanese-house",
"iron-&-wine",
"the-head-and-the-heart",
"golden-shoulders",
"frightened-rabbit",
"bright-eyes",
"bon-iver",
"blind-pilot",
"big-red-machine",
"better-oblivion-community-center",
"all-time-quarterback"
],
"Indie Rock": [
"the-wytches",
"wavves-&-cloud-nothings",
"the-war-on-drugs",
"thom-yorke",
"teenage-fanclub",
"rogue-wave",
"the-replacements",
"radiohead",
"radioactivity",
"pixies",
"pedro-the-lion",
"pedro-the-lion",
"pavement",
"okkervil-river",
"nova-mob",
"no",
"the-national",
"matt-berninger",
"marked-men",
"the-kills",
"jay-farrar-and-benjamin-gibbard",
"japandroids",
"interpol",
"hop-along",
"the-halo-benders",
"frances-quinlan",
"el-vy",
"death-cab-for-cutie",
"dredg",
"doug-martsch",
"the-dismemberment-plan",
"dinosaur-jr.",
"death-from-above-1979",
"death-cab-for-cutie",
"death-cab-for-cutie",
"damien-jurado",
"cloud-nothings",
"built-to-spill",
"built-to-spill",
"boygenius",
"atoms-for-peace",
"arcade-fire"
],
"Emo": [
"worst-party-ever",
"the-wonder-years",
"turnover",
"title-fight",
"tigers-jaw",
"taking-back-sunday",
"state-lines",
"spanish-love-songs",
"sincere-engineer",
"seahaven",
"rites-of-spring",
"prince-daddy-&-the-hyena",
"pity-sex",
"pinegrove",
"oso-oso",
"the-newfound-interest-in-connecticut",
"my-chemical-romance",
"michael-cera-palin",
"macseal",
"knuckle-puck",
"joyce-manor",
"jimmy-eat-world",
"jawbreaker",
"the-hotelier",
"hot-mulligan",
"home-is-where",
"have-mercy",
"the-get-up-kids",
"free-throw",
"fiddlehead",
"fall-out-boy",
"drive-like-jehu",
"dogleg",
"charmer",
"can't-swim",
"the-brave-little-abacus",
"brand-new",
"basement",
"balance-and-composure",
"the-ataris",
"anxious",
"american-football",
"alkaline-trio"
],
"Grindcore": [
"wormrot",
"terrorizer",
"rotten-sound",
"regional-justice-center",
"pig-destroyer",
"napalm-death",
"nails",
"knelt-rote",
"full-of-hell-&-merzbow",
"full-of-hell",
"fawn-limbs",
"dephosphorus",
"dead-in-the-dirt",
"cloud-rat",
"cianide",
"caustic-wound",
"carcass",
"the-body-&-full-of-hell"
],
"Shoegaze": [
"whirr",
"the-verve",
"trauma-ray",
"teenage-wrist",
"swervedriver",
"soul-blind",
"slowdive",
"slow-crush",
"ringo-deathstarr",
"ride",
"pale-saints",
"nothing",
"narrow-head",
"my-bloody-valentine",
"m83",
"just-mustard",
"ison",
"hum",
"greet-death",
"gleemer",
"glare",
"glare",
"drop-nineteens",
"deafcult",
"corps-fleur",
"cloakroom",
"chapterhouse",
"catherine-wheel",
"black-wing",
"beach-house",
"autolux",
"asobi-seksu",
"all-under-heaven",
"all-natural-lemon-&-lime-flavors",
"alcest",
"airiel"
],
"Rock": [
"webbed-wing",
"violent-femmes",
"superheaven",
"superbloom",
"sonic-youth",
"the-smashing-pumpkins",
"sebadoh",
"oasis",
"nirvana",
"kim-gordon-&-j-mascis",
"katatonia",
"jimmy-eat-world",
"foo-fighters",
"counting-crows",
"audioslave"
],
"Progressive Black Metal": [
"waste-of-space-orchestra",
"oranssi-pazuzu",
"negură-bunget",
"ivar-bjørnson-&-einar-selvik",
"ihsahn",
"enslaved"
],
"Progressive Metal": [
"virus",
"unexpect",
"ulver",
"sunn-o)))-&-ulver",
"sculptured",
"opeth",
"maudlin-of-the-well",
"mastodon",
"disillusion",
"dan-swanö",
"cynic",
"cult-of-luna",
"anathema"
],
"Progressive Rock": [
"unicorn",
"steven-wilson",
"riverside",
"porcupine-tree",
"nightingale",
"the-mars-volta",
"lunatic-soul"
],
"Post Rock": [
"unconditional-arms",
"this-will-destroy-you",
"sigur-rós",
"red-sparowes",
"of-the-vine",
"mono",
"mogwai",
"long-distance-calling-&-leech",
"long-distance-calling",
"if-these-trees-could-talk",
"godspeed-you!-black-emperor",
"god-is-an-astronaut",
"explosions-in-the-sky",
"caspian"
],
"Hip-Hop": [
"the-uncluded",
"run-the-jewels",
"pusha-t",
"mckinley-dixon",
"malibu-ken",
"kendrick-lamar",
"injury-reserve",
"hail-mary-mallon",
"el-p",
"death-grips",
"chance-the-rapper",
"aesop-rock"
],
"Indie Pop": ["two-door-cinema-club", "the-smiths"],
"Soundtrack": ["trent-reznor-&-atticus-ross", "mogwai"],
"Country": [
"trampled-by-turtles",
"trampled-by-turtles",
"sunday-valley",
"sturgill-simpson",
"johnny-cash",
"hiss-golden-messenger"
],
"Post Hardcore": [
"touché-amoré",
"thrice",
"self-defense-family",
"seahaven",
"rival-schools",
"quicksand",
"pitchfork",
"modern-color",
"militarie-gun",
"la-dispute",
"jawbox",
"hot-snakes",
"hell-is-for-heroes",
"gospel",
"fleshwater",
"envy",
"drug-church",
"drive-like-jehu",
"birds-in-row",
"big-garden",
"at-the-drive-in"
],
"Singer/Songwriter": [
"tom-waits",
"sun-kil-moon",
"soccer-mommy",
"sharon-van-etten",
"ryan-adams",
"phoebe-bridgers",
"kermit-obert",
"j-mascis",
"giles-corey",
"elliott-smith",
"dan-campbell",
"damien-jurado",
"city-and-colour",
"bob-dylan",
"ben-gibbard"
],
"Grind": ["terrorizer"],
"Pop": ["taylor-swift"],
"Post Punk": [
"talking-heads",
"soft-kill",
"shiner",
"savages",
"revenge-sf",
"the-psychedelic-furs",
"protomartyr",
"peter-&-the-test-tube-babies",
"new-order",
"modern-english",
"mission-of-burma",
"joy-division",
"the-jesus-and-mary-chain",
"idles",
"have-a-nice-life",
"hands-of-anne-boleyn",
"echo-&-the-bunnymen",
"damaged-bug",
"the-cure",
"cold-showers",
"the-chameleons",
"centaur"
],
"Jazz": ["sword-&-sandals", "miles-davis", "mahavishnu-orchestra", "bohren-&-der-club-of-gore"],
"Screamo": ["state-faults", "senza", "ostraca", "loma-prieta", "frail-body"],
"Ambient": ["stars-of-the-lid", "eluvium", "the-dead-texan"],
"Post Metal": [
"spotlights",
"russian-circles",
"mouth-of-the-architect",
"mono",
"lantlôs",
"jesu",
"isis",
"holy-fawn",
"envy",
"cult-of-luna"
],
"Noise Rock": ["sonic-youth", "a-place-to-bury-strangers", "pink-&-brown", "metz", "the-drums"],
"Folk": ["simon-&-garfunkel"],
"Thrash Metal": ["sadus", "kreator"],
"Hardcore": ["refused"],
"Metalcore": [
"portrayal-of-guilt",
"loathe",
"hacksaw-to-the-throat",
"glassing",
"fuming-mouth",
"converge-&-chelsea-wolfe",
"converge",
"chat-pile",
"cave-in"
],
"Psychedelic Rock": ["osees", "john-dwyer", "bent-arcana"],
"Industrial": ["nine-inch-nails"],
"Synthpop": ["m83"],
"Electronic": ["lcd-soundsystem", "jesu-/-sun-kil-moon", "jamie-xx", "depeche-mode"],
"Doom Metal": ["katatonia"],
"Industrial Metal": ["godflesh"],
"Alternative Metal": ["deftones"],
"Melodic Death Metal": ["dark-tranquillity", "carcass", "at-the-gates", "arsis"]
}

View file

@ -1,12 +0,0 @@
{
"i.rXXXdmUa6Nme-1689970612847": {
"name": "Sacrificial Blood Oath In The Temple Of K'zadu",
"artist": "Gateway",
"album": "Galgendood",
"art": "https://store-033.blobstore.apple.com/sq-mq-us-033-000002/18/f1/a3/18f1a37a-8c9a-169a-5458-464aea20ce05/image?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230721T202228Z&X-Amz-SignedHeaders=host&X-Amz-Expires=86400&X-Amz-Credential=MKIAU0HKO2RBEAT0UMZS%2F20230721%2Fstore-033%2Fs3%2Faws4_request&X-Amz-Signature=85790600221880597074559ed3674564f17ca3df6634d6fa15496baf7aca5d56",
"url": "https://rateyourmusic.com/search?searchtype=l&searchterm=Galgendood%20Gateway",
"id": "i.rXXXdmUa6Nme",
"playTime": 1689970612847,
"duration": 338808
}
}

View file

@ -1,124 +1,8 @@
const { S3Client, GetObjectCommand, PutObjectCommand } = require('@aws-sdk/client-s3')
const _ = require('lodash')
const artistGenres = require('./json/artist-genres.json')
const mockedMusic = require('./json/mocks/music.json')
const { getReadableData } = require('../utils/aws')
const { getKeyByValue } = require('../utils/arrays')
const { aliasArtist, sanitizeMedia } = require('../utils/media')
const { AssetCache } = require('@11ty/eleventy-fetch')
const { aliasArtist, sanitizeMedia, sortByPlays } = require('../utils/media')
const { titleCase } = require('../utils/grammar')
const getTracksOneHour = (tracks) => {
const TIMER_CEILING = 3600000 // 1 hour
const tracksOneHour = []
let trackIndex = 0
let trackTimer = 0
while (trackTimer < TIMER_CEILING) {
if (!tracks[trackIndex]) return tracksOneHour
trackTimer = trackTimer + parseInt(tracks[trackIndex].duration)
tracksOneHour.push(tracks[trackIndex])
trackIndex++
}
return tracksOneHour
}
const diffTracks = (cache, tracks) => {
const trackCompareSet = Object.values(tracks)
const cacheCompareSet = _.orderBy(Object.values(cache), ['time'], ['desc'])
const diffedTracks = {}
const cacheCompareOneHour = getTracksOneHour(cacheCompareSet)
const comparedTracks = _.differenceWith(trackCompareSet, cacheCompareOneHour, (a, b) =>
_.isEqual(a.id, b.id)
)
for (let i = 0; i < comparedTracks.length; i++)
diffedTracks[`${comparedTracks[i]?.id}-${comparedTracks[i].playTime}`] = comparedTracks[i]
return diffedTracks
}
const formatTracks = (tracks) => {
let formattedTracks = {}
let time = new Date().getTime()
Object.values(tracks).forEach((track) => {
const artistFormatted = titleCase(aliasArtist(track.attributes['artistName']))
const albumFormatted = titleCase(sanitizeMedia(track.attributes['albumName']))
const trackFormatted = sanitizeMedia(track.attributes['name'])
formattedTracks[`${track.id}-${time}`] = {
name: trackFormatted,
artist: artistFormatted,
album: albumFormatted,
art: track.attributes.artwork.url.replace('{w}', '300').replace('{h}', '300'),
url:
track['relationships'] && track['relationships'].albums.data.length > 0
? `https://song.link/${track['relationships'].albums.data.pop().attributes.url}`
: `https://rateyourmusic.com/search?searchtype=l&searchterm=${encodeURI(
albumFormatted
)}%20${encodeURI(artistFormatted)}`,
id: track.id,
playTime: time - parseInt(track.attributes['durationInMillis']),
duration: parseInt(track.attributes['durationInMillis']),
}
})
return formattedTracks
}
const deriveCharts = (tracks) => {
const charts = {
artists: {},
albums: {},
}
const tracksForLastWeek = Object.values(tracks).filter((track) => {
const currentDate = new Date()
const currentDateTime = new Date().getTime()
const lastWeek = new Date(currentDate.setDate(currentDate.getDate() - 7))
const lastWeekDateTime = lastWeek.getTime()
const trackDateTime = new Date(track.playTime).getTime()
return trackDateTime <= currentDateTime && trackDateTime > lastWeekDateTime
})
tracksForLastWeek.forEach((track) => {
if (!charts.artists[track.artist]) {
charts.artists[track.artist] = {
artist: track.artist,
genre: getKeyByValue(artistGenres, track.artist.replace(/\s+/g, '-').toLowerCase()),
url: `https://rateyourmusic.com/search?searchterm=${encodeURI(track.artist)}`,
plays: 1,
}
} else {
charts.artists[track.artist].plays++
}
if (!charts.albums[track.album]) {
charts.albums[track.album] = {
name: track.album,
artist: track.artist,
art: track.art,
url: track.url,
plays: 1,
}
} else {
charts.albums[track.album].plays++
}
})
return charts
}
module.exports = async function () {
const client = new S3Client({
credentials: {
accessKeyId: process.env.ACCESS_KEY_WASABI,
secretAccessKey: process.env.SECRET_KEY_WASABI,
},
endpoint: {
url: 'https://s3.us-west-1.wasabisys.com',
},
region: 'us-west-1',
})
const WASABI_BUCKET = process.env.BUCKET_WASABI
const APPLE_BEARER = process.env.API_BEARER_APPLE_MUSIC
const APPLE_MUSIC_TOKEN = process.env.API_TOKEN_APPLE_MUSIC
const APPLE_TOKEN_RESPONSE = await fetch(process.env.APPLE_RENEW_TOKEN_URL, {
@ -133,15 +17,21 @@ module.exports = async function () {
.then((data) => data.json())
.catch()
const APPLE_TOKEN = APPLE_TOKEN_RESPONSE['music-token']
const asset = new AssetCache('recent_tracks_data')
const PAGE_SIZE = 30
const TIMER_CEILING = 3600000 // 1 hour
let charts
const PAGES = 10
const charts = {
artists: {},
albums: {},
tracks: {},
}
let CURRENT_PAGE = 0
let trackTimer = 0
let res = []
let cachedTracks = mockedMusic
let hasNextPage = true
while (trackTimer < TIMER_CEILING) {
if (asset.isCacheValid('1h')) return await asset.getCachedValue()
while (CURRENT_PAGE < PAGES && hasNextPage) {
const URL = `https://api.music.apple.com/v1/me/recent/played/tracks?limit=${PAGE_SIZE}&offset=${
PAGE_SIZE * CURRENT_PAGE
}&include[songs]=albums&extend=artistUrl`
@ -154,45 +44,54 @@ module.exports = async function () {
})
.then((data) => data.json())
.catch()
tracks.data.forEach((track) => {
trackTimer = trackTimer + parseInt(track.attributes['durationInMillis'])
if (trackTimer >= TIMER_CEILING) return
res.push(track)
})
if (!tracks.next) hasNextPage = false
if (tracks.data.length) res = [...res, ...tracks.data]
CURRENT_PAGE++
}
res.forEach((track) => {
const formattedArtist = titleCase(aliasArtist(track.attributes['artistName']))
const formattedAlbum = titleCase(sanitizeMedia(track.attributes['albumName']))
const formattedTrack = sanitizeMedia(track.attributes['name'])
if (process.env.ELEVENTY_PRODUCTION === 'true') {
const cachedTracksOutput = await client.send(
new GetObjectCommand({
Bucket: WASABI_BUCKET,
Key: 'music.json',
})
)
const cachedTracksData = getReadableData(cachedTracksOutput.Body)
cachedTracks = await cachedTracksData.then((tracks) => JSON.parse(tracks)).catch()
}
if (!charts.artists[formattedArtist]) {
charts.artists[formattedArtist] = {
artist: formattedArtist,
url: `https://rateyourmusic.com/search?searchterm=${encodeURI(formattedArtist)}`,
plays: 1,
}
} else {
charts.artists[formattedArtist].plays++
}
const diffedTracks = diffTracks(cachedTracks, formatTracks(res))
const updatedCache = {
...cachedTracks,
...diffedTracks,
}
charts = deriveCharts(updatedCache)
charts.artists = _.orderBy(Object.values(charts.artists), ['plays'], ['desc']).splice(0, 8)
charts.albums = _.orderBy(Object.values(charts.albums), ['plays'], ['desc']).splice(0, 8)
if (!_.isEmpty(diffedTracks) && process.env.ELEVENTY_PRODUCTION === 'true') {
await client.send(
new PutObjectCommand({
Bucket: WASABI_BUCKET,
Key: 'music.json',
Body: JSON.stringify(updatedCache),
})
)
}
if (!charts.albums[formattedAlbum]) {
charts.albums[formattedAlbum] = {
name: formattedAlbum,
artist: formattedArtist,
art: track.attributes.artwork.url.replace('{w}', '300').replace('{h}', '300'),
url: track['relationships']
? `https://song.link/${track['relationships'].albums.data.pop().attributes.url}`
: `https://rateyourmusic.com/search?searchtype=l&searchterm=${encodeURI(
formattedAlbum
)}%20${encodeURI(formattedArtist)}`,
plays: 1,
}
} else {
charts.albums[formattedAlbum].plays++
}
if (!charts.tracks[formattedTrack]) {
charts.tracks[formattedTrack] = {
name: formattedTrack,
artist: formattedArtist,
plays: 1,
}
} else {
charts.tracks[formattedTrack].plays++
}
})
charts.artists = sortByPlays(charts.artists).splice(0, 8)
charts.albums = sortByPlays(charts.albums).splice(0, 8)
charts.tracks = sortByPlays(charts.tracks).splice(0, 5)
await asset.save(charts, 'json')
return charts
}

View file

@ -11,7 +11,7 @@
<div class="absolute left-1 bottom-2 drop-shadow-md">
<div class="px-1 text-xs font-bold text-white line-clamp-2">{{ artist.artist }}</div>
<div class="px-1 text-xs text-white">
{{ artist.genre }}
{{ artist.plays }} plays
</div>
</div>
{%- capture artistImg %}{{ artist.artist | artist }}{% endcapture -%}

View file

@ -1,3 +0,0 @@
module.exports = {
getKeyByValue: (object, value) => Object.keys(object).find((key) => object[key].includes(value)),
}

View file

@ -29,4 +29,5 @@ module.exports = {
/-\s*(?:single|ep)\s*|(\[|\()(Deluxe Edition|Special Edition|Remastered|Full Dynamic Range Edition|Anniversary Edition)(\]|\))/gi
return media.replace(denyList, '').trim()
},
sortByPlays: (array) => Object.values(array).sort((a, b) => b.plays - a.plays),
}