File size: 17,643 Bytes
5dcf7d0
07b1df4
3371607
f764335
5dcf7d0
4581b9c
 
5dcf7d0
 
0f25724
8d7afe4
616d8b2
5dcf7d0
 
 
 
 
3371607
 
 
fb8f91f
2454d1f
b6f52d4
fb8f91f
 
 
 
 
 
 
 
 
 
 
 
 
697c68e
 
 
3371607
 
1245b1b
 
 
3371607
 
5dcf7d0
 
 
2b8e58c
5dcf7d0
f0d0df4
2b8e58c
4d0d752
 
2b8e58c
4d0d752
 
 
 
 
 
 
b4a1659
 
4d0d752
 
1d83294
b4a1659
2b8e58c
 
5dcf7d0
2b8e58c
1d83294
bbd2b34
2b8e58c
5dcf7d0
 
 
 
d001f2c
5dcf7d0
616d8b2
 
 
 
 
 
d001f2c
5dcf7d0
 
 
 
 
 
145bc51
5dcf7d0
3371607
5dcf7d0
2b8e58c
4d0d752
 
 
 
 
 
 
 
 
2b8e58c
4d0d752
 
2b8e58c
 
5dcf7d0
2b8e58c
5dcf7d0
 
 
2b8e58c
 
 
 
 
 
 
3371607
5dcf7d0
3371607
5dcf7d0
 
 
9cf4bdf
5dcf7d0
 
 
 
9cf4bdf
5dcf7d0
 
 
 
 
3371607
b6a9752
 
 
 
 
373e6ff
56b8e2d
dd53bc5
56b8e2d
 
 
 
 
 
 
 
dd53bc5
0f25724
 
 
 
 
7af2f44
30edefa
 
 
 
 
 
 
 
 
 
 
 
 
5dcf7d0
 
616d8b2
5dcf7d0
 
 
 
 
 
 
 
616d8b2
5dcf7d0
3371607
5dcf7d0
 
616d8b2
5dcf7d0
 
 
 
 
616d8b2
5dcf7d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7af2f44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5dcf7d0
 
 
 
 
 
 
 
 
 
 
a5a20d5
7af2f44
 
 
 
 
 
 
 
 
 
 
a5a20d5
 
7af2f44
a5a20d5
 
 
 
7af2f44
a5a20d5
92764d6
 
 
 
 
 
 
 
a5a20d5
 
 
92764d6
 
 
 
 
a5a20d5
 
7af2f44
a5a20d5
 
 
 
7af2f44
a5a20d5
92764d6
 
 
 
 
 
a5a20d5
 
 
92764d6
 
 
 
 
a5a20d5
 
 
 
 
 
 
 
 
 
e1317b6
a5a20d5
 
 
 
e1317b6
 
 
755ba90
e1317b6
a5a20d5
7af2f44
a5a20d5
 
 
 
e1317b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5a20d5
 
 
 
7af2f44
a5a20d5
 
 
 
7af2f44
b6f52d4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ed695d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b6f52d4
697c68e
5dcf7d0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
import asyncio
from zenka import zenka, ZZZError, CacheConfig, Lang
import os
from enum import Enum
from enkacard import encbanner
from enkanetwork import EnkaNetworkAPI
from starrailcard.src.api import enka
import concurrent.futures
import requests
import traceback
from fastapi import FastAPI,Query
from io import BytesIO
from fastapi.responses import JSONResponse
import enkacard
import starrailcard
import enkanetwork
import uvicorn
import cloudinary
import cloudinary.uploader
from cloudinary.utils import cloudinary_url
import pydantic
from pydantic import BaseModel
import genshin
from packaging import version

# Check Pydantic version
pydantic_version = version.parse(pydantic.__version__)

if pydantic_version.major >= 2:
    # Use Pydantic v2-compatible imports or replacements
    print("Running with Pydantic v2")
    # Add any required adjustments for Pydantic v2 here
else:
    # Use Pydantic v1-compatible imports or replacements
    print("Running with Pydantic v1")
    # No changes needed for older code compatible with v1

app = FastAPI()

# Cloudinary configuration
cloudinary.config(
    cloud_name=os.getenv("cloudname"),
    api_key=os.getenv("key"),
    api_secret=os.getenv("secret"),
    secure=True
)
# Genshin Impact card creation
async def genshin_card(id, designtype,character_id=None, character_art_url=None):
     # Use the provided character ID and character art URL from user input, if available
    character_art = {str(character_id): character_art_url} if character_id and character_art_url else None
    async with encbanner.ENC(uid=str(id), character_art=character_art) as encard:
        return await encard.creat(template=(2 if str(designtype) == "2" else 1))

