mirror of https://github.com/yupix/mipac
parent
81a502e6fc
commit
e4e7ddd527
@ -0,0 +1,120 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mipac.abstract.action import AbstractAction
|
||||
from mipac.errors.base import NotExistRequiredData
|
||||
from mipac.http import Route
|
||||
from mipac.util import check_multi_arg
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.http import HTTPClient
|
||||
from mipac.manager.client import ClientActions
|
||||
|
||||
|
||||
class AdminEmojiActions(AbstractAction):
|
||||
def __init__(
|
||||
self,
|
||||
emoji_id: None | str = None,
|
||||
*,
|
||||
session: HTTPClient,
|
||||
client: ClientActions
|
||||
):
|
||||
self.__emoji_id = emoji_id
|
||||
self.__session = session
|
||||
self.__client = client
|
||||
|
||||
async def add(
|
||||
self,
|
||||
file_id: str | None = None,
|
||||
*,
|
||||
name: str | None = None,
|
||||
url: str | None = None,
|
||||
category: str | None = None,
|
||||
aliases: list[str] | None = None
|
||||
) -> bool:
|
||||
"""絵文字を追加します
|
||||
|
||||
Parameters
|
||||
----------
|
||||
file_id : str | None, optional
|
||||
追加する絵文字のファイルId, by default None
|
||||
name : str | None, optional
|
||||
絵文字名, by default None
|
||||
url : str | None, optional
|
||||
絵文字があるUrl, by default None
|
||||
category : str | None, optional
|
||||
絵文字のカテゴリー, by default None
|
||||
aliases : list[str] | None, optional
|
||||
絵文字のエイリアス, by default None
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
成功したかどうか
|
||||
|
||||
Raises
|
||||
------
|
||||
NotExistRequiredData
|
||||
必要なデータが不足している
|
||||
"""
|
||||
|
||||
if self.__client._config.is_official:
|
||||
data = {'fileId': file_id}
|
||||
else:
|
||||
data = {
|
||||
'name': name,
|
||||
'url': url,
|
||||
'category': category,
|
||||
'aliases': aliases,
|
||||
}
|
||||
|
||||
if not check_multi_arg(file_id, url):
|
||||
raise NotExistRequiredData('required a file_id or url')
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/admin/emoji/add'),
|
||||
json=data,
|
||||
lower=True,
|
||||
auth=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def remove(self, emoji_id: str | None = None) -> bool:
|
||||
"""指定したIdの絵文字を削除します
|
||||
|
||||
Parameters
|
||||
----------
|
||||
emoji_id : str | None, optional
|
||||
削除する絵文字のId, by default None
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
成功したかどうか
|
||||
|
||||
Raises
|
||||
------
|
||||
NotExistRequiredData
|
||||
Idが不足している
|
||||
"""
|
||||
|
||||
emoji_id = emoji_id or self.__emoji_id
|
||||
|
||||
if emoji_id is None:
|
||||
raise NotExistRequiredData('idが不足しています')
|
||||
|
||||
endpoint = (
|
||||
'/api/admin/emoji/delete'
|
||||
if self.__client._config.is_official
|
||||
else '/api/admin/emoji/remove'
|
||||
)
|
||||
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', endpoint),
|
||||
auth=True,
|
||||
json={'id': emoji_id},
|
||||
lower=True,
|
||||
)
|
||||
)
|
@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mipac.abstract.action import AbstractAction
|
||||
from mipac.http import HTTPClient, Route
|
||||
from mipac.models.chart import ActiveUsersChart, DriveChart
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.manager.client import ClientActions
|
||||
|
||||
|
||||
class ChartActions(AbstractAction):
|
||||
def __init__(self, session: HTTPClient, client: ClientActions):
|
||||
self.__session = session
|
||||
self.__client = client
|
||||
|
||||
async def get_active_user(
|
||||
self, span: str = 'day', limit: int = 30, offset: int = 0
|
||||
) -> ActiveUsersChart:
|
||||
data = {'span': span, 'limit': limit, 'offset': offset}
|
||||
data = await self.__session.request(
|
||||
Route('POST', '/api/charts/active-users'),
|
||||
json=data,
|
||||
auth=True,
|
||||
lower=True,
|
||||
)
|
||||
return ActiveUsersChart(data)
|
||||
|
||||
async def get_drive(
|
||||
self, span: str = 'day', limit: int = 30, offset: int = 0
|
||||
) -> DriveChart:
|
||||
data = {'span': span, 'limit': limit, 'offset': offset}
|
||||
data = await self.__session.request(
|
||||
Route('POST', '/api/charts/drive'),
|
||||
json=data,
|
||||
auth=True,
|
||||
lower=True,
|
||||
)
|
||||
return DriveChart(data)
|
@ -0,0 +1,45 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mipac.abstract.action import AbstractAction
|
||||
from mipac.http import Route
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.http import HTTPClient
|
||||
from mipac.manager.client import ClientActions
|
||||
|
||||
|
||||
class FavoriteActions(AbstractAction):
|
||||
def __init__(
|
||||
self,
|
||||
note_id: str | None = None,
|
||||
*,
|
||||
session: HTTPClient,
|
||||
client: ClientActions
|
||||
):
|
||||
self.__note_id: str | None = note_id
|
||||
self.__session: HTTPClient = session
|
||||
self.__client: ClientActions = client
|
||||
|
||||
async def add(self, note_id: str | None = None) -> bool:
|
||||
note_id = note_id or self.__note_id
|
||||
data = {'noteId': note_id}
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/notes/favorites/create'),
|
||||
json=data,
|
||||
auth=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def remove(self, note_id: str | None = None) -> bool:
|
||||
note_id = note_id or self.__note_id
|
||||
data = {'noteId': note_id}
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/notes/favorites/delete'),
|
||||
json=data,
|
||||
auth=True,
|
||||
)
|
||||
)
|
@ -0,0 +1,150 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mipac.abstract.action import AbstractAction
|
||||
from mipac.http import Route
|
||||
from mipac.models.follow import FollowRequest
|
||||
from mipac.models.user import UserDetailed
|
||||
from mipac.types.follow import IFollowRequest
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.http import HTTPClient
|
||||
from mipac.manager.client import ClientActions
|
||||
|
||||
|
||||
class FollowActions(AbstractAction):
|
||||
def __init__(
|
||||
self,
|
||||
user_id: str | None = None,
|
||||
*,
|
||||
session: HTTPClient,
|
||||
client: ClientActions
|
||||
):
|
||||
self.__user_id: str | None = user_id
|
||||
self.__session = session
|
||||
self.__client = client
|
||||
|
||||
async def add(self, user_id: str | None = None) -> tuple[bool, str | None]:
|
||||
"""
|
||||
ユーザーをフォローします
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
成功ならTrue, 失敗ならFalse
|
||||
str
|
||||
実行に失敗した際のエラーコード
|
||||
"""
|
||||
|
||||
user_id = user_id or self.__user_id
|
||||
|
||||
data = {'userId': user_id}
|
||||
res = await self.__session.request(
|
||||
Route('POST', '/api/following/create'),
|
||||
json=data,
|
||||
auth=True,
|
||||
lower=True,
|
||||
)
|
||||
if res.get('error'):
|
||||
code = res['error']['code']
|
||||
status = False
|
||||
else:
|
||||
code = None
|
||||
status = True
|
||||
return status, code
|
||||
|
||||
async def remove(self, user_id: str | None = None) -> bool:
|
||||
"""
|
||||
ユーザーのフォローを解除します
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
成功ならTrue, 失敗ならFalse
|
||||
"""
|
||||
|
||||
user_id = user_id or self.__user_id
|
||||
|
||||
data = {'userId': user_id}
|
||||
res = await self.__session.request(
|
||||
Route('POST', '/api/following/delete'), json=data, auth=True
|
||||
)
|
||||
return bool(res.status_code == 204 or 200)
|
||||
|
||||
|
||||
class FollowRequestActions(AbstractAction):
|
||||
def __init__(
|
||||
self,
|
||||
user_id: str | None = None,
|
||||
*,
|
||||
session: HTTPClient,
|
||||
client: ClientActions
|
||||
):
|
||||
self.__user_id: str | None = user_id
|
||||
self.__session = session
|
||||
self.__client = client
|
||||
|
||||
async def get_all(self) -> list[FollowRequest]:
|
||||
"""
|
||||
未承認のフォローリクエストを取得します
|
||||
"""
|
||||
|
||||
res: list[IFollowRequest] = await self.__session.request(
|
||||
Route('POST', '/api/following/requests/list'),
|
||||
auth=True,
|
||||
lower=True,
|
||||
)
|
||||
return [
|
||||
FollowRequest(follow_request=i, client=self.__client) for i in res
|
||||
]
|
||||
|
||||
async def get_user(self, user_id: str | None = None) -> UserDetailed:
|
||||
"""
|
||||
フォローリクエスト元のユーザーを取得します
|
||||
Parameters
|
||||
----------
|
||||
user_id : str | None, default=None
|
||||
ユーザーID
|
||||
|
||||
Returns
|
||||
-------
|
||||
UserDetailed
|
||||
フォローリクエスト元のユーザー
|
||||
"""
|
||||
|
||||
user_id = user_id or self.__user_id
|
||||
|
||||
return await self.__client.user.action.get(user_id)
|
||||
|
||||
async def accept(self, user_id: str | None = None) -> bool:
|
||||
"""
|
||||
与えられたIDのユーザーのフォローリクエストを承認します
|
||||
"""
|
||||
|
||||
user_id = user_id or self.__user_id
|
||||
|
||||
data = {'userId': user_id}
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/following/requests/accept'),
|
||||
json=data,
|
||||
auth=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def reject(self, user_id: str | None) -> bool:
|
||||
"""
|
||||
与えられたIDのユーザーのフォローリクエストを拒否します
|
||||
"""
|
||||
|
||||
user_id = user_id or self.__user_id
|
||||
|
||||
data = {'userId': user_id}
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/following/requests/reject'),
|
||||
json=data,
|
||||
auth=True,
|
||||
)
|
||||
)
|
@ -1 +0,0 @@
|
||||
from .models import * # noqa: F403, F401
|
@ -1,13 +0,0 @@
|
||||
from mipac.core.models.chart import (
|
||||
RawActiveUsersChart,
|
||||
RawDriveChart,
|
||||
RawDriveLocalChart,
|
||||
RawDriveRemoteChart,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
'RawActiveUsersChart',
|
||||
'RawDriveRemoteChart',
|
||||
'RawDriveLocalChart',
|
||||
'RawDriveChart',
|
||||
)
|
@ -1,94 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from mipac.types.chart import (
|
||||
ActiveUsersChartPayload,
|
||||
DriveChartPayload,
|
||||
DriveLocalChartPayload,
|
||||
DriveRemoteChartPayload,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
'RawActiveUsersChart',
|
||||
'RawDriveRemoteChart',
|
||||
'RawDriveLocalChart',
|
||||
'RawDriveChart',
|
||||
)
|
||||
|
||||
|
||||
class RawActiveUsersChart:
|
||||
__slots__ = (
|
||||
'read_write',
|
||||
'read',
|
||||
'write',
|
||||
'registered_within_week',
|
||||
'registered_within_month',
|
||||
'registered_within_year',
|
||||
'registered_outside_week',
|
||||
'registered_outside_month',
|
||||
'registered_outside_year',
|
||||
)
|
||||
|
||||
def __init__(self, data: ActiveUsersChartPayload):
|
||||
self.read_write: list[int] = data['read_write']
|
||||
self.read: list[int] = data['read']
|
||||
self.write: list[int] = data['write']
|
||||
self.registered_within_week: list[int] = data['registered_within_week']
|
||||
self.registered_within_month: list[int] = data[
|
||||
'registered_within_month'
|
||||
]
|
||||
self.registered_within_year: list[int] = data['registered_within_year']
|
||||
self.registered_outside_week: list[int] = data[
|
||||
'registered_outside_week'
|
||||
]
|
||||
self.registered_outside_month: list[int] = data[
|
||||
'registered_outside_month'
|
||||
]
|
||||
self.registered_outside_year: list[int] = data[
|
||||
'registered_outside_year'
|
||||
]
|
||||
|
||||
|
||||
class RawDriveLocalChart:
|
||||
__slots__ = (
|
||||
'total_count',
|
||||
'total_size',
|
||||
'inc_count',
|
||||
'inc_size',
|
||||
'dec_count',
|
||||
'dec_size',
|
||||
)
|
||||
|
||||
def __init__(self, data: DriveLocalChartPayload):
|
||||
self.total_count: list[int] = data['total_count']
|
||||
self.total_size: list[int] = data['total_size']
|
||||
self.inc_count: list[int] = data['inc_count']
|
||||
self.inc_size: list[int] = data['inc_size']
|
||||
self.dec_count: list[int] = data['dec_count']
|
||||
self.dec_size: list[int] = data['dec_size']
|
||||
|
||||
|
||||
class RawDriveRemoteChart:
|
||||
__slots__ = (
|
||||
'total_count',
|
||||
'total_size',
|
||||
'inc_count',
|
||||
'inc_size',
|
||||
'dec_count',
|
||||
'dec_size',
|
||||
)
|
||||
|
||||
def __init__(self, data: DriveRemoteChartPayload):
|
||||
self.total_count: list[int] = data['total_count']
|
||||
self.total_size: list[int] = data['total_size']
|
||||
self.inc_count: list[int] = data['inc_count']
|
||||
self.inc_size: list[int] = data['inc_size']
|
||||
self.dec_count: list[int] = data['dec_count']
|
||||
self.dec_size: list[int] = data['dec_size']
|
||||
|
||||
|
||||
class RawDriveChart:
|
||||
__slots__ = ('local', 'remote')
|
||||
|
||||
def __init__(self, data: DriveChartPayload):
|
||||
self.local: RawDriveLocalChart = RawDriveLocalChart(data['local'])
|
||||
self.remote: RawDriveRemoteChart = RawDriveRemoteChart(data['remote'])
|
@ -1 +1,4 @@
|
||||
from .emoji import *
|
||||
from .manager import *
|
||||
from .moderator import *
|
||||
from .user import *
|
||||
|
@ -0,0 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from mipac.abstract.manager import AbstractManager
|
||||
from mipac.actions.admin.emoji import AdminEmojiActions
|
||||
from mipac.http import HTTPClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.client import ClientActions
|
||||
|
||||
|
||||
class AdminEmojiManager(AbstractManager):
|
||||
def __init__(self, session: HTTPClient, client: ClientActions):
|
||||
self.__session: HTTPClient = session
|
||||
self.__client: ClientActions = client
|
||||
|
||||
@property
|
||||
def action(self) -> AdminEmojiActions:
|
||||
return AdminEmojiActions(session=self.__session, client=self.__client)
|
||||
|
||||
def _create_admin_emoji_instance(self, emoji_id: str) -> AdminEmojiActions:
|
||||
return AdminEmojiActions(
|
||||
emoji_id=emoji_id, session=self.__session, client=self.__client
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from mipac.abstract.manager import AbstractManager
|
||||
from mipac.actions.admin.moderator import AdminModeratorActions
|
||||
from mipac.http import HTTPClient
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.client import ClientActions
|
||||
|
||||
__all__ = ('AdminModeratorManager',)
|
||||
|
||||
|
||||
class AdminModeratorManager(AbstractManager):
|
||||
def __init__(
|
||||
self,
|
||||
user_id: Optional[str] = None,
|
||||
*,
|
||||
session: HTTPClient,
|
||||
client: ClientActions
|
||||
):
|
||||
self.__user_id: Optional[str] = user_id
|
||||
self.__session: HTTPClient = session
|
||||
self.__client: ClientActions = client
|
||||
|
||||
@property
|
||||
def action(self) -> AdminModeratorActions:
|
||||
"""Moderatorに関するアクション
|
||||
|
||||
Returns
|
||||
-------
|
||||
AdminModeratorActions
|
||||
Moderatorに対するアクションを行うクラス
|
||||
"""
|
||||
|
||||
return AdminModeratorActions(
|
||||
session=self.__session, client=self.__client
|
||||
)
|
@ -1,69 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from mipac.abstract.manager import AbstractManager
|
||||
from mipac.errors.base import NotExistRequiredData
|
||||
from mipac.http import HTTPClient, Route
|
||||
from mipac.util import check_multi_arg
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mipac.client import ClientActions
|
||||
|
||||
|
||||
class AdminEmojiManager(AbstractManager):
|
||||
def __init__(
|
||||
self,
|
||||
emoji_id: Optional[str] = None,
|
||||
*,
|
||||
session: HTTPClient,
|
||||
client: ClientActions
|
||||
):
|
||||
self.emoji_id: Optional[str] = emoji_id
|
||||
self.__session: HTTPClient = session
|
||||
self.__client: ClientActions = client
|
||||
|
||||
async def add(
|
||||
self,
|
||||
file_id: Optional[str] = None,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
category: Optional[str] = None,
|
||||
aliases: Optional[list[str]] = None
|
||||
) -> bool:
|
||||
if self.__client._config.is_ayuskey:
|
||||
data = {
|
||||
'name': name,
|
||||
'url': url,
|
||||
'category': category,
|
||||
'aliases': aliases,
|
||||
}
|
||||
else:
|
||||
data = {'fileId': file_id}
|
||||
|
||||
if not check_multi_arg(file_id, url):
|
||||
raise NotExistRequiredData('required a file_id or url')
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/admin/emoji/add'),
|
||||
json=data,
|
||||
lower=True,
|
||||
auth=True,
|
||||
)
|
||||
)
|
||||
|
||||
async def remove(self, emoji_id: Optional[str] = None) -> bool:
|
||||
emoji_id = emoji_id or self.emoji_id
|
||||
|
||||
if emoji_id is None:
|
||||
raise NotExistRequiredData('idが不足しています')
|
||||
|
||||
return bool(
|
||||
await self.__session.request(
|
||||
Route('POST', '/api/admin/emoji/remove'),
|
||||
json={'id': emoji_id},
|
||||
lower=True,
|
||||
auth=True,
|
||||
)
|
||||
)
|
@ -0,0 +1,126 @@
|
||||
from mipac.types.chart import (
|
||||
IActiveUsersChart,
|
||||
IDriveChart,
|
||||
IDriveLocalChart,
|
||||
IDriveRemoteChart,
|
||||
)
|
||||
|
||||
|
||||
class ActiveUsersChart:
|
||||
__slots__ = ('__data',)
|
||||
|
||||
def __init__(self, data: IActiveUsersChart):
|
||||
self.__data = data
|
||||
|
||||
@property
|
||||
def read_write(self) -> list[int]:
|
||||
return self.__data['read_write']
|
||||
|
||||
@property
|
||||
def read(self) -> list[int]:
|
||||
return self.__data['read']
|
||||
|
||||
@property
|
||||
def write(self) -> list[int]:
|
||||
return self.__data['write']
|
||||
|
||||
@property
|
||||
def registered_within_week(self) -> list[int]:
|
||||
return self.__data['registered_within_week']
|
||||
|
||||
@property
|
||||
def registered_within_month(self) -> list[int]:
|
||||
return self.__data['registered_within_month']
|
||||
|
||||
@property
|
||||
def registered_within_year(self) -> list[int]:
|
||||
return self.__data['registered_within_year']
|
||||
|
||||
@property
|
||||
def registered_outside_week(self) -> list[int]:
|
||||
return self.__data['registered_outside_week']
|
||||
|
||||
@property
|
||||
def registered_outside_month(self) -> list[int]:
|
||||
return self.__data['registered_outside_month']
|
||||
|
||||
@property
|
||||
def registered_outside_year(self) -> list[int]:
|
||||
return self.__data['registered_outside_year']
|
||||
|
||||
|
||||
class DriveLocalChart:
|
||||
__slots__ = ('__data',)
|
||||
|
||||
def __init__(self, data: IDriveLocalChart):
|
||||
self.__data = data
|
||||
|
||||
@property
|
||||
def total_count(self) -> list[int]:
|
||||
return self.__data['total_count']
|
||||
|
||||
@property
|
||||
def total_size(self) -> list[int]:
|
||||
return self.__data['total_size']
|
||||
|
||||
@property
|
||||
def inc_count(self) -> list[int]:
|
||||
return self.__data['inc_count']
|
||||
|
||||
@property
|
||||
def inc_size(self) -> list[int]:
|
||||
return self.__data['inc_size']
|
||||
|
||||
@property
|
||||
def dec_count(self) -> list[int]:
|
||||
return self.__data['dec_count']
|
||||
|
||||
@property
|
||||
def dec_size(self) -> list[int]:
|
||||
return self.__data['dec_size']
|
||||
|
||||
|
||||
class DriveRemoteChart:
|
||||
__slots__ = ('__data',)
|
||||
|
||||
def __init__(self, data: IDriveRemoteChart):
|
||||
self.__data: IDriveRemoteChart = data
|
||||
|
||||
@property
|
||||
def total_count(self) -> list[int]:
|
||||
return self.__data['total_count']
|
||||
|
||||
@property
|
||||
def total_size(self) -> list[int]:
|
||||
return self.__data['total_size']
|
||||
|
||||
@property
|
||||
def inc_count(self) -> list[int]:
|
||||
return self.__data['inc_count']
|
||||
|
||||
@property
|
||||
def inc_size(self) -> list[int]:
|
||||
return self.__data['inc_size']
|
||||
|
||||
@property
|
||||
def dec_count(self) -> list[int]:
|
||||
return self.__data['dec_count']
|
||||
|
||||
@property
|
||||
def dec_size(self) -> list[int]:
|
||||
return self.__data['dec_size']
|
||||
|
||||
|
||||
class DriveChart:
|
||||
__slots__ = ('__data',)
|
||||
|
||||
def __init__(self, data: IDriveChart):
|
||||
self.__data: IDriveChart = data
|
||||
|
||||
@property
|
||||
def local(self) -> DriveLocalChart:
|
||||
return DriveLocalChart(self.__data['local'])
|
||||
|
||||
@property
|
||||
def remote(self) -> DriveRemoteChart:
|
||||
return DriveRemoteChart(self.__data['remote'])
|
Loading…
Reference in new issue