Skip to content

Commit 614f1b9

Browse files
committed
🐛 More fixes
1 parent 1de5288 commit 614f1b9

File tree

5 files changed

+92
-11
lines changed

5 files changed

+92
-11
lines changed

app/Integrations/Untappd/UntappdPlugin.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ public static function getDomain(): string
9696
public static function getActionTypes(): array
9797
{
9898
return [
99-
'drank_beer' => [
99+
'drank' => [
100100
'icon' => 'fas.beer-mug-empty',
101-
'display_name' => 'Drank Beer',
101+
'display_name' => 'Drank',
102102
'description' => 'Drank a beer',
103103
'display_with_object' => true,
104104
'value_unit' => null,

app/Jobs/Data/Goodreads/GoodreadsRssData.php

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Jobs\Data\Goodreads;
44

55
use App\Jobs\Base\BaseProcessingJob;
6+
use App\Models\EventObject;
67
use Carbon\Carbon;
78
use DOMDocument;
89
use DOMXPath;
@@ -62,16 +63,20 @@ protected function process(): void
6263

6364
// Build target (book)
6465
$bookTitle = $descriptionData['bookTitle'] ?? $parsedData['bookTitle'] ?? 'Unknown Book';
66+
$bookUrl = $descriptionData['bookUrl'] ?? $link;
67+
$bookId = $this->extractBookId($bookUrl);
68+
6569
$target = [
6670
'concept' => 'document',
6771
'type' => 'goodreads_book',
6872
'title' => $bookTitle,
6973
'content' => null,
7074
'metadata' => [
75+
'book_id' => $bookId,
7176
'author' => $descriptionData['authorName'] ?? null,
7277
'author_url' => $descriptionData['authorUrl'] ?? null,
7378
],
74-
'url' => $descriptionData['bookUrl'] ?? $link,
79+
'url' => $bookUrl,
7580
'image_url' => $descriptionData['coverUrl'] ?? null,
7681
'time' => $pubDate ? Carbon::parse($pubDate) : now(),
7782
];
@@ -146,6 +151,47 @@ protected function process(): void
146151
}
147152
}
148153

154+
/**
155+
* Override createOrUpdateObject to handle book deduplication by book_id
156+
*/
157+
protected function createOrUpdateObject(array $objectData): EventObject
158+
{
159+
// For Goodreads books, check if object with same book_id already exists
160+
if ($objectData['type'] === 'goodreads_book' && isset($objectData['metadata']['book_id'])) {
161+
$bookId = $objectData['metadata']['book_id'];
162+
163+
// Find existing book by book_id in metadata
164+
$existingBook = EventObject::where('user_id', $this->integration->user_id)
165+
->where('concept', $objectData['concept'])
166+
->where('type', $objectData['type'])
167+
->whereJsonContains('metadata->book_id', $bookId)
168+
->first();
169+
170+
if ($existingBook) {
171+
// Keep the longer title (handles truncated titles from RSS feed)
172+
$newTitle = $objectData['title'];
173+
$existingTitle = $existingBook->title;
174+
$titleToKeep = mb_strlen($newTitle) > mb_strlen($existingTitle) ? $newTitle : $existingTitle;
175+
176+
// Update the existing book with new data
177+
$existingBook->update([
178+
'time' => $objectData['time'] ?? now(),
179+
'title' => $titleToKeep,
180+
'content' => $objectData['content'] ?? null,
181+
'metadata' => array_merge($existingBook->metadata ?? [], $objectData['metadata'] ?? []),
182+
'url' => $objectData['url'] ?? $existingBook->url,
183+
'media_url' => $objectData['image_url'] ?? $existingBook->media_url,
184+
'embeddings' => $objectData['embeddings'] ?? $existingBook->embeddings,
185+
]);
186+
187+
return $existingBook;
188+
}
189+
}
190+
191+
// Fall back to parent method for other object types
192+
return parent::createOrUpdateObject($objectData);
193+
}
194+
149195
/**
150196
* Parse the RSS item title to extract action type and book information
151197
*/
@@ -316,4 +362,22 @@ private function getFullSizeCoverUrl(?string $url): ?string
316362
// Remove size suffixes like _SX98_, _SY475_, etc.
317363
return preg_replace('/\._[A-Z]{2}\d+_\./', '.', $url);
318364
}
365+
366+
/**
367+
* Extract book ID from Goodreads book URL
368+
* Example: https://www.goodreads.com/book/show/25792894-kings-rising -> 25792894
369+
*/
370+
private function extractBookId(?string $url): ?string
371+
{
372+
if (! $url) {
373+
return null;
374+
}
375+
376+
// Match pattern: /book/show/{book_id}-{slug} or /book/show/{book_id}
377+
if (preg_match('/\/book\/show\/(\d+)(?:-|$)/', $url, $matches)) {
378+
return $matches[1];
379+
}
380+
381+
return null;
382+
}
319383
}