# Star Rail card creation with optional character ID and cookies
async def starrail_card(id, designtype, character_id=None, character_art_url=None, ltmid_v2=None, ltoken_v2=None, ltuid_v2=None):
    character_art = {str(character_id): character_art_url} if character_id and character_art_url else None
    # Use cookies if provided
    if ltmid_v2 and ltoken_v2 and ltuid_v2:
        cookie = {
            "ltmid_v2": ltmid_v2,
            "ltoken_v2": ltoken_v2,
            "ltuid_v2": ltuid_v2
        }
        async with starrailcard.HoYoCard(cookie=cookie,seeleland=True, remove_logo=True, character_art=character_art,boost_speed = True) as card:
            return await card.create(id,force_update = True, style=(2 if str(designtype) == "2" else 1))
    else:
        # Fallback to the existing process
        async with starrailcard.Card(seeleland=True, remove_logo=True, character_art=character_art,boost_speed = True,enka=True) as card:
            return await card.create(id,force_update = True, style=(2 if str(designtype) == "2" else 1))


# Star Rail profile creation
async def starrail_profile(id):
    async with starrailcard.Card(remove_logo=True, seeleland=True,boost_speed = True,enka=True) as card:
        return await card.create_profile(id,force_update = True, style=2)

# Genshin profile creation
async def genshinprofile(id):
    async with encbanner.ENC(uid=id) as encard:
        return await encard.profile(card=True)

# Route for Genshin Impact
@app.get("/genshin/{id}")
async def genshin_characters(id: int, design: str = "1", character_id: int = None, character_art_url: str = None):
    try:
        result = await genshin_card(id, design, character_id, character_art_url)
        characters = process_images(result, id)
        return JSONResponse(content={'response': characters})

    except enkanetwork.exception.VaildateUIDError:
        return JSONResponse(content={'error': 'Invalid UID. Please check your UID.'}, status_code=400)

    except enkacard.enc_error.ENCardError:
        return JSONResponse(content={'error': 'Enable display of the showcase in the game or add characters there.'}, status_code=400)

    except Exception as e:
        return JSONResponse(content={'error': 'UNKNOWN ERR: ' + str(e)}, status_code=500)

# Route for Star Rail with optional character ID
@app.get("/starrail/{id}")
async def starrail_characters(
    id: int,
    design: str = "1",
    character_id: int = None,
    character_art_url: str = None,
    ltmid_v2: str = None,
    ltoken_v2: str = None,
    ltuid_v2: str = None
):
    try:
        # Call starrail_card with cookies if provided
        result = await starrail_card(id, design, character_id, character_art_url, ltmid_v2, ltoken_v2, ltuid_v2)
        characters = process_images(result, id)
        return JSONResponse(content={'response': characters})

    except Exception as e:
        return JSONResponse(content={'error': 'UNKNOWN ERR: ' + str(e)}, status_code=500)

# Route for Star Rail profile
@app.get("/starrail/profile/{id}")
async def starrail_profile_route(id: int):
    try:
        result = await starrail_profile(id)
        profile_data = process_profile(result)
        return JSONResponse(content={'response': profile_data})

    except Exception as e:
        return JSONResponse(content={'error': 'UNKNOWN ERR: ' + str(e)}, status_code=500)

# Route for Genshin profile
@app.get("/genshin/profile/{id}")
async def genshin_profile_route(id: int):
    try:
        result = await genshinprofile(id)
        profile_data = process_profile(result)
        return JSONResponse(content={'response': profile_data})

    except Exception as e:
        return JSONResponse(content={'error': 'UNKNOWN ERR: ' + str(e)}, status_code=500)

@app.get("/")
def root():
    return "خدتك عليه"


@app.get("/ايه")
def root():
    return "خدتك عليه"

@app.get("/update")
async def update():
    try:
        # Update assets using EnkaNetworkAPI and ApiEnkaNetwork
        async def update_assets() -> None:
            async with EnkaNetworkAPI() as client:
                await client.update_assets()
            await enka.ApiEnkaNetwork().update_assets()

        await asyncio.create_task(update_assets())
        return JSONResponse(content={'response': 'Assets updated successfully'})
    except Exception as e:
        error_details = traceback.format_exc()
        return JSONResponse(
            content={'error': f'UNKNOWN ERR: {str(e)}', 'details': error_details},
            status_code=500
        )

@app.get("/updatezzz")
async def maiin():
    try:
        async with zenka.Client() as client:
            await client.update_asset()
        return JSONResponse(content={'response': 'Assets updated successfully'})
    except ZZZError as e:
        error_details = traceback.format_exc()
        return JSONResponse(
            content={'error': f'UNKNOWN ERR: {str(e)}', 'details': error_details},
            status_code=500
        )

