@@ -223,16 +223,68 @@ def find_existing_category_for_url(body: ET.Element, xml_url: str) -> Optional[s
223223 return None
224224
225225
226+ def find_existing_feed_node (body : ET .Element , xml_url : str ) -> Optional [Tuple [ET .Element , ET .Element ]]:
227+ """Find the existing feed node and its parent for a given XML URL.
228+
229+ Returns:
230+ Tuple of (parent, feed_node) if found, None otherwise.
231+ """
232+ wanted = normalize_url (xml_url )
233+ if not wanted :
234+ return None
235+ for parent , rss in iter_rss_nodes (body ):
236+ existing = normalize_url (rss .attrib .get ("xmlUrl" , "" ))
237+ if existing == wanted :
238+ return (parent , rss )
239+ return None
240+
241+
226242def add_feed_to_tree (
227243 tree : ET .ElementTree ,
228244 category_name : str ,
229245 metadata : FeedMetadata ,
230- ) -> Tuple [bool , str ]:
246+ update_if_exists : bool = False ,
247+ ) -> Tuple [bool , str , bool ]:
248+ """Add or update a feed in the OPML tree.
249+
250+ Returns:
251+ Tuple of (success, category_name, was_updated).
252+ - success: True if feed was added or updated
253+ - category_name: The category where the feed is located
254+ - was_updated: True if an existing feed was updated, False if newly added
255+ """
231256 body = get_body (tree , Path ("tiny.opml" ))
232- existing_category = find_existing_category_for_url (body , metadata .xml_url )
233- if existing_category is not None :
234- return False , existing_category
257+ existing = find_existing_feed_node (body , metadata .xml_url )
235258
259+ if existing is not None :
260+ old_parent , old_feed = existing
261+ if not update_if_exists :
262+ # Return the existing category name
263+ if old_parent is body :
264+ return False , "(top-level)" , False
265+ name = category_name (old_parent )
266+ return False , name or "(unnamed)" , False
267+
268+ # Update the existing feed
269+ old_feed .attrib ["title" ] = metadata .title
270+ old_feed .attrib ["text" ] = metadata .title
271+ old_feed .attrib ["htmlUrl" ] = metadata .html_url
272+
273+ # Check if we need to move to a different category
274+ categories = build_category_map (body )
275+ target_name = resolve_category_name (category_name , categories )
276+ target = ensure_category (body , categories , target_name )
277+
278+ if target != old_parent :
279+ # Move the feed to the new category
280+ old_parent .remove (old_feed )
281+ if target .text is None :
282+ target .text = "\n "
283+ target .append (old_feed )
284+
285+ return True , target_name , True
286+
287+ # Add new feed
236288 categories = build_category_map (body )
237289 target_name = resolve_category_name (category_name , categories )
238290 target = ensure_category (body , categories , target_name )
@@ -251,7 +303,7 @@ def add_feed_to_tree(
251303 if target .text is None :
252304 target .text = "\n "
253305 target .append (feed )
254- return True , target_name
306+ return True , target_name , False
255307
256308
257309def serialize_tree (tree : ET .ElementTree ) -> bytes :
@@ -323,6 +375,11 @@ def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
323375 parser .add_argument ("--url" , help = "RSS/Atom URL; interactive prompt if omitted" )
324376 parser .add_argument ("--category" , help = "Target category; interactive prompt if omitted" )
325377 parser .add_argument ("--timeout" , type = float , default = 12 , help = "HTTP timeout in seconds" )
378+ parser .add_argument (
379+ "--update" ,
380+ action = "store_true" ,
381+ help = "Update existing feed if URL already exists (title, htmlUrl, and category)" ,
382+ )
326383 parser .add_argument (
327384 "--no-git-pull" ,
328385 action = "store_true" ,
@@ -368,14 +425,20 @@ def main(argv: Optional[List[str]] = None) -> int:
368425 categories = list_categories (body )
369426 chosen_category = (args .category or "" ).strip () or prompt_for_category (categories )
370427
371- added , target_category = add_feed_to_tree (tree , chosen_category , metadata )
428+ added , target_category , was_updated = add_feed_to_tree (
429+ tree , chosen_category , metadata , update_if_exists = args .update
430+ )
372431 if not added :
373432 print (f"Feed already exists in category: { target_category } " )
374433 print (f"RSS: { metadata .xml_url } " )
434+ print ("Use --update flag to update the existing feed." )
375435 return 0
376436
377437 tiny_path .write_bytes (serialize_tree (tree ))
378- print ("Feed added into tiny.opml successfully:" )
438+ if was_updated :
439+ print ("Feed updated in tiny.opml successfully:" )
440+ else :
441+ print ("Feed added into tiny.opml successfully:" )
379442 print (f"- Category: { target_category } " )
380443 print (f"- Title: { metadata .title } " )
381444 print (f"- Site: { metadata .html_url } " )
0 commit comments