44from datetime import datetime
55from enum import Enum
66from itertools import chain
7+ from requests import Response
78from typing import ClassVar , Dict , Generic , Iterable , List , Optional , TypeVar
89from urllib .parse import parse_qs
9- from requests import Response
1010
1111from feedly .api_client .data import Streamable
1212from feedly .api_client .session import FeedlySession
@@ -24,38 +24,61 @@ class IoCDownloaderABC(ABC, Generic[T]):
2424 RELATIVE_URL = "/v3/enterprise/ioc"
2525 FORMAT : ClassVar [str ]
2626
27- def __init__ (self , session : FeedlySession , newer_than : Optional [datetime ], stream_id : str ):
27+ def __init__ (
28+ self ,
29+ session : FeedlySession ,
30+ newer_than : Optional [datetime ],
31+ older_than : Optional [datetime ],
32+ stream_id : str ,
33+ max_batches : Optional [int ] = None ,
34+ ):
2835 """
2936 Use this class to export the contextualized IoCs from a stream.
3037 Enterprise/personals feeds/boards are supported (see dedicated methods below).
3138 The IoCs will be returned along with their context and relationships in a dictionary representing a valid
3239 STIX v2.1 Bundle object. https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html#_gms872kuzdmg
3340 Use the newer_than parameter to filter articles that are newer than your last call.
41+ Use the older_than parameter to filter articles that are older than your last call.
42+ Use the max_batches parameter to limit the number of batches/pages to retrieve.
3443
3544 :param session: The authenticated session to use to make the api calls
3645 :param newer_than: Only articles newer than this parameter will be used. If None only one call will be make,
3746 and the continuation will be ignored
47+ :param older_than: Only articles older than this parameter will be used. If None only one call will be make,
48+ and the continuation will be ignored
49+ :param max_batches: Maximum number of batches to retrieve. If None, will continue until no more data is available
3850 """
3951 self .newer_than = newer_than
52+ self .older_than = older_than
4053 self .session = session
4154 self .stream_id = stream_id
55+ self .max_batches = max_batches
4256
4357 def download_all (self ) -> List [T ]:
4458 return self ._merge (self .stream_bundles ())
4559
4660 def stream_bundles (self ) -> Iterable [T ]:
4761 continuation = None
62+ batch_count = 0
4863 while True :
49- resp = self .session .make_api_request (
50- f"{ self .RELATIVE_URL } " ,
51- params = {
52- "newerThan" : int (self .newer_than .timestamp ()) if self .newer_than else None ,
53- "continuation" : continuation ,
54- "streamId" : self .stream_id ,
55- "format" : self .FORMAT ,
56- },
57- )
64+ params = {
65+ "continuation" : continuation ,
66+ "streamId" : self .stream_id ,
67+ "format" : self .FORMAT ,
68+ }
69+ if self .newer_than :
70+ params ["newerThan" ] = int (self .newer_than .timestamp ())
71+ if self .older_than :
72+ params ["olderThan" ] = int (self .older_than .timestamp ())
73+
74+ resp = self .session .make_api_request (f"{ self .RELATIVE_URL } " , params = params )
5875 yield self ._parse_response (resp )
76+ batch_count += 1
77+
78+ # Check if we've reached max_batches limit
79+ if self .max_batches and batch_count >= self .max_batches :
80+ return
81+
5982 if not self .newer_than or "link" not in resp .headers :
6083 return
6184 next_url = resp .headers ["link" ][1 :].split (">" )[0 ]
@@ -70,21 +93,35 @@ def _merge(self, resp_jsons: Iterable[T]) -> T:
7093
7194
7295class IoCDownloaderBuilder :
73- def __init__ (self , session : FeedlySession , format : IoCFormat , newer_than : Optional [datetime ] = None ):
96+ def __init__ (
97+ self ,
98+ session : FeedlySession ,
99+ format : IoCFormat ,
100+ newer_than : Optional [datetime ] = None ,
101+ older_than : Optional [datetime ] = None ,
102+ max_batches : Optional [int ] = None ,
103+ ):
74104 """
75105 Use this class to export the contextualized IoCs from a stream.
76106 Enterprise/personals feeds/boards are supported (see dedicated methods below).
77107 The IoCs will be returned along with their context and relationships in a dictionary representing a valid
78108 STIX v2.1 Bundle object. https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html#_gms872kuzdmg
79109 Use the newer_than parameter to filter articles that are newer than your last call.
110+ Use the older_than parameter to filter articles that are older than your last call.
111+ Use the max_batches parameter to limit the number of batches/pages to retrieve.
80112
81113 :param session: The authenticated session to use to make the api calls
82114 :param newer_than: Only articles newer than this parameter will be used. If None only one call will be make,
83115 and the continuation will be ignored
116+ :param older_than: Only articles older than this parameter will be used. If None only one call will be make,
117+ and the continuation will be ignored
118+ :param max_batches: Maximum number of batches to retrieve. If None, will continue until no more data is available
84119 """
85120 self .session = session
86121 self .format = format
87122 self .newer_than = newer_than
123+ self .older_than = older_than
124+ self .max_batches = max_batches
88125
89126 self .session .api_host = "https://cloud.feedly.com"
90127 self .user = self .session .user
@@ -113,7 +150,13 @@ def from_stream_id(self, stream_id: str) -> IoCDownloaderABC:
113150 IoCFormat .STIX : StixIoCDownloader ,
114151 IoCFormat .CSV : CsvIoCDownloader ,
115152 }
116- return format2class [self .format ](session = self .session , newer_than = self .newer_than , stream_id = stream_id )
153+ return format2class [self .format ](
154+ session = self .session ,
155+ newer_than = self .newer_than ,
156+ older_than = self .older_than ,
157+ stream_id = stream_id ,
158+ max_batches = self .max_batches ,
159+ )
117160
118161
119162class StixIoCDownloader (IoCDownloaderABC [Dict ]):
0 commit comments