'', 'api_key' => '', 'broadcast_prefix' => '[Broadcast]', 'broadcast_prefix_color' => '&c', 'broadcast_bracket_color' => '&8', // Neu: Standard Dunkelgrau 'broadcast_message_color' => '&f', ]; add_option(self::OPTION_KEY, $defaults); } if (!get_option(self::SCHEDULES_KEY)) { add_option(self::SCHEDULES_KEY, []); } } public function deactivate() { $schedules = get_option(self::SCHEDULES_KEY, []); foreach ($schedules as $id => $s) { $next = wp_next_scheduled(self::CRON_HOOK, [$id]); if ($next !== false) { wp_unschedule_event($next, self::CRON_HOOK, [$id]); } } } public function add_weekly_cron($s) { if (!isset($s['weekly'])) { $s['weekly'] = ['interval' => 7*24*60*60, 'display' => __('Weekly')]; } return $s; } public function admin_menu() { add_menu_page('PulseCast', 'PulseCast', 'manage_options', 'pulsecast', [$this, 'page_main'], 'dashicons-megaphone', 55); } public function page_main() { if (!current_user_can('manage_options')) wp_die('Keine Berechtigung.'); $settings = get_option(self::OPTION_KEY, []); $schedules = get_option(self::SCHEDULES_KEY, []); // Feedback Nachrichten if (isset($_GET['pulsecast_status'])) { echo '

' . esc_html($_GET['pulsecast_status']) . '

'; } if (isset($_GET['pulsecast_error'])) { echo '

' . esc_html($_GET['pulsecast_error']) . '

'; } $current_server_time = current_time('Y-m-d H:i:s'); $current_utc_time = gmdate('Y-m-d H:i:s'); ?>

PulseCast — Broadcasts

Aktuelle Zeit (Server-Zeitzone):

Aktuelle Zeit (UTC):

Zeitgeplante Broadcasts werden in UTC an die StatusAPI gesendet.

Einstellungen

Gib die Serveradresse an — Port (9191) und Pfad (/broadcast) werden automatisch ergänzt.

Beispiel: Broadcast oder [Broadcast]. Die Klammern werden bei Bedarf automatisch mit der Klammer-Farbe eingefärbt.

Farbe für den Text INNENHALB der Klammern (z. B. &c Rot).

Farbe für die Klammern [ ] (z. B. &8 Dunkelgrau). Leer lassen für Standardfarbe.

Farbcodes mit & (z. B. &f).


Sofortiger Broadcast


Geplante Broadcasts (serverseitig)

Geplante Broadcasts verwenden automatisch die Standard-Einstellungen für Prefix und Farben.

Beispiel: (in einer Stunde)

Aktuelle Zeitgeplante Nachrichten

Keine geplanten Broadcasts.

$s): $local_time = get_date_from_gmt(gmdate('Y-m-d H:i:s', $s['time']), 'Y-m-d H:i:s'); $utc_time = gmdate('Y-m-d H:i:s', $s['time']); $now = time(); $is_past = $s['time'] <= $now; $status = $is_past ? '⚠️ Verpasst/Verarbeitet' : '⏰ Geplant'; ?>
IDNachrichtSendezeit (Lokal)Sendezeit (UTC)StatusWiederholungAktionen

Hinweis: Alle Broadcasts werden global gesendet.

