Advertisement
Guest User

Untitled

a guest
Jul 26th, 2016
75
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.56 KB | None | 0 0
  1. #!/usr/bin/php
  2. <?php
  3. /*******************************************************************************
  4.  
  5. dickbeard
  6.  
  7. author: citizen <hs.citizen@gmail.com>
  8.  
  9. a filthy hack to get http episode retrieval working with sickbeard. scans
  10. sickbeard db for wanted episodes and checks a website for matching files to
  11. download.
  12.  
  13. installation:
  14.  
  15. edit the configuration options below and run this script on cron every WHATEVER
  16. minutes. it'll download wanted episodes and sickbeard then picks up the new
  17. files in its post-processing dir and imports them into your library.
  18.  
  19. ***** you must execute this script as whatever uid sickbeard is running as *****
  20.  
  21. # apt-get install aria2 php5-cli php5-sqlite
  22. # chmod +x dickbeard
  23. # cat > /etc/cron.d/dickbeard
  24. * * * * * sickbeard /usr/bin/php /path/to/dickbeard
  25.  
  26. (where 'sickbeard' is the user you run sickbeard as)
  27.  
  28. sample configuration file: http://wtf.net.au/dickbeard.conf
  29. dickbeard.conf should be placed in the same directory as this script. if you
  30. place it elsewhere, specify the path as an argument when running dickbeard, i.e.
  31. # dickbeard /path/to/dickbeard.conf
  32.  
  33. *******************************************************************************/
  34.  
  35. class Core {
  36. public static $updateURL = 'http://wtf.net.au/dickbeard';
  37. public static $version = '0.2.9';
  38. }
  39.  
  40. // episodes that download themselves
  41. class Episode {
  42. // these are the numeric status codes sb uses in its db
  43. const QUALITY_NONE = 0;
  44. const QUALITY_SDTV = 1;
  45. const QUALITY_SDDVD = 2;
  46. const QUALITY_HDTV = 4;
  47. const QUALITY_RAWHDTV = 8;
  48. const QUALITY_FULLHDTV = 16;
  49. const QUALITY_HDWEBDL = 32;
  50. const QUALITY_FULLHDWEBDL = 64;
  51. const QUALITY_HDBLURAY = 128;
  52. const QUALITY_FULLHDBLURAY = 256;
  53.  
  54. const QUALITY_HD = 508; // QUALITY_RAWHDTV | QUALITY_FULLHDTV | QUALITY_HDWEBDL | QUALITY_FULLHDWEBDL | QUALITY_HDBLURAY | QUALITY_FULLHDBLURAY | QUALITY_HDTV
  55. const QUALITY_SD = 3; // QUALITY_NONE | QUALITY_SDTV | QUALITY_SDDVD
  56.  
  57. const STATUS_UNKNOWN = -1; # should never happen
  58. const STATUS_UNAIRED = 1; # episodes that haven't aired yet
  59. const STATUS_SNATCHED = 2; # qualified with quality
  60. const STATUS_WANTED = 3; # episodes we don't have but want to get
  61. const STATUS_DOWNLOADED = 4; # qualified with quality
  62. const STATUS_SKIPPED = 5; # episodes we don't want
  63. const STATUS_ARCHIVED = 6; # episodes that you don't have locally (counts toward download completion stats)
  64. const STATUS_IGNORED = 7; # episodes that you don't want included in your download stats
  65. const STATUS_SNATCHED_PROPER = 9; # qualified with quality
  66.  
  67. // what am i?
  68. public $show, $season, $episode, $quality;
  69.  
  70. private $url;
  71. public function setURL($url) { $this->url = $url; }
  72. public function getURL() { return (isset($this->url)? $this->url : false); }
  73.  
  74. public function __construct($show = '', $season = 0, $episode = 0, $quality = self::QUALITY_SD) {
  75. $this->show = self::cleanName($show);
  76. $this->season = $season;
  77. $this->episode = $episode;
  78. $this->quality = $quality;
  79. }
  80.  
  81. // try to parse filename
  82. public function fromFilename($f) {
  83. foreach (Config::$filenameRegexes as $reg) {
  84. if (preg_match($reg, $f, $matches)) {
  85. if (strpos($f, '720p') !== false)
  86. $matches[] = '720p';
  87. $this->show = self::cleanName($matches[1]);
  88. $this->season = (int) $matches[2];
  89. $this->episode = (int) $matches[3];
  90. $this->quality = (isset($matches[4])? self::QUALITY_HD : self::QUALITY_SD);
  91. return true;
  92. }
  93. }
  94. return false;
  95. }
  96.  
  97. public function isEqualTo($ep) {
  98. $titleMatched = ($this->show === $ep->show);
  99. if (!$titleMatched) {
  100. foreach (Config::$matchers as $a => $b) {
  101. if ((self::cleanName($a) == $this->show || self::cleanName($b) == $this->show) &&
  102. (self::cleanName($a) == $ep->show || self::cleanName($b) == $ep->show)) {
  103. $titleMatched = true;
  104. break;
  105. }
  106. }
  107. }
  108. return ($titleMatched && $this->season == $ep->season && $this->episode == $ep->episode && ($this->quality & $ep->quality));
  109. }
  110.  
  111. public function __toString() {
  112. return $this->show . ' ' . $this->season . 'x' . $this->episode . ' (' . ($this->quality & self::QUALITY_HD? 'hd' : 'sd') . ')';
  113. }
  114.  
  115. public function download() {
  116. if (!($url = $this->getURL()))
  117. return false;
  118.  
  119. debug("downloading: $this from $url");
  120.  
  121. $srcFile = Config::$workDir . '/' . basename($url);
  122. $dstFile = Config::$outDir . '/' . basename($url);
  123.  
  124. if (Config::$useWorkDir && Config::$hardLink) { // set hardlink from workdir to postdir
  125. if (!link($srcFile, $dstFile))
  126. throw new Exception("could not create hard link: $dstFile -> $srcFile");
  127. debug("created hardlink: $dstFile -> $srcFile\n");
  128. }
  129.  
  130. if (file_exists($dstFile)) {
  131. debug("destination file already exists, skipping: $this");
  132. return false;
  133. }
  134.  
  135. // build cmd
  136. $cmd = Config::$downloadCmd;
  137. $cmd = str_replace(
  138. array('$OUTDIR', '$URL', '$LOGFILE'),
  139. array(escapeshellarg(Config::$useWorkDir? Config::$workDir : Config::$outDir), escapeshellarg($url), escapeshellarg(Config::$logFile)),
  140. $cmd
  141. );
  142.  
  143. // check logfile
  144. if (file_exists(Config::$logFile) && filesize(Config::$logFile) > Config::$logMaxSize)
  145. if (!unlink(Config::$logFile))
  146. throw new Exception("could not clear log file ". Config::$logFile . " - not writeable?");
  147.  
  148. debug("$cmd");
  149. $startTime = time();
  150. exec($cmd, $output, $exitStatus);
  151.  
  152. if ($exitStatus != 0)
  153. throw new Exception("download command failed with exit status $exitStatus");
  154.  
  155. $interval = time() - $startTime;
  156.  
  157. if (Config::$useWorkDir) {
  158. if (Config::$hardLink) {
  159. if (!unlink($srcFile))
  160. throw new Exception("failed to remove staging file: $srcFile");
  161. }
  162. else if (!rename($srcFile, $dstFile))
  163. throw new Exception("could not move: $srcFile -> $dstFile");
  164. }
  165.  
  166. debug("completed: $this in $interval seconds\n");
  167.  
  168. return true;
  169. }
  170.  
  171. public static function cleanName($s) {
  172. // convert to lower and replace ._- with spaces
  173. $s = strtolower(preg_replace('/[._-]/', ' ', $s));
  174. // strip all non-alnum/space
  175. $s = trim(preg_replace('/[^\dA-Za-z ]/', '', $s));
  176. // fold multi-space
  177. $s = preg_replace('/[\s]+/', ' ', $s);
  178. // out this muthafuka
  179. return $s;
  180. }
  181. }
  182.  
  183. /******************************************************************************/
  184.  
  185. set_exception_handler('fatal');
  186.  
  187. // try config
  188. if ($argc == 2)
  189. $configFile = $argv[1];
  190. else
  191. $configFile = dirname(__FILE__) . '/dickbeard.conf';
  192. if (!is_readable($configFile))
  193. throw new Exception("could not read configuration file: " . $configFile);
  194. require_once($configFile);
  195.  
  196. debug("dickbeard-" . Core::$version . " by citizen <hs.citizen@gmail.com>");
  197. // linefeed
  198. debug("");
  199.  
  200. // already running?
  201. if (file_exists(Config::$lockfile))
  202. die("already running?!? lockfile " . Config::$lockfile . " exists\n");
  203. if (!touch(Config::$lockfile))
  204. throw new Exception("could not write lockfile: " . Config::$lockfile);
  205.  
  206. // scan db - find episodes in state=wanted and that are not 'specials' (season=0)
  207. $wanted = array();
  208. if (!($db = new PDO("sqlite:" . Config::$db)))
  209. throw new Exception("could not open sickbeard db: " . Config::$db);
  210. // build our WHERE condition for db query
  211. $statusCondition = "";
  212. $first = true;
  213. foreach (Config::$downloadStatus as $statusCode) {
  214. if (!$first)
  215. $statusCondition .= " OR ";
  216. else
  217. $first = false;
  218. switch ($statusCode) {
  219. case Episode::STATUS_WANTED:
  220. case Episode::STATUS_IGNORED:
  221. case Episode::STATUS_SKIPPED:
  222. $statusCondition .= " tv_episodes.status = " . $statusCode;
  223. break;
  224. case Episode::STATUS_SNATCHED:
  225. $statusCondition .= " tv_episodes.status & 2";
  226. break;
  227. default:
  228. throw new Exception("unhandled status: " . $statusCode);
  229. }
  230. }
  231. $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");
  232. $q->execute();
  233. while ($row = $q->fetch(PDO::FETCH_ASSOC)) {
  234. $ep = new Episode($row['show_name'], $row['season'], $row['episode'], $row['quality']);
  235. $wanted[] = $ep;
  236. debug("wanted: $ep");
  237. }
  238.  
  239. if (count($wanted) == 0) {
  240. debug("nothing wanted, exiting");
  241. exit(1);
  242. }
  243.  
  244. // scan index - see what's available to download
  245. debug("\nindexing available episodes...");
  246. $index = array();
  247. $data = file_get_contents(Config::$indexURL);
  248. if (!preg_match_all('/<\/td><td><a href\=\"(.*?)\"/', $data, $matches))
  249. throw new Exception("failed to get index: " . Config::$indexURL);
  250. foreach ($matches[1] as $file) {
  251. if ($file == '/') continue;
  252. $ep = new Episode();
  253. if (!$ep->fromFilename($file)) {
  254. debug("warning: could not parse index filename: $file");
  255. continue;
  256. }
  257. $ep->setURL(Config::$indexURL . (substr(Config::$indexURL, strlen(Config::$indexURL) - 1) == '/'? '' : '/') . $file);
  258. $index[] = $ep;
  259. }
  260.  
  261. debug("wanted: " . count($wanted) . ", available: " . count($index) . "\n");
  262.  
  263. // iterate through wanted eps, check for matches in index, download those matches.
  264. foreach ($wanted as $wantedEp)
  265. foreach ($index as $indexEp)
  266. if ($wantedEp->isEqualTo($indexEp)) {
  267. $indexEp->download();
  268. break;
  269. }
  270.  
  271. unlink(Config::$lockfile);
  272.  
  273. /*****************************************************************************/
  274.  
  275. function fatal($ex) {
  276. global $config;
  277. $stderr = fopen('php://stderr', 'w');
  278. fwrite($stderr, "dickbeard: fatal: " . $ex->getMessage() . "\n");
  279. if (file_exists(Config::$lockfile))
  280. unlink(Config::$lockfile);
  281. exit(1);
  282. }
  283.  
  284. function debug($msg) {
  285. if (Config::$debug)
  286. echo "$msg\n";
  287. }
  288.  
  289.  
  290. // fuck you
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement