1
0
Fork 0
mirror of https://github.com/miniflux/v2.git synced 2025-06-27 16:36:00 +00:00

fix(googlereader): handle various item ID formats

- Expected format: "tag:google.com,2005:reader/item/00000000148b9369" (hexadecimal string with prefix and padding)
- NetNewsWire uses this format: "tag:google.com,2005:reader/item/2f2" (hexadecimal string with prefix and no padding)
- Reeder uses this format: "000000000000048c" (hexadecimal string without prefix and padding)
- Liferea uses this format: "12345" (decimal string)
This commit is contained in:
Frédéric Guillot 2025-05-04 20:05:17 -07:00
parent cb775bc79e
commit 8d821dfc3b
3 changed files with 49 additions and 56 deletions

View file

@ -292,7 +292,7 @@ func (h *handler) editTagHandler(w http.ResponseWriter, r *http.Request) {
itemIDs, err := parseItemIDsFromRequest(r) itemIDs, err := parseItemIDsFromRequest(r)
if err != nil { if err != nil {
json.ServerError(w, r, err) json.BadRequest(w, r, err)
return return
} }
@ -709,7 +709,7 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
itemIDs, err := parseItemIDsFromRequest(r) itemIDs, err := parseItemIDsFromRequest(r)
if err != nil { if err != nil {
json.ServerError(w, r, err) json.BadRequest(w, r, err)
return return
} }
@ -733,21 +733,10 @@ func (h *handler) streamItemContentsHandler(w http.ResponseWriter, r *http.Reque
return return
} }
if len(entries) == 0 {
json.BadRequest(w, r, fmt.Errorf("googlereader: no items returned from the database for item IDs: %v", itemIDs))
return
}
result := streamContentItems{ result := streamContentItems{
Direction: "ltr", Direction: "ltr",
ID: fmt.Sprintf("feed/%d", entries[0].FeedID), ID: "user/-/state/com.google/reading-list",
Title: entries[0].Feed.Title, Title: "Reading List",
Alternate: []contentHREFType{
{
HREF: entries[0].Feed.SiteURL,
Type: "text/html",
},
},
Updated: time.Now().Unix(), Updated: time.Now().Unix(),
Self: []contentHREF{ Self: []contentHREF{
{ {

View file

@ -4,7 +4,6 @@
package googlereader // import "miniflux.app/v2/internal/googlereader" package googlereader // import "miniflux.app/v2/internal/googlereader"
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strconv" "strconv"
@ -13,37 +12,47 @@ import (
const ( const (
ItemIDPrefix = "tag:google.com,2005:reader/item/" ItemIDPrefix = "tag:google.com,2005:reader/item/"
ItemIDFormat = "tag:google.com,2005:reader/item/%016x"
) )
func convertEntryIDToLongFormItemID(entryID int64) string { func convertEntryIDToLongFormItemID(entryID int64) string {
// The entry ID is a 64-bit integer, so we need to format it as a 16-character hexadecimal string. // The entry ID is a 64-bit integer, so we need to format it as a 16-character hexadecimal string.
return ItemIDPrefix + fmt.Sprintf("%016x", entryID) return fmt.Sprintf(ItemIDFormat, entryID)
} }
// parseItemID parses a Google Reader ID string. // Expected format: "tag:google.com,2005:reader/item/00000000148b9369" (hexadecimal string with prefix and padding)
// It supports both the long form (tag:google.com,2005:reader/item/<hex_id>) and the short form (<decimal_id>). // NetNewsWire uses this format: "tag:google.com,2005:reader/item/2f2" (hexadecimal string with prefix and no padding)
// Reeder uses this format: "000000000000048c" (hexadecimal string without prefix and padding)
// Liferea uses this format: "12345" (decimal string)
// It returns the parsed ID as a int64 and an error if parsing fails. // It returns the parsed ID as a int64 and an error if parsing fails.
func parseItemID(itemIDValue string) (int64, error) { func parseItemID(itemIDValue string) (int64, error) {
var itemID int64
if strings.HasPrefix(itemIDValue, ItemIDPrefix) { if strings.HasPrefix(itemIDValue, ItemIDPrefix) {
hexID := strings.TrimPrefix(itemIDValue, ItemIDPrefix) n, err := fmt.Sscanf(itemIDValue, ItemIDFormat, &itemID)
if err != nil {
// It's always 16 characters wide. return 0, fmt.Errorf("failed to parse hexadecimal item ID %s: %w", itemIDValue, err)
if len(hexID) != 16 { }
return 0, errors.New("long form ID has incorrect length") if n != 1 {
return 0, fmt.Errorf("failed to parse hexadecimal item ID %s: expected 1 value, got %d", itemIDValue, n)
}
if itemID == 0 {
return 0, fmt.Errorf("failed to parse hexadecimal item ID %s: item ID is zero", itemIDValue)
}
return itemID, nil
} }
parsedID, err := strconv.ParseInt(hexID, 16, 64) if len(itemIDValue) == 16 {
if n, err := fmt.Sscanf(itemIDValue, "%016x", &itemID); err == nil && n == 1 {
return itemID, nil
}
}
itemID, err := strconv.ParseInt(itemIDValue, 10, 64)
if err != nil { if err != nil {
return 0, errors.New("failed to parse long form hex ID: " + err.Error()) return 0, fmt.Errorf("failed to parse decimal item ID %s: %w", itemIDValue, err)
}
return parsedID, nil
} else {
parsedID, err := strconv.ParseInt(itemIDValue, 10, 64)
if err != nil {
return 0, errors.New("failed to parse short form decimal ID: " + err.Error())
}
return parsedID, nil
} }
return itemID, nil
} }
func parseItemIDsFromRequest(r *http.Request) ([]int64, error) { func parseItemIDsFromRequest(r *http.Request) ([]int64, error) {

View file

@ -23,7 +23,10 @@ func TestConvertEntryIDToLongFormItemID(t *testing.T) {
func TestParseItemIDsFromRequest(t *testing.T) { func TestParseItemIDsFromRequest(t *testing.T) {
formValues := url.Values{} formValues := url.Values{}
formValues.Add("i", "12345") formValues.Add("i", "12345")
formValues.Add("i", convertEntryIDToLongFormItemID(45678)) formValues.Add("i", "tag:google.com,2005:reader/item/00000000148b9369")
formValues.Add("i", "tag:google.com,2005:reader/item/2f2")
formValues.Add("i", "000000000000046f")
formValues.Add("i", "tag:google.com,2005:reader/item/272")
request := &http.Request{ request := &http.Request{
Form: formValues, Form: formValues,
@ -34,7 +37,7 @@ func TestParseItemIDsFromRequest(t *testing.T) {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
var expected = []int64{12345, 45678} var expected = []int64{12345, 344691561, 754, 1135, 626}
if !reflect.DeepEqual(result, expected) { if !reflect.DeepEqual(result, expected) {
t.Errorf("expected %v, got %v", expected, result) t.Errorf("expected %v, got %v", expected, result)
} }
@ -51,7 +54,7 @@ func TestParseItemIDsFromRequest(t *testing.T) {
} }
func TestParseItemID(t *testing.T) { func TestParseItemID(t *testing.T) {
// Test with long form ID // Test with long form ID and hex ID
result, err := parseItemID("tag:google.com,2005:reader/item/0000000000000001") result, err := parseItemID("tag:google.com,2005:reader/item/0000000000000001")
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
@ -61,6 +64,16 @@ func TestParseItemID(t *testing.T) {
t.Errorf("expected %d, got %d", expected, result) t.Errorf("expected %d, got %d", expected, result)
} }
// Test with hexadecimal long form ID
result, err = parseItemID("0000000000000468")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
expected = int64(1128)
if result != expected {
t.Errorf("expected %d, got %d", expected, result)
}
// Test with short form ID // Test with short form ID
result, err = parseItemID("12345") result, err = parseItemID("12345")
if err != nil { if err != nil {
@ -88,22 +101,4 @@ func TestParseItemID(t *testing.T) {
if err == nil { if err == nil {
t.Fatalf("expected error, got nil") t.Fatalf("expected error, got nil")
} }
// Test with ID that is too short
_, err = parseItemID("tag:google.com,2005:reader/item/00000000000000")
if err == nil {
t.Fatalf("expected error, got nil")
}
// Test with ID that is too long
_, err = parseItemID("tag:google.com,2005:reader/item/000000000000000000")
if err == nil {
t.Fatalf("expected error, got nil")
}
// Test with ID that is not a number
_, err = parseItemID("tag:google.com,2005:reader/item/abc")
if err == nil {
t.Fatalf("expected error, got nil")
}
} }