1
0
mirror of https://github.com/Kovah/LinkAce.git synced 2025-04-22 07:52:43 +02:00

Receive parsed keywords instead of fetched HTML for tag suggestions

This commit is contained in:
Kovah 2023-11-01 13:30:50 +01:00
parent 31331bcd43
commit 3dc4a3d557
No known key found for this signature in database
GPG Key ID: AAAA031BA9830D7B
8 changed files with 106 additions and 39 deletions

View File

@ -10,6 +10,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\Http;
use Masterminds\HTML5;
class FetchController extends Controller
{
@ -121,9 +122,9 @@ class FetchController extends Controller
* implementation.
*
* @param Request $request
* @return Response
* @return JsonResponse
*/
public function htmlForUrl(Request $request): Response
public function htmlKeywordsFromUrl(Request $request)
{
$request->validate([
'url' => ['url'],
@ -138,9 +139,20 @@ class FetchController extends Controller
$response = $newRequest->get($url);
if ($response->successful()) {
return response($response->body());
$html5 = new HTML5();
$dom = $html5->loadHTML($response->body());
$keywords = [];
/** @var \DOMElement $metaTag */
foreach ($dom->getElementsByTagName('meta') as $metaTag) {
if (strtolower($metaTag->getAttribute('name')) === 'keywords') {
$keywords = explode(',', $metaTag->getAttributeNode('content')?->value);
$keywords = array_map(fn($keyword) => trim(e($keyword)), $keywords);
array_push($keywords, ...$keywords);
}
}
return response()->json(['keywords' => $keywords]);
}
return response(null);
return response()->json(['keywords' => null]);
}
}

View File

@ -16,6 +16,7 @@
"league/flysystem-aws-s3-v3": "^3.0",
"league/flysystem-ftp": "^3.0",
"league/flysystem-sftp-v3": "^3.0",
"masterminds/html5": "^2.8",
"predis/predis": "^v2.1",
"rap2hpoutre/laravel-log-viewer": "^v2.2.0",
"sentry/sentry-laravel": "^3.3.0",

69
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3b677018795f662e7f84fe30e4c756e7",
"content-hash": "3059bc7ac295710f1c4b0bda5470e66f",
"packages": [
{
"name": "aws/aws-crt-php",
@ -2946,6 +2946,73 @@
],
"time": "2022-04-17T13:12:02+00:00"
},
{
"name": "masterminds/html5",
"version": "2.8.1",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/f47dcf3c70c584de14f21143c55d9939631bc6cf",
"reference": "f47dcf3c70c584de14f21143c55d9939631bc6cf",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.8.1"
},
"time": "2023-05-10T11:58:31+00:00"
},
{
"name": "monolog/monolog",
"version": "2.9.1",

View File

@ -76,9 +76,7 @@ export default class TagsSelect {
this.$suggestionsContent.innerHTML = '';
tags.forEach(newTag => {
newTag = newTag.trim();
tags.slice(0, 20).forEach(newTag => {
const $tag = document.createElement('span');
$tag.classList.add('btn', 'btn-outline-secondary', 'btn-xs');
$tag.innerText = newTag;

View File

@ -76,7 +76,7 @@ export default class UrlField {
return;
}
fetch(window.appData.routes.fetch.htmlForUrl, {
fetch(window.appData.routes.fetch.keywordsForUrl, {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type': 'application/json'},
@ -85,25 +85,11 @@ export default class UrlField {
url: url
})
})
.then(response => response.text())
.then(body => {
if (body !== null) {
this.parseHtmlForKeywords(body);
.then(response => response.json())
.then(data => {
if (data.keywords !== null) {
this.tagSuggestions.displayNewSuggestions(data.keywords);
}
});
}
parseHtmlForKeywords (body) {
const parser = new DOMParser();
const doc = parser.parseFromString(body, 'text/html');
if (doc.head.children.length > 0) {
const keywords = doc.head.children.namedItem('keywords')
|| doc.head.children.namedItem('Keywords');
if (keywords !== null && keywords.content.length > 0) {
this.tagSuggestions.displayNewSuggestions(keywords.content.split(','));
}
}
}
}

View File

@ -22,7 +22,7 @@
'searchLists' => route('fetch-lists'),
'searchTags' => route('fetch-tags'),
'existingLinks' => route('fetch-existing-links'),
'htmlForUrl' => route('fetch-html-for-url'),
'keywordsForUrl' => route('fetch-keywords-for-url'),
'updateCheck' => route('fetch-update-check'),
'generateApiToken' => route('generate-api-token'),
'generateCronToken' => route('generate-cron-token'),

View File

@ -129,8 +129,8 @@ Route::group(['middleware' => ['auth']], function () {
->name('fetch-lists');
Route::post('fetch/existing-links', [FetchController::class, 'searchExistingUrls'])
->name('fetch-existing-links');
Route::post('fetch/html-for-url', [FetchController::class, 'htmlForUrl'])
->name('fetch-html-for-url');
Route::post('fetch/keywords-for-url', [FetchController::class, 'htmlKeywordsFromUrl'])
->name('fetch-keywords-for-url');
Route::get('fetch/update-check', [FetchController::class, 'checkForUpdates'])
->name('fetch-update-check');

View File

@ -82,46 +82,49 @@ class FetchControllerTest extends TestCase
$response->assertOk()->assertJson(['linkFound' => false]);
}
public function testGetHtmlForUrl(): void
public function testGetHtmlKeywordsForUrl(): void
{
$testHtml = '<!DOCTYPE html><head>' .
'<title>Example Title</title>' .
'<meta name="description" content="This an example description">' .
'<meta name="keywords" content="html, css, javascript">' .
'</head></html>';
Http::fake([
'example.com' => Http::response($testHtml, 200),
]);
$response = $this->post('fetch/html-for-url', [
$response = $this->post('fetch/keywords-for-url', [
'url' => 'https://example.com',
]);
$response->assertOk();
$responseHtml = $response->content();
$this->assertEquals($testHtml, $responseHtml);
$keywords = $response->json('keywords');
$this->assertEquals('html', $keywords[0]);
$this->assertEquals('css', $keywords[1]);
$this->assertEquals('javascript', $keywords[2]);
}
public function testGetHtmlForInvalidUrl(): void
public function testGetKeywordsForInvalidUrl(): void
{
$response = $this->post('fetch/html-for-url', [
$response = $this->post('fetch/keywords-for-url', [
'url' => 'not a url',
]);
$response->assertSessionHasErrors('url');
}
public function testGetHtmlForUrlWithFailure(): void
public function testGetKeywordsForUrlWithFailure(): void
{
Http::fake([
'example.com' => Http::response('', 500),
]);
$response = $this->post('fetch/html-for-url', [
$response = $this->post('fetch/keywords-for-url', [
'url' => 'https://example.com',
]);
$response->assertOk()->assertSee('');
$response->assertOk()->assertJson(['keywords' => null]);
}
}