send_to_api($message, $type, $prefix, $prefix_color, $bracket_color, $message_color); $code = is_wp_error($res) ? 'error' : 'ok'; wp_redirect(add_query_arg('pulsecast_status', $code, wp_get_referer())); exit; } public function handle_schedule_post() { if (!current_user_can('manage_options')) wp_die('Forbidden'); if (isset($_POST['_wpnonce']) && wp_verify_nonce($_POST['_wpnonce'], 'pulsecast_save_settings')) { check_admin_referer('pulsecast_save_settings'); $settings = get_option(self::OPTION_KEY, []); $settings['api_url'] = esc_url_raw($_POST['api_url'] ?? ''); $settings['api_key'] = sanitize_text_field($_POST['api_key'] ?? ''); $settings['broadcast_prefix'] = sanitize_text_field($_POST['broadcast_prefix'] ?? '[Broadcast]'); $settings['broadcast_prefix_color'] = sanitize_text_field($_POST['broadcast_prefix_color'] ?? '&c'); $settings['broadcast_bracket_color'] = sanitize_text_field($_POST['broadcast_bracket_color'] ?? '&8'); // Neu $settings['broadcast_message_color'] = sanitize_text_field($_POST['broadcast_message_color'] ?? '&f'); update_option(self::OPTION_KEY, $settings); wp_redirect(add_query_arg('pulsecast_status', 'settings_saved', wp_get_referer())); exit; } check_admin_referer('pulsecast_schedule_new'); $message = sanitize_textarea_field($_POST['sched_message'] ?? ''); $timeRaw = sanitize_text_field($_POST['sched_time'] ?? ''); $recur = sanitize_text_field($_POST['sched_recur'] ?? 'none'); $type = 'global'; $settings = get_option(self::OPTION_KEY, []); $prefix = sanitize_text_field($_POST['sched_prefix'] ?? ''); $prefix_color = sanitize_text_field($_POST['sched_prefix_color'] ?? ''); $bracket_color = sanitize_text_field($_POST['sched_bracket_color'] ?? ''); // Neu $message_color = sanitize_text_field($_POST['sched_message_color'] ?? ''); if (empty($prefix)) $prefix = $settings['broadcast_prefix'] ?? '[Broadcast]'; if (empty($prefix_color)) $prefix_color = $settings['broadcast_prefix_color'] ?? '&c'; if (empty($bracket_color)) $bracket_color = $settings['broadcast_bracket_color'] ?? '&8'; // Neu if (empty($message_color)) $message_color = $settings['broadcast_message_color'] ?? '&f'; if (empty($message) || empty($timeRaw)) { wp_redirect(add_query_arg('pulsecast_error', 'empty_fields', wp_get_referer())); exit; } $timeRaw = str_replace('T', ' ', $timeRaw); $local_timestamp = strtotime($timeRaw); if ($local_timestamp === false || $local_timestamp <= 0) { wp_redirect(add_query_arg('pulsecast_error', 'bad_time', wp_get_referer())); exit; } $utc_datetime = get_gmt_from_date(date('Y-m-d H:i:s', $local_timestamp), 'Y-m-d H:i:s'); $timestamp_utc = strtotime($utc_datetime . ' UTC'); $schedules = get_option(self::SCHEDULES_KEY, []); $id = (string) time() . rand(1000,9999); $schedules[$id] = [ 'message' => $message, 'time' => $timestamp_utc, 'recur' => $recur, 'type' => $type, 'prefix' => $prefix, 'prefix_color' => $prefix_color, 'bracket_color' => $bracket_color, // Neu 'message_color' => $message_color, ]; update_option(self::SCHEDULES_KEY, $schedules); $sent = $this->send_schedule_to_api($id, $schedules[$id]); if (is_wp_error($sent)) { wp_redirect(add_query_arg('pulsecast_error', 'register_failed', wp_get_referer())); } else { wp_redirect(add_query_arg('pulsecast_status', 'scheduled_server', wp_get_referer())); } exit; } public function handle_resync_post() { if (!current_user_can('manage_options')) wp_die('Forbidden'); check_admin_referer('pulsecast_resync_action'); $schedules = get_option(self::SCHEDULES_KEY, []); $count = 0; $errors = 0; foreach ($schedules as $id => $s) { $res = $this->send_schedule_to_api($id, $s); if (is_wp_error($res)) { $errors++; } else { $count++; } } $msg = "$count Broadcasts synchronisiert."; if ($errors > 0) { $msg .= " $errors Fehler sind aufgetreten."; } wp_redirect(add_query_arg('pulsecast_status', $msg, wp_get_referer())); exit; } public function handle_delete_schedule() { if (!current_user_can('manage_options')) wp_die('Forbidden'); $id = sanitize_text_field($_POST['id'] ?? ''); check_admin_referer('pulsecast_delete_schedule_'.$id); $schedules = get_option(self::SCHEDULES_KEY, []); if (isset($schedules[$id])) { $this->send_cancel_schedule_to_api($id); unset($schedules[$id]); update_option(self::SCHEDULES_KEY, $schedules); } wp_redirect(remove_query_arg(['pulsecast_error','pulsecast_status'], wp_get_referer())); exit; } public function cron_send_broadcast($id) { $schedules = get_option(self::SCHEDULES_KEY, []); if (!isset($schedules[$id])) return; $s = $schedules[$id]; $res = $this->send_to_api( $s['message'], $s['type'] ?? 'global', $s['prefix'] ?? '', $s['prefix_color'] ?? '', $s['bracket_color'] ?? '', // Neu $s['message_color'] ?? '' ); $log = get_option(self::OPTION_KEY . '_last_logs', []); $entry = [ 'time' => time(), 'id' => $id, 'result' => is_wp_error($res) ? $res->get_error_message() : 'ok' ]; $log[] = $entry; if (count($log) > 50) array_shift($log); update_option(self::OPTION_KEY . '_last_logs', $log); if (($s['recur'] ?? 'none') !== 'none') { $next = $this->compute_next_timestamp($s['time'], $s['recur']); if ($next !== null) { $schedules[$id]['time'] = $next; update_option(self::SCHEDULES_KEY, $schedules); } } else { unset($schedules[$id]); update_option(self::SCHEDULES_KEY, $schedules); } } private function compute_next_timestamp($currentTimestamp, $recur) { switch ($recur) { case 'hourly': return $currentTimestamp + HOUR_IN_SECONDS; case 'daily': return $currentTimestamp + DAY_IN_SECONDS; case 'weekly': return $currentTimestamp + WEEK_IN_SECONDS; default: return null; } } private function build_final_api_url($raw_url) { $raw = trim($raw_url); if ($raw === '') return ''; if (!preg_match('#^https?://#i', $raw)) { $raw = 'http://' . $raw; } $parts = parse_url($raw); if ($parts === false || empty($parts['host'])) return ''; $scheme = $parts['scheme'] ?? 'http'; $host = $parts['host']; $port = $parts['port'] ?? 9191; $path = $parts['path'] ?? '/broadcast'; if (substr($path, -1) === '/') $path = rtrim($path, '/'); if ($path === '') $path = '/broadcast'; $url = $scheme . '://' . $host . ($port ? ':' . $port : '') . $path; return $url; } private function send_to_api($message, $type = 'global', $prefix_override = '', $prefix_color = '', $bracket_color = '', $message_color = '') { $settings = get_option(self::OPTION_KEY, []); $raw = rtrim($settings['api_url'] ?? '', '/'); $api_key = $settings['api_key'] ?? ''; $default_prefix = $settings['broadcast_prefix'] ?? ''; $default_prefix_color = $settings['broadcast_prefix_color'] ?? '&c'; $default_bracket_color = $settings['broadcast_bracket_color'] ?? '&8'; // Neu $default_message_color = $settings['broadcast_message_color'] ?? '&f'; if (empty($raw)) { return new WP_Error('no_url', 'StatusAPI URL ist nicht konfiguriert.'); } $final_url = $this->build_final_api_url($raw); if (empty($final_url)) { return new WP_Error('bad_url', 'StatusAPI URL ist ungültig.'); } $payload_prefix = ($prefix_override !== '' ? $prefix_override : $default_prefix); $payload_prefix_color = ($prefix_color !== '' ? $prefix_color : $default_prefix_color); $payload_bracket_color = ($bracket_color !== '' ? $bracket_color : $default_bracket_color); // Neu $payload_message_color = ($message_color !== '' ? $message_color : $default_message_color); $payload = [ 'message' => $message, 'type' => $type, 'prefix' => $payload_prefix, 'prefixColor' => $payload_prefix_color, 'bracketColor' => $payload_bracket_color, // Neu 'messageColor' => $payload_message_color, 'meta' => [ 'source' => 'PulseCast-WordPress', 'time' => gmdate('c'), ], ]; $args = [ 'body' => wp_json_encode($payload), 'headers' => [ 'Content-Type' => 'application/json', ], 'timeout' => 15, ]; if (!empty($api_key)) { $args['headers']['X-Api-Key'] = $api_key; } $response = wp_remote_post($final_url, $args); if (is_wp_error($response)) { return $response; } $code = wp_remote_retrieve_response_code($response); if ($code < 200 || $code >= 300) { return new WP_Error('http_error', 'HTTP ' . $code . ': ' . wp_remote_retrieve_response_message($response)); } return true; } private function send_schedule_to_api($localId, $schedule) { $settings = get_option(self::OPTION_KEY, []); $raw = rtrim($settings['api_url'] ?? '', '/'); $api_key = $settings['api_key'] ?? ''; if (empty($raw)) { return new WP_Error('no_url', 'StatusAPI URL ist nicht konfiguriert.'); } $final_url = $this->build_final_api_url($raw); if (empty($final_url)) { return new WP_Error('bad_url', 'StatusAPI URL ist ungültig.'); } $timeSec = intval($schedule['time'] ?? 0); $timeMs = $timeSec * 1000; $payload = [ 'message' => $schedule['message'] ?? '', 'type' => 'global', 'prefix' => $schedule['prefix'] ?? '', 'prefixColor' => $schedule['prefix_color'] ?? '', 'bracketColor' => $schedule['bracket_color'] ?? '', // Neu 'messageColor' => $schedule['message_color'] ?? '', 'scheduleTime' => $timeMs, 'recur' => $schedule['recur'] ?? 'none', 'clientScheduleId' => $localId, 'meta' => [ 'source' => 'PulseCast-WordPress', 'time' => gmdate('c'), 'timezone' => 'UTC', ], ]; $args = [ 'body' => wp_json_encode($payload), 'headers' => [ 'Content-Type' => 'application/json', ], 'timeout' => 15, ]; if (!empty($api_key)) $args['headers']['X-Api-Key'] = $api_key; $response = wp_remote_post($final_url, $args); if (is_wp_error($response)) { return $response; } $code = wp_remote_retrieve_response_code($response); if ($code < 200 || $code >= 300) { return new WP_Error('http_error', 'HTTP ' . $code . ': ' . wp_remote_retrieve_response_message($response)); } return true; } private function send_cancel_schedule_to_api($localId) { $settings = get_option(self::OPTION_KEY, []); $raw = rtrim($settings['api_url'] ?? '', '/'); $api_key = $settings['api_key'] ?? ''; if (empty($raw)) return new WP_Error('no_url', 'no api'); $base = $this->build_final_api_url($raw); if (empty($base)) return new WP_Error('bad_url', 'bad'); $cancelUrl = rtrim($base, '/') . '/cancel'; $payload = [ 'clientScheduleId' => $localId, 'meta' => ['source' => 'PulseCast-WordPress', 'time' => gmdate('c')], ]; $args = [ 'body' => wp_json_encode($payload), 'headers' => ['Content-Type' => 'application/json'], 'timeout' => 10, ]; if (!empty($api_key)) $args['headers']['X-Api-Key'] = $api_key; return wp_remote_post($cancelUrl, $args); } } new PulseCast();