Conversation
There was a problem hiding this comment.
Gitzilla has reviewed your changes and found 1 potential issue.
Autofix is OFF. To automatically fix reported issues, enable autofix in the Gitzilla dashboard.
| # Database - CHANGE THESE PASSWORDS | ||
| POSTGRES_USER=postgres | ||
| POSTGRES_PASSWORD=PdefSMMIa8N22nKwHxmWz5znC13bUFo | ||
| DATABASE_URL=postgresql://postgres:PdefSMMIa8N22nKwHxmWz5znC13bUFo@postgres:5432/pricebot |
There was a problem hiding this comment.
Hardcoded database password in .env.example
High Severity
The .env.example file contains a hardcoded database password (PdefSMMIa8N22nKwHxmWz5znC13bUFo) and DATABASE_URL instead of placeholder values. Users who copy this file will unknowingly deploy with these credentials. The previous .env.example correctly used placeholder text like your_btc_bot_token_here.
Suggested fix: Replace hardcoded credentials with placeholder values (e.g., your_postgres_password_here) to match the pattern used for Discord tokens in the same file.
There was a problem hiding this comment.
Gitzilla has reviewed your changes and found 2 potential issues.
Autofix is OFF. To automatically fix reported issues, enable autofix in the Gitzilla dashboard.
| content=f"**{crypto.upper()} - {timeframe_str} chart**", | ||
| file=file | ||
| ) | ||
| await interaction.followup.send(content=f"**{self.crypto_name.upper()} - {timeframe} chart**", file=discord.File(buf, filename=f"{self.crypto_name.lower()}_chart.png")) |
There was a problem hiding this comment.
Missing bot entry point — bot cannot be started
High Severity
The if __name__ == "__main__": block that instantiates and runs the bot is completely missing from bot.py. The PriceBot class is defined but never instantiated. The old code had a main block that created PriceBot instances and called asyncio.run(run_all()) to start the bots. This functionality was removed during the merge, making the bot non-functional as a standalone script.
Suggested fix: Add back the main execution block that was in the original code:
if __name__ == "__main__":
configs = load_bot_configs()
if not configs:
logger.error("No bot configurations found!")
sys.exit(1)
async def run_all():
tasks = [asyncio.create_task(run_bot(cfg)) for cfg in configs]
...
asyncio.run(run_all())| await interaction.followup.send(content=f"**{self.crypto_name.upper()} - {timeframe} chart**", file=discord.File(buf, filename=f"{self.crypto_name.lower()}_chart.png")) | ||
| except Exception as e: | ||
| logger.error(f"Chart command failed for {crypto}: {e}") | ||
| logger.error(f"Chart command failed for {self.crypto_name}: {e}") | ||
| await interaction.followup.send(f"Error generating chart: {e}") | ||
|
|
||
|
|
||
|
|
||
| @price.autocomplete("timeframe") | ||
| async def timeframe_autocomplete(self, interaction: discord.Interaction, current: str): | ||
| filtered = [opt for opt in self.TIMEFRAME_OPTIONS if current.lower() in opt.lower()] if current else list(self.TIMEFRAME_OPTIONS)[:9] | ||
| return [app_commands.Choice(name=opt, value=opt) for opt in filtered[:25]] | ||
|
|
||
|
|
||
| class PriceGroup(app_commands.Group): | ||
| def __init__(self, db: Database, price_service: PriceService, default_crypto: str): | ||
| super().__init__(name="price", description="Crypto price commands") | ||
| self.db = db | ||
| self.price_service = price_service | ||
| self.default_crypto = default_crypto.upper() | ||
|
|
||
| def _get_change(self, history: list) -> float: | ||
| if len(history) < 2: | ||
| return 0.0 | ||
| oldest = history[0][1] | ||
| newest = history[-1][1] | ||
| if oldest <= 0: | ||
| return 0.0 | ||
| return ((newest - oldest) / oldest) * 100 | ||
|
|
||
| self.db, self.price_service, self.default_crypto = db, price_service, default_crypto.upper() | ||
|
|
||
| @app_commands.command() | ||
| async def current(self, interaction: discord.Interaction, crypto: str = None): | ||
| """Get current price of a cryptocurrency with conversions.""" | ||
| crypto = (crypto or self.default_crypto).upper() | ||
|
|
||
| try: | ||
| price = await self.db.get_latest_price(crypto) | ||
| if not price: | ||
| fresh_price = await self.price_service.get_price(crypto) | ||
| if fresh_price: | ||
| await self.db.save_price(crypto, fresh_price) | ||
| price = fresh_price | ||
| else: | ||
| await interaction.response.send_message(f"No price data for {crypto}") | ||
| return | ||
|
|
||
| conversions = {} | ||
| for ticker in ["BTC", "ETH", "SOL"]: | ||
| try: | ||
| conv_price = await self.price_service.get_price(ticker) | ||
| if conv_price and conv_price > 0: | ||
| conversions[ticker] = conv_price | ||
| else: | ||
| db_p = await self.db.get_latest_price(ticker) | ||
| if db_p and db_p > 0: | ||
| conversions[ticker] = db_p | ||
| except Exception: | ||
| pass | ||
|
|
||
| history_24h = await self.db.get_price_history(crypto, hours=24) | ||
| history_7d = await self.db.get_price_history(crypto, hours=168) | ||
| history_30d = await self.db.get_price_history(crypto, hours=720) | ||
|
|
||
| change_24h = self._get_change(history_24h) | ||
| change_7d = self._get_change(history_7d) | ||
| change_30d = self._get_change(history_30d) | ||
|
|
||
| def change_block(changes: float, label: str) -> str: | ||
| color = "🟢" if changes >= 0 else "🔴" | ||
| sign = "+" if changes >= 0 else "" | ||
| return f"**{label}**\n{color} {sign}{changes:.2f}%" | ||
|
|
||
| embed = discord.Embed( | ||
| title=f"{crypto}", | ||
| color=0x00ff00 if change_24h >= 0 else 0xff0000 | ||
| ) | ||
|
|
||
| embed.add_field( | ||
| name="USD", | ||
| value=f"**${price:,.6f}**" if price < 1 else f"**${price:,.2f}**" if price >= 100 else f"**${price:,.4f}**", | ||
| inline=False | ||
| ) | ||
|
|
||
| embed.add_field( | ||
| name="24h", | ||
| value=change_block(change_24h, ""), | ||
| inline=True | ||
| ) | ||
|
|
||
| embed.add_field( | ||
| name="7d", | ||
| value=change_block(change_7d, ""), | ||
| inline=True | ||
| ) | ||
|
|
||
| embed.add_field( | ||
| name="30d", | ||
| value=change_block(change_30d, ""), | ||
| inline=True | ||
| ) | ||
|
|
||
| conversions_text = "" | ||
| if "BTC" in conversions and conversions["BTC"] > 0 and crypto != "BTC": | ||
| btc_val = price / conversions["BTC"] | ||
| conversions_text += f"BTC: `{btc_val:.8f}`\n" | ||
| if "ETH" in conversions and conversions["ETH"] > 0 and crypto != "ETH": | ||
| eth_val = price / conversions["ETH"] | ||
| conversions_text += f"ETH: `{eth_val:.8f}`\n" | ||
| if "SOL" in conversions and conversions["SOL"] > 0 and crypto != "SOL": | ||
| sol_val = price / conversions["SOL"] | ||
| conversions_text += f"SOL: `{sol_val:.8f}`\n" | ||
|
|
||
| if conversions_text: | ||
| embed.add_field( | ||
| name="Conversions", | ||
| value=conversions_text, | ||
| inline=False | ||
| ) | ||
|
|
||
| await interaction.response.send_message(embed=embed) | ||
|
|
||
| except Exception as e: | ||
| logger.error(f"Price command failed: {e}") | ||
| await interaction.response.send_message(f"Error: {e}") | ||
| price = await self.db.get_latest_price(crypto) |
There was a problem hiding this comment.
File is truncated — chart command and PriceGroup class are missing
High Severity
The bot.py file appears to be truncated around line 332. The ChartGroup.price method is incomplete (ends mid-string), and the PriceGroup class referenced in setup_hook() at line 253 is never defined in the readable file content. This means the /price slash command cannot work because PriceGroup is undefined.
Suggested fix: The file content was lost during the merge. The complete PriceGroup class definition and the full ChartGroup.price method need to be restored.
There was a problem hiding this comment.
Gitzilla has reviewed your changes and found 1 potential issue.
Autofix is OFF. To automatically fix reported issues, enable autofix in the Gitzilla dashboard.
| bucket_type, query = next(((str(d) if d else "raw", q) for max_h, d, q in queries if hours <= max_h), ("monthly", f"SELECT bucket_start as timestamp, avg_price FROM price_aggregates WHERE crypto_name = $1 AND bucket_start > $2 AND bucket_duration = {ONE_MONTH} ORDER BY bucket_start ASC")) | ||
| crypto = crypto_name.upper() | ||
| async with self.pool.acquire() as conn: | ||
| rows = await conn.fetch(query, crypto, cutoff, limit) |
There was a problem hiding this comment.
Parameter count mismatch in monthly fallback query of get_price_history
High Severity
The monthly fallback query built on line 151 only has 2 placeholders ($1, $2) but line 154 always passes 3 parameters (crypto, cutoff, limit). When this query is selected (for hours > 43800, i.e., >5 years), asyncpg will raise an error due to mismatched parameter count. This causes the /chart price command to fail silently for very long timeframes.
Suggested fix: Change line 154 to pass only 2 parameters when the fallback query is used, or add LIMIT $3 to the fallback query string and pass all 3 parameters. E.g.: rows = await conn.fetch(query, crypto, cutoff) if "$3" not in query else await conn.fetch(query, crypto, cutoff, limit)
Summary
This PR merges the fully optimized v2 branch into main, representing a complete Python rewrite of the Discord crypto bot.
Lines reduced: 1096 → 403 (63% reduction)
Features preserved:
/price current,/chart pricewith autocomplete)Bugs fixed (included in v2):
session.is_closed()bug (wassession.closed)awaitonget_latest_price()callsasyncio.gatherKey optimizations:
bot.pyNote
Low Risk
Overview
No reviewable diff files remained after review path policy filtering.
Written by Gitzilla for commit 4f9e415. This will update automatically on new runs. Configure in the Gitzilla dashboard.