# Helper function to upload the image to Cloudinary
def upload_image(data, character_id, player_id):
    try:
        # Set the public_id to include the character ID and the given player ID
        public_id = f"{character_id}_{player_id}"

        # Upload image to Cloudinary with the specified public_id
        upload_result = cloudinary.uploader.upload(data, folder="card_images", public_id=public_id, invalidate=True)

        # Get the secure URL of the uploaded image
        return upload_result["secure_url"]
    except Exception as e:
        raise Exception(f"Cloudinary upload error: {str(e)}")

# Helper function to upload the image to Cloudinary without player or character ID
def upload_imagee(data):
    try:
        # Upload image to Cloudinary without specifying a public_id
        upload_result = cloudinary.uploader.upload(data, folder="card_images", invalidate=True)

        # Get the secure URL of the uploaded image
        return upload_result["secure_url"]
    except Exception as e:
        raise Exception(f"Cloudinary upload error: {str(e)}")

# Process individual image card
def process_image(dt, player_id):
    with BytesIO() as byte_io:
        dt.card.save(byte_io, "PNG")
        byte_io.seek(0)
        # Upload the image using the character's ID and player ID
        image_url = upload_image(byte_io, dt.id, player_id)
        return {
            "name": dt.name,
            "id": dt.id,
            "url": image_url
        }

# Process the profile image
def process_profile(profile_card):
    with BytesIO() as byte_io:
        profile_card.card.save(byte_io, "PNG")
        byte_io.seek(0)
        # Upload the image without using the character or player ID
        image_url = upload_imagee(byte_io)
        return {
            "url": image_url,
        }

from io import BytesIO

def process_profilee(profile_card):
    # Ensure profile_card contains an image
    if not profile_card.cards or not isinstance(profile_card.cards, list):
        raise ValueError("Invalid profile_card: 'cards' is missing or not a list.")

    profile_image = profile_card.cards[0].card  # Extract the PIL image from the first card

    with BytesIO() as byte_io:
        profile_image.save(byte_io, "PNG")  # Save the image as PNG
        byte_io.seek(0)
        image_url = upload_imagee(byte_io)  # Upload the image
        return {
            "url": image_url,
        }


# Process all the images returned
def process_images(result, player_id):
    characters = []
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = [executor.submit(process_image, dt, player_id) for dt in result.card]
        for future in concurrent.futures.as_completed(futures):
            try:
                characters.append(future.result())
            except Exception as e:
                print(f"Error processing image: {e}")
    return characters

def process_imagess(result, player_id):
    characters = []
    with concurrent.futures.ThreadPoolExecutor() as executor:
        futures = [executor.submit(process_image, dt, player_id) for dt in result.cards]
        for future in concurrent.futures.as_completed(futures):
            try:
                characters.append(future.result())
            except Exception as e:
                print(f"Error processing image: {e}")
    return characters

# ZZZ Card and Profile creation functions
async def zenless_card(uid, character_id=None, character_art=None):

    config = zenka.Config(
        asset_save=True,
        hide_uid=False,
    )

    try:
        # Simplified to match your example exactly
        async with zenka.Client(
            lang=Lang.EN, 
            config=config,
            character_art=character_art,
            character_id=character_id
        ) as client:
            data = await client.card(uid)
        return data
    except ZZZError as e:
        raise Exception(f"Zenless Zone Zero Error: Code:{e.code} Message: {e.text}")
    except Exception as e:
        # Add more detailed error information
        import traceback
        error_details = traceback.format_exc()
        raise Exception(f"Zenless Zone Zero Error: {str(e)}\n{error_details}")

async def zenless_profile(uid):

    config = zenka.Config(
        asset_save=True,
        hide_uid=False,
    )

    try:
        # Simplified to match your example exactly
        async with zenka.Client(
            lang=Lang.EN,
            config=config
        ) as client:
            data = await client.profile(uid)
        return data
    except ZZZError as e:
        raise Exception(f"Zenless Zone Zero Error: Code:{e.code} Message: {e.text}")
    except Exception as e:
        # Add more detailed error information
        import traceback
        error_details = traceback.format_exc()
        raise Exception(f"Zenless Zone Zero Error: {str(e)}\n{error_details}")

# Add these routes to your FastAPI app

@app.get("/zenless/{uid}")
async def zenless_characters(uid: int, character_id: str = None, character_art_url: str = None):
    try:
        # Process character_id from string to list if provided
        char_id = None
        if character_id:
            char_id = [int(id) if id.isdigit() else id for id in character_id.split(',')]
        
        # Process character_art from URL to dict if provided
        char_art = None
        if character_id and character_art_url:
            # Create a dictionary mapping character IDs to art URLs
            char_ids = char_id if isinstance(char_id, list) else []
            # The key in the dictionary must be a string
            if char_ids:
                char_art = {char_ids[0]: character_art_url}
        
        result = await zenless_card(uid, char_id, char_art)
        characters = process_imagess(result, uid)
        return JSONResponse(content={'response': characters})
    except Exception as e:
        return JSONResponse(content={'error': 'UNKNOWN ERR: ' + str(e)}, status_code=500)

