1
\$\begingroup\$

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).

\$\endgroup\$
4
  • 1
    \$\begingroup\$ You should add explanations on how this code is called and why. Not everyone here is familiar with YouTube API or that use case in general \$\endgroup\$ Commented Mar 26 at 4:22
  • 1
    \$\begingroup\$ Please don't modify the code in your question once it has been answered. You could post improved code as a new question, as an answer, or as a link to an external site - as described in I improved my code based on the reviews. What next?. I have rolled back the edit, so that it's clear exactly what version the answer addresses. \$\endgroup\$ Commented Mar 26 at 9:26
  • \$\begingroup\$ @TobySpeight Is I apply some of the prososed changed how I can post/inform the other users for the changed I made. Should I use a seperator OR use make a new Question? \$\endgroup\$ Commented Mar 26 at 9:41
  • \$\begingroup\$ If you want your improved code to be reviewed further, then yes, ask a new question (include a link to this one for context). Otherwise consider using an answer to show how it's improved, or link elsewhere. See I improved my code based on the reviews. What next? for advice. \$\endgroup\$ Commented Mar 26 at 17:06

1 Answer 1

2
\$\begingroup\$

I would say that this code is a huge overengineering of a simple task.

The code is so entangled and confused (granted, I do not entirely understand what does it do or how it's called or why some code was written at all, but still).

  • for example, you are setting an error handler. That's a good idea. But then for some reason you handle the sending manually with try-catch. Why?
  • or why there is echo $e->getMessage();, if, as you say, nobody to look at it?
  • there is a function getBotInfo() which nowhere to be seen used
  • this code checks for empty url or title twice
  • string functions used to parse XML instead of accessing entries directly, like $channel_id = $xml->{'yt:channelId'}

Another problem is sending user responses to Sentry. Why don't you send them right into the channel, like "Hubs Is Subscribed" one? To be honest, I don't see why Sentry is even needed here at all. For the real problems there are error logs. For user responses and simple errors it's much simpler and more convenient to send them right to Telegram.

On a side note, I don't think there are reliable free hosting services that allow outbound connections. Or "power users" who can set up a sentry instance.

\$\endgroup\$
6
  • \$\begingroup\$ or why there is echo $e->getMessage();, if, as you say, nobody to look at it? is Leftover debugging code \$\endgroup\$ Commented Mar 26 at 8:16
  • \$\begingroup\$ Another problem is sending user responses to Sentry I send them because I want it a debug side channel and I use Sentry as this one. \$\endgroup\$ Commented Mar 26 at 8:17
  • \$\begingroup\$ these two statements seemingly contradict with each other \$\endgroup\$ Commented Mar 26 at 8:20
  • \$\begingroup\$ $channel_id = $xml->{'yt:channelId'} idk I could do that \$\endgroup\$ Commented Mar 26 at 8:29
  • \$\begingroup\$ No they do not contradict because the echo was a code that was not meant for production, just an xdebug replacement and I forgot to remove it whilst I was testing the code upon server. (Free hosting has no Xdebug installed) \$\endgroup\$ Commented Mar 26 at 8:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.