Skip to content

Commit

Permalink
Fixes AzuraCast#6362 -- Provide DJ display name in Liquidsoap for scr…
Browse files Browse the repository at this point in the history
…ipting purposes, move DJ username/display name to API response and move djon/djoff to low-priority thread calls.
  • Loading branch information
BusterNeece committed Nov 27, 2024
1 parent 9c49bfc commit beec9f4
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 66 deletions.
33 changes: 0 additions & 33 deletions backend/src/Entity/Repository/StationStreamerRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
use App\Entity\StationStreamerBroadcast;
use App\Flysystem\StationFilesystems;
use App\Media\AlbumArt;
use App\Radio\AutoDJ\Scheduler;

/**
* @extends AbstractStationBasedRepository<StationStreamer>
Expand All @@ -19,42 +18,10 @@ final class StationStreamerRepository extends AbstractStationBasedRepository
protected string $entityClass = StationStreamer::class;

public function __construct(
private readonly Scheduler $scheduler,
private readonly StationStreamerBroadcastRepository $broadcastRepo
) {
}

/**
* Attempt to authenticate a streamer.
*
* @param Station $station
* @param string $username
* @param string $password
*/
public function authenticate(
Station $station,
string $username = '',
string $password = ''
): bool {
// Extra safety check for the station's streamer status.
if (!$station->getEnableStreamers()) {
return false;
}

// Allow connections using the exact broadcast source password.
$sourcePw = $station->getFrontendConfig()->getSourcePassword();
if (!empty($sourcePw) && strcmp($sourcePw, $password) === 0) {
return true;
}

$streamer = $this->getStreamer($station, $username);
if (!($streamer instanceof StationStreamer)) {
return false;
}

return $streamer->authenticate($password) && $this->scheduler->canStreamerStreamNow($streamer);
}

public function onConnect(Station $station, string $username = ''): bool
{
// End all current streamer sessions.
Expand Down
59 changes: 56 additions & 3 deletions backend/src/Radio/Backend/Liquidsoap/Command/DjAuthCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

use App\Entity\Repository\StationStreamerRepository;
use App\Entity\Station;
use App\Radio\AutoDJ\Scheduler;
use App\Utilities\Types;
use InvalidArgumentException;
use RuntimeException;

final class DjAuthCommand extends AbstractCommand
{
public function __construct(
private readonly StationStreamerRepository $streamerRepo,
private readonly Scheduler $scheduler,
) {
}

Expand All @@ -24,11 +28,60 @@ protected function doRun(
throw new RuntimeException('Streamers are disabled on this station.');
}

$user = $payload['user'] ?? '';
$pass = $payload['password'] ?? '';
[$user, $pass] = $this->getCredentials($payload);

// Allow connections using the exact broadcast source password.
if ('source' === $user) {
$sourcePw = $station->getFrontendConfig()->getSourcePassword();

if (!empty($sourcePw) && strcmp($sourcePw, $pass) === 0) {
return [
'allow' => true,
'username' => $user,
];
}
}

$streamer = $this->streamerRepo->getStreamer($station, $user);

if (null === $streamer) {
return [
'allow' => false,
];
}

return [
'allow' => $this->streamerRepo->authenticate($station, $user, $pass),
'allow' => $streamer->authenticate($pass) && $this->scheduler->canStreamerStreamNow($streamer),
'username' => $streamer->getStreamerUsername(),
'display_name' => $streamer->getDisplayName(),
];
}

/**
* @return array{string, string}
*/
private function getCredentials(array $payload = []): array
{
$user = Types::stringOrNull($payload['user'] ?? null, true);
$pass = Types::stringOrNull($payload['password'] ?? null, true);

if (null === $pass) {
throw new InvalidArgumentException('No credentials provided!');
}

if (null === $user || 'source' === $user) {
foreach ([',', ':'] as $separator) {
if (str_contains($pass, $separator)) {
[$user, $pass] = explode($separator, $pass, 2);
return [$user, $pass];
}
}
}

if (null === $user) {
throw new InvalidArgumentException('No credentials provided!');
}

return [$user, $pass];
}
}
77 changes: 47 additions & 30 deletions util/docker/stations/liquidsoap/azuracast.liq
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,13 @@ let azuracast.autodj_is_loading = ref(true)
# Number of attempts that have been made to ping the AutoDJ for the next song.
let azuracast.autodj_ping_attempts = ref(0)

# The username of the last authenticated DJ.
# The username/display name of the last authenticated DJ.
let azuracast.last_authenticated_dj = ref("")
let azuracast.last_authenticated_dj_name = ref("")

# The username of the current live DJ.
# The username/display name of the current live DJ.
let azuracast.live_dj = ref("")
let azuracast.live_dj_name = ref("")

# Whether Liquidsoap is currently transitioning to a live stream.
let azuracast.to_live = ref(false)
Expand Down Expand Up @@ -440,17 +442,7 @@ Live Streamers/DJs
----->#

# DJ Authentication
def azuracast.dj_auth(login) =
sep_re = r/([,:])/
auth_info =
if (login.user == "source" or login.user == "") and (sep_re.test(login.password)) then
sep = sep_re.exec(login.password)
let (u, p) = string.split.first(separator=sep[0], login.password)
{user = u, password = p}
else
{user = login.user, password = login.password}
end

def azuracast.dj_auth(auth_info) =
try
api_response = azuracast.api_call(
timeout=5.0,
Expand All @@ -461,14 +453,23 @@ def azuracast.dj_auth(login) =
if null.defined(api_response) then
let json.parse (
{
allow
allow,
username,
display_name,
} : {
allow: bool
allow: bool,
username: string?,
display_name: string?,
}
) = null.get(api_response)

if allow then
azuracast.last_authenticated_dj := auth_info.user
azuracast.last_authenticated_dj := null.get(default='', username)
azuracast.last_authenticated_dj_name := null.get(
default=settings.azuracast.live_broadcast_text(),
display_name
)

true
else
false
Expand All @@ -489,32 +490,48 @@ end

def azuracast.live_connected(header) =
dj = azuracast.last_authenticated_dj()
dj_name = azuracast.last_authenticated_dj_name()

log(
level=2,
label="azuracast.dj",
"DJ Source connected! Last authenticated DJ: #{dj} - #{header}"
"DJ Source connected! Last authenticated DJ: #{dj} (#{dj_name}) - #{header}"
)

azuracast.live_enabled := true
azuracast.live_dj := dj
azuracast.live_dj_name := dj_name

_ = azuracast.api_call(
timeout=5.0,
"djon",
json.stringify({user = dj})
)
def f() =
_ = azuracast.api_call(
timeout=5.0,
"djon",
json.stringify({
user = azuracast.live_dj()
})
)
end

thread.run(fast=false, f)
end

def azuracast.live_disconnected() =
_ = azuracast.api_call(
timeout=5.0,
"djoff",
json.stringify({user = azuracast.live_dj()})
)

azuracast.live_enabled := false
azuracast.live_dj := ""

def f() =
_ = azuracast.api_call(
timeout=5.0,
"djoff",
json.stringify({
user = azuracast.live_dj()
})
)

azuracast.live_dj := ""
azuracast.live_dj_name := ""
end

thread.run(fast=false, f)
end

#<-----
Expand Down Expand Up @@ -605,7 +622,7 @@ def azuracast.send_feedback(m) =
end
end

thread.run(f)
thread.run(fast=false, f)
end

#<-----
Expand Down

0 comments on commit beec9f4

Please sign in to comment.