Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/php
- <?php
- /*******************************************************************************
- dickbeard
- author: citizen <hs.citizen@gmail.com>
- a filthy hack to get http episode retrieval working with sickbeard. scans
- sickbeard db for wanted episodes and checks a website for matching files to
- download.
- installation:
- edit the configuration options below and run this script on cron every WHATEVER
- minutes. it'll download wanted episodes and sickbeard then picks up the new
- files in its post-processing dir and imports them into your library.
- ***** you must execute this script as whatever uid sickbeard is running as *****
- # apt-get install aria2 php5-cli php5-sqlite
- # chmod +x dickbeard
- # cat > /etc/cron.d/dickbeard
- * * * * * sickbeard /usr/bin/php /path/to/dickbeard
- (where 'sickbeard' is the user you run sickbeard as)
- sample configuration file: http://wtf.net.au/dickbeard.conf
- dickbeard.conf should be placed in the same directory as this script. if you
- place it elsewhere, specify the path as an argument when running dickbeard, i.e.
- # dickbeard /path/to/dickbeard.conf
- *******************************************************************************/
- class Core {
- public static $updateURL = 'http://wtf.net.au/dickbeard';
- public static $version = '0.2.9';
- }
- // episodes that download themselves
- class Episode {
- // these are the numeric status codes sb uses in its db
- const QUALITY_NONE = 0;
- const QUALITY_SDTV = 1;
- const QUALITY_SDDVD = 2;
- const QUALITY_HDTV = 4;
- const QUALITY_RAWHDTV = 8;
- const QUALITY_FULLHDTV = 16;
- const QUALITY_HDWEBDL = 32;
- const QUALITY_FULLHDWEBDL = 64;
- const QUALITY_HDBLURAY = 128;
- const QUALITY_FULLHDBLURAY = 256;
- const QUALITY_HD = 508; // QUALITY_RAWHDTV | QUALITY_FULLHDTV | QUALITY_HDWEBDL | QUALITY_FULLHDWEBDL | QUALITY_HDBLURAY | QUALITY_FULLHDBLURAY | QUALITY_HDTV
- const QUALITY_SD = 3; // QUALITY_NONE | QUALITY_SDTV | QUALITY_SDDVD
- const STATUS_UNKNOWN = -1; # should never happen
- const STATUS_UNAIRED = 1; # episodes that haven't aired yet
- const STATUS_SNATCHED = 2; # qualified with quality
- const STATUS_WANTED = 3; # episodes we don't have but want to get
- const STATUS_DOWNLOADED = 4; # qualified with quality
- const STATUS_SKIPPED = 5; # episodes we don't want
- const STATUS_ARCHIVED = 6; # episodes that you don't have locally (counts toward download completion stats)
- const STATUS_IGNORED = 7; # episodes that you don't want included in your download stats
- const STATUS_SNATCHED_PROPER = 9; # qualified with quality
- // what am i?
- public $show, $season, $episode, $quality;
- private $url;
- public function setURL($url) { $this->url = $url; }
- public function getURL() { return (isset($this->url)? $this->url : false); }
- public function __construct($show = '', $season = 0, $episode = 0, $quality = self::QUALITY_SD) {
- $this->show = self::cleanName($show);
- $this->season = $season;
- $this->episode = $episode;
- $this->quality = $quality;
- }
- // try to parse filename
- public function fromFilename($f) {
- foreach (Config::$filenameRegexes as $reg) {
- if (preg_match($reg, $f, $matches)) {
- if (strpos($f, '720p') !== false)
- $matches[] = '720p';
- $this->show = self::cleanName($matches[1]);
- $this->season = (int) $matches[2];
- $this->episode = (int) $matches[3];
- $this->quality = (isset($matches[4])? self::QUALITY_HD : self::QUALITY_SD);
- return true;
- }
- }
- return false;
- }
- public function isEqualTo($ep) {
- $titleMatched = ($this->show === $ep->show);
- if (!$titleMatched) {
- foreach (Config::$matchers as $a => $b) {
- if ((self::cleanName($a) == $this->show || self::cleanName($b) == $this->show) &&
- (self::cleanName($a) == $ep->show || self::cleanName($b) == $ep->show)) {
- $titleMatched = true;
- break;
- }
- }
- }
- return ($titleMatched && $this->season == $ep->season && $this->episode == $ep->episode && ($this->quality & $ep->quality));
- }
- public function __toString() {
- return $this->show . ' ' . $this->season . 'x' . $this->episode . ' (' . ($this->quality & self::QUALITY_HD? 'hd' : 'sd') . ')';
- }
- public function download() {
- if (!($url = $this->getURL()))
- return false;
- debug("downloading: $this from $url");
- $srcFile = Config::$workDir . '/' . basename($url);
- $dstFile = Config::$outDir . '/' . basename($url);
- if (Config::$useWorkDir && Config::$hardLink) { // set hardlink from workdir to postdir
- if (!link($srcFile, $dstFile))
- throw new Exception("could not create hard link: $dstFile -> $srcFile");
- debug("created hardlink: $dstFile -> $srcFile\n");
- }
- if (file_exists($dstFile)) {
- debug("destination file already exists, skipping: $this");
- return false;
- }
- // build cmd
- $cmd = Config::$downloadCmd;
- $cmd = str_replace(
- array('$OUTDIR', '$URL', '$LOGFILE'),
- array(escapeshellarg(Config::$useWorkDir? Config::$workDir : Config::$outDir), escapeshellarg($url), escapeshellarg(Config::$logFile)),
- $cmd
- );
- // check logfile
- if (file_exists(Config::$logFile) && filesize(Config::$logFile) > Config::$logMaxSize)
- if (!unlink(Config::$logFile))
- throw new Exception("could not clear log file ". Config::$logFile . " - not writeable?");
- debug("$cmd");
- $startTime = time();
- exec($cmd, $output, $exitStatus);
- if ($exitStatus != 0)
- throw new Exception("download command failed with exit status $exitStatus");
- $interval = time() - $startTime;
- if (Config::$useWorkDir) {
- if (Config::$hardLink) {
- if (!unlink($srcFile))
- throw new Exception("failed to remove staging file: $srcFile");
- }
- else if (!rename($srcFile, $dstFile))
- throw new Exception("could not move: $srcFile -> $dstFile");
- }
- debug("completed: $this in $interval seconds\n");
- return true;
- }
- public static function cleanName($s) {
- // convert to lower and replace ._- with spaces
- $s = strtolower(preg_replace('/[._-]/', ' ', $s));
- // strip all non-alnum/space
- $s = trim(preg_replace('/[^\dA-Za-z ]/', '', $s));
- // fold multi-space
- $s = preg_replace('/[\s]+/', ' ', $s);
- // out this muthafuka
- return $s;
- }
- }
- /******************************************************************************/
- set_exception_handler('fatal');
- // try config
- if ($argc == 2)
- $configFile = $argv[1];
- else
- $configFile = dirname(__FILE__) . '/dickbeard.conf';
- if (!is_readable($configFile))
- throw new Exception("could not read configuration file: " . $configFile);
- require_once($configFile);
- debug("dickbeard-" . Core::$version . " by citizen <hs.citizen@gmail.com>");
- // linefeed
- debug("");
- // already running?
- if (file_exists(Config::$lockfile))
- die("already running?!? lockfile " . Config::$lockfile . " exists\n");
- if (!touch(Config::$lockfile))
- throw new Exception("could not write lockfile: " . Config::$lockfile);
- // scan db - find episodes in state=wanted and that are not 'specials' (season=0)
- $wanted = array();
- if (!($db = new PDO("sqlite:" . Config::$db)))
- throw new Exception("could not open sickbeard db: " . Config::$db);
- // build our WHERE condition for db query
- $statusCondition = "";
- $first = true;
- foreach (Config::$downloadStatus as $statusCode) {
- if (!$first)
- $statusCondition .= " OR ";
- else
- $first = false;
- switch ($statusCode) {
- case Episode::STATUS_WANTED:
- case Episode::STATUS_IGNORED:
- case Episode::STATUS_SKIPPED:
- $statusCondition .= " tv_episodes.status = " . $statusCode;
- break;
- case Episode::STATUS_SNATCHED:
- $statusCondition .= " tv_episodes.status & 2";
- break;
- default:
- throw new Exception("unhandled status: " . $statusCode);
- }
- }
- $q = $db->prepare("SELECT tv_shows.show_name,tv_shows.quality,tv_episodes.season,tv_episodes.episode,tv_episodes.status FROM tv_episodes JOIN tv_shows ON tv_shows.indexer_id = tv_episodes.showid WHERE $statusCondition AND tv_episodes.season > 0 AND tv_shows.paused = 0");
- $q->execute();
- while ($row = $q->fetch(PDO::FETCH_ASSOC)) {
- $ep = new Episode($row['show_name'], $row['season'], $row['episode'], $row['quality']);
- $wanted[] = $ep;
- debug("wanted: $ep");
- }
- if (count($wanted) == 0) {
- debug("nothing wanted, exiting");
- exit(1);
- }
- // scan index - see what's available to download
- debug("\nindexing available episodes...");
- $index = array();
- $data = file_get_contents(Config::$indexURL);
- if (!preg_match_all('/<\/td><td><a href\=\"(.*?)\"/', $data, $matches))
- throw new Exception("failed to get index: " . Config::$indexURL);
- foreach ($matches[1] as $file) {
- if ($file == '/') continue;
- $ep = new Episode();
- if (!$ep->fromFilename($file)) {
- debug("warning: could not parse index filename: $file");
- continue;
- }
- $ep->setURL(Config::$indexURL . (substr(Config::$indexURL, strlen(Config::$indexURL) - 1) == '/'? '' : '/') . $file);
- $index[] = $ep;
- }
- debug("wanted: " . count($wanted) . ", available: " . count($index) . "\n");
- // iterate through wanted eps, check for matches in index, download those matches.
- foreach ($wanted as $wantedEp)
- foreach ($index as $indexEp)
- if ($wantedEp->isEqualTo($indexEp)) {
- $indexEp->download();
- break;
- }
- unlink(Config::$lockfile);
- /*****************************************************************************/
- function fatal($ex) {
- global $config;
- $stderr = fopen('php://stderr', 'w');
- fwrite($stderr, "dickbeard: fatal: " . $ex->getMessage() . "\n");
- if (file_exists(Config::$lockfile))
- unlink(Config::$lockfile);
- exit(1);
- }
- function debug($msg) {
- if (Config::$debug)
- echo "$msg\n";
- }
- // fuck you
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement