diff --git a/README.en.md b/README.en.md index 6e1980f..9af4a6f 100644 --- a/README.en.md +++ b/README.en.md @@ -151,6 +151,29 @@ composer install php index.php ``` +## Custom Output Format +To export data according to a specified template, use format=custom and template=template, where the template can include patterns such as: + +| свойство | описание | +|------------|----------------------------------------| +| group | Group name | +| site | Site name | +| data | Selected data | +| shortmask | Subnet mask (short) (for IP and CIDR) | +| mask | Subnet mask (full) (for IP and CIDR) | + +Examples: +``` +Wildcard domains for Twitter DNS static add on MikroTik for forward-to=localhost: +https://iplist.opencck.org/?format=custom&data=domains&site=x.com&wildcard=1&template=%2Fip%20dns%20static%20add%20name%3D%7Bdata%7D%20type%3DFWD%20address-list%3D%7Bgroup%7D_%7Bsite%7D%20match-subdomain%3Dyes%20forward-to%3Dlocalhost + +Wildcard domains in custom format: +https://iplist.opencck.org/?format=custom&data=domains&wildcard=1&template=data%3A%20%7Bdata%7D%20group%3A%20%7Bgroup%7D%20site%3A%20%7Bsite%7D + +Subnet mask in custom format: +https://iplist.opencck.org/?format=custom&data=cidr4&template=data%3A%20%7Bdata%7D%20group%3A%20%7Bgroup%7D%20site%3A%20%7Bsite%7D%20shortmask%3A%20%7Bshortmask%7D%20mask%3A%20%7Bmask%7D +``` + ## Setting up Mikrotik - In the router's admin panel (or via winbox), navigate to System -> Scripts. - Create a new script by clicking "Add new" and give it a name, for example `iplist_v4_cidr` diff --git a/README.md b/README.md index f9eed19..09782e7 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,29 @@ composer install php index.php ``` +## Кастомный формат вывода +Для получения выгрузки данных по заданному шаблону используется format=custom и template=шаблон, где шаблон может содержать такие паттерны как: + +| свойство | описание | +|------------|------------------------------------------| +| group | Имя группы | +| site | Имя сайта | +| data | Выбранные данные | +| shortmask | Маска подсети (короткая) (для ip и cidr) | +| mask | Маска подсети (полная) (для ip и cidr) | + +Примеры: +``` +Wildcard домены twitter для dns static add под mikrotik для forward-to=localhost: +https://iplist.opencck.org/?format=custom&data=domains&site=x.com&wildcard=1&template=%2Fip%20dns%20static%20add%20name%3D%7Bdata%7D%20type%3DFWD%20address-list%3D%7Bgroup%7D_%7Bsite%7D%20match-subdomain%3Dyes%20forward-to%3Dlocalhost + +Wildcard домены в кастомном формате: +https://iplist.opencck.org/?format=custom&data=domains&wildcard=1&template=data%3A%20%7Bdata%7D%20group%3A%20%7Bgroup%7D%20site%3A%20%7Bsite%7D + +Маска подсети в кастомном формате: +https://iplist.opencck.org/?format=custom&data=cidr4&template=data%3A%20%7Bdata%7D%20group%3A%20%7Bgroup%7D%20site%3A%20%7Bsite%7D%20shortmask%3A%20%7Bshortmask%7D%20mask%3A%20%7Bmask%7D +``` + ## Настройка Mikrotik - В администраторской панели роутера (или через winbox) откройте раздел System -> Scripts - Создайте новый скрипт "Add new" с произвольным именем, например `iplist_v4_cidr` diff --git a/src/App/Controller/AbstractIPListController.php b/src/App/Controller/AbstractIPListController.php index 51722ac..7e780e5 100644 --- a/src/App/Controller/AbstractIPListController.php +++ b/src/App/Controller/AbstractIPListController.php @@ -57,7 +57,7 @@ abstract class AbstractIPListController extends AbstractController { */ protected function getGroups(): array { $groups = []; - foreach ($this->service->sites as $siteEntity) { + foreach ($this->getSites() as $siteEntity) { $groups[$siteEntity->group][$siteEntity->name] = $siteEntity; } return $groups; diff --git a/src/App/Controller/BatController.php b/src/App/Controller/BatController.php index 432e43d..eb4108b 100644 --- a/src/App/Controller/BatController.php +++ b/src/App/Controller/BatController.php @@ -3,6 +3,7 @@ namespace OpenCCK\App\Controller; use OpenCCK\Domain\Factory\SiteFactory; +use OpenCCK\Domain\Helper\IP4Helper; /** * @see https://help.keenetic.com/hc/ru/articles/213966749-%D0%94%D0%BE%D0%B1%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85-%D0%BC%D0%B0%D1%80%D1%88%D1%80%D1%83%D1%82%D0%BE%D0%B2-%D0%B8%D0%B7-%D1%84%D0%B0%D0%B9%D0%BB%D0%B0-bat-%D0%B2-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82-%D1%86%D0%B5%D0%BD%D1%82%D1%80-%D0%B4%D0%BB%D1%8F-%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D0%B9-NDMS-2-11-%D0%B8-%D0%B1%D0%BE%D0%BB%D0%B5%D0%B5-%D1%80%D0%B0%D0%BD%D0%BD%D0%B8%D1%85 @@ -39,27 +40,9 @@ class BatController extends AbstractIPListController { "\n", array_map(function (string $item) { $parts = explode('/', $item); - $mask = $this->formatShortIpMask($parts[1] ?? ''); + $mask = IP4Helper::formatShortIpMask($parts[1] ?? ''); return 'route add ' . $parts[0] . ' mask ' . $mask . ' 0.0.0.0'; }, $response) ); } - - /** - * @param string $mask - * @return string - */ - private function formatShortIpMask(string $mask): string { - if ($mask == '') { - $mask = '32'; - } - $binaryMask = str_repeat('1', $mask) . str_repeat('0', 32 - $mask); - $octets = []; - - for ($i = 0; $i < 4; $i++) { - $octets[] = bindec(substr($binaryMask, $i * 8, 8)); - } - - return implode('.', $octets); - } } diff --git a/src/App/Controller/CustomController.php b/src/App/Controller/CustomController.php new file mode 100644 index 0000000..aba34ab --- /dev/null +++ b/src/App/Controller/CustomController.php @@ -0,0 +1,104 @@ +setHeaders(['content-type' => 'text/plain']); + + $sites = SiteFactory::normalizeArray($this->request->getQueryParameters()['site'] ?? []); + $data = $this->request->getQueryParameter('data') ?? ''; + $template = $this->request->getQueryParameter('template') ?? ''; + if ($data == '') { + return "# Error: The 'data' GET parameter is required in the URL to access this page"; + } + if ($template == '') { + return "# Error: The 'template' GET parameter is required in the URL to access this page"; + } + + $response = []; + foreach ($this->getGroups() as $groupName => $groupSites) { + if (count($sites)) { + $groupSites = array_filter($groupSites, fn(Site $siteEntity) => in_array($siteEntity->name, $sites)); + } + if (!count($groupSites)) { + continue; + } + + $items = []; + foreach ($groupSites as $siteName => $siteEntity) { + $items = array_merge( + $items, + $this->generateList( + $siteEntity, + SiteFactory::normalizeArray( + $siteEntity->{$data}, + in_array($data, ['ip4', 'ip6', 'cidr4', 'cidr6']) + ), + $data, + $template + ) + ); + } + + $response = array_merge($response, $items); + } + + return implode("\n", $response); + } + + /** + * @param Site $siteEntity + * @param array $dataArray + * @param string $data + * @param string $template + * @return array + */ + private function generateList(Site $siteEntity, array $dataArray, string $data, string $template): array { + $items = []; + foreach ($dataArray as $item) { + $patterns = [ + 'group' => $siteEntity->group, + 'site' => $siteEntity->name, + 'data' => $item, + ]; + switch ($data) { + case 'ip4': + $patterns['shortmask'] = '32'; + $patterns['mask'] = IP4Helper::formatShortIpMask($patterns['shortmask']); + break; + case 'ip6': + $patterns['shortmask'] = '128'; + $patterns['mask'] = IP6Helper::formatShortIpMask($patterns['shortmask']); + break; + case 'cidr4': + $parts = explode('/', $item); + $patterns['shortmask'] = $parts[1]; + $patterns['mask'] = IP4Helper::formatShortIpMask($patterns['shortmask']); + break; + case 'cidr6': + $parts = explode('/', $item); + $patterns['shortmask'] = $parts[1]; + $patterns['mask'] = IP6Helper::formatShortIpMask($patterns['shortmask']); + break; + case 'domains': + break; + } + + $result = $template; + foreach ($patterns as $key => $value) { + $result = str_replace('{' . $key . '}', $value, $result); + } + $items[] = $result; + } + return $items; + } +} diff --git a/src/Domain/Helper/IP4Helper.php b/src/Domain/Helper/IP4Helper.php index 0d88d2c..b736986 100644 --- a/src/Domain/Helper/IP4Helper.php +++ b/src/Domain/Helper/IP4Helper.php @@ -155,4 +155,23 @@ class IP4Helper { } return false; } + + /** + * преобразование короткой маски IPv4 в полную + * @param string $mask + * @return string + */ + public static function formatShortIpMask(string $mask): string { + if ($mask == '') { + $mask = '32'; + } + $binaryMask = str_repeat('1', (int) $mask) . str_repeat('0', 32 - $mask); + $octets = []; + + for ($i = 0; $i < 4; $i++) { + $octets[] = bindec(substr($binaryMask, $i * 8, 8)); + } + + return implode('.', $octets); + } } diff --git a/src/Domain/Helper/IP6Helper.php b/src/Domain/Helper/IP6Helper.php index 58e1e24..e9828c9 100644 --- a/src/Domain/Helper/IP6Helper.php +++ b/src/Domain/Helper/IP6Helper.php @@ -201,4 +201,24 @@ class IP6Helper { } return false; } + + /** + * преобразование короткой маски IPv6 в полную + * @param string $mask + * @return string + */ + public static function formatShortIpMask(string $mask): string { + if ($mask === '') { + $mask = '128'; + } + + $binaryMask = str_repeat('1', (int) $mask) . str_repeat('0', 128 - $mask); + $hextets = []; + + for ($i = 0; $i < 8; $i++) { + $hextets[] = dechex(bindec(substr($binaryMask, $i * 16, 16))); + } + + return implode(':', $hextets); + } }