289 lines
14 KiB
TypeScript
289 lines
14 KiB
TypeScript
// SPDX-FileCopyrightText: 2024 John Livingston <https://www.john-livingston.fr/>
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import type { RegisterClientOptions } from '@peertube/peertube-types/client'
|
|
import type { Video } from '@peertube/peertube-types'
|
|
import type { ProsodyListRoomsResult } from 'shared/lib/types'
|
|
import { getBaseRoute } from './utils/uri'
|
|
|
|
interface ActionPluginSettingsParams {
|
|
npmName: string
|
|
}
|
|
|
|
function register (clientOptions: RegisterClientOptions): void {
|
|
const { registerHook, registerSettingsScript, peertubeHelpers } = clientOptions
|
|
|
|
registerHook({
|
|
target: 'action:admin-plugin-settings.init',
|
|
handler: ({ npmName }: ActionPluginSettingsParams) => {
|
|
if (npmName !== PLUGIN_CHAT_PACKAGE_NAME) {
|
|
console.log(`[peertube-plugin-livechat] Settings for ${npmName}, not ${PLUGIN_CHAT_PACKAGE_NAME}. Returning.`)
|
|
return
|
|
}
|
|
console.log('[peertube-plugin-livechat] Initializing diagnostic button')
|
|
const diagButtons = document.querySelectorAll('.peertube-plugin-livechat-launch-diagnostic')
|
|
diagButtons.forEach(diagButton => {
|
|
if (diagButton.hasAttribute('href')) { return }
|
|
// TODO: use a modal instead of a target=_blank
|
|
diagButton.setAttribute('href', getBaseRoute(clientOptions) + '/settings/diagnostic')
|
|
diagButton.setAttribute('target', '_blank')
|
|
})
|
|
console.log('[peertube-plugin-livechat] Initializing prosody-list-rooms button')
|
|
const listRoomsButtons: NodeListOf<HTMLAnchorElement> =
|
|
document.querySelectorAll('.peertube-plugin-livechat-prosody-list-rooms-btn')
|
|
|
|
try {
|
|
// Trying to copy Computed CSS for an input[type=submit] to the list rooms button
|
|
const tmpButton = document.querySelector('#content input[type=submit]')
|
|
if (window.getComputedStyle && tmpButton) {
|
|
const styles = window.getComputedStyle(tmpButton)
|
|
// Firerox has a bug: styles.cssText always returns "". https://bugzilla.mozilla.org/show_bug.cgi?id=137687
|
|
if (styles.cssText !== '') {
|
|
listRoomsButtons.forEach(listRoomsButton => { listRoomsButton.style.cssText = styles.cssText })
|
|
} else {
|
|
const cssText = Object.values(styles).reduce(
|
|
(css, propertyName) => `${css}${propertyName}:${styles.getPropertyValue(propertyName)};`
|
|
)
|
|
listRoomsButtons.forEach(listRoomsButton => { listRoomsButton.style.cssText = cssText })
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('[peertube-plugin-livechat] Failed applying styles on the «list rooms» button.', err)
|
|
}
|
|
|
|
listRoomsButtons.forEach(listRoomsButton => {
|
|
if (listRoomsButton.classList.contains('peertube-plugin-livechat-prosody-list-rooms-btn-binded')) { return }
|
|
listRoomsButton.classList.add('peertube-plugin-livechat-prosody-list-rooms-btn-binded')
|
|
listRoomsButton.onclick = async (): Promise<void> => {
|
|
console.log('[peertube-plugin-livechat] Opening the room list...')
|
|
// TODO: use showModal (seems buggy with Peertube 3.2.1)
|
|
|
|
try {
|
|
document.querySelectorAll('.peertube-plugin-livechat-prosody-list-rooms-content')
|
|
.forEach(dom => dom.remove())
|
|
const container = document.createElement('div')
|
|
container.classList.add('peertube-plugin-livechat-prosody-list-rooms-content')
|
|
container.textContent = '...'
|
|
listRoomsButton.after(container)
|
|
|
|
const response = await fetch(getBaseRoute(clientOptions) + '/webchat/prosody-list-rooms', {
|
|
method: 'GET',
|
|
headers: peertubeHelpers.getAuthHeader()
|
|
})
|
|
if (!response.ok) {
|
|
throw new Error('Response is not ok')
|
|
}
|
|
const settings = await peertubeHelpers.getSettings()
|
|
const useChannelConfiguration = !(settings['disable-channel-configuration'])
|
|
const json: ProsodyListRoomsResult = await response.json()
|
|
if (!json.ok) {
|
|
container.textContent = json.error
|
|
container.classList.add('peertube-plugin-livechat-error')
|
|
} else {
|
|
const rooms = json.rooms.sort((a, b) => {
|
|
const timestampA = a.lasttimestamp ?? 0
|
|
const timestampB = b.lasttimestamp ?? 0
|
|
if (timestampA === timestampB) {
|
|
return a.name.localeCompare(b.name)
|
|
} else if (timestampA < timestampB) {
|
|
return 1
|
|
} else {
|
|
return -1
|
|
}
|
|
})
|
|
|
|
container.textContent = ''
|
|
const table = document.createElement('table')
|
|
table.classList.add('peertube-plugin-livechat-prosody-list-rooms')
|
|
container.append(table)
|
|
const labels: any = {
|
|
RoomName: await peertubeHelpers.translate(LOC_ROOM_NAME),
|
|
RoomDescription: await peertubeHelpers.translate(LOC_ROOM_DESCRIPTION),
|
|
NotFound: await peertubeHelpers.translate(LOC_NOT_FOUND),
|
|
Video: await peertubeHelpers.translate(LOC_VIDEO),
|
|
Channel: await peertubeHelpers.translate(LOC_CHANNEL),
|
|
LastActivity: await peertubeHelpers.translate(LOC_LAST_ACTIVITY),
|
|
channelConfiguration: await peertubeHelpers.translate(LOC_LIVECHAT_CONFIGURATION_CHANNEL_TITLE)
|
|
}
|
|
|
|
const titleLineEl = document.createElement('tr')
|
|
const titleNameEl = document.createElement('th')
|
|
titleNameEl.textContent = labels.RoomName
|
|
const titleDescriptionEl = document.createElement('th')
|
|
titleDescriptionEl.textContent = labels.RoomDescription
|
|
const titleVideoEl = document.createElement('th')
|
|
titleVideoEl.textContent = `${labels.Video as string} / ${labels.Channel as string}`
|
|
const titleLastActivityEl = document.createElement('th')
|
|
titleLastActivityEl.textContent = labels.LastActivity
|
|
titleLineEl.append(titleNameEl)
|
|
titleLineEl.append(titleDescriptionEl)
|
|
titleLineEl.append(titleVideoEl)
|
|
titleLineEl.append(titleLastActivityEl)
|
|
if (useChannelConfiguration) {
|
|
const titleChannelConfiguration = document.createElement('th')
|
|
titleChannelConfiguration.textContent = labels.channelConfiguration
|
|
titleLineEl.append(titleChannelConfiguration)
|
|
}
|
|
titleLineEl.append(document.createElement('th'))
|
|
table.append(titleLineEl)
|
|
rooms.forEach(room => {
|
|
const localpart = room.localpart
|
|
const lineEl = document.createElement('tr')
|
|
const nameEl = document.createElement('td')
|
|
const aEl = document.createElement('a')
|
|
aEl.textContent = room.name
|
|
aEl.target = '_blank'
|
|
const descriptionEl = document.createElement('td')
|
|
descriptionEl.textContent = room.description
|
|
const videoEl = document.createElement('td')
|
|
const lastActivityEl = document.createElement('td')
|
|
if (room.lasttimestamp && (typeof room.lasttimestamp === 'number')) {
|
|
const date = new Date(room.lasttimestamp * 1000)
|
|
lastActivityEl.textContent = date.toLocaleDateString() + ' ' + date.toLocaleTimeString()
|
|
}
|
|
const promoteButton = document.createElement('a')
|
|
promoteButton.classList.add('orange-button', 'peertube-button-link')
|
|
promoteButton.style.margin = '5px'
|
|
promoteButton.onclick = async () => {
|
|
await fetch(
|
|
getBaseRoute(clientOptions) + '/api/promote/' + encodeURIComponent(room.jid.replace(/@.*$/, '')),
|
|
{
|
|
method: 'PUT',
|
|
headers: peertubeHelpers.getAuthHeader()
|
|
}
|
|
)
|
|
}
|
|
// FIXME: we can use promoteSVG, which is in client scope...
|
|
promoteButton.innerHTML = `<svg
|
|
xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 4.233 4.233"
|
|
>
|
|
<g style="stroke-width:1.00021;stroke-miterlimit:4;stroke-dasharray:none">
|
|
<path
|
|
style="opacity:.998;fill:currentColor;fill-opacity:1;stroke:currentColor;stroke-width:1.17052;
|
|
stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
|
d="M2.403.583V-.19a.94.94 0 0 1 .942-.943l1.962-.023 1.961.01a.94.94 0 0 1
|
|
.942.942v.772S6.586 4.9 5.307 4.92C4.027 4.939 2.403.583 2.403.583Z"
|
|
transform="matrix(.45208 0 0 .45208 -.526 1.335)"
|
|
/>
|
|
</g>
|
|
</svg>`
|
|
const promoteEl = document.createElement('td')
|
|
promoteEl.append(promoteButton)
|
|
|
|
const channelConfigurationEl = document.createElement('td')
|
|
nameEl.append(aEl)
|
|
lineEl.append(nameEl)
|
|
lineEl.append(descriptionEl)
|
|
lineEl.append(videoEl)
|
|
lineEl.append(lastActivityEl)
|
|
if (useChannelConfiguration) {
|
|
lineEl.append(channelConfigurationEl) // else the element will just be dropped.
|
|
}
|
|
lineEl.append(promoteEl)
|
|
table.append(lineEl)
|
|
|
|
const writeChannelConfigurationLink = (channelId: number | string): void => {
|
|
const a = document.createElement('a')
|
|
a.href = '/p/livechat/configuration/channel?channelId=' + encodeURIComponent(channelId)
|
|
a.textContent = labels.channelConfiguration
|
|
channelConfigurationEl.append(a)
|
|
}
|
|
|
|
const channelMatches = localpart.match(/^channel\.(\d+)$/)
|
|
if (channelMatches) {
|
|
// Here we have a channel chat room
|
|
// The backend should have added informations here
|
|
// (because the Peertube API can't work with channelId...)
|
|
const href = '/p/livechat/room?room=' + encodeURIComponent(localpart) + '&forcetype=1'
|
|
if (room.channel?.name) {
|
|
aEl.href = href // here we know that the channel still exists, so we can open the webchat.
|
|
const aVideoEl = document.createElement('a')
|
|
aVideoEl.textContent = room.channel?.displayName ?? '-'
|
|
aVideoEl.target = '_blank'
|
|
aVideoEl.href = '/video-channels/' + room.channel.name
|
|
videoEl.append(aVideoEl)
|
|
}
|
|
if (room.channel?.id) {
|
|
writeChannelConfigurationLink(room.channel.id)
|
|
}
|
|
} else if (/^[a-zA-A0-9-]+$/.test(localpart)) {
|
|
// localpart must be a video uuid.
|
|
const uuid = localpart
|
|
const href = '/p/livechat/room?room=' + encodeURIComponent(uuid) + '&forcetype=1'
|
|
const p = fetch('/api/v1/videos/' + uuid, {
|
|
method: 'GET',
|
|
headers: peertubeHelpers.getAuthHeader()
|
|
})
|
|
p.then(async res => {
|
|
if (!res.ok) {
|
|
videoEl.textContent = labels.NotFound
|
|
return
|
|
}
|
|
const video: Video | undefined = await res.json()
|
|
if (!video) {
|
|
videoEl.textContent = labels.NotFound
|
|
return
|
|
}
|
|
|
|
aEl.href = href
|
|
const aVideoEl = document.createElement('a')
|
|
aVideoEl.textContent = video.name
|
|
aVideoEl.target = '_blank'
|
|
aVideoEl.href = '/videos/watch/' + uuid
|
|
videoEl.append(aVideoEl)
|
|
if (video.channel.id) {
|
|
writeChannelConfigurationLink(video.channel.id)
|
|
}
|
|
}, () => {
|
|
console.error('[peertube-plugin-livechat] Failed to retrieve video ' + uuid)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error(error)
|
|
peertubeHelpers.notifier.error('Room list failed')
|
|
}
|
|
}
|
|
})
|
|
}
|
|
})
|
|
registerSettingsScript({
|
|
isSettingHidden: options => {
|
|
const name = options.setting.name
|
|
switch (name) {
|
|
case 'prosody-c2s-port':
|
|
return options.formValues['prosody-c2s'] !== true
|
|
case 'prosody-s2s-port':
|
|
case 'prosody-s2s-interfaces':
|
|
case 'prosody-certificates-dir':
|
|
return options.formValues['prosody-room-allow-s2s'] !== true
|
|
case 'prosody-components-port':
|
|
case 'prosody-components-interfaces':
|
|
case 'prosody-components-list':
|
|
return options.formValues['prosody-components'] !== true
|
|
case 'converse-autocolors':
|
|
return options.formValues['converse-theme'] !== 'peertube'
|
|
case 'chat-per-live-video-warning':
|
|
return !(options.formValues['chat-all-lives'] === true && options.formValues['chat-per-live-video'] === true)
|
|
case 'auto-ban-anonymous-ip':
|
|
return options.formValues['chat-no-anonymous'] !== false
|
|
}
|
|
|
|
if (name?.startsWith('external-auth-')) {
|
|
const m = name.match(/^external-auth-(\w+)-oidc-/)
|
|
if (m) {
|
|
return options.formValues['external-auth-' + m[1] + '-oidc'] !== true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
})
|
|
}
|
|
|
|
export {
|
|
register
|
|
}
|