app/Jobs/Data/Untappd/UntappdRssData.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ protected function process(): void
126126
'source_id' => $sourceId,
127127
'time' => $pubDate ? Carbon::parse($pubDate) : now(),
128128
'domain' => 'health',
129-
'action' => 'drank_beer',
129+
'action' => 'drank',
130130
'value' => null,
131131
'value_multiplier' => 1,
132132
'value_unit' => null,

app/Jobs/OAuth/Untappd/UntappdRssPull.php

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,28 @@ protected function fetchData(): array
3636
throw new Exception('RSS fetch failed: ' . $result['error']);
3737
}
3838

39-
// Parse XML
40-
$xml = simplexml_load_string($result['html']);
39+
$xmlContent = $result['html'];
40+
41+
// Extract XML from <pre> tags if wrapped in HTML
42+
if (str_starts_with(trim($xmlContent), '<!DOCTYPE') || str_starts_with(trim($xmlContent), '<html')) {
43+
// Try to extract content from <pre> tags
44+
if (preg_match('/<pre[^>]*>(.*?)<\/pre>/is', $xmlContent, $matches)) {
45+
$xmlContent = html_entity_decode($matches[1], ENT_QUOTES | ENT_HTML5);
46+
} else {
47+
throw new Exception('Received HTML instead of XML and could not extract from <pre> tags. Response starts with: ' . substr($xmlContent, 0, 200));
48+
}
49+
}
50+
51+
// Parse XML with error handling
52+
libxml_use_internal_errors(true);
53+
$xml = simplexml_load_string($xmlContent);
4154

4255
if ($xml === false) {
43-
throw new Exception('Failed to parse RSS XML');
56+
$errors = libxml_get_errors();
57+
$errorMessages = array_map(fn ($error) => trim($error->message), $errors);
58+
libxml_clear_errors();
59+
60+
throw new Exception('Failed to parse RSS XML: ' . implode(', ', $errorMessages) . '. Content preview: ' . substr($xmlContent, 0, 500));
4461
}
4562

4663
$items = [];

tests/Unit/Integrations/UntappdRssDataTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,12 @@ public function it_parses_beer_checkin_with_brewery()
5757

5858
$this->assertDatabaseHas('events', [
5959
'service' => 'untappd',
60-
'action' => 'drank_beer',
60+
'action' => 'drank',
6161
'domain' => 'health',
6262
]);
6363

6464
$event = Event::where('service', 'untappd')->first();
65-
$this->assertEquals('drank_beer', $event->action);
65+
$this->assertEquals('drank', $event->action);
6666
$this->assertNull($event->value);
6767

6868
// Check actor
@@ -217,8 +217,8 @@ public function it_handles_multiple_checkins_in_one_batch()
217217
$events = Event::where('service', 'untappd')->get();
218218
$this->assertCount(3, $events);
219219

220-
// All should be drank_beer actions
221-
$this->assertTrue($events->every(fn ($e) => $e->action === 'drank_beer'));
220+
// All should be drank actions
221+
$this->assertTrue($events->every(fn ($e) => $e->action === 'drank'));
222222

223223
// Check that one has a venue
224224
$eventWithVenue = $events->first(fn ($e) => ! empty($e->target->metadata['venue']));

0 commit comments

Comments
 (0)