/**
* bleh, an extension for the music site Last.fm
* Copyright (c) 2024-2026 katelyn and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { settings } from '@/build/config';
import { log } from '@/build/log';
import { auth, page, recent_activity_list, root } from '@/build/page';
import { romanise, sanitise, set_storage } from '@/build/tools';
import { tl, trans } from '@/build/trans';
import {
correct_artist,
correct_item_by_artist,
name_includes,
smart_artists,
smart_title,
} from '@/components/music/lotus';
import { html, render } from 'lighterhtml';
import { redirect } from '@/components/music/music';
import tippy from 'tippy.js';
import { DateTime } from 'luxon';
import { useSettings } from '@/page.tsx';
export function get_activity_list() {
load_activities();
// we want to show in date order from latest to oldest down
// but .reverse() is destructive, so we copy first
let recent_activity_list_r = recent_activity_list;
recent_activity_list_r.reverse();
return recent_activity_list_r;
}
export function render_activity_list() {
load_activities();
let activity_list = html.node`
`;
// we want to show in date order from latest to oldest down
// but .reverse() is destructive, so we copy first
let recent_activity_list_r = recent_activity_list;
recent_activity_list_r.reverse();
recent_activity_list_r.forEach((activity) => {
activity_list.appendChild(render_activity(activity));
});
return activity_list;
}
export function render_activity(activity) {
// type: string,
// involved: [{name: string, type: user | artist | album | track}, sister?: string],
// context: string,
// date: string
const activity_item = html.node`
`;
let involved_text = '';
let tooltip_name;
let tooltip_sister;
activity.involved.forEach((involved) => {
let involved_link;
if (involved.type == 'user') {
involved_link = `${root}user/${involved.name}`;
} else if (involved.type == 'artist') {
involved_link = `${root}music/${redirect()}${
sanitise(involved.name)
}`;
} else if (involved.type == 'album') {
involved_link = `${root}music/${redirect()}${
sanitise(involved.sister)
}/${sanitise(involved.name)}`;
} else if (involved.type == 'track') {
involved_link = `${root}music/${redirect()}${
sanitise(involved.sister)
}/_/${sanitise(involved.name)}`;
} else if (involved.type == 'tag') {
involved_link = `${root}tag/${sanitise(involved.name)}`;
} else if (involved.type == 'bwaa') involved_link = `${root}bwaa`;
else if (involved.type == 'bleh') involved_link = `${root}bleh`;
let name = involved.name;
let sister = involved.sister;
// tooltip
if (
involved.type != 'artist' &&
involved.type != 'user' &&
involved.type != 'tag' &&
involved.type != 'bwaa' &&
involved.type != 'bleh'
) {
tooltip_name = name;
tooltip_sister = sister;
}
if (
involved.type == 'track' && useSettings.get('format_guest_features')
) {
const formatted = name_includes(name, sister);
name = html.node`${
smart_title(formatted.song_title, formatted.song_tags)
}`;
sister = html.node`${
smart_artists(formatted.song_artist, formatted.song_guests)
}`;
tooltip_name = formatted.corrected_title;
tooltip_sister = formatted.song_artist;
} else if (
(involved.type == 'album' || involved.type == 'track') &&
useSettings.get('corrections')
) {
name = romanise(correct_item_by_artist(name, sister));
tooltip_name = name;
sister = romanise(correct_artist(sister));
tooltip_sister = sister;
} else if (
involved.type == 'artist' && useSettings.get('corrections')
) {
name = romanise(correct_artist(name));
}
if (involved_text != '') {
involved_text = html
.node`${involved_text}, ${name}`;
} else {
involved_text = html
.node`${involved_text}${name}`;
}
});
render(
activity_item,
html`
${tl(trans.activity.listing[activity.type])}
${DateTime.fromISO(activity.date).toRelative()}
${involved_text}
`,
);
if (tooltip_name) {
tippy(activity_item.querySelector('.name a'), {
theme: 'name-sister-combo',
content: html.node`
${tooltip_name}
${tooltip_sister}
`,
});
}
return activity_item;
}
export function subscribe_to_events() {
if (!settings.activities || !page.structure.main) return;
let love_track = page.structure.container.querySelectorAll(
`form[action="${root}user/${auth.name}/loved"]:not([data-bleh-subscribed])`,
);
love_track.forEach((form) => {
form.setAttribute('data-bleh-subscribed', 'true');
let track = form.querySelector('[name="track"]')?.getAttribute('value');
let artist = form
?.querySelector('[name="artist"]')
?.getAttribute('value');
if (!track || !artist) return;
artist = correct_artist(artist);
track = correct_item_by_artist(track, artist);
let btn = form.querySelector('button');
btn.addEventListener(
'click',
(event) => {
event.preventDefault();
log('heard', 'event', 'info', event);
let action = btn.getAttribute('data-analytics-action');
if (btn.getAttribute('data-type') == 'love') {
btn.textContent = tl(trans.love_track);
} else if (btn.getAttribute('data-type') == 'bookmark') {
btn.textContent = tl(trans.bookmark_item, {
v: tl(trans[`${page.type}_lower`]),
});
}
register_activity(
action == 'LoveTrack' ? 'love' : 'unlove',
[{ name: track, type: 'track', sister: artist }],
`${root}music/${redirect()}${sanitise(artist)}/_/${
sanitise(track)
}`,
);
},
false,
);
});
let bookmark_item = document.body.querySelectorAll(
`form[action="/music/+bookmarks"]:not([data-bleh-subscribed])`,
);
bookmark_item.forEach((form) => {
form.setAttribute('data-bleh-subscribed', 'true');
let btn = form.querySelector('button');
btn.addEventListener(
'click',
(event) => {
log('heard', 'event', 'info', event);
let action = btn.getAttribute('data-analytics-action');
register_activity(
action.startsWith('Bookmark') ? 'bookmark' : 'unbookmark',
[{ name: page.name, type: page.type, sister: page.sister }],
window.location.href,
);
},
false,
);
});
let obsess = document.body.querySelectorAll(
`.modal-body form[action$="${auth.name}/obsessions"]:not([data-bleh-subscribed])`,
);
obsess.forEach((form) => {
form.setAttribute('data-bleh-subscribed', 'true');
let track = form.querySelector('[name="name"]').getAttribute('value');
let artist = form
.querySelector('[name="artist_name"]')
.getAttribute('value');
artist = correct_artist(artist);
track = correct_item_by_artist(track, artist);
let btn = form.querySelector('button');
btn.addEventListener(
'click',
(event) => {
log('heard', 'event', 'info', event);
register_activity(
'obsess',
[{ name: track, type: 'track', sister: artist }],
window.location.href,
);
},
false,
);
});
const post_shouts = page.structure.main.querySelectorAll(
'.btn-post-shout:not([data-bleh-subscribed])',
);
post_shouts.forEach((post) => {
post.setAttribute('data-bleh-subscribed', 'true');
post.addEventListener(
'click',
(e) => {
log('heard', 'event', 'info', e);
// wait 0.15s
setTimeout(() => {
const is_loading = post.classList.contains('btn--loading');
console.info(is_loading, post.classList);
if (!is_loading) return;
register_activity(
'shout',
[
{
name: page.name,
type: page.type,
sister: page.sister,
},
],
window.location.href,
);
}, 150);
},
false,
);
});
let save_wiki_form = document.body.querySelector(
'.wiki-edit-form:not([data-bleh-subscribed])',
);
if (save_wiki_form) {
save_wiki_form.setAttribute('data-bleh-subscribed', 'true');
let btn = save_wiki_form.querySelector('.form-submit button');
btn.addEventListener(
'click',
(event) => {
log('heard', 'event', 'info', event);
register_activity(
'wiki',
[{ name: page.name, type: page.type, sister: page.sister }],
window.location.href,
);
},
false,
);
}
let upload_img_form = document.body.querySelector(
'form[action$="/+images/upload"]:not([data-bleh-subscribed])',
);
if (upload_img_form) {
upload_img_form.setAttribute('data-bleh-subscribed', 'true');
let btn = upload_img_form.querySelector('.form-submit button');
if (!btn) btn = upload_img_form.querySelector('button[type="submit"]');
if (btn) {
btn.addEventListener(
'click',
(event) => {
log('heard', 'event', 'info', event);
register_activity(
'image_upload',
[
{
name: page.name,
type: page.type,
sister: page.sister,
},
],
window.location.href,
);
},
false,
);
}
}
}
export function load_activities() {
if (!settings.activities) return;
recent_activity_list.length = 0;
recent_activity_list.push(
...(JSON.parse(localStorage.getItem('bwaa_recent_activity')) || []),
);
log('loaded', 'activity', 'info', recent_activity_list);
// check if over 10
check_activities_length();
log('saved', 'activity', 'info', recent_activity_list);
set_storage('bwaa_recent_activity', JSON.stringify(recent_activity_list));
}
function check_activities_length() {
if (recent_activity_list.length > 10) {
let to_delete = recent_activity_list.length - 10;
recent_activity_list.splice(0, to_delete);
log(`reached maximum of 10, removed leftovers`, 'activity');
}
return recent_activity_list;
}
export function register_activity(type, involved, context, date = new Date()) {
if (!settings.activities) return;
if (type == 'shout') {
if (!settings.activity_shout) return;
} else if (type == 'image_upload' || type == 'image_star') {
if (!settings.activity_image) return;
} else if (type == 'obsess' || type == 'unobsess') {
if (!settings.activity_obsess) return;
} else if (type == 'love' || type == 'unlove') {
if (!settings.activity_love) return;
} else if (type == 'bookmark' || type == 'unbookmark') {
if (!settings.activity_bookmark) return;
} else if (
type == 'install_bwaa' ||
type == 'update_bwaa' ||
type == 'install_bleh' ||
type == 'update_bleh'
) {
if (!settings.activity_install) return;
} else if (type == 'wiki') {
if (!settings.wiki) return;
}
recent_activity_list.length = 0;
recent_activity_list.push(
...(JSON.parse(localStorage.getItem('bwaa_recent_activity')) || []),
);
log('loaded', 'activity', 'info', recent_activity_list);
recent_activity_list.push({
type: type,
involved: involved,
context: context,
date: date,
});
log('registered new', 'activity', 'info', {
type: type,
involved: involved,
context: context,
date: date,
});
// check if over 10
check_activities_length();
log('saved', 'activity', 'info', recent_activity_list);
set_storage('bwaa_recent_activity', JSON.stringify(recent_activity_list));
}