vysion 1.0.16__py3-none-any.whl → 2.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
vysion/dto/dto.py CHANGED
@@ -14,37 +14,25 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
- import hashlib
17
+
18
18
  from datetime import datetime
19
19
  from enum import Enum
20
- import re
21
-
22
- from vysion.model import enum
23
- from vysion.taxonomy import Monero_Address, Ripple_Address
24
20
 
25
21
  try:
26
22
  from types import NoneType
27
23
  except:
28
24
  NoneType: type = type(None)
29
25
 
30
- from typing import List, Optional, Union
26
+ import uuid
27
+ from typing import Generic, List, Optional, TypeVar, Union
31
28
  from urllib.parse import urlparse
32
29
 
33
- from pydantic import (
34
- BaseModel,
35
- Field,
36
- ConfigDict,
37
- validator,
38
- field_validator,
39
- root_validator,
40
- )
41
- from pydantic_core.core_schema import FieldValidationInfo
30
+ from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
42
31
 
43
32
  from vysion import taxonomy as vystaxonomy
44
- from vysion.model import URL as URL_model
45
- from vysion.model.enum import Language, Network, RansomGroup, Services
33
+ from vysion.model.enum import Language, Network
46
34
 
47
- from .tag import *
35
+ from .tag import Tag
48
36
 
49
37
 
50
38
  class Email(BaseModel):
@@ -118,65 +106,138 @@ class WhatsApp(BaseModel):
118
106
  class URL(BaseModel):
119
107
  _taxonomy = [vystaxonomy.URL]
120
108
 
121
- protocol: Optional[str]
122
- domain: Optional[str]
123
- port: Optional[int]
124
- path: Optional[str]
125
- signature: str
109
+ url: str
110
+ networkProtocol: str = Field(default_factory=lambda: "http")
111
+ domainName: str = Field(default_factory=lambda: None)
112
+ port: int = Field(default_factory=lambda: 80)
113
+ path: str = Field(default_factory=lambda: None)
114
+ signature: uuid.UUID = Field(
115
+ default_factory=lambda: uuid.UUID("{00000000-0000-0000-0000-000000000000}")
116
+ )
126
117
  network: Network = Field(default_factory=lambda: Network.clearnet)
127
118
 
128
- model_config = ConfigDict(arbitrary_types_allowed=True)
119
+ def __generate_signature(self) -> uuid.UUID:
120
+ return uuid.uuid5(uuid.NAMESPACE_URL, self.build())
129
121
 
130
122
  @classmethod
131
123
  def parse(cls, url):
