cherry-shared2 0.1.26__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.
@@ -0,0 +1,511 @@
1
+ from datetime import datetime, timedelta
2
+ import hashlib
3
+ import json
4
+ import random
5
+ import re
6
+ import string
7
+ import time
8
+ from typing import Optional, Union
9
+ from pyrogram.types import Message, Photo, Video, Document, VideoNote, Animation, Audio
10
+ from itertools import zip_longest
11
+ from cherry_shared.emojis import Emojis
12
+ from web3 import Web3
13
+
14
+
15
+ def extract_arguments(text: str) -> str:
16
+ """
17
+ Returns the argument after the command.
18
+ :param text: String to extract the arguments from a command
19
+ :return: the arguments if `text` is a command (according to is_command), else None.
20
+ """
21
+ regexp = re.compile(r"/\w*(@\w*)*\s*([\s\S]*)", re.IGNORECASE)
22
+ result = regexp.match(text)
23
+ return result.group(2) if is_command(text) else None
24
+
25
+
26
+ def extract_entities(entities: list, start: int, len: int) -> list:
27
+ """
28
+ Returns the entities of a Message Object.
29
+ :param entities: list of MessageEntity
30
+ :param start: start index of the reduced text
31
+ :param len: length of the reduced text
32
+ :return: list of new reduced MessageEntity
33
+ """
34
+ if not entities:
35
+ return []
36
+ new_entities = []
37
+ for entity in entities:
38
+ if entity.offset < start:
39
+ continue
40
+ if entity.offset + entity.length > start + len:
41
+ continue
42
+ entity.offset -= start
43
+ new_entities.append(entity)
44
+ return new_entities
45
+
46
+
47
+ def is_command(text: str) -> bool:
48
+ """
49
+ Checks if `text` is a command. Telegram chat commands start with the '/' character.
50
+
51
+ :param text: Text to check.
52
+ :return: True if `text` is a command, else False.
53
+ """
54
+ if not text:
55
+ return False
56
+ return text.startswith("/")
57
+
58
+
59
+ url_pattern = re.compile(
60
+ r"(?<!\S)(?:https?://)" # Scheme with negative lookbehind
61
+ r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|" # Domain
62
+ r"localhost|" # Localhost
63
+ r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})" # IP address
64
+ r"(?::\d+)?" # Port (optional)
65
+ r"(?:/?|[/?]\S*?)(?=[\s\]\"']|$)", # Path (non-greedy) with lookahead for space, closing bracket, quote, or end of string
66
+ re.IGNORECASE,
67
+ )
68
+
69
+
70
+ def get_url_from_text(text: str):
71
+ try:
72
+ matches = url_pattern.findall(text)
73
+ if matches:
74
+ return matches[0]
75
+ except Exception as e:
76
+ return None
77
+
78
+
79
+ def get_file_id(message: Message):
80
+ """Returns file_id of media message"""
81
+ media = get_media(message)
82
+ if media:
83
+ return media.file_id
84
+ return None
85
+
86
+
87
+ def get_media(
88
+ m: Message,
89
+ ) -> Optional[Union[Photo, Video, Document, Audio, VideoNote, Animation]]:
90
+ """Returns file_id of media message"""
91
+ if not m.media:
92
+ return None
93
+ media = str(m.media).lower().split(".")[1]
94
+ if media in ("photo", "video", "audio", "document", "video_note", "animation"):
95
+ return getattr(m, media, None)
96
+ return None
97
+
98
+
99
+ def smart_split(text: str, chars_per_string: int = 4096) -> list[str]:
100
+ r"""
101
+ Splits one string into multiple strings, with a maximum amount of `chars_per_string` characters per string.
102
+ This is very useful for splitting one giant message into multiples.
103
+ If `chars_per_string` > 4096: `chars_per_string` = 4096.
104
+ Splits by '\n', '. ' or ' ' in exactly this priority.
105
+ :param text: The text to split
106
+ :type text: :obj:`str`
107
+ :param chars_per_string: The number of maximum characters per part the text is split to.
108
+ :type chars_per_string: :obj:`int`
109
+ :return: The splitted text as a list of strings.
110
+ :rtype: :obj:`list` of :obj:`str`
111
+ """
112
+
113
+ def _text_before_last(substr: str) -> str:
114
+ return substr.join(part.split(substr)[:-1]) + substr
115
+
116
+ if chars_per_string > chars_per_string:
117
+ chars_per_string = chars_per_string
118
+
119
+ parts = []
120
+ while True:
121
+ if len(text) < chars_per_string:
122
+ parts.append(text)
123
+ return parts
124
+
125
+ part = text[:chars_per_string]
126
+
127
+ if "\n" in part:
128
+ part = _text_before_last("\n")
129
+ elif ". " in part:
130
+ part = _text_before_last(". ")
131
+ elif " " in part:
132
+ part = _text_before_last(" ")
133
+
134
+ parts.append(part)
135
+ text = text[len(part) :]
136
+
137
+
138
+ class Address: # TODO: Must find address in text not match it
139
+ _base58_pattern = re.compile(r"^[1-9A-HJ-NP-Za-km-zA-L]{43,44}$")
140
+ _sei_pattern = re.compile(r"^sei1[a-z0-9]{58}$")
141
+ _evm_address_pattern = re.compile(r"0x[a-fA-F0-9]{40}")
142
+ _evm_txHash_pattern = re.compile(r"0x[a-fA-F0-9]{66}")
143
+ _trx_address_pattern = re.compile(r"T[1-9A-HJ-NP-Za-km-z]{33}")
144
+ _ton_address_pattern = re.compile(r"^[A-Za-z0-9_-]{48}$")
145
+ _sui_address_pattern = re.compile(r"^(0x[a-fA-F0-9]{64}|[A-Za-z0-9_-]{48})$")
146
+ _sui_address_module = re.compile(
147
+ r"^0x[a-fA-F0-9]{64}::[a-zA-Z_][a-zA-Z0-9_]*::[a-zA-Z_][a-zA-Z0-9_]*$"
148
+ )
149
+
150
+ def __init__(self, chain: str, address: str) -> None:
151
+ self.chain = chain
152
+ self.address = address
153
+
154
+ @classmethod
155
+ def is_solana_address(cls, address: str):
156
+ return cls._base58_pattern.match(address)
157
+
158
+ @classmethod
159
+ def is_sei_contract_address(cls, address: str):
160
+ return cls._sei_pattern.match(address)
161
+
162
+ @classmethod
163
+ def is_evm_address(cls, text: str):
164
+ return cls._evm_address_pattern.match(text)
165
+
166
+ @classmethod
167
+ def is_tx_hash(cls, text: str):
168
+ return re.match(cls._evm_txHash_pattern, text)
169
+
170
+ @classmethod
171
+ def is_trx_address(cls, text: str):
172
+ return cls._trx_address_pattern.match(text)
173
+
174
+ @classmethod
175
+ def is_ton_address(cls, text: str):
176
+ return cls._ton_address_pattern.match(text)
177
+
178
+ @classmethod
179
+ def is_sui_address(cls, text: str):
180
+ return cls._sui_address_pattern.match(text) or cls._sui_address_module.match(
181
+ text
182
+ )
183
+
184
+ @classmethod
185
+ def detect(cls, address: str):
186
+ if address.startswith("0x"):
187
+ sui = cls.is_sui_address(address)
188
+ if sui:
189
+ address = sui.group(0)
190
+ return cls("sui", address)
191
+
192
+ eth = cls.is_evm_address(address)
193
+ if eth:
194
+ address = eth.group(0)
195
+ checksum = Web3.to_checksum_address(address)
196
+ return cls("evm", checksum)
197
+
198
+ elif address.startswith("sei"):
199
+ sei = cls.is_sei_contract_address(address)
200
+ if sei:
201
+ return cls("sei-network", sei.group(0))
202
+ else:
203
+ sol = cls.is_solana_address(address)
204
+ if sol:
205
+ return cls("solana", sol.group(0))
206
+
207
+ trx = cls.is_trx_address(address)
208
+ if trx:
209
+ return cls("tron", trx.group(0))
210
+
211
+ def __str__(self):
212
+ return f"Address({self.address}, {self.chain})"
213
+
214
+
215
+ def generate_promo_code(user_id: int, code_length: int = 5):
216
+ byte_string = str(user_id).encode()
217
+ hash = hashlib.sha256(byte_string).hexdigest()
218
+ seed = int(hash[:8], 16)
219
+ random.seed(seed)
220
+ code_chars = string.ascii_uppercase + string.digits
221
+ promo_code = "".join(random.choice(code_chars) for _ in range(code_length))
222
+ return promo_code
223
+
224
+
225
+ def generate_random_promo_code(code_length: int = 5):
226
+ """
227
+ Generate a single random promo code.
228
+
229
+ Args:
230
+ code_length (int, optional): The length of the promo code. Defaults to 8.
231
+
232
+ Returns:
233
+ str: A randomly generated promo code.
234
+ """
235
+ code_chars = string.ascii_uppercase + string.digits
236
+ promo_code = "".join(random.choice(code_chars) for _ in range(code_length))
237
+ return promo_code
238
+
239
+
240
+ def format_number(value, f_points=1):
241
+ if value < 1000:
242
+ return f"{value:.{f_points}f}"
243
+ elif value < 1000000:
244
+ return f"{value / 1000:.{f_points}f}K"
245
+ elif value < 1000000000:
246
+ return f"{value / 1000000:.{f_points}f}M"
247
+ else:
248
+ return f"{value / 1000000000:.{f_points}f}B"
249
+
250
+
251
+ def time_difference(dt: datetime):
252
+ now_utc = datetime.utcnow()
253
+ diff = dt - now_utc
254
+ total_seconds = int(diff.total_seconds())
255
+ hours, remainder = divmod(total_seconds, 3600)
256
+ minutes, _ = divmod(remainder, 60)
257
+
258
+ if hours > 0:
259
+ return f"{hours} hours and {minutes} minutes"
260
+ else:
261
+ return f"{minutes} minutes"
262
+
263
+
264
+ def contains_text(text1: str, text2: str) -> bool:
265
+ """
266
+ Check if text1 is present in text2, ignoring case.
267
+
268
+ :param text1: The first string to compare.
269
+ :param text2: The second string to compare.
270
+ :return: True if text1 is present in text2, False otherwise.
271
+ """
272
+ pattern = re.compile(re.escape(text1), re.IGNORECASE)
273
+ return pattern.search(text2) is not None
274
+
275
+
276
+ def truncate_text(text: str, max_length: int) -> str:
277
+ if not text:
278
+ return None
279
+ if len(text) > max_length:
280
+ return text[: max_length - 2] + ".."
281
+ else:
282
+ return text
283
+
284
+
285
+ def estimate_async_runtime(func):
286
+ async def wrapper(*args, **kwargs):
287
+ start_time = time.process_time_ns()
288
+ result = await func(*args, **kwargs)
289
+ end_time = time.process_time_ns()
290
+ runtime = (end_time - start_time) / 1_000_000
291
+ print(f"Estimated runtime of {func.__name__}: {runtime:.2f} ms")
292
+ return result
293
+
294
+ return wrapper
295
+
296
+
297
+ def estimate_sync_runtime(func):
298
+ def wrapper(*args, **kwargs):
299
+ start_time = time.process_time_ns()
300
+ result = func(*args, **kwargs)
301
+ end_time = time.process_time_ns()
302
+ runtime = (end_time - start_time) / 1_000_000
303
+ print(f"Estimated runtime of {func.__name__}: {runtime:.2f} ms")
304
+ return result
305
+
306
+ return wrapper
307
+
308
+
309
+ def create_progress_bar(current_progress, max_length=12):
310
+ filled_blocks = round(current_progress / 100 * max_length)
311
+
312
+ # Ensure the bar is not completely green unless the progress is 100%
313
+ if filled_blocks == max_length and current_progress < 100:
314
+ filled_blocks -= 1
315
+
316
+ empty_blocks = max_length - filled_blocks
317
+ progress_bar = "".join(["🟩"] * filled_blocks + ["⬛️"] * empty_blocks)
318
+
319
+ return progress_bar
320
+
321
+
322
+ def animated_progress_bar(current_progress: int, max_length=10):
323
+
324
+ filled_blocks = round(current_progress / 100 * max_length)
325
+
326
+ # Ensure the bar is not completely green unless the progress is 100%
327
+ if filled_blocks == max_length and current_progress < 100:
328
+ filled_blocks -= 1
329
+
330
+ empty_blocks = max_length - filled_blocks
331
+
332
+ progress_bar = [Emojis.progress_bar.mid_full] * filled_blocks + [
333
+ Emojis.progress_bar.mid_empty
334
+ ] * empty_blocks
335
+
336
+ # Replace the first block
337
+ if progress_bar[0] == Emojis.progress_bar.mid_full:
338
+ progress_bar[0] = Emojis.progress_bar.start_full
339
+ else:
340
+ progress_bar[0] = Emojis.progress_bar.start_empty
341
+
342
+ # Replace the last block
343
+ if progress_bar[-1] == Emojis.progress_bar.mid_full:
344
+ progress_bar[-1] = Emojis.progress_bar.end_full
345
+ else:
346
+ progress_bar[-1] = Emojis.progress_bar.end_empty
347
+
348
+ progress_bar = "".join(progress_bar)
349
+ return progress_bar
350
+
351
+
352
+ def get_elapsed_time_str(start: float, show_seconds: bool = False) -> str:
353
+ """
354
+ Get the elapsed time since a given start time.
355
+
356
+ Parameters:
357
+ - start (float): The start time, as returned by time.time().
358
+
359
+ Returns:
360
+ - str: The elapsed time, in the format "Xh Ym Zs", but without hours and minutes if their value is 0.
361
+ """
362
+ elapsed_seconds = int(time.time() - start)
363
+ if elapsed_seconds < 1:
364
+ return "a few moments"
365
+ hours, remainder = divmod(elapsed_seconds, 3600)
366
+ minutes, seconds = divmod(remainder, 60)
367
+ parts = []
368
+ if hours > 0:
369
+ parts.append(f"{hours}h")
370
+ if minutes > 0:
371
+ parts.append(f"{minutes}m")
372
+ if seconds > 0:
373
+ if show_seconds:
374
+ parts.append(f"{seconds}s")
375
+ elif not show_seconds and minutes + hours == 0:
376
+ parts.append("a few seconds")
377
+ return " ".join(parts)
378
+
379
+
380
+ def get_elapsed_time_full(
381
+ start: float = None, seconds: int = None, show_seconds: bool = False
382
+ ) -> str:
383
+ """
384
+ Get the elapsed time since a given start time, in full words.
385
+
386
+ Parameters:
387
+ - start (float): The start time, as returned by time.time()..
388
+ - seconds (int): estimated seconds.
389
+
390
+ Returns:
391
+ - str: The elapsed time, in the format "X hours Y minutes Z seconds", but without hours and minutes if their value is 0, and with singular/plural forms.
392
+ """
393
+ elapsed_seconds = int(time.time() - start) if start else seconds
394
+ hours, remainder = divmod(elapsed_seconds, 3600)
395
+ minutes, reminded_seconds = divmod(remainder, 60)
396
+ parts = []
397
+ if hours > 0:
398
+ parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
399
+ if minutes > 0:
400
+ parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
401
+ if reminded_seconds > 0:
402
+ if show_seconds:
403
+ parts.append(
404
+ f"{reminded_seconds} second{'s' if reminded_seconds != 1 else ''}"
405
+ )
406
+ elif hours + minutes == 0:
407
+ parts.append(f"a few moments")
408
+
409
+ return " and ".join(parts)
410
+
411
+
412
+ def grouper(iterable, n, fillvalue=None):
413
+ """Collect data into fixed-length chunks or blocks"""
414
+ args = [iter(iterable)] * n
415
+ return [list(filter(None, i)) for i in zip_longest(*args, fillvalue=fillvalue)]
416
+
417
+
418
+ def get_str_time_full(seconds: float, show_seconds: bool = False) -> str:
419
+ try:
420
+ hours, remainder = divmod(seconds, 3600)
421
+ minutes, seconds = divmod(remainder, 60)
422
+ parts = []
423
+ if hours > 0:
424
+ parts.append(f"{int(hours)} Hour{'s' if int(hours) != 1 else ''}")
425
+ if minutes > 0:
426
+ parts.append(f"{int(minutes)} Minute{'s' if int(minutes) != 1 else ''}")
427
+ if seconds > 0:
428
+ parts.append(f"{int(seconds)} Second{'s' if int(seconds)!=1 else ''}")
429
+ if not parts:
430
+ return "0 seconds"
431
+ return " and ".join(parts)
432
+ except Exception as e:
433
+ print(f"get_str_time_full: {e}")
434
+ return "0 seconds"
435
+
436
+
437
+ def get_str_time_full2(
438
+ start: float = None, seconds: int = None, show_seconds: bool = False
439
+ ) -> str:
440
+ """
441
+ Get the elapsed time since a given start time, in full words.
442
+
443
+ Parameters:
444
+ - start (float): The start time, as returned by time.time().
445
+ - seconds (int): estimated seconds.
446
+
447
+ Returns:
448
+ - str: The elapsed time, in the format "X hours Y minutes Z seconds", but without hours and minutes if their value is 0, and with singular/plural forms.
449
+ """
450
+ elapsed_seconds = int(time.time() - start) if start else seconds
451
+ hours, remainder = divmod(elapsed_seconds, 3600)
452
+ minutes, reminded_seconds = divmod(remainder, 60)
453
+ parts = []
454
+ if hours > 0:
455
+ parts.append(f"{hours} hour{'s' if hours != 1 else ''}")
456
+ if minutes > 0:
457
+ parts.append(f"{minutes} minute{'s' if minutes != 1 else ''}")
458
+ if reminded_seconds > 0:
459
+ if show_seconds:
460
+ parts.append(
461
+ f"{reminded_seconds} second{'s' if reminded_seconds != 1 else ''}"
462
+ )
463
+ elif hours + minutes == 0:
464
+ parts.append(f"a few moments")
465
+
466
+ return " and ".join(parts)
467
+
468
+
469
+ def time_ago_str(event_timestamp):
470
+ current_time = time.time()
471
+ time_difference = datetime.fromtimestamp(current_time) - datetime.fromtimestamp(
472
+ event_timestamp
473
+ )
474
+
475
+ days_ago = time_difference.days
476
+ seconds_ago = time_difference.seconds
477
+
478
+ if days_ago == 0:
479
+ hours_ago = seconds_ago // 3600
480
+ if hours_ago > 0:
481
+ return f"{hours_ago} hour{'s' if hours_ago > 1 else ''} ago"
482
+
483
+ minutes_ago = seconds_ago // 60
484
+ if minutes_ago > 0:
485
+ return f"{minutes_ago} minute{'s' if minutes_ago > 1 else ''} ago"
486
+
487
+ return "just now"
488
+
489
+ if days_ago < 7:
490
+ return f"{days_ago} day{'s' if days_ago > 1 else ''} ago"
491
+
492
+ weeks_ago = days_ago // 7
493
+ if days_ago < 30:
494
+ return f"{weeks_ago} week{'s' if weeks_ago > 1 else ''} ago"
495
+
496
+ months_ago = days_ago // 30
497
+ return f"{months_ago} month{'s' if months_ago > 1 else ''} ago"
498
+
499
+
500
+ def seconds_until_next_minute_mod_5():
501
+ now = datetime.now()
502
+ minutes_to_add = (5 - now.minute % 5) % 5
503
+ next_time = (now + timedelta(minutes=minutes_to_add)).replace(
504
+ second=0, microsecond=0
505
+ )
506
+
507
+ # If we're within a minute divisible by 5 but not at the exact start, move to the next 5-minute mark
508
+ if now.minute % 5 == 0 and now.second > 0:
509
+ next_time += timedelta(minutes=5)
510
+
511
+ return (next_time - now).total_seconds()
@@ -0,0 +1,193 @@
1
+ from enum import Enum
2
+ from typing import List, Union
3
+ from cherry_shared.blockchains import ChainType
4
+ from cherry_shared.constants import Constants
5
+
6
+
7
+ class LaunchPadId(Enum):
8
+ PINKSALE = "pinksale"
9
+ FJORD = "fjord"
10
+ SOLPAD = "solpad"
11
+ PUMPFUN = "pumpfun"
12
+ LetsBonk = "letsbonk"
13
+ MOONSHOT = "moonshot"
14
+ SUNPUMP = "sunpump"
15
+ FOURMEME = "fourmeme"
16
+ ETHERVISTA = "ethervista"
17
+ MOVEPUMP = "movepump"
18
+ BOOPFUN = "boopfun"
19
+
20
+ @staticmethod
21
+ def all():
22
+ return [
23
+ LaunchPadId.PUMPFUN,
24
+ # LaunchPadId.FJORD,
25
+ # LaunchPadId.SOLPAD,
26
+ # LaunchPadId.LetsBonk,
27
+ # LaunchPadId.MOONSHOT,
28
+ # LaunchPadId.SUNPUMP,
29
+ # LaunchPadId.FOURMEME,
30
+ # LaunchPadId.ETHERVISTA,
31
+ # LaunchPadId.MOVEPUMP,
32
+ # LaunchPadId.BOOPFUN,
33
+ ]
34
+
35
+
36
+ class LaunchPad:
37
+ def __init__(
38
+ self,
39
+ id: LaunchPadId,
40
+ group: int,
41
+ name: str,
42
+ website: str,
43
+ info_route: str,
44
+ supported_chains: List[ChainType],
45
+ trending_channel: str,
46
+ ):
47
+ self.id = id
48
+ self.group = group
49
+ self.name = name
50
+ self.website = website
51
+ self.info_route = info_route
52
+ self.supported_chains = supported_chains
53
+ self.trending_channel = trending_channel
54
+
55
+
56
+ class LaunchPads:
57
+ # GROUP 1
58
+ pumpfun = LaunchPad(
59
+ LaunchPadId.PUMPFUN,
60
+ 1,
61
+ "Pump.fun",
62
+ "https://pump.fun",
63
+ "pumpfun",
64
+ [ChainType.SOLANA],
65
+ "https://t.me/pumpfuncherry",
66
+ )
67
+ letsbonk = LaunchPad(
68
+ LaunchPadId.LetsBonk,
69
+ 1,
70
+ "LetsBonk",
71
+ "https://letsbonk.com",
72
+ "letsbonk",
73
+ [ChainType.SOLANA],
74
+ Constants.trending_channel_link,
75
+ )
76
+ moonshot = LaunchPad(
77
+ LaunchPadId.MOONSHOT,
78
+ 1,
79
+ "MoonShot",
80
+ "https://dexscreener.com/moonshot",
81
+ "moonshot",
82
+ [ChainType.SOLANA],
83
+ "https://t.me/moonshotcherry",
84
+ )
85
+ sunpump = LaunchPad(
86
+ LaunchPadId.SUNPUMP,
87
+ 1,
88
+ "SunPump",
89
+ "https://sunpump.meme",
90
+ "sunpump",
91
+ [ChainType.TRON],
92
+ "https://t.me/sunpumptrendingcherry",
93
+ )
94
+ fourmeme = LaunchPad(
95
+ LaunchPadId.FOURMEME,
96
+ 1,
97
+ "Four.Meme",
98
+ "https://four.meme",
99
+ "fourmemes",
100
+ [ChainType.EVM],
101
+ Constants.trending_channel_link,
102
+ )
103
+ ethervista = LaunchPad(
104
+ LaunchPadId.ETHERVISTA,
105
+ 1,
106
+ "Ether Vista",
107
+ "https://ethervista.app",
108
+ "ethervista",
109
+ [ChainType.EVM],
110
+ Constants.trending_channel_link,
111
+ )
112
+
113
+ movepump = LaunchPad(
114
+ LaunchPadId.MOVEPUMP,
115
+ 1,
116
+ "Move Pump",
117
+ "https://movepump.com",
118
+ "movepump",
119
+ [ChainType.SUI],
120
+ Constants.trending_channel_link,
121
+ )
122
+
123
+ boopfun = LaunchPad(
124
+ LaunchPadId.BOOPFUN,
125
+ 1,
126
+ "Boop.Fun",
127
+ "https://boop.fun",
128
+ "boopfun",
129
+ [ChainType.SOLANA],
130
+ Constants.trending_channel_link,
131
+ )
132
+
133
+ # GROUP 2
134
+ pinksale = LaunchPad(
135
+ LaunchPadId.PINKSALE,
136
+ 2,
137
+ "PinkSale",
138
+ "https://www.pinksale.finance",
139
+ "pink",
140
+ [ChainType.EVM, ChainType.SOLANA],
141
+ Constants.trending_channel_link,
142
+ )
143
+ fjord = LaunchPad(
144
+ LaunchPadId.FJORD,
145
+ 2,
146
+ "FJORD Sale",
147
+ "https://app.fjordfoundry.com",
148
+ "fjord",
149
+ [ChainType.EVM],
150
+ Constants.trending_channel_link,
151
+ )
152
+ solpad = LaunchPad(
153
+ LaunchPadId.SOLPAD,
154
+ 2,
155
+ "SolPad",
156
+ "https://solpad.io",
157
+ "solpad",
158
+ [ChainType.SOLANA],
159
+ Constants.trending_channel_link,
160
+ )
161
+
162
+ @staticmethod
163
+ def all() -> List[LaunchPad]:
164
+ return [
165
+ LaunchPads.pumpfun,
166
+ # LaunchPads.letsbonk,
167
+ # LaunchPads.moonshot,
168
+ # LaunchPads.sunpump,
169
+ # LaunchPads.fourmeme,
170
+ # LaunchPads.ethervista,
171
+ # LaunchPads.boopfun,
172
+ # LaunchPads.movepump,
173
+ # LaunchPads.pinksale,
174
+ # LaunchPads.fjord,
175
+ # LaunchPads.solpad,
176
+ ]
177
+
178
+ @staticmethod
179
+ def get_by_id(id: Union[LaunchPadId | str]) -> LaunchPad:
180
+ if isinstance(id, str):
181
+ id = LaunchPadId(id)
182
+
183
+ for lp in LaunchPads.all():
184
+ if lp.id == id:
185
+ return lp
186
+
187
+ @staticmethod
188
+ def get_group(group_id: int):
189
+ return [lp for lp in LaunchPads.all() if lp.group == group_id]
190
+
191
+ @staticmethod
192
+ def get_by_chain_type(chainType: ChainType):
193
+ return [l for l in LaunchPads.all() if chainType in l.supported_chains]
@@ -0,0 +1,6 @@
1
+ from .blockchain import Blockchain, SupportedFeatures
2
+ from .dexscreener import DEXScreener, Pair
3
+ from .launchpad import LaunchpadToken
4
+ from .leaderboardEntry import LeaderboardEntry
5
+ from .raid_info import RaidInfo, PostInfo
6
+ from .user_wallet import UserWallet