Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#64 IpAddressV4Service: added support for parsing IPv4 addresses #109

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
47 changes: 47 additions & 0 deletions src/libs/BetterLocation/Service/IpAddressV4Service.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);

namespace App\BetterLocation\Service;

use App\BetterLocation\BetterLocation;
use App\BetterLocation\BetterLocationCollection;
use App\Config;
use App\MiniCurl\MiniCurl;
use stdClass;

final class IpAddressV4Service extends AbstractService
{
public const ID = 45;
public const NAME = 'IP address';
private const SEPARATOR = [' ', "\t", PHP_EOL, ',', ';', '=', '-'];
private const IP_V4_MASK = '/(?:^|_)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:$|_)/';

public static function findInText(string $input): BetterLocationCollection
{
// replace all potential delimiters with identifiers pair
// to correctly retrieve closely adjacent addresses
// use delimiter as identifier multiplies them 4 times :)

$collection = new BetterLocationCollection();
$input = str_replace(self::SEPARATOR, '__', $input);

if (preg_match_all(self::IP_V4_MASK, $input, $matches)) {
foreach ($matches[1] as $ipAddress) {
if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) {
$response = self::loadApi($ipAddress);
$collection->add(new BetterLocation($ipAddress, $response->lat, $response->lon, self::class));
}
}
}
return $collection;
}

private static function loadApi(string $ipAddress): stdClass
{
$response = (new MiniCurl('http://ip-api.com/json/' . $ipAddress))
->allowCache(Config::CACHE_TTL_IP_API)
->run()
->getBody();
return json_decode($response);
}

}
2 changes: 2 additions & 0 deletions src/libs/BetterLocation/ServicesManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use App\BetterLocation\Service\HradyCzService;
use App\BetterLocation\Service\IngressIntelService;
use App\BetterLocation\Service\IngressPrimeService;
use App\BetterLocation\Service\IpAddressV4Service;
use App\BetterLocation\Service\MapyCzPanoramaGeneratorService;
use App\BetterLocation\Service\MapyCzService;
use App\BetterLocation\Service\NeshanOrgService;
Expand Down Expand Up @@ -82,6 +83,7 @@ class ServicesManager

public function __construct()
{
$this->services[IpAddressV4Service::ID] = IpAddressV4Service::class;
$this->services[BetterLocationService::ID] = BetterLocationService::class;
$this->services[WGS84DegreesService::ID] = WGS84DegreesService::class;
$this->services[WGS84DegreeCompactService::ID] = WGS84DegreeCompactService::class;
Expand Down
1 change: 1 addition & 0 deletions src/libs/DefaultConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ class DefaultConfig
const CACHE_TTL_HRADY_CZ = 60 * 60 * 24;
const CACHE_TTL_OPEN_ELEVATION = 60 * 60 * 24;
const CACHE_TTL_PRAZDNE_DOMY = 60 * 60 * 24;
const CACHE_TTL_IP_API = 60 * 60 * 24;

/** @var string[] */
const API_KEYS = [];
Expand Down
80 changes: 80 additions & 0 deletions tests/BetterLocation/Service/ipAddressV4ServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php declare(strict_types=1);

namespace Tests\BetterLocation\Service;

use App\BetterLocation\Service\BannergressService;
use App\BetterLocation\Service\Exceptions\NotSupportedException;
use App\MiniCurl\Exceptions\InvalidResponseException;
use PHPUnit\Framework\TestCase;

final class IpAddressV4ServiceTest extends TestCase
{
private function assertLocation(string $url, float $lat, float $lon): void
{
$collection = IpAddressV4Service::processStatic($url)->getCollection();
$this->assertCount(1, $collection);
$location = $collection->getFirst();
$this->assertEqualsWithDelta($lat, $location->getLat(), 0.000001);
$this->assertEqualsWithDelta($lon, $location->getLon(), 0.000001);
}

public function testGenerateShareLink(): void
{
$this->assertSame('https://bannergress.com/map?lat=50.087451&lng=14.420671&zoom=15', IpAddressV4Service::getLink(50.087451, 14.420671));
$this->assertSame('https://bannergress.com/map?lat=50.100000&lng=14.500000&zoom=15', IpAddressV4Service::getLink(50.1, 14.5));
$this->assertSame('https://bannergress.com/map?lat=-50.200000&lng=14.600000&zoom=15', IpAddressV4Service::getLink(-50.2, 14.6000001)); // round down
$this->assertSame('https://bannergress.com/map?lat=50.300000&lng=-14.700001&zoom=15', IpAddressV4Service::getLink(50.3, -14.7000009)); // round up
$this->assertSame('https://bannergress.com/map?lat=-50.400000&lng=-14.800008&zoom=15', IpAddressV4Service::getLink(-50.4, -14.800008));
}

public function testGenerateDriveLink(): void
{
$this->expectException(NotSupportedException::class);
IpAddressV4Service::getLink(50.087451, 14.420671, true);
}

public function testIsValid(): void
{
$this->assertTrue(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/czech-cubism-and-its-representative-ce4b'));
$this->assertTrue(IpAddressV4Service::isValidStatic('http://bannergress.com/banner/czech-cubism-and-its-representative-ce4b'));
$this->assertTrue(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/barrie-skyline-f935'));
$this->assertTrue(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/hist%C3%B3rica-catedral-de-san-lorenzo-55dd'));
$this->assertTrue(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/histórica-catedral-de-san-lorenzo-55dd'));
$this->assertTrue(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/長良川鉄道-乗りつぶし-観光編-adea'));
$this->assertTrue(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/%E9%95%B7%E8%89%AF%E5%B7%9D%E9%89%84%E9%81%93-%E4%B9%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97-%E8%A6%B3%E5%85%89%E7%B7%A8-adea'));

// Invalid
$this->assertFalse(IpAddressV4Service::isValidStatic('some invalid url'));
$this->assertFalse(IpAddressV4Service::isValidStatic('https://bannergress.com'));
$this->assertFalse(IpAddressV4Service::isValidStatic('http://bannergress.com'));
$this->assertFalse(IpAddressV4Service::isValidStatic('https://bannergress.com/banner/'));
$this->assertFalse(IpAddressV4Service::isValidStatic('http://www.some-domain.cz/'));
$this->assertFalse(IpAddressV4Service::isValidStatic('http://www.some-domain.cz/some-path'));
}

/**
* @group request
*/
public function testProcessPlace(): void
{
$this->assertLocation('https://bannergress.com/banner/czech-cubism-and-its-representative-ce4b', 50.087213, 14.425674);
// This is failing only in unit tests but works if processed manually (via tester in browser or Telegram)
// $this->assertLocation('https://bannergress.com/banner/長良川鉄道-乗りつぶし-観光編-adea', 35.445393, 137.019408);
$this->assertLocation('https://bannergress.com/banner/%E9%95%B7%E8%89%AF%E5%B7%9D%E9%89%84%E9%81%93-%E4%B9%97%E3%82%8A%E3%81%A4%E3%81%B6%E3%81%97-%E8%A6%B3%E5%85%89%E7%B7%A8-adea', 35.445393, 137.019408);
$this->assertLocation('https://bannergress.com/banner/a-visit-to-te-papa-dffa', -41.287008, 174.778374);
$this->assertLocation('https://bannergress.com/banner/hist%C3%B3rica-catedral-de-san-lorenzo-55dd', -25.3414, -57.508801);
$this->assertLocation('https://bannergress.com/banner/histórica-catedral-de-san-lorenzo-55dd', -25.3414, -57.508801);
}

/**
* Pages, that do not have any location
* @group request
*/
public function testInvalid(): void
{
$this->expectException(InvalidResponseException::class);
$this->expectExceptionCode(404);
$this->expectExceptionMessage('Invalid response code "404" but required "200" for URL "https://api.bannergress.com/bnrs/aaaa-bbbb"');
IpAddressV4Service::processStatic('https://bannergress.com/banner/aaaa-bbbb')->getCollection();
}
}