Skip to content

Commit c7fa062

Browse files
zer0yuclaude
andcommitted
feat: add update capability for existing feeds and new RSS sources
- Add --update flag to modify existing feeds (title, htmlUrl, category) - Support moving feeds between categories during update - Add new feeds: Mas0n's blog and AI/LLM related sources (PromptLayer, LangChain, etc.) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 40ddf47 commit c7fa062

2 files changed

Lines changed: 77 additions & 7 deletions

File tree

scripts/add_feed_to_tiny.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
226242
def 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

257309
def 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}")

tiny.opml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
<outline text="l1nk3dHouse" htmlUrl="http://showlinkroom.me" title="l1nk3dHouse" xmlUrl="http://showlinkroom.me/atom.xml" type="rss" />
8686
<outline title="博客园 - hac425" type="rss" text="博客园 - hac425" htmlUrl="https://www.cnblogs.com" xmlUrl="https://www.cnblogs.com/hac425/rss" />
8787
<outline htmlUrl="https://fuzzing.kr/" xmlUrl="https://fuzzing.kr/feed" text="@l33d0hyun" title="@l33d0hyun" type="rss" />
88+
<outline type="rss" title="Mas0n's blog" text="Mas0n's blog" htmlUrl="https://mas0n.org" xmlUrl="https://mas0n.org/feed" />
8889
</outline>
8990
<outline text="RedTeam" title="RedTeam">
9091
<outline type="rss" htmlUrl="https://br-sn.github.io/" xmlUrl="https://br-sn.github.io/feed.xml" text="bs" title="bs" />
@@ -606,6 +607,12 @@
606607
<outline text="mjg59.dreamwidth.org" title="mjg59.dreamwidth.org" htmlUrl="https://mjg59.dreamwidth.org" type="rss" xmlUrl="https://mjg59.dreamwidth.org/data/rss" />
607608
<outline text="computer.rip" title="computer.rip" htmlUrl="https://computer.rip" type="rss" xmlUrl="https://computer.rip/rss.xml" />
608609
<outline text="tedunangst.com" title="tedunangst.com" htmlUrl="https://tedunangst.com" type="rss" xmlUrl="https://www.tedunangst.com/flak/rss" />
610+
<outline type="rss" title="PromptLayer" text="PromptLayer" htmlUrl="https://blog.promptlayer.com/" xmlUrl="https://blog.promptlayer.com/rss/" />
611+
<outline type="rss" title="Chain Of Thought" text="Chain Of Thought" htmlUrl="https://vrungta.substack.com" xmlUrl="https://vrungta.substack.com/feed" />
612+
<outline type="rss" title="LangChain Blog" text="LangChain Blog" htmlUrl="https://blog.langchain.com/" xmlUrl="https://blog.langchain.com/rss/" />
613+
<outline type="rss" title="Honra.io Insights" text="Honra.io Insights" htmlUrl="https://www.honra.io" xmlUrl="https://www.honra.io/articles/rss.xml" />
614+
<outline type="rss" title="Decision Intelligence" text="Decision Intelligence" htmlUrl="https://decision.substack.com" xmlUrl="https://decision.substack.com/feed" />
615+
<outline type="rss" title="Emergent Minds | paddo.dev" text="Emergent Minds | paddo.dev" htmlUrl="https://paddo.dev/" xmlUrl="https://paddo.dev/rss.xml" />
609616
</outline>
610617
</body>
611618
</opml>

0 commit comments

Comments
 (0)