diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81f8b2e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.vscode/ +/vendor/ + +/database/yggtracker.mwb.bak +/src/config/app.php + +/composer.lock \ No newline at end of file diff --git a/README.md b/README.md index 2e96018..6411e6a 100644 --- a/README.md +++ b/README.md @@ -1 +1,103 @@ -# YGGtracker \ No newline at end of file +# YGGtracker + +BitTorrent Catalog for Yggdrasil ecosystem + +YGGtracker uses [Yggdrasil](https://github.com/yggdrasil-network/yggdrasil-go) IPv6 addresses to identify users without registration. + +#### Online instances + + * [http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker](http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/) + * [http://94.140.114.241/yggtracker](http://94.140.114.241/yggtracker/) + +#### Trackers + + * `http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/announce` [stats](http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/stats) + * `http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/announce` [stats](http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/stats) + +#### Requirements + +``` +php8^ +php-pdo +php-mysql +sphinxsearch +``` +#### Installation + +* `git clone https://github.com/YGGverse/YGGtracker.git` +* `cd YGGtracker` +* `composer update` + +#### Setup +* Server configuration `/example/environment` +* The web root dir is `/src/public` +* Deploy the database using [MySQL Workbench](https://www.mysql.com/products/workbench) project presented in the `/database` folder +* Install [Sphinx Search Server](https://sphinxsearch.com) +* Configuration examples presented at `/config` folder +* Set up the `/src/crontab` by following [example](https://github.com/YGGverse/YGGtracker/blob/main/%20example/environment%20/crontab) + +#### Contribute + +Please make new branch for each PR + +``` +git checkout main +git checkout -b my-pr-branch-name +``` + +#### Roadmap + +* [ ] Magnet + + [x] Options + + [x] Public + + [x] Sensitive + + [x] Comments + + [ ] Features + + [x] Stars + + [x] Downloads + + [ ] Comments + + [ ] Views + + [ ] Info page + +* [ ] User + + [ ] Magnets + + [ ] Downloads + + [ ] Stars + + [ ] Following + + [ ] Followers + + [ ] Profile menu + + [ ] Magnets + + [ ] Downloads + + [ ] Stars + + [ ] Comments + +* [x] Other + + [x] RSS + + [x] Moderation + +#### Donate to contributors + +* @d47081: [BTC](https://www.blockchain.com/explorer/addresses/btc/bc1qngdf2kwty6djjqpk0ynkpq9wmlrmtm7e0c534y) | [LTC](https://live.blockcypher.com/ltc/address/LUSiqzKsfB1vBLvpu515DZktG9ioKqLyj7) | [XMR](835gSR1Uvka19gnWPkU2pyRozZugRZSPHDuFL6YajaAqjEtMwSPr4jafM8idRuBWo7AWD3pwFQSYRMRW9XezqrK4BEXBgXE) | [ZEPH](ZEPHsADHXqnhfWhXrRcXnyBQMucE3NM7Ng5ZVB99XwA38PTnbjLKpCwcQVgoie8EJuWozKgBiTmDFW4iY7fNEgSEWyAy4dotqtX) | [DOGE](https://dogechain.info/address/D5Sez493ibLqTpyB3xwQUspZvJ1cxEdRNQ) | Support our server by order [Linux VPS](https://www.yourserver.se/portal/aff.php?aff=610) + +#### License + +* Engine sources [MIT License](https://github.com/YGGverse/YGGtracker/blob/main/LICENSE) + +#### Components + +[Icons](https://icons.getbootstrap.com) + +#### Feedback + +Feel free to [share](https://github.com/YGGverse/YGGtracker/issues) your ideas and bug reports! + +#### Community + +* [Mastodon](https://mastodon.social/@YGGverse) +* [[matrix]](https://matrix.to/#/#YGGtracker:matrix.org) + +#### See also + +* [YGGo - YGGo! Distributed Web Search Engine ](https://github.com/YGGverse/YGGo) +* [YGGwave ~ The Radio Catalog](https://github.com/YGGverse/YGGwave) +* [YGGstate - Yggdrasil Network Explorer](https://github.com/YGGverse/YGGstate) \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..1deb105 --- /dev/null +++ b/composer.json @@ -0,0 +1,21 @@ +{ + "name": "yggverse/yggtracker", + "description": "Public BitTorrent tracker for Yggdrasil network", + "type": "library", + "require": { + "php": "^8.1", + "yggverse/parser": ">=0.2.0" + }, + "license": "MIT", + "autoload": { + "psr-4": { + "Yggverse\\Yggtracker\\": "src/" + } + }, + "authors": [ + { + "name": "YGGverse" + } + ], + "minimum-stability": "alpha" +} diff --git a/database/yggtracker.mwb b/database/yggtracker.mwb new file mode 100644 index 0000000..f069686 Binary files /dev/null and b/database/yggtracker.mwb differ diff --git a/example/environment /crontab b/example/environment /crontab new file mode 100644 index 0000000..0ec8c1d --- /dev/null +++ b/example/environment /crontab @@ -0,0 +1,4 @@ +@reboot searchd +@reboot indexer --all --rotate + +* * * * * indexer magnet --rotate \ No newline at end of file diff --git a/example/environment /sphinx.conf b/example/environment /sphinx.conf new file mode 100644 index 0000000..c8e5089 --- /dev/null +++ b/example/environment /sphinx.conf @@ -0,0 +1,52 @@ +source yggtracker +{ + type = mysql + + sql_port = 3306 + sql_host = localhost + sql_user = + sql_pass = + sql_db = +} + +source magnet : yggtracker +{ + sql_query = \ + SELECT `magnet`.`timeAdded`, \ + `magnet`.`timeUpdated`, \ + `magnet`.`magnetId`, \ + `magnet`.`metaTitle`, \ + `magnet`.`metaDescription`, \ + `magnet`.`dn`, \ + (SELECT GROUP_CONCAT(DISTINCT `keywordTopic`.`value`) \ + FROM `keywordTopic` \ + JOIN `magnetToKeywordTopic` ON (`magnetToKeywordTopic`.`magnetId` = `magnet`.`magnetId`) \ + WHERE `keywordTopic`.`keywordTopicId` = `magnetToKeywordTopic`.`keywordTopicId`) AS `keywords`, \ + (SELECT GROUP_CONCAT(DISTINCT `magnetComment`.`value`) \ + FROM `magnetComment` \ + WHERE `magnetComment`.`magnetId` = `magnet`.`magnetId`) AS `comments` \ + FROM `magnet`\ + + sql_attr_uint = magnetId +} + +index magnet +{ + source = magnet + path = /var/lib/sphinxsearch/data/magnet + + morphology = stem_cz, stem_ar, lemmatize_de_all, lemmatize_ru_all, lemmatize_en_all # stem_enru + + min_word_len = 2 + min_prefix_len = 2 + + html_strip = 1 + + index_exact_words = 1 +} + +indexer +{ + mem_limit = 256M + lemmatizer_cache = 256M +} \ No newline at end of file diff --git a/src/config/app.php.example b/src/config/app.php.example new file mode 100644 index 0000000..7761939 --- /dev/null +++ b/src/config/app.php.example @@ -0,0 +1,102 @@ + (object) + [ + 'announce' => 'http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/announce', + 'stats' => 'http://[201:23b4:991a:634d:8359:4521:5576:15b7]/yggtracker/stats', + ], + 'Tracker 2' => (object) + [ + 'announce' => 'http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/announce', + 'stats' => 'http://[200:1e2f:e608:eb3a:2bf:1e62:87ba:e2f7]/stats', + ], + // ... + ] +); + +// Yggdrasil +define('YGGDRASIL_URL_REGEX', '/^0{0,1}[2-3][a-f0-9]{0,2}:/'); // thanks to @ygguser (https://github.com/YGGverse/YGGo/issues/1#issuecomment-1498182228 ) diff --git a/src/library/database.php b/src/library/database.php new file mode 100644 index 0000000..4c8dccc --- /dev/null +++ b/src/library/database.php @@ -0,0 +1,1032 @@ +_db = new PDO('mysql:dbname=' . $database . ';host=' . $host . ';port=' . $port . ';charset=utf8', $username, $password, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); + $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + $this->_db->setAttribute(PDO::ATTR_TIMEOUT, 600); + + $this->_debug = (object) + [ + 'query' => (object) + [ + 'select' => (object) + [ + 'total' => 0 + ], + 'insert' => (object) + [ + 'total' => 0 + ], + 'update' => (object) + [ + 'total' => 0 + ], + 'delete' => (object) + [ + 'total' => 0 + ], + ] + ]; + } + + // Tools + public function beginTransaction() { + + $this->_db->beginTransaction(); + } + + public function commit() { + + $this->_db->commit(); + } + + public function rollBack() { + + $this->_db->rollBack(); + } + + public function getDebug() { + + return $this->_debug; + } + + // Scheme + public function addScheme(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `scheme` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function getScheme(int $schemeId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `scheme` WHERE `schemeId` = ?'); + + $query->execute([$schemeId]); + + return $query->fetch(); + } + + public function findScheme(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `scheme` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function initSchemeId(string $value) : int { + + if ($result = $this->findScheme($value)) { + + return $result->schemeId; + } + + return $this->addScheme($value); + } + + // Host + public function addHost(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `host` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function getHost(int $hostId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `host` WHERE `hostId` = ?'); + + $query->execute([$hostId]); + + return $query->fetch(); + } + + public function findHost(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `host` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function initHostId(string $value) : int { + + if ($result = $this->findHost($value)) { + + return $result->hostId; + } + + return $this->addHost($value); + } + + // Port + public function addPort(mixed $value) : int { + + $this->_debug->query->insert->total++; + + if ($value) { + + $query = $this->_db->prepare('INSERT INTO `port` SET `value` = ?'); + + $query->execute([$value]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `port` SET `value` = NULL'); + + $query->execute(); + } + + return $this->_db->lastInsertId(); + } + + public function getPort(int $portId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `port` WHERE `portId` = ?'); + + $query->execute([$portId]); + + return $query->fetch(); + } + + public function findPort(mixed $value) { + + $this->_debug->query->select->total++; + + if ($value) { + + $query = $this->_db->prepare('SELECT * FROM `port` WHERE `value` = ?'); + + $query->execute([$value]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `port` WHERE `value` IS NULL'); + + $query->execute(); + } + + return $query->fetch(); + } + + public function initPortId(mixed $value) : int { + + if ($result = $this->findPort($value)) { + + return $result->portId; + } + + return $this->addPort($value); + } + + // URI + public function addUri(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `uri` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function getUri(int $uriId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `uri` WHERE `uriId` = ?'); + + $query->execute([$uriId]); + + return $query->fetch(); + } + + public function findUri(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `uri` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function initUriId(string $value) : int { + + if ($result = $this->findUri($value)) { + + return $result->uriId; + } + + return $this->addUri($value); + } + + // Address Tracker + public function addAddressTracker(int $schemeId, int $hostId, int $portId, int $uriId) : int { + + $this->_debug->query->insert->total++; + + if ($portId) { + + $query = $this->_db->prepare('INSERT INTO `addressTracker` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `addressTracker` SET `schemeId` = ?, `hostId` = ?, `portId` = NULL, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $this->_db->lastInsertId(); + } + + public function getAddressTracker(int $addressTrackerId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `addressTrackerId` = ?'); + + $query->execute([$addressTrackerId]); + + return $query->fetch(); + } + + public function findAddressTracker(int $schemeId, int $hostId, mixed $portId, int $uriId) { + + $this->_debug->query->select->total++; + + if ($portId) { + + $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` = ? AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `addressTracker` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` IS NULL AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $query->fetch(); + } + + public function initAddressTrackerId(int $schemeId, int $hostId, mixed $portId, int $uriId) : int { + + if ($result = $this->findAddressTracker($schemeId, $hostId, $portId, $uriId)) { + + return $result->addressTrackerId; + } + + return $this->addAddressTracker($schemeId, $hostId, $portId, $uriId); + } + + // Acceptable Source + public function addAcceptableSource(int $schemeId, int $hostId, int $portId, int $uriId) : int { + + $this->_debug->query->insert->total++; + + if ($portId) { + + $query = $this->_db->prepare('INSERT INTO `acceptableSource` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `acceptableSource` SET `schemeId` = ?, `hostId` = ?, `portId` = NULL, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $this->_db->lastInsertId(); + } + + public function getAcceptableSource(int $acceptableSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `acceptableSourceId` = ?'); + + $query->execute([$acceptableSourceId]); + + return $query->fetch(); + } + + public function findAcceptableSource(int $schemeId, int $hostId, mixed $portId, int $uriId) { + + $this->_debug->query->select->total++; + + if ($portId) { + + $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` = ? AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `acceptableSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` IS NULL AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $query->fetch(); + } + + public function initAcceptableSourceId(int $schemeId, int $hostId, mixed $portId, int $uriId) : int { + + if ($result = $this->findAcceptableSource($schemeId, $hostId, $portId, $uriId)) { + + return $result->acceptableSourceId; + } + + return $this->addAcceptableSource($schemeId, $hostId, $portId, $uriId); + } + + // eXact Source + public function addExactSource(int $schemeId, int $hostId, int $portId, int $uriId) : int { + + $this->_debug->query->insert->total++; + + if ($portId) { + + $query = $this->_db->prepare('INSERT INTO `eXactSource` SET `schemeId` = ?, `hostId` = ?, `portId` = ?, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('INSERT INTO `eXactSource` SET `schemeId` = ?, `hostId` = ?, `portId` = NULL, `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $this->_db->lastInsertId(); + } + + public function getExactSource(int $eXactSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `eXactSourceId` = ?'); + + $query->execute([$eXactSourceId]); + + return $query->fetch(); + } + + public function findExactSource(int $schemeId, int $hostId, mixed $portId, int $uriId) { + + $this->_debug->query->select->total++; + + if ($portId) { + + $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` = ? AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $portId, $uriId]); + + } else { + + $query = $this->_db->prepare('SELECT * FROM `eXactSource` WHERE `schemeId` = ? AND `hostId` = ? AND `portId` IS NULL AND `uriId` = ?'); + + $query->execute([$schemeId, $hostId, $uriId]); + } + + return $query->fetch(); + } + + public function initExactSourceId(int $schemeId, int $hostId, mixed $portId, int $uriId) : int { + + if ($result = $this->findExactSource($schemeId, $hostId, $portId, $uriId)) { + + return $result->eXactSourceId; + } + + return $this->addExactSource($schemeId, $hostId, $portId, $uriId); + } + + // Keyword Topic + public function addKeywordTopic(string $value) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `keywordTopic` SET `value` = ?'); + + $query->execute([$value]); + + return $this->_db->lastInsertId(); + } + + public function findKeywordTopic(string $value) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `keywordTopic` WHERE `value` = ?'); + + $query->execute([$value]); + + return $query->fetch(); + } + + public function getKeywordTopic(int $keywordTopicId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `keywordTopic` WHERE `keywordTopicId` = ?'); + + $query->execute([$keywordTopicId]); + + return $query->fetch(); + } + + public function initKeywordTopicId(string $value) : int { + + if ($result = $this->findKeywordTopic($value)) { + + return $result->keywordTopicId; + } + + return $this->addKeywordTopic($value); + } + + // User + public function addUser(string $address, bool $approved, $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `user` SET `address` = ?, `approved` = ?, `timeAdded` = ?'); + + $query->execute([$address, (int) $approved, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function getUser(int $userId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `user` WHERE `userId` = ?'); + + $query->execute([$userId]); + + return $query->fetch(); + } + + public function findUserByAddress(string $address) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `user` WHERE `address` = ?'); + + $query->execute([$address]); + + return $query->fetch(); + } + + public function initUserId(string $address, bool $approved, int $timeAdded) : int { + + if ($result = $this->findUserByAddress($address)) { + + return $result->userId; + } + + return $this->addUser($address, $approved, $timeAdded); + } + + // Magnet + public function addMagnet(int $userId, + string $xt, + int $xl, + string $dn, + string $linkSource, + bool $public, + bool $comments, + bool $sensitive, + bool $approved, + int $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnet` SET `userId` = ?, + `xt` = ?, + `xl` = ?, + `dn` = ?, + `linkSource` = ?, + `public` = ?, + `comments` = ?, + `sensitive` = ?, + `approved` = ?, + `timeAdded` = ?'); + + $query->execute( + [ + $userId, + $xt, + $xl, + $dn, + $linkSource, + $public ? 1 : 0, + $comments ? 1 : 0, + $sensitive ? 1 : 0, + $approved ? 1 : 0, + $timeAdded + ] + ); + + return $this->_db->lastInsertId(); + } + + public function getMagnet(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch(); + } + + public function findMagnet(int $userId, string $xt) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnet` WHERE `userId` = ? AND `xt` = ?'); + + $query->execute([$userId, $xt]); + + return $query->fetch(); + } + + public function initMagnetId( int $userId, + string $xt, + int $xl, + string $dn, + string $linkSource, + bool $public, + bool $comments, + bool $sensitive, + bool $approved, + int $timeAdded) : int { + + if ($result = $this->findMagnet($userId, $xt)) { + + return $result->magnetId; + } + + return $this->addMagnet($userId, + $xt, + $xl, + $dn, + $linkSource, + $public, + $comments, + $sensitive, + $approved, + $timeAdded); + } + + public function updateMagnetDn(int $magnetId, string $dn, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `dn` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([$dn, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetMetaTitle(int $magnetId, string $metaTitle, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `metaTitle` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([$metaTitle, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetMetaDescription(int $magnetId, string $metaDescription, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `metaDescription` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([$metaDescription, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetPublic(int $magnetId, bool $public, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `public` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $public, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetComments(int $magnetId, bool $comments, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `comments` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $comments, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetSensitive(int $magnetId, bool $sensitive, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `sensitive` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $sensitive, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + public function updateMagnetApproved(int $magnetId, bool $approved, int $timeUpdated) : int { + + $this->_debug->query->update->total++; + + $query = $this->_db->prepare('UPDATE `magnet` SET `approved` = ?, `timeUpdated` = ? WHERE `magnetId` = ?'); + + $query->execute([(int) $approved, $timeUpdated, $magnetId]); + + return $query->rowCount(); + } + + // Magnet to AddressTracker + public function addMagnetToAddressTracker(int $magnetId, int $addressTrackerId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToAddressTracker` SET `magnetId` = ?, `addressTrackerId` = ?'); + + $query->execute([$magnetId, $addressTrackerId]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetToAddressTrackerByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->rowCount(); + } + + public function findMagnetToAddressTracker(int $magnetId, int $addressTrackerId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` WHERE `magnetId` = ? AND `addressTrackerId` = ?'); + + $query->execute([$magnetId, $addressTrackerId]); + + return $query->fetch(); + } + + public function findAddressTrackerByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAddressTracker` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToAddressTrackerId(int $magnetId, int $addressTrackerId) : int { + + if ($result = $this->findMagnetToAddressTracker($magnetId, $addressTrackerId)) { + + return $result->magnetToAddressTrackerId; + } + + return $this->addMagnetToAddressTracker($magnetId, $addressTrackerId); + } + + // Magnet to AcceptableSource + public function addMagnetToAcceptableSource(int $magnetId, int $acceptableSourceId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToAcceptableSource` SET `magnetId` = ?, `acceptableSourceId` = ?'); + + $query->execute([$magnetId, $acceptableSourceId]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetToAcceptableSourceByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->rowCount(); + } + + public function findMagnetToAcceptableSource(int $magnetId, int $acceptableSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAcceptableSource` WHERE `magnetId` = ? AND `acceptableSourceId` = ?'); + + $query->execute([$magnetId, $acceptableSourceId]); + + return $query->fetch(); + } + + public function findAcceptableSourceByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToAcceptableSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToAcceptableSourceId(int $magnetId, int $acceptableSourceId) : int { + + if ($result = $this->findMagnetToAcceptableSource($magnetId, $acceptableSourceId)) { + + return $result->magnetToAcceptableSourceId; + } + + return $this->addMagnetToAcceptableSource($magnetId, $acceptableSourceId); + } + + // Magnet to eXactSource + public function addMagnetToExactSource(int $magnetId, int $eXactSourceId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToExactSource` SET `magnetId` = ?, `eXactSourceId` = ?'); + + $query->execute([$magnetId, $eXactSourceId]); + + return $this->_db->lastInsertId(); + } + + + public function deleteMagnetToExactSourceByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToExactSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $this->_db->lastInsertId(); + } + + public function findMagnetToExactSource(int $magnetId, int $eXactSourceId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToExactSource` WHERE `magnetId` = ? AND `eXactSourceId` = ?'); + + $query->execute([$magnetId, $eXactSourceId]); + + return $query->fetch(); + } + + public function findExactSourceByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToExactSource` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToExactSourceId(int $magnetId, int $eXactSourceId) : int { + + if ($result = $this->findMagnetToEXactSource($magnetId, $eXactSourceId)) { + + return $result->magnetToExactSourceId; + } + + return $this->addMagnetToEXactSource($magnetId, $eXactSourceId); + } + + // Magnet to KeywordTopic + public function addMagnetToKeywordTopic(int $magnetId, int $keywordTopicId) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetToKeywordTopic` SET `magnetId` = ?, `keywordTopicId` = ?'); + + $query->execute([$magnetId, $keywordTopicId]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetToKeywordTopicByMagnetId(int $magnetId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetToKeywordTopic` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->rowCount(); + } + + public function findMagnetToKeywordTopic(int $magnetId, int $keywordTopicId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToKeywordTopic` WHERE `magnetId` = ? AND `keywordTopicId` = ?'); + + $query->execute([$magnetId, $keywordTopicId]); + + return $query->fetch(); + } + + public function findKeywordTopicByMagnetId(int $magnetId) { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT * FROM `magnetToKeywordTopic` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetchAll(); + } + + public function initMagnetToKeywordTopicId(int $magnetId, int $keywordTopicId) : int { + + if ($result = $this->findMagnetToKeywordTopic($magnetId, $keywordTopicId)) { + + return $result->magnetToKeywordTopicId; + } + + return $this->addMagnetToKeywordTopic($magnetId, $keywordTopicId); + } + + // Magnet comment + public function getMagnetCommentsTotal(int $magnetId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetComment` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch()->result; + } + + public function findMagnetCommentsTotalByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetComment` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->fetch()->result; + } + + // Magnet star + public function addMagnetStar(int $magnetId, int $userId, int $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetStar` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); + + $query->execute([$magnetId, $userId, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function deleteMagnetStarByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetStar` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->rowCount(); + } + + public function getMagnetStarsTotal(int $magnetId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetStar` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch()->result; + } + + public function findMagnetStarsTotalByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetStar` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->fetch()->result; + } + + // Magnet download + public function addMagnetDownload(int $magnetId, int $userId, int $timeAdded) : int { + + $this->_debug->query->insert->total++; + + $query = $this->_db->prepare('INSERT INTO `magnetDownload` SET `magnetId` = ?, `userId` = ?, `timeAdded` = ?'); + + $query->execute([$magnetId, $userId, $timeAdded]); + + return $this->_db->lastInsertId(); + } + + public function getMagnetDownloadsTotal(int $magnetId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetDownload` WHERE `magnetId` = ?'); + + $query->execute([$magnetId]); + + return $query->fetch()->result; + } + + public function deleteMagnetDownloadByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->delete->total++; + + $query = $this->_db->prepare('DELETE FROM `magnetDownload` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->rowCount(); + } + + public function findMagnetDownloadsTotalByUserId(int $magnetId, int $userId) : int { + + $this->_debug->query->select->total++; + + $query = $this->_db->prepare('SELECT COUNT(*) AS `result` FROM `magnetDownload` WHERE `magnetId` = ? AND `userId` = ?'); + + $query->execute([$magnetId, $userId]); + + return $query->fetch()->result; + } +} \ No newline at end of file diff --git a/src/library/sphinx.php b/src/library/sphinx.php new file mode 100644 index 0000000..2eff9aa --- /dev/null +++ b/src/library/sphinx.php @@ -0,0 +1,49 @@ +_sphinx = new PDO('mysql:host=' . $host . ';port=' . $port . ';charset=utf8', false, false, [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8']); + $this->_sphinx->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->_sphinx->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + } + + public function searchMagnetsTotal(string $keyword) { + + $query = $this->_sphinx->prepare('SELECT COUNT(*) AS `total` FROM `magnet` WHERE MATCH(?)'); + + $query->execute( + [ + $keyword + ] + ); + + return $query->fetch()->total; + } + + public function searchMagnets(string $keyword, int $start, int $limit, int $maxMatches) { + + $query = $this->_sphinx->prepare("SELECT * + + FROM `magnet` + + WHERE MATCH(?) + + ORDER BY `magnetId` DESC, WEIGHT() DESC + + LIMIT " . (int) ($start >= $maxMatches ? ($maxMatches > 0 ? $maxMatches - 1 : 0) : $start) . "," . (int) $limit . " + + OPTION `max_matches`=" . (int) ($maxMatches >= 1 ? $maxMatches : 1)); + + $query->execute( + [ + $keyword + ] + ); + + return $query->fetchAll(); + } +} diff --git a/src/library/time.php b/src/library/time.php new file mode 100644 index 0000000..9199d3c --- /dev/null +++ b/src/library/time.php @@ -0,0 +1,45 @@ + _('year'), + 30 * 24 * 60 * 60 => _('month'), + 24 * 60 * 60 => _('day'), + 60 * 60 => _('hour'), + 60 => _('minute'), + 1 => _('second') + ]; + + $plural = [ + _('year') => _('years'), + _('month') => _('months'), + _('day') => _('days'), + _('hour') => _('hours'), + _('minute') => _('minutes'), + _('second') => _('seconds') + ]; + + foreach ($values as $key => $value) + { + $result = $diff / $key; + + if ($result >= 1) + { + $round = round($result); + + return sprintf('%s %s ago', $round, $round > 1 ? $plural[$value] : $value); + } + } + } +} diff --git a/src/public/action.php b/src/public/action.php new file mode 100644 index 0000000..58a9763 --- /dev/null +++ b/src/public/action.php @@ -0,0 +1,442 @@ + true, + 'message' => _('Internal server error') +]; + +// Begin action request +switch (isset($_GET['target']) ? urldecode($_GET['target']) : false) +{ + case 'comment': + + break; + + case 'star': + + // Yggdrasil connections only + if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) + { + $response->success = false; + $response->message = _('Yggdrasil connection required for this action'); + } + + // Init session + else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) + { + $response->success = false; + $response->message = _('Could not init user session'); + } + + // Magnet exists + else if (!$magnet = $db->getMagnet(isset($_GET['magnetId']) && $_GET['magnetId'] > 0 ? (int) $_GET['magnetId'] : 0)) + { + $response->success = false; + $response->message = _('Requested magnet not found'); + } + + // Access allowed + else if (!($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved))) { + + $response->success = false; + $response->message = _('Magnet not available for this action'); + } + + // Validate callback + else if (empty($_GET['callback'])) + { + $response->success = false; + $response->message = _('Callback required'); + } + + // Validate base64 + else if (!$callback = (string) @base64_decode($_GET['callback'])) + { + $response->success = false; + $response->message = _('Invalid callback encoding'); + } + + // Request valid + else + { + // Star exists, trigger delete + if ($db->findMagnetStarsTotalByUserId($magnet->magnetId, $userId)) + { + $db->deleteMagnetStarByUserId($magnet->magnetId, $userId); + } + else + { + // Star not exists, trigger add + $db->addMagnetStar($magnet->magnetId, $userId, time()); + } + + // Redirect to edit page + header( + sprintf('Location: %s', $callback) + ); + } + + break; + + case 'download': + + // Yggdrasil connections only + if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) + { + $response->success = false; + $response->message = _('Yggdrasil connection required for this action'); + } + + // Init session + else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) + { + $response->success = false; + $response->message = _('Could not init user session'); + } + + // Magnet exists + else if (!$magnet = $db->getMagnet(isset($_GET['magnetId']) && $_GET['magnetId'] > 0 ? (int) $_GET['magnetId'] : 0)) + { + $response->success = false; + $response->message = _('Requested magnet not found'); + } + + // Access allowed + else if (!($_SERVER['REMOTE_ADDR'] == $db->getUser($magnet->userId)->address || in_array($_SERVER['REMOTE_ADDR'], MODERATOR_IP_LIST) || ($magnet->public && $magnet->approved))) { + + $response->success = false; + $response->message = _('Magnet not available for this action'); + } + + // Request valid + else + { + // Download exists, trigger delete + if (!$db->findMagnetDownloadsTotalByUserId($magnet->magnetId, $userId)) + { + // Download not exists, add new record + $db->addMagnetDownload($magnet->magnetId, $userId, time()); + } + + // Build magnet link + $link = []; + + /// Exact Topic + $link[] = sprintf('magnet:?xt=%s', $magnet->xt); + + /// Display Name + $link[] = sprintf('dn=%s', urlencode($magnet->dn)); + + // Keyword Topic + $kt = []; + + foreach ($db->findKeywordTopicByMagnetId($magnet->magnetId) as $result) + { + $kt[] = urlencode($db->getKeywordTopic($result->keywordTopicId)->value); + } + + $link[] = sprintf('kt=%s', implode('+', $kt)); + + /// Address Tracker + foreach ($db->findAddressTrackerByMagnetId($magnet->magnetId) as $result) + { + $addressTracker = $db->getAddressTracker($result->addressTrackerId); + + $scheme = $db->getScheme($addressTracker->schemeId); + $host = $db->getHost($addressTracker->hostId); + $port = $db->getPort($addressTracker->portId); + $uri = $db->getUri($addressTracker->uriId); + + $link[] = sprintf('tr=%s', urlencode($port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value))); + } + + /// Acceptable Source + foreach ($db->findAcceptableSourceByMagnetId($magnet->magnetId) as $result) + { + $acceptableSource = $db->getAcceptableSource($result->acceptableSourceId); + + $scheme = $db->getScheme($acceptableSource->schemeId); + $host = $db->getHost($acceptableSource->hostId); + $port = $db->getPort($acceptableSource->portId); + $uri = $db->getUri($acceptableSource->uriId); + + $link[] = sprintf('as=%s', urlencode($port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value))); + } + + /// Exact Source + foreach ($db->findExactSourceByMagnetId($magnet->magnetId) as $result) + { + $eXactSource = $db->getExactSource($result->eXactSourceId); + + $scheme = $db->getScheme($eXactSource->schemeId); + $host = $db->getHost($eXactSource->hostId); + $port = $db->getPort($eXactSource->portId); + $uri = $db->getUri($eXactSource->uriId); + + $link[] = sprintf('xs=%s', urlencode($port->value ? sprintf('%s://%s:%s%s', $scheme->value, + $host->value, + $port->value, + $uri->value) : sprintf('%s://%s%s', $scheme->value, + $host->value, + $uri->value))); + } + + // Redirect to edit page + header( + sprintf('Location: %s', implode('&', $link)) + ); + } + + break; + + case 'new': + + // Yggdrasil connections only + if (!preg_match(YGGDRASIL_URL_REGEX, $_SERVER['REMOTE_ADDR'])) + { + $response->success = false; + $response->message = _('Yggdrasil connection required for this action'); + } + + // Init session + else if (!$userId = $db->initUserId($_SERVER['REMOTE_ADDR'], USER_DEFAULT_APPROVED, time())) + { + $response->success = false; + $response->message = _('Could not init user session'); + } + + // Validate link + if (empty($_GET['magnet'])) + { + $response->success = false; + $response->message = _('Link required'); + } + + // Validate base64 + else if (!$link = (string) @base64_decode($_GET['magnet'])) + { + $response->success = false; + $response->message = _('Invalid link encoding'); + } + + // Validate magnet + else if (!$magnet = Yggverse\Parser\Magnet::parse($link)) + { + $response->success = false; + $response->message = _('Invalid magnet link'); + } + + // Request valid + else + { + // Begin magnet registration + try + { + $db->beginTransaction(); + + // Init magnet + if (Yggverse\Parser\Urn::parse($magnet->xt)) + { + if ($magnetId = $db->initMagnetId($userId, + strip_tags($magnet->xt), + strip_tags($magnet->xl), + strip_tags($magnet->dn), + $link, + MAGNET_DEFAULT_PUBLIC, + MAGNET_DEFAULT_COMMENTS, + MAGNET_DEFAULT_SENSITIVE, + MAGNET_DEFAULT_APPROVED, + time())) + { + foreach ($magnet as $key => $value) + { + switch ($key) + { + case 'tr': + foreach ($value as $tr) + { + if ($url = Yggverse\Parser\Url::parse($tr)) + { + $db->initMagnetToAddressTrackerId( + $magnetId, + $db->initAddressTrackerId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + } + } + break; + case 'ws': + foreach ($value as $ws) + { + // @TODO + } + break; + case 'as': + foreach ($value as $as) + { + if ($url = Yggverse\Parser\Url::parse($as)) + { + $db->initMagnetToAcceptableSourceId( + $magnetId, + $db->initAcceptableSourceId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + } + } + break; + case 'xs': + foreach ($value as $xs) + { + if ($url = Yggverse\Parser\Url::parse($xs)) + { + $db->initMagnetToExactSourceId( + $magnetId, + $db->initExactSourceId( + $db->initSchemeId($url->host->scheme), + $db->initHostId($url->host->name), + $db->initPortId($url->host->port), + $db->initUriId($url->page->uri) + ) + ); + } + } + break; + case 'mt': + foreach ($value as $mt) + { + // @TODO + } + break; + case 'x.pe': + foreach ($value as $xPe) + { + // @TODO + } + break; + case 'kt': + foreach ($value as $kt) + { + $db->initMagnetToKeywordTopicId( + $magnetId, + $db->initKeywordTopicId(trim(mb_strtolower(strip_tags(html_entity_decode($kt))))) + ); + } + break; + } + } + + $db->commit(); + + // Redirect to edit page + header(sprintf('Location: %s/edit.php?magnetId=%s', trim(WEBSITE_URL, '/'), $magnetId)); + } + } + + } catch (Exception $e) { + + var_dump($e); + + $db->rollBack(); + } + } + + break; + default: + header( + sprintf('Location: %s', WEBSITE_URL) + ); +} + +?> + + + +
+ + +