Compare commits

..

3 Commits

Author SHA1 Message Date
2b1ec13f53 Fixed some style errors; Updated the PHP_CodeSniffer rules to be better
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2022-03-08 12:23:41 -05:00
c4afe6f08e Added database to keep track of videos already uploaded, so that only new videos send out notifications
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2022-03-08 12:12:21 -05:00
803ad25cda Moved code to under the src/ directory (to keep the directory structure tidy); created a separate instance of the EventLoop to have a periodic YouTube check
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2022-03-07 23:03:48 -05:00
11 changed files with 1821 additions and 386 deletions

4
.gitignore vendored
View File

@ -6,3 +6,7 @@ config/config.yaml
# PHP_Codesniffer cache # PHP_Codesniffer cache
.phpcs-cache .phpcs-cache
# database file
data/pigeon.db

View File

@ -13,7 +13,9 @@
"symfony/yaml": "^6.0", "symfony/yaml": "^6.0",
"hassankhan/config": "^3.0", "hassankhan/config": "^3.0",
"madcoda/php-youtube-api": "^1.2", "madcoda/php-youtube-api": "^1.2",
"team-reflex/discord-php": "^7.0" "team-reflex/discord-php": "^7.0",
"robmorgan/phinx": "^0.12.10",
"illuminate/database": "^9.3"
}, },
"require-dev": { "require-dev": {
"squizlabs/php_codesniffer": "^3.6", "squizlabs/php_codesniffer": "^3.6",
@ -21,6 +23,11 @@
}, },
"scripts": { "scripts": {
"phpcs": "phpcs --standard=./phpcs.xml", "phpcs": "phpcs --standard=./phpcs.xml",
"phpmd": "phpmd pigeon.php text phpmd.xml" "phpmd": "phpmd src/ text phpmd.xml"
},
"autoload": {
"psr-4": {
"Pigeon\\": "src"
}
} }
} }

1985
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,7 @@
pigeon:
check_interval: 10
db_path: "data/pigeon.db"
youtube: youtube:
api_key: "supersecretyoutubeapiky" api_key: "supersecretyoutubeapiky"
channel_id: "mychannelid" channel_id: "mychannelid"

0
data/.gitkeep Normal file
View File

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class AddVideosTable extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change(): void
{
// create the table
$table = $this->table('videos');
$table->addColumn('video_id', 'string')
->addColumn('published_at', 'datetime')
->addTimestamps()
->addIndex(['video_id'], ['unique' => true])
->create();
}
}

28
phinx.php Normal file
View File

@ -0,0 +1,28 @@
<?php
return
[
'paths' => [
'migrations' => '%%PHINX_CONFIG_DIR%%/db/migrations',
'seeds' => '%%PHINX_CONFIG_DIR%%/db/seeds'
],
'environments' => [
'default_migration_table' => 'phinxlog',
'default_environment' => 'development',
'production' => [
'adapter' => 'sqlite',
'name' => './data/pigeon',
'suffix' => 'db',
],
'development' => [
'adapter' => 'sqlite',
'name' => './data/pigeon',
'suffix' => 'db',
],
'testing' => [
'adapter' => 'sqlite',
'memory' => true,
]
],
'version_order' => 'creation'
];

View File

@ -1,27 +1,33 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<ruleset name="PHP_CodeSniffer"> <ruleset name="PHP_CodeSniffer">
<description>PHPCS configuration file.</description> <description>PHPCS configuration file.</description>
<arg name="basepath" value="." /> <arg name="basepath" value="." />
<arg name="extensions" value="php" /> <arg name="extensions" value="php" />
<arg name="colors" /> <arg name="colors" />
<arg name="cache" value=".phpcs-cache" /> <arg name="cache" value=".phpcs-cache" />
<arg value="p" /> <arg value="p" />
<arg value="s" /> <arg value="s" />
<!-- pigeon.php is the only file to check currently --> <!-- Check PHP files in the src/ directory -->
<file>pigeon.php</file> <file>src/</file>
<!-- Our base rule: set to PSR12--> <!-- Our base rule: set to PSR12-->
<rule ref="PSR12"/> <rule ref="PSR12">
<exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine" />
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine" />
</rule>
<!-- Set indent size to 2 --> <!-- Some custom rules -->
<rule ref="Generic.WhiteSpace.ScopeIndent"> <rule ref="Generic.Functions.OpeningFunctionBraceKernighanRitchie" />
<properties> <rule ref="Generic.Classes.OpeningBraceSameLine" />
<property name="indent" value="2" /> <!-- Set indent size to 2 -->
</properties> <rule ref="Generic.WhiteSpace.ScopeIndent">
</rule> <properties>
<property name="indent" value="2" />
</properties>
</rule>
</ruleset> </ruleset>

View File

@ -11,9 +11,7 @@
</description> </description>
<!-- Import some rule sets --> <!-- Import some rule sets -->
<rule ref="rulesets/cleancode.xml"> <rule ref="rulesets/cleancode.xml" />
<exclude name="StaticAccess" />
</rule>
<rule ref="rulesets/codesize.xml" /> <rule ref="rulesets/codesize.xml" />
<rule ref="rulesets/design.xml" /> <rule ref="rulesets/design.xml" />
<rule ref="rulesets/naming.xml" /> <rule ref="rulesets/naming.xml" />

10
src/Models/Video.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace Pigeon\Models;
use Illuminate\Database\Eloquent\Model;
class Video extends Model {
// define SQL attributes that can be modified
protected $fillable = ['video_id', 'published_at'];
}

90
src/pigeon.php Normal file
View File

@ -0,0 +1,90 @@
<?php
include __DIR__ . '/../vendor/autoload.php';
use Discord\Discord;
use Discord\Parts\Channel\Message;
use Discord\WebSockets\Intents;
use Illuminate\Database\Capsule\Manager as Capsule;
use Madcoda\Youtube\Youtube;
use Noodlehaus\Config;
use Pigeon\Models\Video;
// load config file
$config = new Config('config/config.yaml');
// create Eloquent connection object
$capsule = new Capsule();
$capsule->addConnection([
'driver' => 'sqlite',
'database' => $config->get('pigeon.db_path'),
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();
// create YouTube API object
$youtube = new Youtube(array('key' => $config->get('youtube.api_key')));
// get channel
$yt_res = $youtube->getChannelById($config->get('youtube.channel_id'));
// get channel info
$config->set('youtube.channel_name', $yt_res->snippet->title);
$config->set('youtube.uploads_id', $yt_res->contentDetails->relatedPlaylists->uploads);
// get the channel's uploads so we can seed the DB
$uploads = $youtube->getPlaylistItemsByPlaylistId($config->get('youtube.uploads_id'));
// create Video model objects with the above uploads
foreach ($uploads as $u) {
$video = new Video();
$video->video_id = $u->contentDetails->videoId;
$video->published_at = date($u->contentDetails->videoPublishedAt);
// create the table entry if it doesn't already exist
Video::where('video_id', $video->video_id)->firstOr(function () use ($video) {
$video->save();
});
}
// create our event loop
$loop = React\EventLoop\Factory::create();
// instantiate Discord API object
$discord = new Discord([
'token' => $config->get('discord.api_key'),
'intents' => Intents::getDefaultIntents() | Intents::GUILDS,
'loop' => $loop,
]);
// periodic youtube uploads check
$loop->addPeriodicTimer($config->get('pigeon.check_interval') * 60, function () use ($config, $discord, $youtube) {
// get latest video from uploads playlist
$uploads = $youtube->getPlaylistItemsByPlaylistId($config->get('youtube.uploads_id'), 1);
$uploadDetails = $uploads[0]->contentDetails;
// search for the latest video in the DB
Video::where('video_id', $uploadDetails->videoId)->firstOr(function () use ($discord, $config, $uploadDetails) {
$latest_url = "https://youtu.be/$uploadDetails->videoId";
$message_text = str_replace('{link}', $latest_url, $config->get('discord.message_template'));
$discord->guilds->first()->channels->find(function ($channel) use ($config) {
return $channel->name === $config->get('discord.announcement_channel');
})->sendMessage($message_text)->done(function ($message) use ($discord) {
echo 'Message sent!', PHP_EOL;
});
// add the video to the DB
$video = new Video();
$video->video_id = $uploadDetails->videoId;
$video->published_at = date($uploadDetails->videoPublishedAt);
$video->save();
});
});
// run tasks when Discord is ready
$discord->on('ready', function (Discord $discord) {
echo "Bot is ready!", PHP_EOL;
});
$discord->run();