132
- # TODO Save this parsed in a private variable? (e.g., _pared_)
133
- parsed = URL_model.parse(url)
134
- print(parsed)
135
- tmp_result = cls(
136
- protocol=parsed.protocol,
137
- domain=parsed.domain,
138
- port=parsed.port,
139
- path=parsed.path,
140
- signature=str(parsed.signature), # TODO Replace signature: str --> UUID
124
+ parsed = urlparse(url)
125
+
126
+ # Elements
127
+ scheme = parsed.scheme
128
+ netloc = parsed.netloc
129
+ path = parsed.path
130
+ query = parsed.query
131
+ fragment = parsed.fragment
132
+
133
+ # Build domain:port
134
+ try:
135
+ domain_port = (netloc.split(":") + [80])[:2]
136
+ except Exception as e:
137
+ print(e)
138
+
139
+ domainName = domain_port[0]
140
+
141
+ if ".onion" in domainName:
142
+ network = Network.tor
143
+ elif ".i2p" in domainName:
144
+ network = Network.i2p
145
+ else:
146
+ network = Network.clearnet
147
+
148
+ port = domain_port[1]
149
+
150
+ # Build /path?query#fragment
151
+ res_path = path
152
+
153
+ # Rebuild path's query
154
+ if len(query) > 0:
155
+ query_parts = [param.split("=") for param in query.split("&")]
156
+ query_dict = {}
157
+ for part in query_parts:
158
+ if len(part) <= 1:
159
+ query_dict[part[0]] = str()
160
+ else:
161
+ query_dict[part[0]] = part[1]
162
+
163
+ query_keys = list(query_dict.keys())
164
+ query_keys.sort()
165
+ res_query_parts = [f"{k}={query_dict[k]}" for k in query_keys]
166
+
167
+ res_query = "?" + "&".join(res_query_parts)
168
+
169
+ res_path += res_query
170
+
171
+ if len(fragment) > 0:
172
+ res_path += f"#{fragment}"
173
+
174
+ return cls(
175
+ url=url,
176
+ networkProtocol=scheme,
177
+ domainName=domainName,
178
+ port=port,
179
+ path=res_path,
180
+ network=network,
141
181
  )
142
182
 
143
- return tmp_result
183
+ def __init_subclass__(self, *args, **kwargs):
184
+ super().__init__(*args, **kwargs)
185
+ self.urlSignature = self.__generate_signature()
144
186
 
145
187
  def build(self) -> str:
146
- return f"{self.protocol}://{self.domain}:{self.port}{self.path}"
188
+ url = self.path
189
+
190
+ if self.domainName != "":
191
+ if self.port != 80:
192
+ url = f":{self.port}" + url
193
+
194
+ url = f"{self.domainName}" + url
195
+
196
+ if self.networkProtocol != "":
197
+ url = f"{self.networkProtocol}://" + url
198
+
199
+ return url
147
200
 
148
201
 
149
202
  class Page(BaseModel):
150
203
  id: str
151
204
  url: URL
152
- parent: Optional[str] = None
153
- title: Optional[str] = None
205
+ foundAt: Optional[str] = None
206
+ pageTitle: Optional[str] = None
154
207
  language: Optional[Language]
155
- html: str = None
208
+ html: Optional[str] = None
209
+ text: Optional[str] = None
156
210
  sha1sum: Optional[str] = None
157
211
  sha256sum: Optional[str] = None
158
212
  ssdeep: Optional[str] = None
159
- date: datetime = None
213
+ detectionDate: datetime = None
160
214
  chunk: bool = False
161
215
 
162
216
 
163
217
  class RansomwareHit(BaseModel):
164
- id: str
165
- url: URL
166
- # title: str = None
167
- group: str
168
- company: Optional[str]
169
- company_address: Optional[str]
170
- company_link: Optional[str]
171
- info: Optional[str]
172
- html: Optional[str]
173
- country: Optional[str]
174
- sha256sum: Optional[str] = None
175
- ssdeep: Optional[str] = None
176
- date: datetime
177
- chunk: bool = False
218
+ page: Page
219
+ tag: List[Tag] = Field(default_factory=lambda: [])
220
+ ransomwareGroup: str
221
+ companyName: Optional[str] = None
222
+ companyAddress: Optional[str] = None
223
+ companyLink: Optional[str] = None
224
+ country: Optional[str] = None
178
225
 
179
- model_config = ConfigDict(arbitrary_types_allowed=True)
226
+
227
+ class DocumentHit(BaseModel):
228
+ page: Page
229
+ tag: List[Tag] = Field(default_factory=lambda: [])
230
+ email: List[Email] = Field(default_factory=lambda: [])
231
+ paste: List[Paste] = Field(default_factory=lambda: [])
232
+ skype: List[Skype] = Field(default_factory=lambda: [])
233
+ telegram: List[Telegram] = Field(default_factory=lambda: [])
234
+ whatsapp: List[WhatsApp] = Field(default_factory=lambda: [])
235
+ bitcoin_address: List[BitcoinAddress] = Field(default_factory=lambda: [])
236
+ polkadot_address: List[PolkadotAddress] = Field(default_factory=lambda: [])
237
+ ethereum_address: List[EthereumAddress] = Field(default_factory=lambda: [])
238
+ monero_address: List[MoneroAddress] = Field(default_factory=lambda: [])
239
+ ripple_address: List[RippleAddress] = Field(default_factory=lambda: [])
240
+ zcash_address: List[ZcashAddress] = Field(default_factory=lambda: [])
180
241
 
181
242
 
182
243
  class Media(BaseModel):
@@ -185,7 +246,7 @@ class Media(BaseModel):
185
246
  objectName: Optional[str] = Field(default_factory=lambda: None)
186
247
  contentType: str
187
248
 
188
- @validator("bucketName", "objectPath", "objectName")
249
+ @field_validator("bucketName", "objectPath", "objectName")
189
250
  def validate_strings(cls, v):
190
251
  if v is not None and not isinstance(v, str):
191
252
  raise ValueError("value must be a string")
@@ -202,12 +263,13 @@ class LanguagePair(BaseModel):
202
263
  language: str
203
264
  probability: float
204
265
 
205
- @root_validator(pre=True)
206
- def split_key_value(cls, value: str) -> dict:
207
- if isinstance(value, str):
208
- key, value = value.split(":")
209
- return {"key": key, "value": float(value)}
210
- return value
266
+ @model_validator(mode="before")
267
+ @classmethod
268
+ def split_key_value(cls, data: str) -> dict:
269
+ if isinstance(data, str):
270
+ key, value = data.split(":")
271
+ return {"language": key, "probability": float(value)}
272
+ return data
211
273
 
212
274
  @field_validator("language")
213
275
  def validate_language(cls, v: str) -> str:
@@ -222,11 +284,11 @@ class LanguagePair(BaseModel):
222
284
  return v
223
285
 
224
286
 
225
- class TelegramHit(BaseModel):
287
+ class ImMessageHit(BaseModel):
226
288
  userId: Optional[int] = Field(default_factory=lambda: None)
227
289
  username: Optional[str] = Field(default_factory=lambda: None)
228
290
  channelId: Optional[int] = Field(default_factory=lambda: None)
229
- messageId: int
291
+ messageId: str
230
292
  message: Optional[str] = Field(default_factory=lambda: None)
231
293
  channelTitle: Optional[str] = Field(default_factory=lambda: None)
232
294
  languages: Optional[List[LanguagePair]] = Field(default_factory=lambda: None)
@@ -241,7 +303,8 @@ class TelegramHit(BaseModel):
241
303
  raise ValueError("MessageId field cannot be empty")
242
304
  return v
243
305
 
244
- class TelegramProfileHit(BaseModel):
306
+
307
+ class ImProfileHit(BaseModel):
245
308
  userId: int
246
309
  usernames: Optional[List[str]] = Field(default_factory=lambda: None)
247
310
  firstName: Optional[List[str]] = Field(default_factory=lambda: None)
@@ -255,18 +318,18 @@ class TelegramProfileHit(BaseModel):
255
318
  raise ValueError("UserId field cannot be empty")
256
319
  return v
257
320
 
258
-
259
321
  @field_validator("detectionDate")
260
322
  def validate_detectionDate(cls, v: datetime) -> datetime:
261
323
  if not v:
262
324
  raise ValueError("DetectionDate field cannot be empty")
263
325
  return v
264
-
265
- class TelegramChannelHit(BaseModel):
326
+
327
+
328
+ class ImChannelHit(BaseModel):
266
329
  channelId: int
267
330
  channelTitles: Optional[List[str]] = Field(default_factory=lambda: None)
268
331
  detectionDate: datetime
269
- creationDate: datetime
332
+ creationDate: datetime
270
333
  channelPhoto: Optional[List[str]] = Field(default_factory=lambda: None)
271
334
 
272
335
  @field_validator("channelId")
@@ -280,63 +343,63 @@ class TelegramChannelHit(BaseModel):
280
343
  if not v:
281
344
  raise ValueError("DetectionDate field cannot be empty")
282
345
  return v
283
-
346
+
284
347
  @field_validator("creationDate")
285
348
  def validate_creationDate(cls, v: datetime) -> datetime:
286
349
  if not v:
287
350
  raise ValueError("creationDate field cannot be empty")
288
351
  return v
289
352
 
290
- class Hit(BaseModel):
291
- page: Page
292
- tag: List[Tag]
293
- email: List[Email] = Field(default_factory=lambda: [])
294
- paste: List[Paste] = Field(default_factory=lambda: [])
295
- skype: List[Skype] = Field(default_factory=lambda: [])
296
- telegram: List[Telegram] = Field(default_factory=lambda: [])
297
- whatsapp: List[WhatsApp] = Field(default_factory=lambda: [])
298
- bitcoin_address: List[BitcoinAddress] = Field(default_factory=lambda: [])
299
- polkadot_address: List[PolkadotAddress] = Field(default_factory=lambda: [])
300
- ethereum_address: List[EthereumAddress] = Field(default_factory=lambda: [])
301
- monero_address: List[MoneroAddress] = Field(default_factory=lambda: [])
302
- ripple_address: List[RippleAddress] = Field(default_factory=lambda: [])
303
- zcash_address: List[ZcashAddress] = Field(default_factory=lambda: [])
353
+
354
+ class ImFeedHit(BaseModel):
355
+ id: str
356
+ telegram: List[str]
357
+ detectionDate: datetime
358
+ url: str
359
+ path: str
360
+ network: str
304
361
 
305
362
 
306
363
  class RansomFeedHit(BaseModel):
307
364
  id: str
308
- company: Optional[str]
309
- company_link: Optional[str]
310
- link: str
311
- group: str
312
- date: datetime
313
- info: Optional[str]
365
+ companyName: Optional[str]
366
+ companyLink: Optional[str]
367
+ url: str
368
+ ransomwareGroup: str
369
+ detectionDate: datetime
370
+ text: Optional[str]
314
371
  country: Optional[str]
315
372
 
316
373
  model_config = ConfigDict(arbitrary_types_allowed=True)
317
374
 
318
375
 
319
- class TelegramFeedHit(BaseModel):
320
- id: str
321
- telegram: List[str]
322
- date: datetime
323
- url: str
324
- path: str
325
- network: str
376
+ class Stat(BaseModel):
377
+ key: Union[str, int]
378
+ doc_count: int
379
+
380
+
381
+ class Buckets(BaseModel):
382
+ buckets: List[Stat]
383
+
384
+
385
+ class AggStats(Stat):
386
+ key_as_string: str
387
+ agg: Optional[Buckets] = None
326
388
 
327
389
 
328
- class Result(BaseModel):
329
- # TODO Add pagination, query, etc?
390
+ class PhoneInfo(BaseModel):
391
+ name: str
392
+ source: str
393
+ href: Optional[str] = None
394
+ carrier: Optional[str] = None
395
+
396
+
397
+ T = TypeVar("T")
398
+
399
+
400
+ class Result(BaseModel, Generic[T]):
330
401
  total: int = 0
331
- hits: Union[
332
- List[Hit],
333
- List[TelegramHit],
334
- List[RansomFeedHit],
335
- List[TelegramFeedHit],
336
- List[RansomwareHit],
337
- List[TelegramProfileHit],
338
- List[TelegramChannelHit]
339
- ] = Field(default_factory=lambda: [])
402
+ hits: List[T] = Field(default_factory=lambda: [])
340
403
 
341
404
  def __init__(self, **kwargs):
342
405
  super().__init__(**kwargs)
@@ -349,24 +412,43 @@ class Result(BaseModel):
349
412
 
350
413
  return type(self.hits[0])
351
414
 
415
+ @model_validator(mode="before")
416
+ @classmethod
417
+ def check_hits(cls, data):
418
+ hits = data.get("hits")
419
+ if not isinstance(hits, list):
420
+ raise ValueError("hits must be a list")
421
+
422
+ return data
423
+
424
+
425
+ class ErrorCode(int, Enum):
426
+ BAD_REQUEST = 400
427
+ UNAUTHORIZED = 401
428
+ FORBIDDEN = 403
429
+ NOT_FOUND = 404
430
+ CONFLICT = 409
431
+ UNPROCESSABLE_ENTITY = 422
432
+ TOO_MANY_REQUESTS = 429
433
+ INTERNAL_SERVER_ERROR = 500
434
+
352
435
 
353
- # TODO Move API responses to other class
354
- class VysionResponse(BaseModel):
355
- """
356
- VysionResponse is a json:api flavoured response from the API
357
- """
436
+ class ErrorMessage(str, Enum):
437
+ BAD_REQUEST = "Bad Request"
438
+ UNAUTHORIZED = "Unauthorized"
439
+ FORBIDDEN = "Forbidden"
440
+ NOT_FOUND = "Not Found"
441
+ CONFLICT = "Conflict"
442
+ UNPROCESSABLE_ENTITY = "Unprocessable Entity"
443
+ TOO_MANY_REQUESTS = "Too Many Requests"
444
+ INTERNAL_SERVER_ERROR = "Internal Server Error"
358
445
 
359
- data: Result # TODO Add type to all JSON:API responses
360
446
 
447
+ class Error(BaseModel):
448
+ code: ErrorCode
449
+ message: str
361
450
 
362
- class VysionError(BaseModel):
363
- class StatusCode(int, Enum):
364
- UNK = 000
365
- OK = 200
366
- REQ_ERROR = 400
367
- UNAUTHORIZED = 403
368
- NOT_FOUND = 404
369
- INTERNAL_ERROR = 500
370
451
 
371
- code: StatusCode = StatusCode.UNK
372
- message: str = "UNK_ERR"
452
+ class VysionResponse(BaseModel, Generic[T]):
453
+ data: Optional[Result[T]] = None
454
+ error: Optional[Error] = None
vysion/dto/util.py CHANGED
@@ -14,9 +14,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
- import json
18
17
 
19
- from pymisp import MISPAttribute, MISPEvent, MISPObject, MISPTag
18
+ from pymisp import MISPAttribute, MISPEvent, MISPObject
20
19
 
21
20
  try:
22
21
  from types import NoneType
@@ -24,15 +23,14 @@ except:
24
23
  NoneType = type(None)
25
24
 
26
25
  import vysion.dto as dto
27
- from vysion.dto import URL, Hit, Page, RansomFeedHit
26
+ from vysion.dto import URL, DocumentHit, Page, RansomFeedHit
28
27
 
29
28
 
30
29
  class MISPProcessor:
31
30
  def __init__(self):
32
31
  self.misp_event = MISPEvent()
33
32
 
34
- def parse_hit(self, hit: Hit, ref_attribute: MISPAttribute = None, **_):
35
-
33
+ def parse_hit(self, hit: DocumentHit, ref_attribute: MISPAttribute = None, **_):
36
34
  page: Page = hit.page
37
35
 
38
36
  # TODO Add more page parameters
@@ -48,8 +46,8 @@ class MISPProcessor:
48
46
  network = url.network
49
47
  misp_object.add_attribute("network", type="text", value=network)
50
48
 
51
- title = page.title
52
- misp_object.add_attribute("title", type="text", value=title)
49
+ pageTitle = page.pageTitle
50
+ misp_object.add_attribute("title", type="text", value=pageTitle)
53
51
 
54
52
  url_vysion: URL = "https://app.vysion.ai/document/" + page.id
55
53
  misp_object.add_attribute("url_vysion", type="url", value=url_vysion)
@@ -64,7 +62,7 @@ class MISPProcessor:
64
62
  # TODO Remove this addition when the vysion-page object works
65
63
  self.misp_event.add_attribute("url", value=url.build())
66
64
 
67
- self.misp_event.add_attribute("domain", value=url.domain)
65
+ self.misp_event.add_attribute("domain", value=url.domainName)
68
66
 
69
67
  for email in hit.email:
70
68
  self.misp_event.add_attribute("email", value=email.value)
@@ -91,31 +89,34 @@ class MISPProcessor:
91
89
  self.misp_event.add_tag(str(tag))
92
90
 
93
91
  def parse_ransom_feed_hit(self, hit: RansomFeedHit, **kwargs):
94
-
95
92
  # TODO Add event info!
96
93
 
97
94
  misp_object = MISPObject("vysion-ransomware-feed")
98
95
  misp_object.template_uuid = "e0bfa994-c184-4894-bfaa-73b1350746e1"
99
- misp_object[
100
- "meta-category"
101
- ] = "misc" # TODO Esto se tiene que poder hacer de otra manera... Y sólo es necesario en los feeds
96
+ misp_object["meta-category"] = (
97
+ "misc" # TODO Esto se tiene que poder hacer de otra manera... Y sólo es necesario en los feeds
98
+ )
102
99
 
103
100
  misp_object.add_attribute("id", type="text", value=hit.id)
104
- misp_object.add_attribute("company", type="target-org", value=hit.company)
105
- misp_object.add_attribute("company_link", type="link", value=hit.company_link)
106
- misp_object.add_attribute("link", type="link", value=hit.link)
107
- misp_object.add_attribute("group", type="threat-actor", value=hit.group)
108
- misp_object.add_attribute("date", type="datetime", value=hit.date)
109
- misp_object.add_attribute("info", type="text", value=hit.info)
101
+ misp_object.add_attribute("companyName", type="target-org", value=hit.companyName)
102
+ misp_object.add_attribute("companyLink", type="link", value=hit.companyLink)
103
+ misp_object.add_attribute("url", type="link", value=hit.url)
104
+ misp_object.add_attribute("ransomwareGroup", type="threat-actor", value=hit.ransomwareGroup)
105
+ misp_object.add_attribute("detectionDate", type="datetime", value=hit.detectionDate)
106
+ misp_object.add_attribute("text", type="text", value=hit.text)
110
107
  misp_object.add_attribute("country", type="text", value=hit.country)
111
- misp_object.add_attribute("url_vysion_ransom", type="link", value= "https://app.vysion.ai/victim/" + hit.id)
108
+ misp_object.add_attribute(
109
+ "url_vysion_ransom",
110
+ type="link",
111
+ value="https://app.vysion.ai/victim/" + hit.id,
112
+ )
112
113
 
113
114
  self.misp_event.add_object(misp_object)
114
115
 
115
116
  def process(self, result: dto.Result, **kwargs) -> MISPEvent:
116
-
117
+
117
118
  processor = {
118
- Hit: self.parse_hit,
119
+ DocumentHit: self.parse_hit,
119
120
  RansomFeedHit: self.parse_ransom_feed_hit,
120
121
  }.get(result.get_type(), lambda *_, **__: {})
121
122
 
vysion/model/__init__.py CHANGED
@@ -15,4 +15,3 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
  from . import enum
18
- from .model import *
@@ -19,7 +19,6 @@ from enum import Enum
19
19
 
20
20
 
21
21
  class Language(str, Enum):
22
-
23
22
  """
24
23
  Using names from ISO 639-1
25
24
  """
@@ -19,7 +19,6 @@ from enum import Enum
19
19
 
20
20
 
21
21
  class Network(str, Enum):
22
-
23
22
  tor = "tor"
24
23
  i2p = "i2p"
25
24
  zeronet = "zeronet"
@@ -19,7 +19,6 @@ from softenum import Softenum
19
19
 
20
20
 
21
21
  class RansomGroup(str, Softenum):
22
-
23
22
  conti = "Conti"
24
23
  lockbit = "LockBit"
25
24
  hive = "Hive"
@@ -19,7 +19,6 @@ from enum import IntEnum
19
19
 
20
20
 
21
21
  class Services(IntEnum):
22
-
23
22
  acr_nema = 104
24
23
  afpovertcp = 548
25
24
  afs3_bos = 7007
@@ -14,13 +14,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
- from typing import Dict
18
17
 
19
18
  from vysion.taxonomy.flavours import (
20
19
  MISP,
21
20
  DBSafe,
22
21
  EmptyFlavour,
23
- Flavour,
24
22
  Flavours,
25
23
  Vysion,
26
24
  )
vysion/version.py CHANGED
@@ -15,4 +15,4 @@ See the License for the specific language governing permissions and
15
15
  limitations under the License.
16
16
  """
17
17
 
18
- __version__ = "1.0.7"
18
+ __version__ = "2.0.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vysion
3
- Version: 1.0.16
3
+ Version: 2.0.1
4
4
  Summary: The official Python client library for Vysion
5
5
  Home-page: https://vysion.ai
6
6
  License: Apache-2.0
@@ -29,7 +29,7 @@ Welcome to the PyPi webpage for Vysion, our implementation as a Python library t
29
29
 
30
30
  You can request a demo for the web app or an API-key to use in this library at [vysion.ai](https://vysion.ai).
31
31
 
32
- Latest version: [1.0.16](https://pypi.org/project/vysion/)
32
+ Latest version: [2.0.1](https://pypi.org/project/vysion/)
33
33
 
34
34
  You can visit [the documentation](https://developers.vysion.ai/?python) for more information on the searches and requests performed with the library or directly on the API.
35
35
 
@@ -0,0 +1,22 @@
1
+ vysion/__init__.py,sha256=yxJiM-S29q8GB3PBff9tV70P2nvydd8aKLEprLkXw5o,664
2
+ vysion/client/__init__.py,sha256=aVHmBuetPdybp7TgcNzUx4HkxTjEuXYzSdDYiYfFQd0,630
3
+ vysion/client/client.py,sha256=RjsPv5Zrr6CBEC2tWB7xDk7tuF0WAssrhCYp_hXFCd8,11178
4
+ vysion/client/error.py,sha256=-3NGr9ngTGSy-ctqk6S9eMBAvtY3lnjv9i0KiuRjd5g,1158
5
+ vysion/dto/__init__.py,sha256=ct8JxVMfJ0APiOTgr9ju-JIuBlXOrPkx7n2qISSXUts,605
6
+ vysion/dto/dto.py,sha256=H7uQMwcbFsVh2TTESdCooA-l6cP8XQ8iS9hrNC2F-UQ,13054
7
+ vysion/dto/tag.py,sha256=_Dn4-_xiC1PD4udp3m4FsDdyHVCSgrq7NFJomjsMYtU,1503
8
+ vysion/dto/util.py,sha256=UNpy_A7rsfH57xgEmDultCIOtyM0a9OzI1PpWUSJi_U,4571
9
+ vysion/model/__init__.py,sha256=rJX9eNW9-jQGTgwm97W04jhZa-dV_dSTTTFcbWg73w0,606
10
+ vysion/model/enum/__init__.py,sha256=lhG6rgaYjrFBR8_IfJL0OoT0L7vooR5_ht4zUahQcOc,690
11
+ vysion/model/enum/languages.py,sha256=yrYDwLEM2lK5iqdLR16n8xeCeSmsCsIj4sTiuNz33FM,4204
12
+ vysion/model/enum/networks.py,sha256=t7aVmLeHqfZ5gQwJPLeS68DSDBl6IEnG21h6kOjvCaw,788
13
+ vysion/model/enum/ransom_groups.py,sha256=_-DFvxUUSxb6r4Xy1Cgpoib2S2qWQmAok-BH-1uEVo0,1526
14
+ vysion/model/enum/services.py,sha256=fUfZEO0RNNctU4Gzae5TV3c7BU5A_cEwaEb1tzqgcr0,5568
15
+ vysion/taxonomy/__init__.py,sha256=Bc364AYxRCyjIn26-XBPeEDCPe3Hma4r5RAI8R8eblU,635
16
+ vysion/taxonomy/flavours.py,sha256=ubImHBE8v0zhzFy_2YozAQk2SzN1XDQwHAxb2RebtCo,2347
17
+ vysion/taxonomy/taxonomy.py,sha256=mG6vHX5Eyp38AKyJBgfBDqdpEmDMbkv8TA8YUBzko54,12227
18
+ vysion/version.py,sha256=UZ0L2QPjFezLeUXGLXw32JOqE2BwkSNth2rnLnRXYlw,610
19
+ vysion-2.0.1.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
20
+ vysion-2.0.1.dist-info/METADATA,sha256=vlt_td133OtsJuwEo6_58i3alvbhGlPU0Wc1jBE_6Bo,2122
21
+ vysion-2.0.1.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
22
+ vysion-2.0.1.dist-info/RECORD,,