mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-11 12:09:36 +00:00
Added URL handling to a few more existing info providers
This commit is contained in:
parent
10acc2e130
commit
24f0f0d23c
5 changed files with 115 additions and 19 deletions
|
|
@ -30,9 +30,10 @@ use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||||
use App\Settings\InfoProviderSystem\ConradSettings;
|
use App\Settings\InfoProviderSystem\ConradSettings;
|
||||||
|
use App\Settings\InfoProviderSystem\ConradShopIDs;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
readonly class ConradProvider implements InfoProviderInterface
|
readonly class ConradProvider implements InfoProviderInterface, URLHandlerInfoProviderInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
private const SEARCH_ENDPOINT = '/search/1/v3/facetSearch';
|
private const SEARCH_ENDPOINT = '/search/1/v3/facetSearch';
|
||||||
|
|
@ -317,4 +318,26 @@ readonly class ConradProvider implements InfoProviderInterface
|
||||||
ProviderCapabilities::PRICE,
|
ProviderCapabilities::PRICE,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHandledDomains(): array
|
||||||
|
{
|
||||||
|
$domains = [];
|
||||||
|
foreach (ConradShopIDs::cases() as $shopID) {
|
||||||
|
$domains[] = $shopID->getDomain();
|
||||||
|
}
|
||||||
|
return array_unique($domains);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIDFromURL(string $url): ?string
|
||||||
|
{
|
||||||
|
//Input: https://www.conrad.de/de/p/apple-iphone-air-wolkenweiss-256-gb-eek-a-a-g-16-5-cm-6-5-zoll-3475299.html
|
||||||
|
//The numbers before the optional .html are the product ID
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
if (preg_match('/-(\d+)(\.html)?$/', $url, $matches) === 1) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ use App\Settings\InfoProviderSystem\Element14Settings;
|
||||||
use Composer\CaBundle\CaBundle;
|
use Composer\CaBundle\CaBundle;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
class Element14Provider implements InfoProviderInterface
|
class Element14Provider implements InfoProviderInterface, URLHandlerInfoProviderInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
private const ENDPOINT_URL = 'https://api.element14.com/catalog/products';
|
private const ENDPOINT_URL = 'https://api.element14.com/catalog/products';
|
||||||
|
|
@ -309,4 +309,21 @@ class Element14Provider implements InfoProviderInterface
|
||||||
ProviderCapabilities::DATASHEET,
|
ProviderCapabilities::DATASHEET,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHandledDomains(): array
|
||||||
|
{
|
||||||
|
return ['element14.com', 'farnell.com', 'newark.com'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIDFromURL(string $url): ?string
|
||||||
|
{
|
||||||
|
//Input URL example: https://de.farnell.com/on-semiconductor/bc547b/transistor-npn-to-92/dp/1017673
|
||||||
|
//The digits after the /dp/ are the part ID
|
||||||
|
$matches = [];
|
||||||
|
if (preg_match('#/dp/(\d+)#', $url, $matches) === 1) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ use App\Services\InfoProviderSystem\DTOs\ParameterDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
use App\Services\InfoProviderSystem\DTOs\PartDetailDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
use App\Services\InfoProviderSystem\DTOs\PriceDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||||
|
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||||
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
use App\Services\InfoProviderSystem\PartInfoRetriever;
|
||||||
use App\Services\InfoProviderSystem\ProviderRegistry;
|
use App\Services\InfoProviderSystem\ProviderRegistry;
|
||||||
use App\Settings\InfoProviderSystem\GenericWebProviderSettings;
|
use App\Settings\InfoProviderSystem\GenericWebProviderSettings;
|
||||||
|
|
@ -78,9 +79,17 @@ class GenericWebProvider implements InfoProviderInterface
|
||||||
|
|
||||||
public function searchByKeyword(string $keyword): array
|
public function searchByKeyword(string $keyword): array
|
||||||
{
|
{
|
||||||
|
$url = $this->fixAndValidateURL($keyword);
|
||||||
|
|
||||||
|
//Before loading the page, try to delegate to another provider
|
||||||
|
$delegatedPart = $this->delegateToOtherProvider($url);
|
||||||
|
if ($delegatedPart !== null) {
|
||||||
|
return [$delegatedPart];
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return [
|
return [
|
||||||
$this->getDetails($keyword)
|
$this->getDetails($keyword, false) //We already tried delegation
|
||||||
]; } catch (ProviderIDNotSupportedException $e) {
|
]; } catch (ProviderIDNotSupportedException $e) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -234,9 +243,9 @@ class GenericWebProvider implements InfoProviderInterface
|
||||||
/**
|
/**
|
||||||
* Delegates the URL to another provider if possible, otherwise return null
|
* Delegates the URL to another provider if possible, otherwise return null
|
||||||
* @param string $url
|
* @param string $url
|
||||||
* @return PartDetailDTO|null
|
* @return SearchResultDTO|null
|
||||||
*/
|
*/
|
||||||
private function delegateToOtherProvider(string $url): ?PartDetailDTO
|
private function delegateToOtherProvider(string $url): ?SearchResultDTO
|
||||||
{
|
{
|
||||||
//Extract domain from url:
|
//Extract domain from url:
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
|
|
@ -250,7 +259,10 @@ class GenericWebProvider implements InfoProviderInterface
|
||||||
try {
|
try {
|
||||||
$id = $provider->getIDFromURL($url);
|
$id = $provider->getIDFromURL($url);
|
||||||
if ($id !== null) {
|
if ($id !== null) {
|
||||||
return $this->infoRetriever->getDetails($provider->getProviderKey(), $id);
|
$results = $this->infoRetriever->searchByKeyword($id, [$provider]);
|
||||||
|
if (count($results) > 0) {
|
||||||
|
return $results[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
} catch (ProviderIDNotSupportedException $e) {
|
} catch (ProviderIDNotSupportedException $e) {
|
||||||
|
|
@ -262,30 +274,39 @@ class GenericWebProvider implements InfoProviderInterface
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDetails(string $id): PartDetailDTO
|
private function fixAndValidateURL(string $url): string
|
||||||
{
|
{
|
||||||
|
$originalUrl = $url;
|
||||||
|
|
||||||
//Add scheme if missing
|
//Add scheme if missing
|
||||||
if (!preg_match('/^https?:\/\//', $id)) {
|
if (!preg_match('/^https?:\/\//', $url)) {
|
||||||
//Remove any leading slashes
|
//Remove any leading slashes
|
||||||
$id = ltrim($id, '/');
|
$url = ltrim($url, '/');
|
||||||
|
|
||||||
$id = 'https://'.$id;
|
$url = 'https://'.$url;
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $id;
|
|
||||||
|
|
||||||
//If this is not a valid URL with host, domain and path, throw an exception
|
//If this is not a valid URL with host, domain and path, throw an exception
|
||||||
if (filter_var($url, FILTER_VALIDATE_URL) === false ||
|
if (filter_var($url, FILTER_VALIDATE_URL) === false ||
|
||||||
parse_url($url, PHP_URL_HOST) === null ||
|
parse_url($url, PHP_URL_HOST) === null ||
|
||||||
parse_url($url, PHP_URL_PATH) === null) {
|
parse_url($url, PHP_URL_PATH) === null) {
|
||||||
throw new ProviderIDNotSupportedException("The given ID is not a valid URL: ".$id);
|
throw new ProviderIDNotSupportedException("The given ID is not a valid URL: ".$originalUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDetails(string $id, bool $check_for_delegation = true): PartDetailDTO
|
||||||
|
{
|
||||||
|
$url = $this->fixAndValidateURL($id);
|
||||||
|
|
||||||
|
if ($check_for_delegation) {
|
||||||
//Before loading the page, try to delegate to another provider
|
//Before loading the page, try to delegate to another provider
|
||||||
$delegatedPart = $this->delegateToOtherProvider($url);
|
$delegatedPart = $this->delegateToOtherProvider($url);
|
||||||
if ($delegatedPart !== null) {
|
if ($delegatedPart !== null) {
|
||||||
return $delegatedPart;
|
return $delegatedPart;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Try to get the webpage content
|
//Try to get the webpage content
|
||||||
$response = $this->httpClient->request('GET', $url);
|
$response = $this->httpClient->request('GET', $url);
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ use App\Settings\InfoProviderSystem\LCSCSettings;
|
||||||
use Symfony\Component\HttpFoundation\Cookie;
|
use Symfony\Component\HttpFoundation\Cookie;
|
||||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
|
||||||
class LCSCProvider implements BatchInfoProviderInterface
|
class LCSCProvider implements BatchInfoProviderInterface, URLHandlerInfoProviderInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
private const ENDPOINT_URL = 'https://wmsc.lcsc.com/ftps/wm';
|
private const ENDPOINT_URL = 'https://wmsc.lcsc.com/ftps/wm';
|
||||||
|
|
@ -452,4 +452,21 @@ class LCSCProvider implements BatchInfoProviderInterface
|
||||||
ProviderCapabilities::FOOTPRINT,
|
ProviderCapabilities::FOOTPRINT,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHandledDomains(): array
|
||||||
|
{
|
||||||
|
return ['lcsc.com'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIDFromURL(string $url): ?string
|
||||||
|
{
|
||||||
|
//Input example: https://www.lcsc.com/product-detail/C258144.html?s_z=n_BC547
|
||||||
|
//The part between the "C" and the ".html" is the unique ID
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
if (preg_match("#/product-detail/(\w+)\.html#", $url, $matches) > 0) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ use App\Services\InfoProviderSystem\DTOs\PurchaseInfoDTO;
|
||||||
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
|
||||||
use App\Settings\InfoProviderSystem\TMESettings;
|
use App\Settings\InfoProviderSystem\TMESettings;
|
||||||
|
|
||||||
class TMEProvider implements InfoProviderInterface
|
class TMEProvider implements InfoProviderInterface, URLHandlerInfoProviderInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
private const VENDOR_NAME = 'TME';
|
private const VENDOR_NAME = 'TME';
|
||||||
|
|
@ -296,4 +296,22 @@ class TMEProvider implements InfoProviderInterface
|
||||||
ProviderCapabilities::PRICE,
|
ProviderCapabilities::PRICE,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getHandledDomains(): array
|
||||||
|
{
|
||||||
|
return ['tme.eu'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIDFromURL(string $url): ?string
|
||||||
|
{
|
||||||
|
//Input: https://www.tme.eu/de/details/fi321_se/kuhler/alutronic/
|
||||||
|
//The ID is the part after the details segment and before the next slash
|
||||||
|
|
||||||
|
$matches = [];
|
||||||
|
if (preg_match('#/details/([^/]+)/#', $url, $matches) === 1) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue