initial commit
This commit is contained in:
commit
fa3b4afa67
|
@ -0,0 +1,4 @@
|
|||
.vscode/
|
||||
venv/
|
||||
__pycache__/
|
||||
tests/
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
|||
DEFAULT_QUERY = ['English', 'Russian']
|
||||
DEFAULT_RESULT = 'Russian'
|
||||
|
||||
LANGUAGES = ['English', 'French', 'German', 'Indonesian', 'Italian',
|
||||
'Japanese', 'Korean', 'Portuguese', 'Russian', 'Spanish',
|
||||
'Thai', 'Turkish', 'Vietnamese']
|
|
@ -0,0 +1,58 @@
|
|||
from aiohttp import ClientSession
|
||||
from pygidb.constants import DEFAULT_QUERY, DEFAULT_RESULT
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from pygidb.types.api import Response
|
||||
from pygidb.types.characters import Character
|
||||
|
||||
|
||||
class GenshinDBException(Exception):
|
||||
def __init__(self, error_text, error_code):
|
||||
super().__init__(error_text)
|
||||
self.message = error_text
|
||||
self.code = error_code
|
||||
|
||||
|
||||
class GenshinDBOptions:
|
||||
def __init__(self, query=DEFAULT_QUERY, result=DEFAULT_RESULT):
|
||||
self._query_languages = query
|
||||
self._result_language: str = result
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'query_languages': ','.join(self._query_languages),
|
||||
'result_language': self._result_language
|
||||
}
|
||||
|
||||
|
||||
class GenshinDB:
|
||||
def __init__(self, options=None):
|
||||
self.options = options
|
||||
if not self.options:
|
||||
self.options = GenshinDBOptions()
|
||||
|
||||
self.client = ClientSession()
|
||||
self.base_url = 'https://gidb.nix13.pw'
|
||||
|
||||
def __del__(self):
|
||||
pass
|
||||
|
||||
async def __request(self, folder: str, query: str, **kwargs) -> Response:
|
||||
params = urlencode({**self.options.to_dict(), **kwargs})
|
||||
async with self.client.get(f'{self.base_url}/{folder}/{query}?{params}') as r:
|
||||
json = await r.json()
|
||||
res = Response(**json)
|
||||
if res.error:
|
||||
raise GenshinDBException(**res.response)
|
||||
return res
|
||||
|
||||
async def get_characters(self) -> dict[str, list[str]]:
|
||||
res = await self.__request('characters', '')
|
||||
return res.response[0]
|
||||
|
||||
async def get_character(
|
||||
self, name: str, images=False, stats=False, url=False
|
||||
) -> Character:
|
||||
res = await self.__request('characters', name, images=images,
|
||||
stats=stats, url=url)
|
||||
return Character(**res.response)
|
|
@ -0,0 +1,36 @@
|
|||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class GroupReward(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class AchievementGroups(BaseModel):
|
||||
name: str
|
||||
sortorder: int
|
||||
reward: GroupReward
|
||||
|
||||
|
||||
# Achievement
|
||||
class StageReward(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class Stage(BaseModel):
|
||||
title: str
|
||||
description: str
|
||||
progress: int
|
||||
reward: StageReward
|
||||
|
||||
|
||||
class Achievement(BaseModel):
|
||||
name: str
|
||||
achievementgroup: str
|
||||
sortorder: int
|
||||
stages: int
|
||||
stage1: Stage
|
||||
stage2: Optional[Stage]
|
||||
stage3: Optional[Stage]
|
|
@ -0,0 +1,16 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RewardItem(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
type: str
|
||||
|
||||
|
||||
class AdventureRank(BaseModel):
|
||||
name: str
|
||||
exp: Optional[int] = 0
|
||||
unlockdescription: str
|
||||
reward: List[RewardItem]
|
|
@ -0,0 +1,9 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Animal(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
category: str
|
||||
counttype: str
|
||||
sortorder: int
|
|
@ -0,0 +1,12 @@
|
|||
from typing import Any
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ErrorData(BaseModel):
|
||||
error_code: int
|
||||
error_text: str
|
||||
|
||||
|
||||
class Response(BaseModel):
|
||||
error: bool = False
|
||||
response: Any | ErrorData
|
|
@ -0,0 +1,22 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Piece(BaseModel):
|
||||
name: str
|
||||
relictype: str
|
||||
description: str
|
||||
story: str
|
||||
|
||||
|
||||
class Artifact(BaseModel):
|
||||
name: str
|
||||
rarity: List[str]
|
||||
field_2pc: str = Field(..., alias='2pc')
|
||||
field_4pc: str = Field(..., alias='4pc')
|
||||
flower: Piece
|
||||
plume: Piece
|
||||
sands: Piece
|
||||
goblet: Piece
|
||||
circlet: Piece
|
|
@ -0,0 +1,158 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class Cv(BaseModel):
|
||||
english: str
|
||||
chinese: str
|
||||
japanese: str
|
||||
korean: str
|
||||
|
||||
|
||||
class AscendItem(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class Costs(BaseModel):
|
||||
ascend1: List[AscendItem]
|
||||
ascend2: List[AscendItem]
|
||||
ascend3: List[AscendItem]
|
||||
ascend4: List[AscendItem]
|
||||
ascend5: List[AscendItem]
|
||||
ascend6: List[AscendItem]
|
||||
|
||||
|
||||
class Images(BaseModel):
|
||||
card: Optional[str] = None
|
||||
portrait: Optional[str] = None
|
||||
icon: Optional[str] = None
|
||||
sideicon: Optional[str] = None
|
||||
cover1: Optional[str] = None
|
||||
cover2: Optional[str] = None
|
||||
hoyolab_avatar: Optional[str] = Field(None, alias='hoyolab-avatar')
|
||||
nameicon: Optional[str] = None
|
||||
nameiconcard: Optional[str] = None
|
||||
namegachasplash: Optional[str] = None
|
||||
namegachaslice: Optional[str] = None
|
||||
namesideicon: Optional[str] = None
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
hp: float
|
||||
attack: float
|
||||
defense: float
|
||||
critrate: float
|
||||
critdmg: float
|
||||
|
||||
|
||||
class Curve(BaseModel):
|
||||
hp: str
|
||||
attack: str
|
||||
defense: str
|
||||
|
||||
|
||||
class PromotionItem(BaseModel):
|
||||
maxlevel: int
|
||||
hp: float
|
||||
attack: float
|
||||
defense: float
|
||||
specialized: float
|
||||
|
||||
|
||||
class Stats(BaseModel):
|
||||
base: Base
|
||||
curve: Curve
|
||||
specialized: str
|
||||
promotion: List[PromotionItem]
|
||||
|
||||
|
||||
class URL(BaseModel):
|
||||
fandom: str
|
||||
|
||||
|
||||
class Character(BaseModel):
|
||||
name: str
|
||||
fullname: str
|
||||
title: str
|
||||
description: str
|
||||
rarity: str
|
||||
element: str
|
||||
weapontype: str
|
||||
substat: str
|
||||
gender: str
|
||||
body: str
|
||||
association: str
|
||||
region: str
|
||||
affiliation: str
|
||||
birthdaymmdd: str
|
||||
birthday: str
|
||||
constellation: str
|
||||
cv: Cv
|
||||
costs: Costs
|
||||
images: Optional[Images] = None
|
||||
stats: Optional[Stats] = None
|
||||
url: Optional[URL] = None
|
||||
version: str
|
||||
|
||||
|
||||
# Constellations
|
||||
class Constellation(BaseModel):
|
||||
name: str
|
||||
effect: str
|
||||
|
||||
|
||||
class Constellations(BaseModel):
|
||||
name: str
|
||||
c1: Constellation
|
||||
c2: Constellation
|
||||
c3: Constellation
|
||||
c4: Constellation
|
||||
c5: Constellation
|
||||
c6: Constellation
|
||||
|
||||
|
||||
# Talents
|
||||
class Attributes(BaseModel):
|
||||
labels: List[str]
|
||||
|
||||
|
||||
class Combat(BaseModel):
|
||||
name: str
|
||||
info: str
|
||||
description: Optional[str] = None
|
||||
attributes: Attributes
|
||||
|
||||
|
||||
class Passive(BaseModel):
|
||||
name: str
|
||||
info: str
|
||||
|
||||
|
||||
class LvlItem(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class TalentCosts(BaseModel):
|
||||
lvl2: List[LvlItem]
|
||||
lvl3: List[LvlItem]
|
||||
lvl4: List[LvlItem]
|
||||
lvl5: List[LvlItem]
|
||||
lvl6: List[LvlItem]
|
||||
lvl7: List[LvlItem]
|
||||
lvl8: List[LvlItem]
|
||||
lvl9: List[LvlItem]
|
||||
lvl10: List[LvlItem]
|
||||
|
||||
|
||||
class Talents(BaseModel):
|
||||
name: str
|
||||
combat1: Combat
|
||||
combat2: Combat
|
||||
combat3: Combat
|
||||
passive1: Passive
|
||||
passive2: Passive
|
||||
passive3: Passive
|
||||
costs: TalentCosts
|
|
@ -0,0 +1,18 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RecipeItem(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class Craft(BaseModel):
|
||||
name: str
|
||||
filter: str
|
||||
sortorder: int
|
||||
unlockrank: int
|
||||
resultcount: int
|
||||
moracost: Optional[int] = 0
|
||||
recipe: List[RecipeItem]
|
|
@ -0,0 +1,23 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RewardpreviewItem(BaseModel):
|
||||
name: str
|
||||
count: Optional[int] = None
|
||||
rarity: Optional[str] = None
|
||||
|
||||
|
||||
class Domain(BaseModel):
|
||||
name: str
|
||||
region: str
|
||||
domainentrance: str
|
||||
domaintype: str
|
||||
description: str
|
||||
recommendedlevel: int
|
||||
recommendedelements: List[str]
|
||||
unlockrank: int
|
||||
rewardpreview: List[RewardpreviewItem]
|
||||
disorder: List[str]
|
||||
monsterlist: List[str]
|
|
@ -0,0 +1,10 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Element(BaseModel):
|
||||
name: str
|
||||
type: str
|
||||
color: str
|
||||
region: str
|
||||
archon: str
|
||||
theme: str
|
|
@ -0,0 +1,18 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class RewardpreviewItem(BaseModel):
|
||||
name: str
|
||||
count: Optional[float] = None
|
||||
rarity: Optional[str] = None
|
||||
|
||||
|
||||
class Enemy(BaseModel):
|
||||
name: str
|
||||
specialname: str
|
||||
enemytype: str
|
||||
category: str
|
||||
description: str
|
||||
rewardpreview: List[RewardpreviewItem]
|
|
@ -0,0 +1,29 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Quality(BaseModel):
|
||||
effect: str
|
||||
description: str
|
||||
|
||||
|
||||
class Ingredient(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class Food(BaseModel):
|
||||
name: str
|
||||
rarity: str
|
||||
foodtype: str
|
||||
foodfilter: str
|
||||
foodcategory: str
|
||||
effect: str
|
||||
description: str
|
||||
basedish: Optional[str] = None
|
||||
character: Optional[str] = None
|
||||
suspicious: Optional[Quality] = None
|
||||
normal: Optional[Quality] = None
|
||||
delicious: Optional[Quality] = None
|
||||
ingredients: List[Ingredient]
|
|
@ -0,0 +1,10 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Geography(BaseModel):
|
||||
name: str
|
||||
area: str
|
||||
description: str
|
||||
region: str
|
||||
showonlyunlocked: bool = False
|
||||
sortorder: int
|
|
@ -0,0 +1,12 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Glider(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
rarity: str
|
||||
story: str
|
||||
sortorder: int
|
||||
source: List[str]
|
|
@ -0,0 +1,13 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Material(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
sortorder: int
|
||||
rarity: str
|
||||
category: str
|
||||
materialtype: str
|
||||
source: List[str]
|
|
@ -0,0 +1,10 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class NameCard(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
sortorder: int
|
||||
source: List[str]
|
|
@ -0,0 +1,11 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Outfit(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
isdefault: bool
|
||||
character: str
|
||||
source: Optional[List[str]] = None
|
|
@ -0,0 +1,14 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TalentMaterialType(BaseModel):
|
||||
name: str
|
||||
field_2starname: str = Field(..., alias='2starname')
|
||||
field_3starname: str = Field(..., alias='3starname')
|
||||
field_4starname: str = Field(..., alias='4starname')
|
||||
day: List[str]
|
||||
location: str
|
||||
region: str
|
||||
domainofmastery: str
|
|
@ -0,0 +1,152 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Playcost(BaseModel):
|
||||
costtype: str
|
||||
count: int
|
||||
|
||||
|
||||
# Action Cards
|
||||
class ActionCard(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
cardtype: str
|
||||
cardtypetext: str
|
||||
tags: List[str]
|
||||
tagstext: List[str]
|
||||
description: str
|
||||
descriptionraw: str
|
||||
descriptionreplaced: str
|
||||
storytitle: str
|
||||
storytext: str
|
||||
source: str = None
|
||||
playcost: List[Playcost]
|
||||
|
||||
|
||||
# Card backs
|
||||
class CardBack(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str
|
||||
descriptionraw: str
|
||||
source: str
|
||||
rarity: int
|
||||
|
||||
|
||||
class CardBox(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str
|
||||
descriptionraw: str
|
||||
source: str
|
||||
rarity: int
|
||||
|
||||
|
||||
# Character cards
|
||||
class Skill(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
descriptionraw: str
|
||||
basedamage: Optional[int] = None
|
||||
baseelement: Optional[str] = None
|
||||
descriptionreplaced: str
|
||||
description: str
|
||||
typetag: str
|
||||
type: str
|
||||
playcost: List[Playcost]
|
||||
|
||||
|
||||
class CharacterCard(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
hp: int
|
||||
maxenergy: int
|
||||
tags: List[str]
|
||||
tagstext: List[str]
|
||||
storytitle: str
|
||||
storytext: str
|
||||
source: str
|
||||
skills: List[Skill]
|
||||
|
||||
|
||||
# Enemy card
|
||||
class EnemyCard(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
hp: int
|
||||
maxenergy: int
|
||||
tags: List[str]
|
||||
tagstext: List[str]
|
||||
skills: List[Skill]
|
||||
|
||||
|
||||
# Detailed rules
|
||||
class Rule(BaseModel):
|
||||
title: str
|
||||
titleraw: str
|
||||
content: str
|
||||
contentraw: str
|
||||
filename_image: Optional[str] = None
|
||||
|
||||
|
||||
class DetailedRule(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
rules: List[Rule]
|
||||
|
||||
|
||||
# Keywords
|
||||
class Keyword(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
nameraw: str
|
||||
description: str
|
||||
descriptionraw: str
|
||||
|
||||
|
||||
# Level rewards
|
||||
class Reward(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class LevelReward(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
exp: int = None
|
||||
icontype: str
|
||||
unlockdescription: str
|
||||
unlockdescriptionraw: str
|
||||
rewards: List[Reward]
|
||||
|
||||
|
||||
# Status effects
|
||||
class StatusEffect(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
statustypetext: str
|
||||
cardtype: str
|
||||
cardtypetext: str
|
||||
tags: List[str]
|
||||
description: str
|
||||
descriptionraw: str
|
||||
descriptionreplaced: str
|
||||
|
||||
|
||||
# Summons
|
||||
class Summon(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
cardtypetext: str
|
||||
tags: List
|
||||
tagstext: List
|
||||
description: str
|
||||
descriptionraw: str
|
||||
descriptionreplaced: str
|
||||
countingtype: str
|
||||
tokentype: str
|
||||
hinttype: str
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class WeaponMaterialType(BaseModel):
|
||||
name: str
|
||||
field_2starname: str = Field(..., alias='2starname')
|
||||
field_3starname: str = Field(..., alias='3starname')
|
||||
field_4starname: str = Field(..., alias='4starname')
|
||||
field_5starname: str = Field(..., alias='5starname')
|
||||
day: List[str]
|
||||
location: str
|
||||
region: str
|
||||
domainofforgery: str
|
|
@ -0,0 +1,75 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AscendItem(BaseModel):
|
||||
name: str
|
||||
count: int
|
||||
|
||||
|
||||
class Costs(BaseModel):
|
||||
ascend1: List[AscendItem]
|
||||
ascend2: List[AscendItem]
|
||||
ascend3: List[AscendItem]
|
||||
ascend4: List[AscendItem]
|
||||
ascend5: List[AscendItem]
|
||||
ascend6: List[AscendItem]
|
||||
|
||||
|
||||
class URL(BaseModel):
|
||||
fandom: str
|
||||
|
||||
|
||||
class Images(BaseModel):
|
||||
nameicon: str
|
||||
namegacha: str
|
||||
icon: str
|
||||
nameawakenicon: str
|
||||
awakenicon: Optional[str] = None
|
||||
|
||||
|
||||
class Base(BaseModel):
|
||||
attack: float
|
||||
specialized: float
|
||||
|
||||
|
||||
class Curve(BaseModel):
|
||||
attack: str
|
||||
specialized: str
|
||||
|
||||
|
||||
class PromotionItem(BaseModel):
|
||||
maxlevel: int
|
||||
attack: float
|
||||
|
||||
|
||||
class Stats(BaseModel):
|
||||
base: Base
|
||||
curve: Curve
|
||||
specialized: str
|
||||
promotion: List[PromotionItem]
|
||||
|
||||
|
||||
class Weapon(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
weapontype: str
|
||||
rarity: str
|
||||
story: str
|
||||
baseatk: int
|
||||
substat: str
|
||||
subvalue: str
|
||||
effectname: str
|
||||
effect: str
|
||||
r1: List[str]
|
||||
r2: List[str]
|
||||
r3: List[str]
|
||||
r4: List[str]
|
||||
r5: List[str]
|
||||
weaponmaterialtype: str
|
||||
costs: Costs
|
||||
url: Optional[URL] = None
|
||||
images: Optional[Images] = None
|
||||
stats: Optional[Stats] = None
|
||||
version: str
|
|
@ -0,0 +1,16 @@
|
|||
[project]
|
||||
name = "py-aiovk"
|
||||
version = "0.0.1"
|
||||
authors = [{ name="ScuroNeko" }]
|
||||
description = "Library for operating with Genshin Impact DB"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://git.nix13.pw/gidb/py-gidb"
|
||||
"Bug Tracker" = "https://git.nix13.pw/gidb/py-gidb/issues"
|
|
@ -0,0 +1,2 @@
|
|||
pydantic
|
||||
aiohttp
|
Loading…
Reference in New Issue