✨ Forge: Implement /export command to download saved articles as Markdown#11
✨ Forge: Implement /export command to download saved articles as Markdown#11
Conversation
Implemented a highly useful "/export" command that lets users download their saved articles as a grouped Markdown document. This provides better data portability and read-it-later capabilities. - Added `export_command` to `functions/telegram_bot.py`. - Registered the `/export` command in `setup_bot_commands`. - Grouped exported links cleanly by category. - Updated english and russian translations in `functions/translations.py`. - Modified help text to describe the new command. Co-authored-by: AminSS99 <139346033+AminSS99@users.noreply.github.com>
|
👋 Jules, reporting for duty! I'm here to lend a hand with this pull request. When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down. I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job! For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new feature that enables users to export their saved articles from the bot. The articles are compiled into a structured Markdown file, categorized for easy readability, and delivered directly to the user. This enhancement provides a convenient way for users to access and manage their saved content outside the bot, improving data portability and user experience. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a useful /export command for users to download their saved articles as a Markdown file. However, a critical security concern has been identified regarding the lack of sanitization of user-supplied data (article titles and sources) when generating the Markdown content, which could lead to Markdown injection. Additionally, the review focuses on improving code clarity with Pythonic idioms, enhancing robustness through better exception handling, and increasing maintainability by addressing hardcoded values and ensuring timestamp consistency.
| except Exception as e: | ||
| await update.message.reply_text(f"Error exporting: {e}") |
There was a problem hiding this comment.
This broad except Exception as e: clause leaks internal error details to the user, which is a potential information disclosure risk. It's better to log the specific error for debugging and show a generic, user-friendly error message.
except Exception as e:
print(f"Error exporting for user {telegram_id}: {e}")
await update.message.reply_text(t('ai_error', user_lang, error="Could not generate export file."))| title = item.get('title', 'Untitled') | ||
| url = item.get('url', '') | ||
| saved_at = item.get('saved_at', '')[:10] | ||
| source = item.get('source', '') | ||
|
|
||
| if url.startswith('http'): | ||
| line = f"- [{title}]({url})" | ||
| else: | ||
| line = f"- {title}" | ||
|
|
||
| meta = [] | ||
| if source: meta.append(f"Source: {source}") | ||
| if saved_at: meta.append(f"Saved: {saved_at}") | ||
|
|
||
| if meta: | ||
| line += f" ({', '.join(meta)})" | ||
| md_lines.append(line) |
There was a problem hiding this comment.
The /export command constructs a Markdown file using article titles and sources directly from the database without escaping. Since these fields can contain arbitrary text from external news sources or user input, an attacker could inject malicious Markdown syntax. This could lead to link spoofing (phishing) or potentially XSS if the exported file is opened in a vulnerable Markdown viewer.
Remediation: Escape special Markdown characters in the title and source fields before including them in the generated Markdown content. You can use the existing escape_markdown_v1 utility or a similar sanitization function.
title = item.get('title', 'Untitled')
url = item.get('url', '')
saved_at = item.get('saved_at', '')[:10]
source = item.get('source', '')
# Escape markdown characters to prevent injection
from .security_utils import escape_markdown_v1
safe_title = escape_markdown_v1(title)
safe_source = escape_markdown_v1(source)
if url.startswith('http'):
line = f"- [{safe_title}]({url})"
else:
line = f"- {safe_title}"
meta = []
if safe_source: meta.append(f"Source: {safe_source}")
if saved_at: meta.append(f"Saved: {saved_at}")
if meta:
line += f" ({', '.join(meta)})"
md_lines.append(line)| await update.message.reply_text(t('export_preparing', user_lang), parse_mode='Markdown') | ||
|
|
||
| # Fetch articles (up to 1000) | ||
| articles = get_saved_articles(telegram_id, limit=1000) |
| by_category = {} | ||
| for article in articles: | ||
| cat = article.get('category', 'tech') | ||
| if cat not in by_category: | ||
| by_category[cat] = [] | ||
| by_category[cat].append(article) |
There was a problem hiding this comment.
This block for grouping articles by category can be simplified by using collections.defaultdict. This makes the code more concise and Pythonic. Remember to add from collections import defaultdict to the function's imports at the top.
from collections import defaultdict
by_category = defaultdict(list)
for article in articles:
by_category[article.get('category', 'tech')].append(article)| # Build markdown content | ||
| md_lines = [] | ||
| md_lines.append("# LensAI - Saved Articles Export") | ||
| md_lines.append(f"Generated on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") |
There was a problem hiding this comment.
This call to datetime.now() and the one on line 744 could produce different values if the function execution crosses a second or day boundary. It's better to call datetime.now() once at the beginning of the function (e.g., now = datetime.now()) and reuse the now variable for consistent timestamps in both the file content and the filename.
| md_lines.append(line) | ||
| md_lines.append("") | ||
|
|
||
| md_text = chr(10).join(md_lines) |
This PR adds an
/exportcommand, which allows users to download all their saved articles grouped by category in a cleanly formatted Markdown (.md) file. It leveragesio.BytesIOto securely generate the document in-memory without causing disk side-effects, remaining within the project's boundaries and architectural conventions.Changes:
export_commandinfunctions/telegram_bot.py.create_bot_applicationandsetup_bot_commands(for the native Telegram menu).functions/translations.pyfor both English and Russian, including updating the/helpmenu.PR created automatically by Jules for task 11378726837750665501 started by @AminSS99