async def zenless_card(uid, character_id=None, character_art=None):
    config = zenka.Config(
        asset_save=True,
        hide_uid=False,  # Fixed typo from Falde to False
    )
    try:
        # Make sure character_art is properly formatted as a dictionary
        async with zenka.Client(
            lang=Lang.EN, 
            config=config,
            character_art=character_art,  # This should be a dict like {"1121": "https://example.com/image.webp"}
            character_id=character_id     # This should be a list like [1151, "1121"]
        ) as client:
            data = await client.card(uid)
        return data
    except ZZZError as e:
        raise Exception(f"Zenless Zone Zero Error: Code:{e.code} Message: {e.text}")
    except Exception as e:
        import traceback
        error_details = traceback.format_exc()
        raise Exception(f"Zenless Zone Zero Error: {str(e)}\n{error_details}")

@app.get("/zenless/profile/{uid}")
async def zenless_profile_route(uid: int):
    try:
        result = await zenless_profile(uid)
        profile_data = process_profilee(result)
        return JSONResponse(content={'response': profile_data})

    except Exception as e:
        return JSONResponse(content={'error': 'UNKNOWN ERR: ' + str(e)}, status_code=500)

@app.get("/check_train_score")
async def check_train_score(ltoken_v2: str, ltuid_v2: str):
    """
    Check if the Star Rail training score is at maximum (500).
    
    Parameters:
    - ltoken_v2: Your ltoken_v2 cookie value
    - ltuid_v2: Your ltuid_v2 cookie value as an integer
    
    Returns:
    - "yes" if train score is 500
    - "no" if train score is less than 500
    """
    try:
        # Convert ltuid_v2 to int if it's passed as a string
        ltuid_v2_int = int(ltuid_v2)
        
        # Set up cookies for authentication
        cookies = {
            "ltoken_v2": ltoken_v2,
            "ltuid_v2": ltuid_v2_int
        }
        
        # Initialize genshin client
        client = genshin.Client(cookies)
        
        # Fetch Star Rail notes data
        data = await client.get_starrail_notes()
        
        # Check train score
        if data.current_train_score >= 500:
            return "yes"
        else:
            return "no"
            
    except genshin.errors.InvalidCookies:
        raise HTTPException(status_code=401, detail="Invalid cookies provided")
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error checking train score: {str(e)}")

class GameOptions(str, Enum):
    STARRAIL = "hsr"
    GENSHIN = "genshin"
    HONKAI = "hi3rd"
    ZZZ = "zenless"

class RewardInfo(BaseModel):
    name: str
    amount: int
    icon: str = None

@app.get("/claim_daily_reward", response_model=dict)
async def claim_daily_reward(
    ltoken_v2: str, 
    ltuid_v2: str,
    game: GameOptions = Query(..., description="Game to claim rewards for")
):
    """
    Claim daily reward for selected Hoyoverse game.
    
    Parameters:
    - ltoken_v2: Your ltoken_v2 cookie value
    - ltuid_v2: Your ltuid_v2 cookie value as an integer
    - game: Game to claim rewards for (hsr, genshin, hi3rd, zenless)
    
    Returns:
    - Claim result information including reward details
    """
    try:
        # Convert ltuid_v2 to int if it's passed as a string
        ltuid_v2_int = int(ltuid_v2)
        
        # Set up cookies for authentication
        cookies = {
            "ltoken_v2": ltoken_v2,
            "ltuid_v2": ltuid_v2_int
        }
        
        # Initialize genshin client
        client = genshin.Client(cookies)
        
        # Map the game option to genshin library enum
        game_map = {
            GameOptions.STARRAIL: genshin.Game.STARRAIL,
            GameOptions.GENSHIN: genshin.Game.GENSHIN,
            GameOptions.HONKAI: genshin.Game.HONKAI,
            GameOptions.ZZZ: genshin.Game.ZZZ
        }
        
        selected_game = game_map[game]
        
        # Claim daily reward
        result = await client.claim_daily_reward(game=selected_game)
        
        # Parse reward information
        reward = {
            "name": getattr(result, "name", "Unknown"),
            "amount": getattr(result, "amount", 0),
            "icon": getattr(result, "icon", None)
        }
        
        return {
            "success": True,
            "message": f"Successfully claimed daily reward for {game}",
            "reward": reward
        }
            
    except genshin.errors.InvalidCookies:
        raise HTTPException(status_code=401, detail="Invalid cookies provided")
    except genshin.errors.AlreadyClaimed:
        return {
            "success": False,
            "message": f"Daily reward for {game} already claimed today"
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error claiming daily reward: {str(e)}")



if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=7860, workers=8, timeout_keep_alive=60000)