|
6 | 6 | from datetime import datetime, timedelta |
7 | 7 | from ipaddress import ip_address |
8 | 8 |
|
| 9 | +import feedparser |
| 10 | +import requests |
9 | 11 | from django.conf import settings |
10 | 12 | from django.contrib.postgres.aggregates import ArrayAgg |
| 13 | +from django.core.cache import cache |
11 | 14 | from django.db.models import Count, F, Max, Min, Sum |
12 | 15 | from django.http import HttpResponse, HttpResponseBadRequest, StreamingHttpResponse |
13 | 16 | from rest_framework import status |
14 | 17 | from rest_framework.response import Response |
15 | 18 |
|
16 | 19 | from api.serializers import FeedsRequestSerializer |
| 20 | +from greedybear.consts import CACHE_KEY_GREEDYBEAR_NEWS, CACHE_TIMEOUT_SECONDS, RSS_FEED_URL |
17 | 21 | from greedybear.models import IOC, GeneralHoneypot, Statistics |
18 | 22 |
|
19 | 23 | logger = logging.getLogger(__name__) |
@@ -401,3 +405,57 @@ def asn_aggregated_queryset(iocs_qs, request, feed_params): |
401 | 405 | result.append(row_dict) |
402 | 406 |
|
403 | 407 | return result |
| 408 | + |
| 409 | + |
| 410 | +def get_greedybear_news() -> list[dict]: |
| 411 | + """ |
| 412 | + Fetch GreedyBear-related blog posts from the IntelOwl RSS feed. |
| 413 | +
|
| 414 | + Returns: |
| 415 | + List of dicts with keys: title, date, link, subtext |
| 416 | + Sorted newest first, or empty list on failure. |
| 417 | + """ |
| 418 | + cached = cache.get(CACHE_KEY_GREEDYBEAR_NEWS) |
| 419 | + if cached is not None: |
| 420 | + return cached |
| 421 | + |
| 422 | + try: |
| 423 | + response = requests.get(RSS_FEED_URL, timeout=5) |
| 424 | + response.raise_for_status() |
| 425 | + feed = feedparser.parse(response.content) |
| 426 | + |
| 427 | + filtered_entries = sorted( |
| 428 | + [entry for entry in feed.entries if "greedybear" in entry.get("title", "").lower() and entry.get("published_parsed")], |
| 429 | + key=lambda e: e.published_parsed, |
| 430 | + reverse=True, |
| 431 | + ) |
| 432 | + |
| 433 | + news_items: list[dict] = [] |
| 434 | + for entry in filtered_entries: |
| 435 | + summary = entry.get("summary", "").strip().replace("\n", " ") |
| 436 | + |
| 437 | + subtext = summary[:180].rsplit(" ", 1)[0] + "..." if len(summary) > 180 else summary |
| 438 | + |
| 439 | + news_items.append( |
| 440 | + { |
| 441 | + "title": entry.get("title"), |
| 442 | + "date": entry.get("published"), |
| 443 | + "link": entry.get("link"), |
| 444 | + "subtext": subtext, |
| 445 | + } |
| 446 | + ) |
| 447 | + |
| 448 | + cache.set( |
| 449 | + CACHE_KEY_GREEDYBEAR_NEWS, |
| 450 | + news_items, |
| 451 | + CACHE_TIMEOUT_SECONDS, |
| 452 | + ) |
| 453 | + |
| 454 | + return news_items |
| 455 | + |
| 456 | + except Exception as exc: |
| 457 | + logger.error( |
| 458 | + "Failed to fetch GreedyBear news from RSS feed", |
| 459 | + exc_info=exc, |
| 460 | + ) |
| 461 | + return [] |
0 commit comments