I made a simple script that notifies a Telegram Channel when a new youtube video is uploaded upon a YouTube channel:
index.php
<?php
// Bootstrap Sentry
require_once "./sentry.php";
// Sentry also requires Settings as well therefore wqe use require
$settings = require("./config.php");
use Sentry\Severity;
$traceId = rand();
if(!isset($settings['YT_CHANNEL_ID'])){
http_response_code(500);
\Sentry\captureMessage("$traceId : No youtube channel Id has been Configured",\Sentry\Severity::error());
exit(0);
}
if($_SERVER['REQUEST_METHOD'] == "GET"){
if(!isset($_GET['hub_mode']) || $_GET['hub_mode']!=='subscribe'){
http_response_code(204);
exit(0);
}
if(!isset($_GET['hub_topic'])){
http_response_code(400);
exit(0);
}
$expected_topic = "https://www.youtube.com/feeds/videos.xml?channel_id=".$settings['YT_CHANNEL_ID'];
if($expected_topic!=trim($_GET['hub_topic'])){
http_response_code(403);
\Sentry\captureMessage("$traceId : Googlwe websub provided wrong channel id",\Sentry\Severity::error());
exit(0);
}
if(!isset($_GET['hub_challenge'])){
http_response_code(400);
exit(0);
}
http_response_code(204);
\Sentry\captureMessage("Hubs Is Subscribed",\Sentry\Severity::info());
exit(0);
}
$string = file_get_contents('php://input');
$xml = simplexml_load_string($string);
if($xml === false){
http_response_code(400);
\Sentry\captureMessage("$traceId : No valid XML has been provided as http body ",\Sentry\Severity::error());
\Sentry\captureMessage("BODY FOR: ".$traceId.":\n".$string,\Sentry\Severity::info());
exit(0);
}
if(preg_match("#<yt:channelId>.*</yt:channelId>#",$string,$matches) === false){
http_response_code(400);
\Sentry\captureMessage("$traceId : No channel Id has found",\Sentry\Severity::error());
\Sentry\captureMessage("BODY FOR: ".$traceId.":\n".$string,\Sentry\Severity::info());
exit(0);
}
$channel_id = $matches[0];
$channel_id = str_replace("<yt:channelId>","",$channel_id);
$channel_id = str_replace("</yt:channelId>","",$channel_id);
$channel_id = trim($channel_id);
if($channel_id !== $settings['YT_CHANNEL_ID']){
\Sentry\captureMessage("$traceId : No channel Id has found",\Sentry\Severity::error());
\Sentry\captureMessage("BODY FOR: ".$traceId.":\n".$string,\Sentry\Severity::info());
exit(0);
}
$title = $xml->title??null;
$url = $xml->entry->link->attributes()['href']??null;
if(empty($title)|| empty($url)){
http_response_code(400);
\Sentry\captureMessage("$traceId : Either title or url is missing",\Sentry\Severity::error());
\Sentry\captureMessage("BODY FOR: ".$traceId.":\n".$string,\Sentry\Severity::info());
\Sentry\captureMessage("Title FOR: ".$traceId.":\n".$title,\Sentry\Severity::info());
\Sentry\captureMessage("URL FOR: ".$traceId.":\n".$url,\Sentry\Severity::info());
exit(0);
}
require_once "./bot_actions.php";
try{
sendMessageToChannel($settings['TELEGRAM_CHANNEL_ID'],$settings['BOT_TOKEN'],$url,$title);
http_response_code(204);
}catch(\Exception $e){
\Sentry\captureException($e);
}
It is a single purpose script that reports its errors via Sentry (sentry.php
):
<?php
use Sentry\Severity;
/**
* Because App has np way to report errors directly to user.
* I use telementry and sentry to capture the errors.
*/
require_once './vendor/autoload.php';
function initSentry(){
$config = require('./config.php');
if(empty($config['SENTRY_DSN'])){
// DO not exist because we want this file to be Included
return;
}
\Sentry\init([
'dsn' => $config['SENTRY_DSN'],
// Specify a fixed sample rate
'traces_sample_rate' => 1.0,
// Set a sampling rate for profiling - this is relative to traces_sample_rate
'profiles_sample_rate' => 1.0,
]);
set_error_handler(function($error,$level){
\Sentry\captureMessage($error,new Severity(Severity::ERROR));
});
}
initSentry();
Whereas basic actions are handled via simple curl (bot_actions.php
):
<?php
function sendMessageToChannel(string $channel_id, string $bot_token, string $youtube_link,string $title)
{
$youtube_link=trim($youtube_link);
if(empty($youtube_link)){
throw new InvalidArgumentException("No youtube link been provided", 1);
}
if(empty($title)){
throw new InvalidArgumentException("No youtube link been provided", 1);
}
if(empty($channel_id)){
throw new InvalidArgumentException("Channel Id is missing", 2);
}
if(empty($bot_token)){
throw new InvalidArgumentException("No Bot token", 3);
}
$message="Νέο βίντεο απο τον γέροντα.<a href=\"$youtube_link\">$title</a>";
$params = [
'chat_id' => trim($channel_id),
'text' => $message,
'parse_mode'=>'html'
];
$url = "https://api.telegram.org/bot$bot_token/sendMessage";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, 1);
$data = json_encode($params);
curl_setopt( $ch, CURLOPT_POSTFIELDS, $data );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
// Close cURL session
curl_close($ch);
return $result;
}
function getBotInfo(string $bot_token){
if(empty($bot_token)){
throw new InvalidArgumentException("No Bot token", 3);
}
$url = "https://api.telegram.org/bot$bot_token/getUpdates";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
// Close cURL session
curl_close($ch);
return json_decode($result,true);
}
Also it is configuration like this (./config.php
):
<?php
return [
// Bot Info Secret
'SECRET'=>"XXXXXXXX",
// Telegram Bot Token
'BOT_TOKEN'=>"XXXXXXXXXXX",
// Telegram Channel Id
'TELEGRAM_CHANNEL_ID'=>"XXXXXXXXXXXX",
// Youtube Channel Id
'YT_CHANNEL_ID'=>'XXXXXXXXXX',
// DSN used in Sentry thos can be left as empty if no telemetry is used.
'SENTRY_DSN'=> "XXXXXXXXX"
];
The idea is to use free shared hosting in order to host a single purpose notification bot for a single YouTube channel. The notifications will be sent to a single telegram channel as well.
Do you think a framework would make it better? I think no because it overengineeres a simple task. But IDK if comment upon botActions.php
methods would make it better. I try to keep it simple with as few dependencies as possible (I only use Sentry because it offers me an easy way to have an error.log without messing with shared hosting settings.)
I could use a database and look for duplicate requests but that also introduces migration scripts and I want to keep it as simple to deploy (by hand) as possible for power users but not too tech savvy people (i.e people who do not know how to code and follow directions blindly).
The code above is called as a WebSub via youtube itself as described upon: https://developers.google.com/youtube/v3/guides/push_notifications. In other words it is a youtube webhook that I host it upon a free hosting where I will donate it to a youtube channel (thus the free hosting).