async def check(self, aio_client: aiohttp.ClientSession, usernames: AbstractSet[str]) -> ni_abc.Status: base_url = "https://bugs.python.org/user?@template=clacheck&github_names=" url = base_url + ','.join(usernames) self.server.log("Checking CLA status: " + url) async with aio_client.get(url) as response: if response.status >= 300: msg = f'unexpected response for {response.url!r}: {response.status}' raise client.HTTPException(msg) # Explicitly decode JSON as b.p.o doesn't set the content-type as # `application/json`. results = json.loads(await response.text()) self.server.log("Raw CLA status: " + str(results)) status_results = [results[k] for k in results.keys() if k in usernames] self.server.log("Filtered CLA status: " + str(status_results)) if len(status_results) != len(usernames): raise ValueError("# of usernames don't match # of results " "({} != {})".format(len(usernames), len(status_results))) elif any(x not in (True, False, None) for x in status_results): raise TypeError("unexpected value in " + str(status_results)) if all(status_results): return ni_abc.Status.signed elif any(value is None for value in status_results): return ni_abc.Status.username_not_found else: return ni_abc.Status.not_signed
async def query_imdb(*, imdb_id: Optional[int], year: int, session: ClientSession) -> Dict[str, Any]: params = dict(i=f'tt{imdb_id:0>{IMDB_ID_LENGTH}}', y=year, plot='full', tomatoes='true', r='json') while True: attempt_num = 0 async with session.get(IMDB_API_URL, params=params) as response: attempt_num += 1 if response.status == A_TIMEOUT_OCCURRED: logger.debug(f'Attempt #{attempt_num} failed: ' f'server "{IMDB_API_URL}" answered with ' f'status code {A_TIMEOUT_OCCURRED}. ' f'Waiting {RETRY_INTERVAL_IN_SECONDS} second(s) ' 'before next attempt.') await sleep(RETRY_INTERVAL_IN_SECONDS) continue try: response_json = await response.json() except JSONDecodeError: logger.exception('') return dict() return response_json
def github_action(action, plugin, config, sort_by='updated'): url = API_URL.format( api=API_URL, user=config['user'], repo=config['repository'], action=action, ) query = { 'sort': sort_by, 'direction': 'desc', 'sha': config.get('branch', 'master') } headers = {'Accept': 'application/vnd.github.v3+json'} etag = plugin.temp.get(action) if etag is not None: headers['If-None-Match'] = etag session = ClientSession() try: resp = yield from asyncio.wait_for( session.get(url, params=query, headers=headers), timeout=5 ) try: plugin.temp[action] = resp.headers.get('etag') if resp.status != 200 or etag is None: # etag must be cached first raise NothingChangeException(etag) data = yield from resp.json() finally: resp.close() finally: session.close() return data[0]
def go(): _, srv, url = yield from self.create_server(None, '/', None) client = ClientSession(loop=self.loop) resp = yield from client.get(url) self.assertEqual(404, resp.status) yield from resp.release() client.close()
async def _fetch_url( url: str, session: aiohttp.ClientSession, timeout: float = 10.0) -> str: with async_timeout.timeout(timeout): async with session.get(url) as response: response.raise_for_status() return await response.text()
async def fetch_with_sleep(session: aiohttp.ClientSession, url: str, sleep_time: int) -> Response: async with session.get(url) as response: print(f"request {url} with {sleep_time}s") await sleep(sleep_time) print(f"request {url} sleep done") return response
def go(): _, srv, url = yield from self.create_server('GET', '/', handler) client = ClientSession(loop=self.loop) resp = yield from client.get(url) self.assertEqual(200, resp.status) data = yield from resp.read() self.assertEqual(b'xyz', data) yield from resp.release()
def go(): _, srv, url = yield from self.create_server('GET', '/', handler) client = ClientSession(loop=self.loop) resp = yield from client.get(url) self.assertEqual(200, resp.status) data = yield from resp.read() self.assertEqual(b'mydata', data) self.assertEqual(resp.headers.get('CONTENT-ENCODING'), 'deflate') yield from resp.release() client.close()
async def afetch(session: ClientSession, url: str): """ Asynchronous fetch. Do a GET request, return text, properly shutdown :param session: ClientSession object for aiohttp connection :param url: The URL we want :return: """ async with session.get(url) as response: return await response.text()
async def process_partition( loop: asyncio.BaseEventLoop, results_queue: asyncio.Queue, server_address: URL, http: aiohttp.ClientSession, partition: PointsPartition, mission_template: Template, mission_loader: str, mission_name: str, width: int, scale: int, ) -> Awaitable[None]: LOG.debug( f"query range [{partition.start}:{partition.end}] on server " f"{server_address}" ) file_name = f"{mission_name}_{partition.start}_{partition.end}.mis" missions_url = server_address / "missions" mission_dir_url = missions_url / "heightmap" mission_url = mission_dir_url / file_name points = ( index_to_point(i, width, scale) for i in range(partition.start, partition.end + 1) ) mission = mission_template.render( loader=mission_loader, points=points, ) data = FormData() data.add_field( 'mission', mission.encode(), filename=file_name, content_type='plain/text', ) await http.post(mission_dir_url, data=data) await http.post(mission_url / "load") await http.post(missions_url / "current" / "begin") async with http.get(server_address / "radar" / "stationary-objects") as response: data = await response.json() data = [ pack(HEIGHT_PACK_FORMAT, int(point['pos']['z'])) for point in data ] data = b''.join(data) await http.post(missions_url / "current" / "unload") await http.delete(mission_url) await results_queue.put((partition, data))
def get_html(url): session = ClientSession() try: resp = yield from session.get(url) try: raw_data = yield from resp.read() data = raw_data.decode('utf-8', errors='xmlcharrefreplace') return fromstring_to_html(data) finally: resp.close() finally: session.close()
async def bfetch(session: ClientSession, url: str): """ Asynchronous binary fetch. Do a GET request, return binary data, properly shutdown :param session: ClientSession object for aiohttp connection :param url: The URL we want :return: """ async with session.get(url) as response: if response.status >= 400: LOGGER.error("Got status %s for GET %s", response.status, url) response.raise_for_status() return await response.read()
async def get_imdb_id(article_title: str, *, session: ClientSession ) -> Optional[int]: params = dict(action='expandtemplates', text='{{IMDb title}}', prop='wikitext', title=article_title, format='json') async with session.get(WIKIPEDIA_API_URL, params=params) as response: response_json = await response.json() templates = response_json['expandtemplates'] imdb_link = templates.get('wikitext', '') search_res = IMDB_ID_RE.search(imdb_link) if search_res is None: return None return int(search_res.group(0))
class Downloader(Actor): async def startup(self): self.session = ClientSession(loop=self.loop) @concurrent async def download_content(self, url): async with self.session.get(url) as response: content = await response.read() print('{}: {:.80}...'.format(url, content.decode())) return len(content) async def shutdown(self): self.session.close() async def __aenter__(self): return self async def __aexit__(self, exc_type, exc, tb): await self.close()
async def create_line_to_position_map( self, client_session: aiohttp.ClientSession ) -> MutableMapping[str, Dict[int, int]]: result = defaultdict(dict) # type: MutableMapping[str, Dict[int, int]] current_file = '' position = -1 right_line_number = -1 headers = { 'Accept': 'application/vnd.github.diff', } url = ('{api_url}/repos/' '{organization}/{repo}/pulls/{pr}'.format( api_url=GITHUB_API_URL, organization=self.organization, repo=self.repo, pr=self.pr)) async with client_session.get(url, headers=headers) as response: async for line in response.content: line = line.decode() file_match = FILE_START_REGEX.match(line) if file_match: current_file = file_match.groups()[0].strip() right_line_number = -1 position = -1 continue elif line.startswith(NEW_FILE_SECTION_START): current_file = '' if not current_file: continue position += 1 hunk_match = HUNK_REGEX.match(line) if hunk_match: right_line_number = int(hunk_match.groups()[0]) - 1 elif not line.startswith('-'): right_line_number += 1 result[current_file][right_line_number] = position return result
def create_line_to_position_map( self, client_session: aiohttp.ClientSession ) -> MutableMapping[str, Dict[int, int]]: result = defaultdict(dict) # type: MutableMapping[str, Dict[int, int]] current_file = '' position = -1 right_line_number = -1 headers = { 'Accept': 'application/vnd.github.diff', } url = ('https://api.github.com/repos/' '{organization}/{repo}/pulls/{pr}'.format( organization=self.organization, repo=self.repo, pr=self.pr)) response = yield from client_session.get(url, headers=headers) content = yield from response.text() for line in content.split('\n'): file_match = FILE_START_REGEX.match(line) if file_match: current_file = file_match.groups()[0] right_line_number = -1 position = -1 continue elif line.startswith(NEW_FILE_SECTION_START): current_file = '' if not current_file: continue position += 1 hunk_match = HUNK_REGEX.match(line) if hunk_match: right_line_number = int(hunk_match.groups()[0]) - 1 elif not line.startswith('-'): right_line_number += 1 result[current_file][right_line_number] = position return result
class Github: _BASE_URL = 'https://api.github.com' def __init__(self, username, password, timeout=10): self._loop = asyncio.get_event_loop() self._session = ClientSession(loop=self._loop, auth=BasicAuth(username, password)) self._timeout = timeout def close(self): self._session.close() async def fetch(self, url, params): with Timeout(self._timeout): async with self._session.get('{}{}'.format(self._BASE_URL, url), params=params) as response: return await response.json() async def search_repositories(self, language, pushed, sort, order): q = 'language:{}'.format(language) if pushed: q = '{} pushed:>={}'.format(q, pushed) params = {'q': q, 'sort': sort, 'order': order} return await self.fetch('/search/repositories', params=params)
async def fetch_page(session: aiohttp.ClientSession, url: str): with aiohttp.Timeout(10): async with session.get(url) as resp: assert resp.status == 200 return await resp.read()
async def get_projects(session: aiohttp.ClientSession, params: Dict = None) -> Dict: async with session.get(GITLAB_API_URI + "/projects", params=params) as resp: return await resp.json()
class OwnerOnly(object): def __init__(self, bot): self.bot = bot self.session = ClientSession(loop=bot.loop) def __unload(self): self.session.close() @commands.command(pass_context=True) @permissionChecker(check='is_owner') async def editrolecolour(self, ctx, colour, *, name): ''' Lets you change the profile. ''' r = [ i for i in ctx.message.server.roles if name.lower() in i.name.lower() ] if len(r) > 1: await self.bot.say('Too many roles etc') return elif len(r) < 1: await self.bot.say('nop no roles like that') return if len(colour) == 6: colour = Colour(int(colour, 16)) else: await self.bot.say('Idk what colours are, man') return await self.bot.edit_role(ctx.message.server, r[0], colour=colour) await self.bot.say('Done.') @commands.command(pass_context=True) @permissionChecker(check='is_owner') async def givemerole(self, ctx, *, name): ''' Lets you change the profile. ''' r = [ i for i in ctx.message.server.roles if name.lower() in i.name.lower() ] if len(r) > 1: await self.bot.say('Too many roles etc') return elif len(r) < 1: await self.bot.say('nop no roles like that') return await self.bot.add_roles(ctx.message.author, r[0]) await self.bot.say('Done.') @commands.command(pass_context=True) @permissionChecker(check='is_owner') async def takemerole(self, ctx, *, name): ''' Lets you change the profile. ''' r = [ i for i in ctx.message.server.roles if name.lower() in i.name.lower() ] if len(r) > 1: await self.bot.say('Too many roles etc') return elif len(r) < 1: await self.bot.say('nop no roles like that') return await self.bot.remove_roles(ctx.message.author, r[0]) await self.bot.say('Done.') @commands.group() @permissionChecker(check='is_owner') async def profile(self): ''' Lets you change the profile. ''' pass @profile.command() @permissionChecker(check='is_owner') async def name(self, *, name: str = None): ''' Changes the name of the profile. ''' if name == None: await self.bot.say('`!profile name [NAME]`') return await self.bot.edit_profile(username=name) w = await self.bot.say('👌') await sleep(2) await self.bot.delete_message(w) @profile.command() @permissionChecker(check='is_owner') async def avatar(self, *, avatar: str = None): ''' Changes the profile picture of the bot. ''' if avatar == None: await self.bot.say('`!profile avatar [URL]`') return async with self.session.get(avatar) as r: content = r.read() await self.bot.edit_profile(avatar=content) w = await self.bot.say('👌') await sleep(2) await self.bot.delete_message(w) @commands.command(pass_context=True, hidden=True) @permissionChecker(check='is_owner') async def ev(self, ctx, *, content: str): ''' Evaluates a given Python expression ''' # Eval and print the answer try: output = eval(content) except Exception: type_, value_, traceback_ = exc_info() ex = format_exception(type_, value_, traceback_) output = ''.join(ex) await self.bot.say('```python\n{}```'.format(output)) @commands.command(pass_context=True, hidden=True) @permissionChecker(check='is_owner') async def cls(self, ctx): ''' Clears the console ''' print('\n' * 50) await self.bot.say('Done.') @commands.command(pass_context=True, hidden=True) @permissionChecker(check='is_owner') async def kill(self, ctx): ''' Kills the bot. Makes it deaded ''' # If it is, tell the user the bot it dying await self.bot.say('*Finally*.') await self.bot.change_presence(status=Status.invisible, game=None) exit() @commands.command(pass_context=True, hidden=True, aliases=['rs']) @permissionChecker(check='is_owner') async def restart(self, ctx): ''' Restarts the bot. Literally everything ''' # If it is, tell the user the bot it dying await self.bot.say('Now restarting.') await self.bot.change_presence(status=Status.dnd, game=None) execl(executable, *([executable] + argv)) @commands.command(pass_context=True, hidden=True) @permissionChecker(check='is_owner') async def rld(self, ctx, *, extention: str): ''' Reloads a cog from the bot ''' extention = f'Cogs.{extention}' # Unload the extention await self.bot.say("Reloading extension **{}**...".format(extention)) try: self.bot.unload_extension(extention) except: pass # Load the new one try: self.bot.load_extension(extention) except Exception: type_, value_, traceback_ = exc_info() ex = format_exception(type_, value_, traceback_) output = ''.join(ex) await self.bot.say('```python\n{}```'.format(output)) return # Boop the user await self.bot.say("Done!")
class AioHttpClient(HttpClient): def __init__(self, *, connector=None, loop=None, cookies=None, headers=None, skip_auto_headers=None, auth=None, json_serialize=json.dumps, request_class=ClientRequest, response_class=ClientResponse, ws_response_class=ClientWebSocketResponse, version=http.HttpVersion11, cookie_jar=None, connector_owner=True, raise_for_status=False, read_timeout=sentinel, conn_timeout=None, auto_decompress=True, trust_env=False, **kwargs): """ The class packaging a class ClientSession to perform HTTP request and manager that these HTTP connection. For details of the params: http://aiohttp.readthedocs.io/en/stable/client_advanced.html#client-session """ super(AioHttpClient, self).__init__(**kwargs) self.client = ClientSession(connector=connector, loop=loop, cookies=cookies, headers=headers, skip_auto_headers=skip_auto_headers, auth=auth, json_serialize=json_serialize, request_class=request_class, response_class=response_class, ws_response_class=ws_response_class, version=version, cookie_jar=cookie_jar, connector_owner=connector_owner, raise_for_status=raise_for_status, read_timeout=read_timeout, conn_timeout=conn_timeout, auto_decompress=auto_decompress, trust_env=trust_env) def request(self, method, url, *args, **kwargs): return self.client.request(method=method, url=url, **kwargs) def get(self, url, *args, **kwargs): return self.client.get(url=url, **kwargs) def post(self, url, *args, data=None, **kwargs): return self.client.post(url=url, data=data, **kwargs) def put(self, url, *args, data=None, **kwargs): return self.client.put(url=url, data=data, **kwargs) def delete(self, url, *args, **kwargs): return self.client.delete(url=url, **kwargs) def options(self, url, *args, **kwargs): return self.client.options(url=url, **kwargs) def head(self, url, *args, **kwargs): return self.client.head(url=url, **kwargs) def patch(self, url, *args, data=None, **kwargs): return self.client.patch(url=url, data=data, **kwargs) async def close(self): await self.client.close() async def get_response(self, response): text = await response.text() return Response(url=response.url, status=response.status, charset=response.charset, content_type=response.content_type, content_length=response.content_length, reason=response.reason, headers=response.headers, text=text, selector=etree.HTML(text)) async def __aenter__(self): return self async def __aexit__(self, exc_type, exc_val, exc_tb): await self.close()
async def fetch(client: ClientSession, url): async with client.get(url=url) as resp: assert resp.status == 200 return await resp.text()
async def fetch(url: str, session: ClientSession) -> str: async with session.get(url) as response: return await response.text()
class DaapSession(object): """This class makes it easy to perform DAAP requests. It automatically adds the required headers and also does DMAP parsing. """ def __init__(self, loop, timeout=DEFAULT_TIMEOUT): """Initialize a new DaapSession.""" self._session = ClientSession(loop=loop) self._timeout = timeout def close(self): """Close the underlying client session.""" return self._session.close() @asyncio.coroutine def get_data(self, url, should_parse=True): """Perform a GET request. Optionally parse reponse as DMAP data.""" _LOGGER.debug('GET URL: %s', url) resp = yield from self._session.get(url, headers=_DMAP_HEADERS, timeout=self._timeout) try: resp_data = yield from resp.read() extracted = self._extract_data(resp_data, should_parse) return extracted, resp.status except Exception as ex: resp.close() raise ex finally: yield from resp.release() @asyncio.coroutine def post_data(self, url, data=None, parse=True): """Perform a POST request. Optionally parse reponse as DMAP data.""" _LOGGER.debug('POST URL: %s', url) headers = copy(_DMAP_HEADERS) headers['Content-Type'] = 'application/x-www-form-urlencoded' resp = yield from self._session.post(url, headers=headers, data=data, timeout=self._timeout) try: resp_data = yield from resp.read() extracted = self._extract_data(resp_data, parse) return extracted, resp.status except Exception as ex: resp.close() raise ex finally: yield from resp.release() @staticmethod def _extract_data(data, should_parse): if _LOGGER.isEnabledFor(logging.DEBUG): output = data[0:128] _LOGGER.debug('Data[%d]: %s%s', len(data), binascii.hexlify(output), '...' if len(output) != len(data) else '') if should_parse: return dmap.parse(data, lookup_tag) else: return data
async def get_flag(session: ClientSession, cc: str) -> bytes: # <4> cc = cc.lower() url = f'{BASE_URL}/{cc}/{cc}.gif' async with session.get(url) as resp: # <5> print(resp) return await resp.read() # <6>
class SmartThingsTV: def __init__( self, api_key: str, device_id: str, use_channel_info: bool = True, session: Optional[ClientSession] = None, ): """Initialize SmartThingsTV.""" self._api_key = api_key self._device_id = device_id self._use_channel_info = use_channel_info if session: self._session = session self._managed_session = False else: self._session = ClientSession() self._managed_session = True self._device_name = None self._state = STStatus.STATE_UNKNOWN self._prev_state = STStatus.STATE_UNKNOWN self._muted = False self._volume = 10 self._source_list = None self._source_list_map = None self._source = "" self._channel = "" self._channel_name = "" self._sound_mode = None self._sound_mode_list = None self._picture_mode = None self._picture_mode_list = None self._is_forced_val = False self._forced_count = 0 def __enter__(self): return self def __exit__(self, type, value, traceback): pass @property def api_key(self) -> str: """Return current api_key.""" return self._api_key @property def device_id(self) -> str: """Return current device_id.""" return self._device_id @property def device_name(self) -> str: """Return current device_name.""" return self._device_name @property def state(self): """Return current state.""" return self._state @property def prev_state(self): """Return current state.""" return self._prev_state @property def muted(self) -> bool: """Return current muted state.""" return self._muted @property def volume(self) -> int: """Return current volume.""" return self._volume @property def source(self) -> str: """Return current source.""" return self._source @property def channel(self) -> str: """Return current channel.""" return self._channel @property def channel_name(self) -> str: """Return current channel name.""" return self._channel_name @property def source_list(self): """Return available source list.""" return self._source_list @property def sound_mode(self): """Return current sound mode.""" if self._state != STStatus.STATE_ON: return None return self._sound_mode @property def sound_mode_list(self): """Return available sound modes.""" if self._state != STStatus.STATE_ON: return None return self._sound_mode_list @property def picture_mode(self): """Return current picture mode.""" if self._state != STStatus.STATE_ON: return None return self._picture_mode @property def picture_mode_list(self): """Return available picture modes.""" if self._state != STStatus.STATE_ON: return None return self._picture_mode_list def get_source_name(self, source_id: str) -> str: """Get source name based on source id.""" if not self._source_list_map: return "" if source_id.upper() == DIGITAL_TV.upper(): source_id = "dtv" for map_value in self._source_list_map: map_id = map_value.get("id") if map_id and map_id == source_id: return map_value.get("name", "") return "" def set_application(self, app_id): """Set running application info.""" if self._use_channel_info: self._channel = "" self._channel_name = app_id self._is_forced_val = True self._forced_count = 0 def _set_source(self, source): """Set current source info.""" if source != self._source: self._source = source self._channel = "" self._channel_name = "" self._is_forced_val = True self._forced_count = 0 @staticmethod def _load_json_list(dev_data, list_name): """Try load a list from string to json format.""" load_list = [] json_list = dev_data.get(list_name, {}).get("value") if json_list: try: load_list = json.loads(json_list) except (TypeError, ValueError): pass return load_list @staticmethod async def get_devices_list(api_key, session: ClientSession, device_label=""): """Get list of available SmartThings devices""" result = {} async with session.get( API_DEVICES, headers=_headers(api_key), raise_for_status=True, ) as resp: device_list = await resp.json() if device_list: _LOGGER.debug("SmartThings available devices: %s", str(device_list)) for k in device_list.get("items", []): device_id = k.get("deviceId", "") device_type = k.get("type", "") device_type_id = k.get("deviceTypeId", "") if device_id and (device_type_id == DEVICE_TYPEID_OCF or device_type == DEVICE_TYPE_OCF): label = k.get("label", "") if device_label == "" or (label == device_label and label != ""): result.setdefault(device_id, {})["name"] = k.get("name", "") result.setdefault(device_id, {})["label"] = label _LOGGER.info("SmartThings discovered TV devices: %s", str(result)) return result @Throttle(MIN_TIME_BETWEEN_UPDATES) async def _device_refresh(self, **kwargs): """Refresh device status on SmartThings""" device_id = self._device_id if not device_id: return api_device = f"{API_DEVICES}/{device_id}" api_command = f"{api_device}/commands" if self._use_channel_info: async with self._session.post( api_command, headers=_headers(self._api_key), data=_command(COMMAND_REFRESH), raise_for_status=False, ) as resp: if resp.status == 409: self._state = STStatus.STATE_OFF return resp.raise_for_status() await resp.json() return async def _async_send_command(self, data_cmd): """Send a command via SmartThings""" device_id = self._device_id if not device_id: return if not data_cmd: return api_device = f"{API_DEVICES}/{device_id}" api_command = f"{api_device}/commands" async with self._session.post( api_command, headers=_headers(self._api_key), data=data_cmd, raise_for_status=True, ) as resp: await resp.json() await self._device_refresh() async def async_device_health(self): """Check device availability""" device_id = self._device_id if not device_id: return False api_device = f"{API_DEVICES}/{device_id}" api_device_health = f"{api_device}/health" # this get the real status of the device async with self._session.get( api_device_health, headers=_headers(self._api_key), raise_for_status=True, ) as resp: health = await resp.json() _LOGGER.debug(health) if health["state"] == "ONLINE": return True return False async def async_device_update(self, use_channel_info: bool = None): """Query device status on SmartThing""" device_id = self._device_id if not device_id: return if use_channel_info is not None: self._use_channel_info = use_channel_info api_device = f"{API_DEVICES}/{device_id}" api_device_status = f"{api_device}/states" # not used, just for reference api_device_main_status = f"{api_device}/components/main/status" self._prev_state = self._state try: is_online = await self.async_device_health() except ( AsyncTimeoutError, ClientConnectionError, ClientResponseError, ): self._state = STStatus.STATE_UNKNOWN return if is_online: self._state = STStatus.STATE_ON else: self._state = STStatus.STATE_OFF return await self._device_refresh() if self._state == STStatus.STATE_OFF: return async with self._session.get( api_device_status, headers=_headers(self._api_key), raise_for_status=True, ) as resp: data = await resp.json() _LOGGER.debug(data) dev_data = data.get("main", {}) # device_state = data['main']['switch']['value'] # Volume device_volume = dev_data.get("volume", {}).get("value", 0) if device_volume and device_volume.isdigit(): self._volume = int(device_volume) / 100 else: self._volume = 0 # Muted state device_muted = dev_data.get("mute", {}).get("value", "") self._muted = (device_muted == "mute") # Sound Mode self._sound_mode = dev_data.get("soundMode", {}).get("value") self._sound_mode_list = self._load_json_list(dev_data, "supportedSoundModes") # Picture Mode self._picture_mode = dev_data.get("pictureMode", {}).get("value") self._picture_mode_list = self._load_json_list( dev_data, "supportedPictureModes") # Sources and channel self._source_list = self._load_json_list(dev_data, "supportedInputSources") self._source_list_map = self._load_json_list( dev_data, "supportedInputSourcesMap") if self._is_forced_val and self._forced_count <= 0: self._forced_count += 1 return self._is_forced_val = False self._forced_count = 0 device_source = dev_data.get("inputSource", {}).get("value", "") device_tv_chan = dev_data.get("tvChannel", {}).get("value", "") device_tv_chan_name = dev_data.get("tvChannelName", {}).get("value", "") if device_source: if device_source.upper() == DIGITAL_TV.upper(): device_source = DIGITAL_TV self._source = device_source # if the status is not refreshed this info may become not reliable if self._use_channel_info: self._channel = device_tv_chan self._channel_name = device_tv_chan_name else: self._channel = "" self._channel_name = "" async def async_turn_off(self): """Turn off TV via SmartThings""" await self._async_send_command(COMMAND_POWER_OFF) async def async_turn_on(self): """Turn on TV via SmartThings""" await self._async_send_command(COMMAND_POWER_ON) async def async_send_command(self, cmd_type, command=""): """Send a command to the device""" data_cmd = None if cmd_type == "setvolume": # sets volume data_cmd = _command(COMMAND_SET_VOLUME, [int(command)]) elif cmd_type == "stepvolume": # steps volume up or down if command == "up": data_cmd = _command(COMMAND_VOLUME_UP) elif command == "down": data_cmd = _command(COMMAND_VOLUME_DOWN) elif cmd_type == "audiomute": # mutes audio if command == "on": data_cmd = _command(COMMAND_MUTE) elif command == "off": data_cmd = _command(COMMAND_UNMUTE) elif cmd_type == "selectchannel": # changes channel data_cmd = _command(COMMAND_SET_CHANNEL, [command]) elif cmd_type == "stepchannel": # steps channel up or down if command == "up": data_cmd = _command(COMMAND_CHANNEL_UP) elif command == "down": data_cmd = _command(COMMAND_CHANNEL_DOWN) else: return await self._async_send_command(data_cmd) async def async_select_source(self, source): """Select source""" # if source not in self._source_list: # return data_cmd = _command(COMMAND_SET_SOURCE, [source]) # set property to reflect new changes self._set_source(source) await self._async_send_command(data_cmd) async def async_set_sound_mode(self, mode): """Select sound mode""" if self._state != STStatus.STATE_ON: return if mode not in self._sound_mode_list: raise InvalidSmartThingsSoundMode() data_cmd = _command(COMMAND_SOUND_MODE, [mode]) await self._async_send_command(data_cmd) self._sound_mode = mode async def async_set_picture_mode(self, mode): """Select picture mode""" if self._state != STStatus.STATE_ON: return if mode not in self._picture_mode_list: raise InvalidSmartThingsPictureMode() data_cmd = _command(COMMAND_PICTURE_MODE, [mode]) await self._async_send_command(data_cmd) self._picture_mode = mode
class Projects(Cog): """Cog that reacts appropriately to project urls.""" _index_channel: int = ConfigValue("default", "projects", "index_channel_id") _default_kwargs = { "pattern": r"^(?:webhook!)?https?://($hosts)(?:.{0,200})$$", "message_channel_id": _index_channel, "message_author_bot": False, "formatter": lambda client: { "hosts": "|".join(client.config["default"]["projects"]["hosts"]) }, } _negative_lookahed = { **_default_kwargs, "filter_": (lambda match: isinstance(match, Match)), } _category_id: int = ConfigValue("default", "projects", "category_id") _member_role_id: int = ConfigValue("default", "guild_member_role") session: ClientSession def __post_init__(self): self.session = ClientSession() # Internal async def _activate_webhook_channel( self, channel: TextChannel, timeout: int = DEALLOCATION_TIMEOUT) -> None: def is_webhook_message(message: Message) -> bool: return message.channel == channel and message.webhook_id is not None wait_for = partial(self.client.wait_for, "message") try: await wait_for(check=is_webhook_message, timeout=timeout) except TimeoutError: await channel.delete() return self.logger.info("Activating webhook channel %s", repr(channel)) def is_not_webhook(message: Message) -> bool: return message.webhook_id is None await channel.purge(limit=None, check=is_not_webhook, bulk=True) await channel.edit(sync_permissions=True) # Zombie rescheduler @Cog.task @Cog.wait_until_ready async def _reschedule_orphan_channels(self) -> None: category = self.client.get_channel(self._category_id) assert category is not None schedule_task = self.client.schedule_task for channel in category.channels: if channel.id == self._index_channel: continue timeout = None now = datetime.now() async for message in channel.history(limit=None, oldest_first=True): if message.webhook_id is not None: timeout = None break if timeout is None: timeout = DEALLOCATION_TIMEOUT - ( now - message.created_at).seconds if timeout <= 0: timeout = DEALLOCATION_TIMEOUT if timeout is not None: self.logger.warn( "Rescheduling activation task for channel timeout=%s channel=%s", timeout, repr(channel), ) schedule_task(self._activate_webhook_channel(channel, timeout)) # Listeners @Cog.formatted_regex(**_negative_lookahed) async def filter_project_message(self, message: Message) -> None: """Handle a :class:`discord.Message` sent in the projects channel.""" await message.channel.send( f"{message.author.mention}, That doesn't look like a valid vcs link.", delete_after=3.0, ) await sleep(0.5) await message.delete() @Cog.formatted_regex(**_default_kwargs) @Cog.wait_until_ready async def webhook_project_repository(self, message: Message) -> None: """Generate a channel and webhook for a repository.""" assert message.content.startswith("webhook!") url = message.content[len("webhook!"):] async with self.session.get(url) as resp: if not resp.status in range(200, 300): await message.channel.send( f"{message.author.mention} - I couldn't verify that link, I got a HTTP/{resp.status} back.", delete_after=3.0) await sleep(2) await message.delete() return kwargs = {"topic": url, "reason": f"Invoked by {message.author!s}"} match = await self.client.loop.run_in_executor( None, partial(search, r"([^/][\w-]+)(?:/)([^/].+)$", url)) if match is not None: name = "-".join(match.groups()) else: self.logger.error("Failed to match against %s", repr(url)) name = url if len(name) > 80: name = name[:80] assert len(name) <= 80 kwargs["name"] = name category = self.client.get_channel(self._category_id) assert category is not None member_role = message.guild.get_role(self._member_role_id) assert member_role is not None kwargs["overwrites"] = { **category.overwrites, member_role: PermissionOverwrite(read_messages=False, read_message_history=False), message.author: PermissionOverwrite(read_messages=True, read_message_history=True, send_messages=True), } channel = await category.create_text_channel(**kwargs) webhook = await channel.create_webhook(name=name) await channel.send( ALLOCATED_MESSAGE.format(message=message, webhook=webhook)) self.client.schedule_task(self._activate_webhook_channel(channel))
class SeasonalBot(commands.Bot): """ Base bot instance. While in debug mode, the asset upload methods (avatar, banner, ...) will not perform the upload, and will instead only log the passed download urls and pretend that the upload was successful. See the `mock_in_debug` decorator for further details. """ def __init__(self, **kwargs): super().__init__(**kwargs) self.http_session = ClientSession(connector=TCPConnector( resolver=AsyncResolver(), family=socket.AF_INET)) self._guild_available = asyncio.Event() self.loop.create_task(self.send_log("SeasonalBot", "Connected!")) @property def member(self) -> Optional[discord.Member]: """Retrieves the guild member object for the bot.""" guild = self.get_guild(Client.guild) if not guild: return None return guild.me def add_cog(self, cog: commands.Cog) -> None: """ Delegate to super to register `cog`. This only serves to make the info log, so that extensions don't have to. """ super().add_cog(cog) log.info(f"Cog loaded: {cog.qualified_name}") async def on_command_error(self, context: commands.Context, exception: DiscordException) -> None: """Check command errors for UserInputError and reset the cooldown if thrown.""" if isinstance(exception, commands.UserInputError): context.command.reset_cooldown(context) else: await super().on_command_error(context, exception) async def _fetch_image(self, url: str) -> bytes: """Retrieve and read image from `url`.""" log.debug(f"Getting image from: {url}") async with self.http_session.get(url) as resp: return await resp.read() async def _apply_asset(self, target: Union[Guild, User], asset: AssetType, url: str) -> bool: """ Internal method for applying media assets to the guild or the bot. This shouldn't be called directly. The purpose of this method is mainly generic error handling to reduce needless code repetition. Return True if upload was successful, False otherwise. """ log.info(f"Attempting to set {asset.name}: {url}") kwargs = {asset.value: await self._fetch_image(url)} try: async with async_timeout.timeout(5): await target.edit(**kwargs) except asyncio.TimeoutError: log.info("Asset upload timed out") return False except discord.HTTPException as discord_error: log.exception("Asset upload failed", exc_info=discord_error) return False else: log.info(f"Asset successfully applied") return True @mock_in_debug(return_value=True) async def set_banner(self, url: str) -> bool: """Set the guild's banner to image at `url`.""" guild = self.get_guild(Client.guild) if guild is None: log.info("Failed to get guild instance, aborting asset upload") return False return await self._apply_asset(guild, AssetType.BANNER, url) @mock_in_debug(return_value=True) async def set_icon(self, url: str) -> bool: """Sets the guild's icon to image at `url`.""" guild = self.get_guild(Client.guild) if guild is None: log.info("Failed to get guild instance, aborting asset upload") return False return await self._apply_asset(guild, AssetType.SERVER_ICON, url) @mock_in_debug(return_value=True) async def set_avatar(self, url: str) -> bool: """Set the bot's avatar to image at `url`.""" return await self._apply_asset(self.user, AssetType.AVATAR, url) @mock_in_debug(return_value=True) async def set_nickname(self, new_name: str) -> bool: """Set the bot nickname in the main guild to `new_name`.""" member = self.member if member is None: log.info( "Failed to get bot member instance, aborting asset upload") return False log.info(f"Attempting to set nickname to {new_name}") try: await member.edit(nick=new_name) except discord.HTTPException as discord_error: log.exception("Setting nickname failed", exc_info=discord_error) return False else: log.info("Nickname set successfully") return True async def send_log(self, title: str, details: str = None, *, icon: str = None) -> None: """Send an embed message to the devlog channel.""" await self.wait_until_guild_available() devlog = self.get_channel(Channels.devlog) if not devlog: log.info( f"Fetching devlog channel as it wasn't found in the cache (ID: {Channels.devlog})" ) try: devlog = await self.fetch_channel(Channels.devlog) except discord.HTTPException as discord_exc: log.exception("Fetch failed", exc_info=discord_exc) return if not icon: icon = self.user.avatar_url_as(format="png") embed = Embed(description=details) embed.set_author(name=title, icon_url=icon) await devlog.send(embed=embed) async def on_guild_available(self, guild: discord.Guild) -> None: """ Set the internal `_guild_available` event when PyDis guild becomes available. If the cache appears to still be empty (no members, no channels, or no roles), the event will not be set. """ if guild.id != Client.guild: return if not guild.roles or not guild.members or not guild.channels: log.warning( "Guild available event was dispatched but the cache appears to still be empty!" ) return self._guild_available.set() async def on_guild_unavailable(self, guild: discord.Guild) -> None: """Clear the internal `_guild_available` event when PyDis guild becomes unavailable.""" if guild.id != Client.guild: return self._guild_available.clear() async def wait_until_guild_available(self) -> None: """ Wait until the PyDis guild becomes available (and the cache is ready). The on_ready event is inadequate because it only waits 2 seconds for a GUILD_CREATE gateway event before giving up and thus not populating the cache for unavailable guilds. """ await self._guild_available.wait()
async def _get_xlsx_url(session: aiohttp.ClientSession) -> str: """Получить url для файла с инфляцией.""" async with session.get(URL) as resp: html = await resp.text() if match := re.search(FILE_PATTERN, html): return match.group(0)
class FirebaseHTTP: """ HTTP Client for Firebase. Args: base_url (str): URL to your data. auth (string): Auth key. loop (class:`asyncio.BaseEventLoop`): Loop. """ def __init__(self, base_url, auth=None, loop=None): """Initialise the class.""" self._loop = loop or asyncio.get_event_loop() self._base_url = base_url self._auth = auth self._session = ClientSession(loop=self._loop) async def close(self): """Gracefully close the session.""" await self._session.close() async def get(self, *, path=None, params=None): """Perform a GET request.""" return await self._request(method='GET', path=path, params=params) async def put(self, *, value, path=None, params=None): """Perform a put request.""" return await self._request(method='PUT', value=value, path=path, params=params) async def post(self, *, value, path=None, params=None): """Perform a POST request.""" return await self._request(method='POST', value=value, path=path, params=params) async def patch(self, *, value, path=None, params=None): """Perform a PATCH request.""" return await self._request(method='PATCH', value=value, path=path, params=params) async def delete(self, *, path=None, params=None): """Perform a DELETE request.""" return await self._request(method='DELETE', path=path, params=params) async def stream(self, *, callback, path=None): """Hook up to the EventSource stream.""" url = posixpath.join(self._base_url, path) if path else self._base_url headers = {'accept': 'text/event-stream'} async with self._session.get(url, headers=headers) as resp: while True: await FirebaseHTTP._iterate_over_stream(resp.content.read(), callback) @staticmethod async def _iterate_over_stream(iterable, callback): """Iterate over the EventSource stream and pass the event and data to the callback as and when we receive it.""" async for msg in iterable: msg_str = msg.decode('utf-8').strip() if not msg_str: continue key, value = msg_str.split(':', 1) if key == 'event' and value == 'cancel': raise StreamCancelled('The requested location is no longer allowed due to security/rules changes.') elif key == 'event' and value == 'auth_revoked': raise StreamAuthRevoked('The auth credentials has expired.') elif key == 'event': event = value elif key == 'data': await callback(event=event, data=json.loads(value)) async def _request(self, *, method, value=None, path=None, params=None): """Perform a request to Firebase.""" url = posixpath.join(self._base_url, path.strip('/')) if path else self._base_url url += '.json' data = json.dumps(value) if value else None params = params or {} headers = {} if self._auth: params.update({'auth': self._auth}) headers.update({'typ': 'JWT', 'alg': 'HS256'}) async with self._session.request(method, url, data=data, params=params, headers=headers) as resp: assert resp.status == 200 return await resp.json()
async def make_riot_request( cls: AAshe.sqlite.SQLite, aiosession: aiohttp.ClientSession, region: str, url: str, headers: dict, timeout: int=10, count=True) \ ->(bytes, dict): """ Makes a web request with an aiosession and returns the data. Args: cls: AAshe.sqlite.MessagePrint Used to broadcast status messages. aiosession: aiosession used to make the request. region: Region used in the request. url: URL to call. headers: Headers used for the call, usually empty. timeout: Timeout timer. count: If it should count on the rate limit. Returns: (bytes, dict) Contains the raw data, and the return headers. """ # Insures there is a Rate Limit object if ratelimit.RateLimit.key_limit is None: ratelimit.RateLimit.key_limit = ratelimit.RateLimit( name="Api Key Limit") # Checks the rate limits await ratelimit.RateLimit.key_limit.check_cooldown(region=region, count=count) resp_data = None resp_headers = None # Makes actual request with aiohttp.Timeout(timeout): async with aiosession.get(url=url.format(region.lower()), headers=headers) as resp: resp_data = await resp.read() resp_headers = resp.headers if region.lower() not in ratelimit.RateLimit.key_limit.region_limits: cls.info(msg="Region was not within dictionary, adding.") ratelimit.RateLimit.key_limit.region_limits[ region.lower()] = ratelimit.RateLimit.Region(region=region, lock=True) region_limit = ratelimit.RateLimit.key_limit.region_limits[region.lower()] if resp_headers: if "X-App-Rate-Limit" in resp_headers and "X-App-Rate-Limit-Count" in resp_headers: if time.time( ) - region_limit.time > ratelimit.RateLimit.key_refresh_cooldown: if ratelimit.RateLimit.key_refresh_cooldown != 0 or not region_limit.limits: # Cleanup old if isinstance(region_limit.limits, list): for i, limit in enumerate(region_limit.limits): del region_limit.limits[i] region_limit.limits = [] # Prepare new rate_limits = {} for str_limit in resp_headers["X-App-Rate-Limit"].strip( ).split(","): period, every = str_limit.split(":", 1) rate_limits[int(every)] = int(period) rate_limit_count = {} for str_limit in resp_headers[ "X-App-Rate-Limit-Count"].strip().split(","): count, every = str_limit.split(":", 1) rate_limit_count[int(every)] = int(count) # Adds them for every in list(rate_limits.keys()): if ratelimit.RateLimit.key_limit.add_limit( period=rate_limits[every], every=float(every), region=region, count=rate_limit_count[every]): cls.debug( msg= f"Added limit <{rate_limits[every]}/{every}s> with count {rate_limit_count[every]}." ) if "status" in json.loads(resp_data.decode()): exceptions = \ { # You f****d up 400: errors.BadRequest, 401: errors.Unauthorized, 403: errors.Forbidden, 404: errors.DataNotFound, 405: errors.MethodNotAllowed, 415: errors.UnsupportedMediaType, 429: errors.RateLimitExceeded, # Server f****d up 500: errors.InternalServerError, 502: errors.BadGateway, 503: errors.ServiceUnavailable, 504: errors.GatewayTimeout } exception = exceptions[json.loads( resp_data.decode())["status"]["status_code"]] exception.server_message = json.loads( resp_data.decode())["status"]["message"] raise exception return resp_data, resp_headers
async def _get(url: str, session: aiohttp.ClientSession): async with session.get(url, headers=_HEADERS) as response: assert response.status == 200 return await response.json(loads=ujson.loads)
async def draw_rank(session: ClientSession, user: Member) -> Image: name_fnt = ImageFont.truetype(font_heavy_file, 24) name_u_fnt = ImageFont.truetype(font_unicode_file, 24) label_fnt = ImageFont.truetype(font_bold_file, 16) exp_fnt = ImageFont.truetype(font_bold_file, 9) large_fnt = ImageFont.truetype(font_thin_file, 24) symbol_u_fnt = ImageFont.truetype(font_unicode_file, 15) def _write_unicode(text, init_x, y, font, unicode_font, fill): write_pos = init_x for char in text: if char.isalnum() or char in string.punctuation or char in string.whitespace: draw.text((write_pos, y), char, font=font, fill=fill) write_pos += font.getsize(char)[0] else: draw.text((write_pos, y), u"{}".format(char), font=unicode_font, fill=fill) write_pos += unicode_font.getsize(char)[0] user_info = db.user(user) bg_url = await user_info.rank_background() profile_url = user.avatar_url async with session.get(bg_url) as r: bg_image = Image.open(BytesIO(await r.content.read())).convert("RGBA") async with session.get(profile_url) as r: profile_image = Image.open(BytesIO(await r.content.read())).convert("RGBA") member_info = db.member(user) user_exp = await member_info.current_exp() user_level = await member_info.level() exp_color = tuple(await user_info.rank_info_color()) # set canvas width = 390 height = 100 bg_color = (255, 255, 255, 0) bg_width = width - 50 result = Image.new("RGBA", (width, height), bg_color) process = Image.new("RGBA", (width, height), bg_color) draw = ImageDraw.Draw(process) # info section info_section = Image.new("RGBA", (bg_width, height), bg_color) info_section_process = Image.new("RGBA", (bg_width, height), bg_color) # puts in background bg_image = bg_image.resize((width, height), Image.ANTIALIAS) bg_image = bg_image.crop((0, 0, width, height)) info_section.paste(bg_image, (0, 0)) # draw transparent overlays draw_overlay = ImageDraw.Draw(info_section_process) draw_overlay.rectangle([(0, 0), (bg_width, 20)], fill=(230, 230, 230, 200)) draw_overlay.rectangle([(0, 20), (bg_width, 30)], fill=(120, 120, 120, 180)) # Level bar exp_width = int(bg_width * (user_exp / _required_exp(user_level))) draw_overlay.rectangle([(0, 20), (exp_width, 30)], fill=exp_color) # Exp bar draw_overlay.rectangle([(0, 30), (bg_width, 31)], fill=(0, 0, 0, 255)) # Divider for i in range(0, 70): draw_overlay.rectangle( [(0, height - i), (bg_width, height - i)], fill=(20, 20, 20, 255 - i * 3) ) # title overlay # draw corners and finalize info_section = Image.alpha_composite(info_section, info_section_process) info_section = _add_corners(info_section, 25) process.paste(info_section, (35, 0)) # draw level circle multiplier = 6 lvl_circle_dia = 100 circle_left = 0 circle_top = int((height - lvl_circle_dia) / 2) raw_length = lvl_circle_dia * multiplier # create mask mask = Image.new("L", (raw_length, raw_length), 0) draw_thumb = ImageDraw.Draw(mask) draw_thumb.ellipse((0, 0) + (raw_length, raw_length), fill=255, outline=0) # drawing level border lvl_circle = Image.new("RGBA", (raw_length, raw_length)) draw_lvl_circle = ImageDraw.Draw(lvl_circle) draw_lvl_circle.ellipse([0, 0, raw_length, raw_length], fill=(250, 250, 250, 250)) # put on profile circle background lvl_circle = lvl_circle.resize((lvl_circle_dia, lvl_circle_dia), Image.ANTIALIAS) lvl_bar_mask = mask.resize((lvl_circle_dia, lvl_circle_dia), Image.ANTIALIAS) process.paste(lvl_circle, (circle_left, circle_top), lvl_bar_mask) # draws mask total_gap = 6 border = int(total_gap / 2) profile_size = lvl_circle_dia - total_gap # put in profile picture mask = mask.resize((profile_size, profile_size), Image.ANTIALIAS) profile_image = profile_image.resize((profile_size, profile_size), Image.ANTIALIAS) process.paste(profile_image, (circle_left + border, circle_top + border), mask) # draw text grey_color = (100, 100, 100, 255) white_color = (220, 220, 220, 255) # name _write_unicode( _truncate_text(get_user_display_name(user, 20), 20), 100, 0, name_fnt, name_u_fnt, grey_color ) # Name # labels v_label_align = 75 info_text_color = white_color draw.text( (_center(100, 200, " RANK", label_fnt), v_label_align), " RANK", font=label_fnt, fill=info_text_color, ) # Rank draw.text( (_center(100, 360, " LEVEL", label_fnt), v_label_align), " LEVEL", font=label_fnt, fill=info_text_color, ) # Rank draw.text( (_center(260, 360, "BALANCE", label_fnt), v_label_align), "BALANCE", font=label_fnt, fill=info_text_color, ) # Rank if "linux" in platform.system().lower(): local_symbol = u"\U0001F3E0 " _write_unicode( local_symbol, 117, v_label_align + 4, label_fnt, symbol_u_fnt, info_text_color ) # Symbol _write_unicode( local_symbol, 195, v_label_align + 4, label_fnt, symbol_u_fnt, info_text_color ) # Symbol # userinfo guild_rank = f"#{await _find_guild_rank(user)}" draw.text( (_center(100, 200, guild_rank, large_fnt), v_label_align - 30), guild_rank, font=large_fnt, fill=info_text_color, ) # Rank level_text = str(user_level) draw.text( (_center(95, 360, level_text, large_fnt), v_label_align - 30), level_text, font=large_fnt, fill=info_text_color, ) # Level credit_txt = f"${await bank.get_balance(user)}" draw.text( (_center(260, 360, credit_txt, large_fnt), v_label_align - 30), credit_txt, font=large_fnt, fill=info_text_color, ) # Balance exp_text = f"{user_exp}/{_required_exp(user_level)}" draw.text( (_center(80, 360, exp_text, exp_fnt), 19), exp_text, font=exp_fnt, fill=info_text_color, ) # Rank return Image.alpha_composite(result, process)
async def one_request(cls, session: aiohttp.ClientSession, url, params): async with session.get(url=url, params=params) as res: rs = await res.read() return rs
async def worker(queue: asyncio.Queue, session: aiohttp.ClientSession): global done, wait_a_little while True: if done: return else: try: info = await asyncio.wait_for(queue.get(), 100) except asyncio.exceptions.TimeoutError: print("等待超过了100秒, 如果在接下来120秒内没有响应将退出.") try: info = await asyncio.wait_for(queue.get(), 120) except asyncio.exceptions.TimeoutError: print("已经等待了120秒, 生成器没有输出, 该协程退出.") break """ if Path(f"./mods/{info['name']}").exists(): # 校验其长度 size = os.path.getsize(Path(f"./mods/{info['name']}").absolute()) if size == info['file-length']: print(f"warn: {info['name']} 已存在, 跳过.") continue else: print(f"warn: {info['name']} 不完整, 重新爬取") """ print(f"开始获取 [{info['name']}][{info['url']}] ") try: async with session.get(info['url']) as response: file_content = await response.read() with tempfile.TemporaryFile("r+b") as f: f.write(file_content) f.seek(0) zipFile = zipfile.ZipFile(f) with tempfile.TemporaryDirectory() as td_name: # td = TemporaryDirectory zipFile.extractall(td_name) td_path = Path(td_name) paths = glob.glob( str(td_path.absolute()) + "/assets/*/lang") domains = [i.string[i.start():i.end()] for i in [ re.search(r"(?<=(assets\/))[a-zA-Z0-9]*(?=(\/lang))", i.replace("\\", "/"))\ for i in paths ] if i] for domain in domains: if not domain: continue if f"{info['name']}::{domain}" in config.namespace_domain_blacklist: print( f"检测到模组 {info['name']} 正在尝试修改被列入命名空间黑名单的资源域 {domain}, 已阻止." ) continue domain_dir = Path(f"./project/assets/{domain}") if not domain_dir.exists(): domain_dir.absolute().mkdir() domain_lang = domain_dir / "lang" if not domain_lang.exists(): domain_lang.absolute().mkdir() # touch 各个文件 touch_list: List[Path] = [ (td_path / "assets" / domain / "lang" / filename) for filename in [ "en_us.lang", "zh_cn.lang", "en_us_old.lang", "zh_cn_old.lang" ] ] for filename in touch_list: if not filename.exists(): filename.touch() # 复制文件到{tmp}/assets/{domain}/lang下 domain_root = Path(f"./project/assets/{domain}") project_en_us: Path = domain_root / "lang" / "en_us.lang" project_zh_cn: Path = domain_root / "lang" / "zh_cn.lang" if project_en_us.exists( ) and project_en_us.is_file(): shutil.copy( str(project_en_us.absolute()), str(td_path / "assets" / domain / "lang" / "en_us_old.lang")) if project_zh_cn.exists( ) and project_zh_cn.is_file(): shutil.copy( str(project_zh_cn.absolute()), str(td_path / "assets" / domain / "lang" / "zh_cn.lang")) # 复制重构完毕. # 开始预处理.... # ph = pre handle ph_en_us: Path = td_path / "assets" / domain / "lang" / "en_us.lang" if ph_en_us.exists() and ph_en_us.is_file(): ph_en_us.write_text(ph_en_us.read_text( encoding="utf-8", errors="replace"), encoding="utf-8", errors="replace") # 自动转换编码 if not utils.is_properties_language_file( str(ph_en_us.absolute())): utils.language_file_delete_and_fix( str(ph_en_us.absolute())) # 开始删除.. for item in config.delete_list: if os.path.exists(td_name + "/assets/" + item): shutil.rmtree(td_name + "/assets/" + item) if Path(f"./project/assets/{item}").exists(): print(f"删除项 [{item}]") shutil.rmtree(f"./project/assets/{item}") if item in domains: domains.remove(item) # 开始复制 for i in domains: local_lang = Path( f"./project/assets/{i}/lang/en_us.lang") if not local_lang.exists(): with open(str(local_lang.absolute()), "a") as f: f.write("") else: # 不需要处理 en_us_old.lang 了 pass shutil.copyfile( f"{td_name}/assets/{i}/lang/en_US.lang", str(local_lang.absolute())) # 处理date文件 print(f"处理 [{info['name']}] 完毕.") except Exception as e: print(f"在爬取 [{info['name']}] 时发生了错误, 尝试重新排入队列", type(e)) traceback.print_exc() wait_a_little = True await queue.put(info)
class Client: """MELCloud client. Please do not use this class directly. It is better to use the get_devices method exposed by the __init__.py. """ def __init__( self, token: str, session: Optional[ClientSession] = None, *, conf_update_interval=timedelta(minutes=5), device_set_debounce=timedelta(seconds=1), ): """Initialize MELCloud client.""" self._token = token if session: self._session = session self._managed_session = False else: self._session = ClientSession() self._managed_session = True self._conf_update_interval = conf_update_interval self._device_set_debounce = device_set_debounce self._last_conf_update = None self._device_confs: List[Dict[str, Any]] = [] self._account: Optional[Dict[str, Any]] = None @property def token(self) -> str: """Return currently used token.""" return self._token @property def device_confs(self) -> List[Dict[Any, Any]]: """Return device configurations.""" return self._device_confs @property def account(self) -> Optional[Dict[Any, Any]]: """Return account.""" return self._account async def _fetch_user_details(self): """Fetch user details.""" async with self._session.get( f"{BASE_URL}/User/GetUserDetails", headers=_headers(self._token), raise_for_status=True, ) as resp: self._account = await resp.json() async def _fetch_device_confs(self): """Fetch all configured devices.""" url = f"{BASE_URL}/User/ListDevices" async with self._session.get(url, headers=_headers(self._token), raise_for_status=True) as resp: entries = await resp.json() new_devices = [] for entry in entries: new_devices = new_devices + entry["Structure"]["Devices"] for area in entry["Structure"]["Areas"]: new_devices = new_devices + area["Devices"] for floor in entry["Structure"]["Floors"]: new_devices = new_devices + floor["Devices"] for area in floor["Areas"]: new_devices = new_devices + area["Devices"] visited = set() self._device_confs = [ d for d in new_devices if d["DeviceID"] not in visited and not visited.add(d["DeviceID"]) ] async def update_confs(self): """Update device_confs and account. Calls are rate limited to allow Device instances to freely poll their own state while refreshing the device_confs list and account. """ now = datetime.now() if (self._last_conf_update is not None and now - self._last_conf_update < self._conf_update_interval): return None self._last_conf_update = now await self._fetch_user_details() await self._fetch_device_confs() async def fetch_device_units(self, device) -> Optional[Dict[Any, Any]]: """Fetch unit information for a device. User provided info such as indoor/outdoor unit model names and serial numbers. """ async with self._session.post( f"{BASE_URL}/Device/ListDeviceUnits", headers=_headers(self._token), json={"deviceId": device.device_id}, raise_for_status=True, ) as resp: return await resp.json() async def fetch_device_state(self, device) -> Optional[Dict[Any, Any]]: """Fetch state information of a device. This method should not be called more than once a minute. Rate limiting is left to the caller. """ device_id = device.device_id building_id = device.building_id async with self._session.get( f"{BASE_URL}/Device/Get?id={device_id}&buildingID={building_id}", headers=_headers(self._token), raise_for_status=True, ) as resp: return await resp.json() async def set_device_state(self, device): """Update device state. This method is as dumb as it gets. Device is responsible for updating the state and managing EffectiveFlags. """ device_type = device.get("DeviceType") if device_type == 0: setter = "SetAta" elif device_type == 1: setter = "SetAtw" elif device_type == 3: setter = "SetErv" else: raise ValueError(f"Unsupported device type [{device_type}]") async with self._session.post( f"{BASE_URL}/Device/{setter}", headers=_headers(self._token), json=device, raise_for_status=True, ) as resp: return await resp.json()
async def update_index( http_client: aiohttp.ClientSession, token: str, uploader: Uploader ) -> bool: try: logger.info("finding shared folder link") headers = {"Authorization": "Bearer " + token} params = { "path": "/Public", "direct_only": True, } async with http_client.post( DROPBOX_API_URL + "/2/sharing/list_shared_links", headers=headers, json=params, ) as resp: await raise_for_status_body(resp) for link in (await resp.json())["links"]: if link[".tag"] != "folder": continue try: visibility = link["link_permissions"]["resolved_visibility"][".tag"] except KeyError: continue if visibility == "public": break else: raise Exception("shared folder link not found") logger.info("walking shared folder") async for path, files, dirs in walk_shared_folder(http_client, link["url"]): lines = [] old_lines = [] for name, href in files: href = make_download_url(href) lines.append(name + "\t" + href + "\n") if name == "INDEX": async with http_client.get(href, raise_for_status=True) as resp: old_lines = (await resp.text()).splitlines(keepends=True) lines.extend(name + "/\t" + href + "\n" for name, href in dirs) lines.sort() index_path = (path + "/" if path else "") + "INDEX" if lines == old_lines: logger.info("%s is up to date", index_path) continue diff = difflib.unified_diff( old_lines, lines, fromfile="a/" + index_path, tofile="b/" + index_path ) logger.info("updating %s:\n%s", index_path, "".join(diff).rstrip("\n")) uploader.queue_file_obj( io.BytesIO("".join(lines).encode()), "/Public/" + index_path, mode="overwrite", ) succeeded, failed = await uploader.wait() if failed: logger.info("updates failed: %s", ", ".join(failed)) return False return True except Exception: logger.exception("updating INDEX files failed") return False
async def _load_from_url(session: ClientSession, url: URL) -> Tuple[Dict, str]: async with session.get(url) as resp: text = await resp.text() spec_dict = yaml.safe_load(text) return spec_dict, str(url)
async def get_full_image_data(session: ClientSession, token: str, image_id: str) -> dict: async with session.get(f"{API_BASE_URL}{API_IMAGES_URI}/{image_id}", headers={"Authorization": f"Bearer {token}"}) as response: return await response.json()
class HttpTrackerSession(TrackerSession): def __init__(self, tracker_url, tracker_address, announce_page, timeout, proxy): super().__init__('http', tracker_url, tracker_address, announce_page, timeout) self._session = ClientSession( connector=Socks5Connector(proxy) if proxy else None, raise_for_status=True, timeout=ClientTimeout(total=self.timeout)) async def connect_to_tracker(self): # create the HTTP GET message # Note: some trackers have strange URLs, e.g., # http://moviezone.ws/announce.php?passkey=8ae51c4b47d3e7d0774a720fa511cc2a # which has some sort of 'key' as parameter, so we need to use the add_url_params # utility function to handle such cases. url = add_url_params( "http://%s:%s%s" % (self.tracker_address[0], self.tracker_address[1], self.announce_page.replace('announce', 'scrape')), {"info_hash": self.infohash_list}) # no more requests can be appended to this session self.is_initiated = True self.last_contact = int(time.time()) try: self._logger.debug("%s HTTP SCRAPE message sent: %s", self, url) async with self._session: async with self._session.get( url.encode('ascii').decode('utf-8')) as response: body = await response.read() except UnicodeEncodeError as e: raise e except ClientResponseError as e: self._logger.warning("%s HTTP SCRAPE error response code %s", self, e.status) self.failed(msg=f"error code {e.status}") except Exception as e: self.failed(msg=str(e)) return self._process_scrape_response(body) def _process_scrape_response(self, body): """ This function handles the response body of a HTTP tracker, parsing the results. """ # parse the retrieved results if body is None: self.failed(msg="no response body") response_dict = bdecode_compat(body) if not response_dict: self.failed(msg="no valid response") response_list = [] unprocessed_infohash_list = self.infohash_list[:] if b'files' in response_dict and isinstance(response_dict[b'files'], dict): for infohash in response_dict[b'files']: complete = 0 incomplete = 0 if isinstance(response_dict[b'files'][infohash], dict): complete = response_dict[b'files'][infohash].get( b'complete', 0) incomplete = response_dict[b'files'][infohash].get( b'incomplete', 0) # Sow complete as seeders. "complete: number of peers with the entire file, i.e. seeders (integer)" # - https://wiki.theory.org/BitTorrentSpecification#Tracker_.27scrape.27_Convention seeders = complete leechers = incomplete # Store the information in the dictionary response_list.append({ 'infohash': hexlify(infohash), 'seeders': seeders, 'leechers': leechers }) # remove this infohash in the infohash list of this session if infohash in unprocessed_infohash_list: unprocessed_infohash_list.remove(infohash) elif b'failure reason' in response_dict: self._logger.info("%s Failure as reported by tracker [%s]", self, repr(response_dict[b'failure reason'])) self.failed(msg=repr(response_dict[b'failure reason'])) # handle the infohashes with no result (seeders/leechers = 0/0) for infohash in unprocessed_infohash_list: response_list.append({ 'infohash': hexlify(infohash), 'seeders': 0, 'leechers': 0 }) self.is_finished = True return {self.tracker_url: response_list} async def cleanup(self): """ Cleans the session by cancelling all deferreds and closing sockets. :return: A deferred that fires once the cleanup is done. """ await self._session.close() await super().cleanup()
class SmartThingsTV: def __init__( self, api_key: str, device_id: str, use_channel_info: bool = True, session: Optional[ClientSession] = None, ): """Initialize SmartThingsTV.""" self._api_key = api_key self._device_id = device_id self._use_channel_info = use_channel_info if session: self._session = session self._managed_session = False else: self._session = ClientSession() self._managed_session = True self._device_name = None self._state = STStatus.STATE_UNKNOWN self._prev_state = STStatus.STATE_UNKNOWN self._muted = False self._volume = 10 self._source_list = None self._source = "" self._channel = "" self._channel_name = "" self._is_forced_val = False self._forced_count = 0 def __enter__(self): return self def __exit__(self, type, value, traceback): pass @property def api_key(self) -> str: """Return currently api_key.""" return self._api_key @property def device_id(self) -> str: """Return currently device_id.""" return self._device_id @property def device_name(self) -> str: """Return currently device_name.""" return self._device_name @property def state(self): """Return currently state.""" return self._state @property def prev_state(self): """Return currently state.""" return self._prev_state @property def muted(self) -> bool: """Return currently muted state.""" return self._muted @property def volume(self) -> int: """Return currently volume.""" return self._volume @property def source(self) -> str: """Return currently source.""" return self._source @property def channel(self) -> str: """Return currently channel.""" return self._channel @property def channel_name(self) -> str: """Return currently channel name.""" return self._channel_name @property def source_list(self): """Return currently source list.""" return self._source_list def set_application(self, app_id): if self._use_channel_info: self._channel = "" self._channel_name = app_id self._is_forced_val = True self._forced_count = 0 def _set_source(self, source): if source != self._source: self._source = source self._channel = "" self._channel_name = "" self._is_forced_val = True self._forced_count = 0 @staticmethod async def get_devices_list(api_key, session: ClientSession, device_label=""): """Get list of available devices""" result = {} async with session.get( API_DEVICES, headers=_headers(api_key), raise_for_status=True, ) as resp: device_list = await resp.json() if device_list: _LOGGER.debug("SmartThings available devices: %s", str(device_list)) for k in device_list.get("items", []): device_id = k.get("deviceId", "") device_type = k.get("type", "") device_type_id = k.get("deviceTypeId", "") if device_id and (device_type_id == DEVICE_TYPEID_OCF or device_type == DEVICE_TYPE_OCF): label = k.get("label", "") if device_label == "" or (label == device_label and label != ""): result.setdefault(device_id, {})["name"] = k.get("name", "") result.setdefault(device_id, {})["label"] = label _LOGGER.info("SmartThings discovered TV devices: %s", str(result)) return result @Throttle(MIN_TIME_BETWEEN_UPDATES) async def _device_refresh(self, **kwargs): """Refresh device status on SmartThings""" device_id = self._device_id if not device_id: return api_device = f"{API_DEVICES}/{device_id}" api_command = f"{api_device}/commands" if self._use_channel_info: async with self._session.post( api_command, headers=_headers(self._api_key), data=COMMAND_REFRESH, raise_for_status=False, ) as resp: if resp.status == 409: self._state = STStatus.STATE_OFF return resp.raise_for_status() await resp.json() return async def async_device_health(self): """Check device availability""" device_id = self._device_id if not device_id: return False api_device = f"{API_DEVICES}/{device_id}" api_device_health = f"{api_device}/health" # this get the real status of the device async with self._session.get( api_device_health, headers=_headers(self._api_key), raise_for_status=True, ) as resp: health = await resp.json() _LOGGER.debug(health) if health["state"] == "ONLINE": return True return False async def async_device_update(self, use_channel_info: bool = None): """Query device status on SmartThing""" device_id = self._device_id if not device_id: return if use_channel_info is not None: self._use_channel_info = use_channel_info api_device = f"{API_DEVICES}/{device_id}" api_device_status = f"{api_device}/states" # not used, just for reference api_device_main_status = f"{api_device}/components/main/status" self._prev_state = self._state try: is_online = await self.async_device_health() except ( AsyncTimeoutError, ClientConnectionError, ClientResponseError, ): self._state = STStatus.STATE_UNKNOWN return if is_online: self._state = STStatus.STATE_ON else: self._state = STStatus.STATE_OFF return await self._device_refresh() if self._state == STStatus.STATE_OFF: return async with self._session.get( api_device_status, headers=_headers(self._api_key), raise_for_status=True, ) as resp: data = await resp.json() _LOGGER.debug(data) dev_data = data.get("main", {}) # device_state = data['main']['switch']['value'] device_volume = dev_data.get("volume", {}).get("value", 0) device_muted = dev_data.get("mute", {}).get("value", "") device_source = dev_data.get("inputSource", {}).get("value", "") device_tv_chan = dev_data.get("tvChannel", {}).get("value", "") device_tv_chan_name = dev_data.get("tvChannelName", {}).get("value", "") device_all_sources = {} json_sources = dev_data.get("supportedInputSources", {}).get("value") if json_sources: try: device_all_sources = json.loads(json_sources) except (TypeError, ValueError): pass if device_volume and device_volume.isdigit(): self._volume = int(device_volume) / 100 else: self._volume = 0 self._source_list = device_all_sources if device_muted == "mute": self._muted = True else: self._muted = False if self._is_forced_val and self._forced_count <= 0: self._forced_count += 1 return self._is_forced_val = False self._forced_count = 0 self._source = device_source # if the status is not refreshed this info may become not reliable if self._use_channel_info: self._channel = device_tv_chan self._channel_name = device_tv_chan_name else: self._channel = "" self._channel_name = "" async def async_send_command(self, cmdtype, command=""): """Send a command too the device""" device_id = self._device_id if not device_id: return api_device = f"{API_DEVICES}/{device_id}" api_command = f"{api_device}/commands" datacmd = None if cmdtype == "turn_off": # turns off datacmd = COMMAND_POWER_OFF elif cmdtype == "turn_on": # turns on datacmd = COMMAND_POWER_ON elif cmdtype == "setvolume": # sets volume cmdargs = ARGS_SET_VOLUME.format(command) datacmd = COMMAND_SET_VOLUME + cmdargs elif cmdtype == "stepvolume": # steps volume up or down if command == "up": datacmd = COMMAND_VOLUME_UP elif command == "down": datacmd = COMMAND_VOLUME_DOWN elif cmdtype == "audiomute": # mutes audio if command == "on": datacmd = COMMAND_MUTE elif command == "off": datacmd = COMMAND_UNMUTE elif cmdtype == "selectchannel": # changes channel cmdargs = ARGS_SET_CHANNEL.format(command) datacmd = COMMAND_SET_CHANNEL + cmdargs elif cmdtype == "stepchannel": # steps channel up or down if command == "up": datacmd = COMMAND_CHANNEL_UP elif command == "down": datacmd = COMMAND_CHANNEL_DOWN elif cmdtype == "selectsource": # changes source cmdargs = ARGS_SET_SOURCE.format(command) datacmd = COMMAND_SET_SOURCE + cmdargs # set property to reflect new changes self._set_source(command) if datacmd: async with self._session.post( api_command, headers=_headers(self._api_key), data=datacmd, raise_for_status=True, ) as resp: await resp.json() await self._device_refresh()
async def fetch(session: aiohttp.ClientSession): print("Query http://httpbin.org/get") async with session.get("http://httpbin.org/get") as resp: print(resp.status) data = await resp.json() print(data)
class OwnerCommands(object): def __init__(self, bot: CustomBot): self.bot = bot self.session = ClientSession(loop=bot.loop) def __unload(self): self.session.close() async def __local_check(self, ctx: Context): x = await is_owner(ctx) if x: return True raise NotOwner() @command() async def runsql(self, ctx: Context, *, content: str): ''' Runs a line of SQL into the sparcli database ''' async with self.bot.database() as db: x = await db(content) or 'No content.' if type(x) in [str, type(None)]: await ctx.send(x) return # Get the results into groups column_headers = list(x[0].keys()) grouped_outputs = {} for i in column_headers: grouped_outputs[i] = [] for guild_data in x: for i, o in guild_data.items(): grouped_outputs[i].append(str(o)) # Everything is now grouped super nicely # Now to get the maximum length of each column and add it as the last item for key, item_list in grouped_outputs.items(): max_len = max([len(i) for i in item_list + [key]]) grouped_outputs[key].append(max_len) # Format the outputs and add to a list key_headers = [] temp_output = [] for key, value in grouped_outputs.items(): # value is a list of unformatted strings key_headers.append(format(key, '<' + str(value[-1]))) formatted_values = [ format(i, '<' + str(value[-1])) for i in value[:-1] ] # string_value = '|'.join(formatted_values) temp_output.append(formatted_values) key_string = '|'.join(key_headers) # Rotate the list because apparently I need to output = [] for i in range(len(temp_output[0])): temp = [] for o in temp_output: temp.append(o[i]) output.append('|'.join(temp)) # Add some final values before returning to the user line = '-' * len(key_string) output = [key_string, line] + output string_output = '\n'.join(output) await ctx.send('```\n{}```'.format(string_output)) @group() async def profile(self, ctx: Context): ''' A parent group for the different profile commands ''' pass @profile.command(aliases=['username']) async def name(self, ctx: Context, *, username: str): ''' Lets you change the username of the bot ''' if len(username) > 32: await ctx.send( 'That username is too long to be compatible with Discord.') return await self.bot.user.edit(username=username) await ctx.send('Done.') @profile.command(aliases=['picture']) async def avatar(self, ctx: Context, *, url: str = None): ''' Allows you to change the avatar of the bot to a URL or attached picture ''' # Make sure a URL is passed if url == None: try: url = ctx.message.attachments[0].url except IndexError: raise MissingRequiredArgument(self.avatar.params['url']) # Get the image async with self.session.get(url) as r: content = await r.read() # Edit the profile await self.bot.user.edit(avatar=content) await ctx.send('Done.') @profile.command() async def game(self, ctx: Context, game_type: int = None, *, name: str = None): ''' Change the game that the bot is playing ''' if not name: name = self.bot.config['Game']['name'] if not game_type: game_type = self.bot.config['Game']['type'] game = Game(name=name, type=game_type) await self.bot.change_presence(activity=game) await ctx.send('Done.') @command() async def embed(self, ctx: Context, *, content: str): ''' Creates an embed from raw JSON data ''' e = Embed.from_data(eval(content)) await ctx.send(embed=e) @command() async def kill(self, ctx: Context): ''' Turns off the bot and anything related to it ''' async with self.bot.database() as db: for i in self.bot._die.values(): await db.store_die(i) await ctx.send('Turning off now.') await self.bot.logout() @command() async def ev(self, ctx: Context, *, content: str): ''' Runs some text through Python's eval function ''' try: ans = eval(content) except Exception as e: await ctx.send('```py\n' + format_exc() + '```') return if iscoroutine(ans): ans = await ans await ctx.send('```py\n' + str(ans) + '```') @command(aliases=['uld']) async def unload(self, ctx: Context, *cog_name: str): ''' Unloads a cog from the bot ''' self.bot.unload_extension('cogs.' + '_'.join([i.lower() for i in cog_name])) await ctx.send('Cog unloaded.') @command() async def load(self, ctx: Context, *cog_name: str): ''' Unloads a cog from the bot ''' self.bot.load_extension('cogs.' + '_'.join([i.lower() for i in cog_name])) await ctx.send('Cog loaded.') @command(aliases=['rld']) async def reload(self, ctx: Context, *, cog_name: str): ''' Unloads a cog from the bot ''' self.bot.unload_extension('cogs.' + cog_name.lower()) try: self.bot.load_extension('cogs.' + cog_name.lower()) except Exception as e: await ctx.send('```py\n' + format_exc() + '```') return await ctx.send('Cog reloaded.') @command() async def leaveguild(self, ctx: Context, guild_id: int): ''' Leaves a given guild ''' guild = self.bot.get_guild(guild_id) await guild.leave() await ctx.send('Done.')
async def get_feed(session: aiohttp.ClientSession, url: str) -> str: async with async_timeout.timeout(10): async with session.get(url) as response: raw_data = await response.text() return feedparser.parse(raw_data)
async def get_response(self, url: URL, session: aiohttp.ClientSession) -> str: async with session.get(url, timeout=self.timeout) as response: if response.status != 200: return '' html = await response.text() # response.read() bytes return html
async def notify_mattermost_header(mattermost_config: Dict, app_session: ClientSession, state: State, status_message: str): if mattermost_config["enabled"]: status_emoji = ":+1: " if state is State.FAILED: status_emoji = ":x:" elif state is State.PAUSED: status_emoji = ":x:" header_unique_name = mattermost_config["header_unique_name"] date = datetime.datetime.utcnow().isoformat(timespec='seconds') message = f"{header_unique_name} {status_emoji} {status_message} - {date} |" personal_token = mattermost_config["personal_token"] channel_id = mattermost_config["channel_id"] headers = {"Authorization": f"Bearer {personal_token}"} # get the current header to update it url = URL(mattermost_config["url"]).with_path( f"api/v4/channels/{channel_id}") current_header = "" async with app_session.get(url, headers=headers) as resp: log.debug("requested channel description: received with code %s", resp.status) if resp.status == 404: log.error("could not find route in %s", url) raise ConfigurationError( "Could not find channel within Mattermost app in {}:\n {}". format(url, await resp.text())) if not resp.status == 200: log.error("Unknown error") raise AutoDeployAgentException( "Unknown error while accessing Mattermost app in {}:\n {}". format(url, await resp.text())) data = await resp.json() log.debug("received data: %s", data) current_header = data["header"] new_header = message start_index = current_header.find(header_unique_name) if start_index != -1: # update the message instead lastindex = current_header.find("|", start_index) new_header = "{}{}{}".format(current_header[0:start_index], message, current_header[lastindex + 1:]) url = URL(mattermost_config["url"]).with_path( f"api/v4/channels/{channel_id}/patch") async with app_session.put(url, headers=headers, data=json.dumps({"header": new_header})) as resp: log.debug( "requested patch channel description: response received with code %s", resp.status) if resp.status == 200: data = await resp.json() return data if resp.status == 404: log.error("could not find route in %s", url) raise ConfigurationError( "Could not find channel within Mattermost app in {}:\n {}". format(url, await resp.text())) log.error("Unknown error") raise AutoDeployAgentException( "Unknown error while accessing Mattermost app in {}:\n {}". format(url, await resp.text()))
class YoutubeProvider(Provider): name = 'youtube' can_search = True endpoint = 'https://www.googleapis.com/youtube/v3/search' default_params = { 'part': 'id,snippet', 'maxResults': 15, 'type': 'video', } def __init__(self, *, api_key: str = None): self.loop = asyncio.get_event_loop() self.session = ClientSession() self.api_key = api_key if not self.api_key: log.critical('API KEY is missing!') raise ProviderNotReady() self.default_params = {**self.default_params, 'key': self.api_key} async def search(self, keyword: str) -> Sequence[EntryOverview]: ret = [] res = await self.youtube_api(params={'q': keyword}) # should we validate with lib? if 'items' in res: for item in res['items']: try: video_id = item['id']['videoId'] title = item['snippet']['title'] thumbnail = item['snippet']['thumbnails']['high']['url'] thumbnail_small = item['snippet']['thumbnails']['default'][ 'url'] except: log.error('missing key: ', exc_info=True) ret.append( EntryOverview( self.name, title, f'https://www.youtube.com/watch?v={video_id}', thumbnail, thumbnail_small)) return ret async def resolve_playable(self, uri, cache_dir): # search-only provider return async def resolve(self, uri: str) -> Optional[EntryOverview]: # search-only provider return async def youtube_api(self, params={}) -> Optional[dict]: ret = None try: async with self.session.get(self.endpoint, params={ **self.default_params, **params }) as res: ret = await res.json() log.debug(ret) except: log.error(f'Failed to communicate with YouTube API ({params}): ', exc_info=True) return ret
async def draw_profile(user: Member, session: ClientSession) -> Image: name_fnt = ImageFont.truetype(font_heavy_file, 30) name_u_fnt = ImageFont.truetype(font_unicode_file, 30) title_fnt = ImageFont.truetype(font_heavy_file, 22) title_u_fnt = ImageFont.truetype(font_unicode_file, 23) label_fnt = ImageFont.truetype(font_bold_file, 18) exp_fnt = ImageFont.truetype(font_bold_file, 13) large_fnt = ImageFont.truetype(font_thin_file, 33) rep_fnt = ImageFont.truetype(font_heavy_file, 26) rep_u_fnt = ImageFont.truetype(font_unicode_file, 30) text_fnt = ImageFont.truetype(font_file, 14) text_u_fnt = ImageFont.truetype(font_unicode_file, 14) symbol_u_fnt = ImageFont.truetype(font_unicode_file, 15) def _write_unicode(text, init_x, y, font, unicode_font, fill): write_pos = init_x for char in text: if char.isalnum() or char in string.punctuation or char in string.whitespace: draw.text((write_pos, y), char, font=font, fill=fill) write_pos += font.getsize(char)[0] else: draw.text((write_pos, y), u"{}".format(char), font=unicode_font, fill=fill) write_pos += unicode_font.getsize(char)[0] user_info = db.user(user) # get urls bg_url = await user_info.profile_background() profile_url = user.avatar_url async with session.get(bg_url) as r: bg_image = Image.open(BytesIO(await r.content.read())).convert("RGBA") async with session.get(profile_url) as r: profile_image = Image.open(BytesIO(await r.content.read())).convert("RGBA") # COLORS white_color = (240, 240, 240, 255) rep_fill = await user_info.rep_color() badge_fill = await user_info.badge_col_color() info_fill = await user_info.profile_info_color() info_fill_tx = (*info_fill[:3], 150) exp_fill = await user_info.profile_exp_color() title = await user_info.title() info = await user_info.info() total_exp = await user_info.total_exp() badges = await user_info.badges() rep = await user_info.rep() if badge_fill == (128, 151, 165, 230): level_fill = white_color else: level_fill = _contrast(exp_fill, rep_fill, badge_fill) # set canvas bg_color = (255, 255, 255, 0) result = Image.new("RGBA", (340, 390), bg_color) process = Image.new("RGBA", (340, 390), bg_color) # draw draw = ImageDraw.Draw(process) # puts in background bg_image = bg_image.resize((340, 340), Image.ANTIALIAS) bg_image = bg_image.crop((0, 0, 340, 305)) result.paste(bg_image, (0, 0)) # draw filter draw.rectangle([(0, 0), (340, 340)], fill=(0, 0, 0, 10)) draw.rectangle([(0, 134), (340, 325)], fill=info_fill_tx) # general content # draw profile circle multiplier = 8 lvl_circle_dia = 116 circle_left = 14 circle_top = 48 raw_length = lvl_circle_dia * multiplier # create mask mask = Image.new("L", (raw_length, raw_length), 0) draw_thumb = ImageDraw.Draw(mask) draw_thumb.ellipse((0, 0) + (raw_length, raw_length), fill=255, outline=0) # border lvl_circle = Image.new("RGBA", (raw_length, raw_length)) draw_lvl_circle = ImageDraw.Draw(lvl_circle) draw_lvl_circle.ellipse( [0, 0, raw_length, raw_length], fill=(255, 255, 255, 255), outline=(255, 255, 255, 250) ) # put border lvl_circle = lvl_circle.resize((lvl_circle_dia, lvl_circle_dia), Image.ANTIALIAS) lvl_bar_mask = mask.resize((lvl_circle_dia, lvl_circle_dia), Image.ANTIALIAS) process.paste(lvl_circle, (circle_left, circle_top), lvl_bar_mask) # put in profile picture total_gap = 6 border = int(total_gap / 2) profile_size = lvl_circle_dia - total_gap mask = mask.resize((profile_size, profile_size), Image.ANTIALIAS) profile_image = profile_image.resize((profile_size, profile_size), Image.ANTIALIAS) process.paste(profile_image, (circle_left + border, circle_top + border), mask) # write label text white_color = (240, 240, 240, 255) light_color = (160, 160, 160, 255) dark_color = (35, 35, 35, 255) head_align = 140 # determine info text color info_text_color = _contrast(info_fill, white_color, dark_color) _write_unicode( _truncate_text(user.name, 22).upper(), head_align, 142, name_fnt, name_u_fnt, info_text_color, ) # NAME _write_unicode( title.upper(), head_align, 170, title_fnt, title_u_fnt, info_text_color ) # draw divider draw.rectangle([(0, 323), (340, 324)], fill=(0, 0, 0, 255)) # box # draw text box draw.rectangle( [(0, 324), (340, 390)], fill=(info_fill[0], info_fill[1], info_fill[2], 255) ) # box _write_unicode("❤", 257, 9, rep_fnt, rep_u_fnt, info_text_color) draw.text( (_center(278, 340, str(rep), rep_fnt), 10), str(rep), font=rep_fnt, fill=info_text_color, ) # Exp Text label_align = 362 # vertical draw.text( (_center(0, 140, " RANK", label_fnt), label_align), " RANK", font=label_fnt, fill=info_text_color, ) # Rank draw.text( (_center(0, 340, " LEVEL", label_fnt), label_align), " LEVEL", font=label_fnt, fill=info_text_color, ) # Exp draw.text( (_center(200, 340, "BALANCE", label_fnt), label_align), "BALANCE", font=label_fnt, fill=info_text_color, ) # Credits if "linux" in platform.system().lower(): global_symbol = u"\U0001F30E " _write_unicode( global_symbol, 36, label_align + 5, label_fnt, symbol_u_fnt, info_text_color ) # Symbol _write_unicode( global_symbol, 134, label_align + 5, label_fnt, symbol_u_fnt, info_text_color ) # Symbol # userinfo global_rank = f"#{await _find_global_rank(user)}" global_level = str(_find_level(total_exp)) draw.text( (_center(0, 140, global_rank, large_fnt), label_align - 27), global_rank, font=large_fnt, fill=info_text_color, ) # Rank draw.text( (_center(0, 340, global_level, large_fnt), label_align - 27), global_level, font=large_fnt, fill=info_text_color, ) # Exp # draw level bar exp_font_color = _contrast(exp_fill, light_color, dark_color) exp_frac = int(total_exp - _level_exp(int(global_level))) exp_total = _required_exp(int(global_level) + 1) bar_length = int(exp_frac / exp_total * 340) draw.rectangle( [(0, 305), (340, 323)], fill=(level_fill[0], level_fill[1], level_fill[2], 245) ) # level box draw.rectangle( [(0, 305), (bar_length, 323)], fill=(exp_fill[0], exp_fill[1], exp_fill[2], 255) ) # box exp_text = "{}/{}".format(exp_frac, exp_total) # Exp draw.text( (_center(0, 340, exp_text, exp_fnt), 305), exp_text, font=exp_fnt, fill=exp_font_color, ) # Exp Text credit_txt = f"${await bank.get_balance(user)}" draw.text( (_center(200, 340, credit_txt, large_fnt), label_align - 27), _truncate_text(credit_txt, 18), font=large_fnt, fill=info_text_color, ) # Credits if title == "": offset = 170 else: offset = 195 margin = 140 txt_color = _contrast(info_fill, white_color, dark_color) for line in textwrap.wrap(info, width=32): _write_unicode(line, margin, offset, text_fnt, text_u_fnt, txt_color) offset += text_fnt.getsize(line)[1] + 2 # sort badges priority_badges = [] for badgename in badges.keys(): badge = badges[badgename] priority_num = badge["priority_num"] if priority_num != 0 and priority_num != -1: priority_badges.append((badge, priority_num)) sorted_badges = sorted(priority_badges, key=operator.itemgetter(1), reverse=True) # TODO: simplify this. it shouldn't be this complicated... sacrifices conciseness for customizability if (await db.badge_type()) == "circles": # circles require antialiasing vert_pos = 172 right_shift = 0 left = 9 + right_shift size = 38 total_gap = 4 # /2 hor_gap = 6 vert_gap = 6 border_width = int(total_gap / 2) multiplier = 6 # for antialiasing raw_length = size * multiplier mult = [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)] for num in range(9): coord = ( left + int(mult[num][0]) * int(hor_gap + size), vert_pos + int(mult[num][1]) * int(vert_gap + size), ) if num < len(sorted_badges[:9]): pair = sorted_badges[num] badge = pair[0] bg_color = badge["bg_img"] border_color = badge["border_color"] # draw mask circle mask = Image.new("L", (raw_length, raw_length), 0) draw_thumb = ImageDraw.Draw(mask) draw_thumb.ellipse((0, 0) + (raw_length, raw_length), fill=255, outline=0) # determine image or color for badge bg if await self._valid_image_url(bg_color): # get image async with session.get(bg_color) as r: badge_image = Image.open(BytesIO(await r.content.read())).convert("RGBA") badge_image = badge_image.resize((raw_length, raw_length), Image.ANTIALIAS) # structured like this because if border = 0, still leaves outline. if border_color: square = Image.new("RGBA", (raw_length, raw_length), border_color) # put border on ellipse/circle output = ImageOps.fit( square, (raw_length, raw_length), centering=(0.5, 0.5) ) output = output.resize((size, size), Image.ANTIALIAS) outer_mask = mask.resize((size, size), Image.ANTIALIAS) process.paste(output, coord, outer_mask) # put on ellipse/circle output = ImageOps.fit( badge_image, (raw_length, raw_length), centering=(0.5, 0.5) ) output = output.resize( (size - total_gap, size - total_gap), Image.ANTIALIAS ) inner_mask = mask.resize( (size - total_gap, size - total_gap), Image.ANTIALIAS ) process.paste( output, (coord[0] + border_width, coord[1] + border_width), inner_mask, ) else: # put on ellipse/circle output = ImageOps.fit( badge_image, (raw_length, raw_length), centering=(0.5, 0.5) ) output = output.resize((size, size), Image.ANTIALIAS) outer_mask = mask.resize((size, size), Image.ANTIALIAS) process.paste(output, coord, outer_mask) else: plus_fill = exp_fill # put on ellipse/circle plus_square = Image.new("RGBA", (raw_length, raw_length)) plus_draw = ImageDraw.Draw(plus_square) plus_draw.rectangle( [(0, 0), (raw_length, raw_length)], fill=(info_fill[0], info_fill[1], info_fill[2], 245), ) # draw plus signs margin = 60 thickness = 40 v_left = int(raw_length / 2 - thickness / 2) v_right = v_left + thickness v_top = margin v_bottom = raw_length - margin plus_draw.rectangle( [(v_left, v_top), (v_right, v_bottom)], fill=(plus_fill[0], plus_fill[1], plus_fill[2], 245), ) h_left = margin h_right = raw_length - margin h_top = int(raw_length / 2 - thickness / 2) h_bottom = h_top + thickness plus_draw.rectangle( [(h_left, h_top), (h_right, h_bottom)], fill=(plus_fill[0], plus_fill[1], plus_fill[2], 245), ) # put border on ellipse/circle output = ImageOps.fit( plus_square, (raw_length, raw_length), centering=(0.5, 0.5) ) output = output.resize((size, size), Image.ANTIALIAS) outer_mask = mask.resize((size, size), Image.ANTIALIAS) process.paste(output, coord, outer_mask) result = Image.alpha_composite(result, process) result = _add_corners(result, 25) return result
class EdgeOSWebAPI: def __init__(self, hass, config_manager: ConfigManager, disconnection_handler=None): self._config_manager = config_manager self._last_update = datetime.now() self._session: Optional[ClientSession] = None self._last_valid = EMPTY_LAST_VALID self._hass = hass self._is_connected = False self._cookies = {} self._product = PRODUCT_NAME self._disconnection_handler = disconnection_handler self._disconnections = 0 async def initialize(self): cookie_jar = CookieJar(unsafe=True) if self._hass is None: if self._session is not None: await self._session.close() self._session = ClientSession(cookie_jar=cookie_jar) else: self._session = async_create_clientsession( hass=self._hass, cookies=self._cookies, cookie_jar=cookie_jar, ) @property def is_initialized(self): return self._session is not None and not self._session.closed @property def is_connected(self): return self._is_connected @property def product(self): return self._product @property def session_id(self): session_id = self.get_cookie_data(COOKIE_PHPSESSID) return session_id @property def beaker_session_id(self): beaker_session_id = self.get_cookie_data(COOKIE_BEAKER_SESSION_ID) return beaker_session_id @property def cookies_data(self): return self._cookies def get_cookie_data(self, cookie_key): cookie_data = None if self._cookies is not None: cookie_data = self._cookies.get(cookie_key) return cookie_data async def login(self, throw_exception=False): logged_in = False try: username = self._config_manager.data.username password = self._config_manager.data.password_clear_text credentials = {CONF_USERNAME: username, CONF_PASSWORD: password} url = self._config_manager.data.url.get_url() async with self._session.post(url, data=credentials) as response: all_cookies = self._session.cookie_jar.filter_cookies(response.url) for key, cookie in all_cookies.items(): self._cookies[cookie.key] = cookie.value status_code = response.status response.raise_for_status() logged_in = ( self.beaker_session_id is not None and self.beaker_session_id == self.session_id ) if logged_in: html = await response.text() html_lines = html.splitlines() for line in html_lines: if "EDGE.DeviceModel" in line: line_parts = line.split(" = ") value = line_parts[len(line_parts) - 1] self._product = value.replace("'", "") else: _LOGGER.error(f"Failed to login, Invalid credentials") if self.beaker_session_id is None and self.session_id is not None: status_code = 500 else: status_code = 403 except Exception as ex: exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error(f"Failed to login, Error: {ex}, Line: {line_number}") status_code = 404 if throw_exception and status_code is not None and status_code >= 400: raise LoginException(status_code) return logged_in async def async_get(self, url): result = None try: async with self._session.get(url) as response: _LOGGER.debug(f"Status of {url}: {response.status}") self._is_connected = response.status < 400 if response.status == 403: if self._disconnections + 1 < MAXIMUM_RECONNECT: self._disconnections = self._disconnections + 1 if self._disconnection_handler is not None: await self._disconnection_handler() else: _LOGGER.error( f"Failed to make authenticated request to {url} {self._disconnections} times" ) else: response.raise_for_status() result = await response.json() self._last_update = datetime.now() except Exception as ex: self._is_connected = False exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error(f"Failed to connect {url}, Error: {ex}, Line: {line_number}") return result @property def last_update(self): result = self._last_update return result async def heartbeat(self, max_age=HEARTBEAT_MAX_AGE): try: if self.is_initialized: ts = datetime.now() current_invocation = datetime.now() - self._last_valid if current_invocation > timedelta(seconds=max_age): current_ts = str(int(ts.timestamp())) heartbeat_req_url = self.get_edgeos_api_endpoint( EDGEOS_API_HEARTBREAT ) heartbeat_req_full_url = API_URL_HEARTBEAT_TEMPLATE.format( heartbeat_req_url, current_ts ) response = await self.async_get(heartbeat_req_full_url) _LOGGER.debug(f"Heartbeat response: {response}") self._last_valid = ts else: _LOGGER.warning(f"Heartbeat not ran due to closed session") except Exception as ex: exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error( f"Failed to perform heartbeat, Error: {ex}, Line: {line_number}" ) async def get_devices_data(self): result = None try: if self.is_initialized: get_req_url = self.get_edgeos_api_endpoint(EDGEOS_API_GET) result_json = await self.async_get(get_req_url) if result_json is not None and RESPONSE_SUCCESS_KEY in result_json: success_key = str(result_json.get(RESPONSE_SUCCESS_KEY, "")).lower() if success_key == TRUE_STR: if EDGEOS_API_GET.upper() in result_json: result = result_json.get(EDGEOS_API_GET.upper(), {}) else: error_message = result_json[RESPONSE_ERROR_KEY] _LOGGER.error(f"Failed, Error: {error_message}") else: _LOGGER.error("Invalid response, not contain success status") else: _LOGGER.warning(f"Get devices data not ran due to closed session") except Exception as ex: exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error( f"Failed to get devices data, Error: {ex}, Line: {line_number}" ) return result async def get_general_data(self, item): result = None try: if self.is_initialized: clean_item = item.replace(STRING_DASH, STRING_UNDERSCORE) data_req_url = self.get_edgeos_api_endpoint(EDGEOS_API_DATA) data_req_full_url = API_URL_DATA_TEMPLATE.format( data_req_url, clean_item ) data = await self.async_get(data_req_full_url) if data is not None and RESPONSE_SUCCESS_KEY in data: if str(data.get(RESPONSE_SUCCESS_KEY)) == RESPONSE_FAILURE_CODE: error = data.get(RESPONSE_ERROR_KEY, EMPTY_STRING) _LOGGER.error(f"Failed to load {item}, Reason: {error}") result = None else: result = data.get(RESPONSE_OUTPUT) else: _LOGGER.warning(f"Get data of {item} not ran due to closed session") except Exception as ex: exc_type, exc_obj, tb = sys.exc_info() line_number = tb.tb_lineno _LOGGER.error(f"Failed to load {item}, Error: {ex}, Line: {line_number}") result = None return result def get_edgeos_api_endpoint(self, controller): url = EDGEOS_API_URL.format(self._config_manager.data.url, controller) return url
async def get_response(session: aiohttp.ClientSession, url: str): async with session.get(url=url, headers=headers) as response: return await response.text()
async def proer(queue: asyncio.Queue, session: aiohttp.ClientSession): global done, wait_a_little for page in infos: for mod in page: # 寻找可用版本: FileInfo = None deep = False if mod['slug'] in config.mod_blacklist_slug: print(f"模组 [{mod['name']}] 被列入黑名单, 已跳过") continue """ date_cache = Path(f"./cache/{mod['id']}.json") if date_cache.exists(): # 一次检查. date = maya.parse(orjson.loads(date_cache.read_text("utf-8"))['date']) if date: timediff = maya.now() - date if datetime.timedelta(**config.update_setting) > timediff: print(f"模组 [{mod['name']}] 未到更新时间({date.add(**config.update_setting)}), 已跳过") continue """ update_date = date.get(str(mod['id'])) if update_date: # 一次检查. date_ = maya.parse(update_date) if date_: timediff = maya.now() - date_ if datetime.timedelta(**config.update_setting) > timediff: print( f"模组 [{mod['name']}] 未到更新时间({date_.add(**config.update_setting)}), 已跳过" ) continue print(f"开始找寻模组 [{mod['name']}] 可用于版本 [{version}] 的jar文件") for i in mod['latestFiles']: if version in i['gameVersion']: print(f"已找到模组 [{mod['name']}] 可用于版本 [{version}] 的jar文件") FileInfo = i break else: print( f"未找到模组 [{mod['name']}] 可用于版本 [{version}] 的jar文件, 开始请求远端数据库以寻找可用的版本" ) deep = True if deep: for versionInfo in mod['gameVersionLatestFiles']: if versionInfo['gameVersion'] == version: print( f"已找到远端数据库中模组 [{mod['name']}] 可用于版本 [{version}] 的jar文件, 开始深层数据取出" ) info_url = f'https://addons-ecs.forgesvc.net/api/v2/addon/{mod["id"]}/file/{versionInfo["projectFileId"]}' async with session.get(info_url) as response: FileInfo = await response.json() else: print( f"未找到模组 [{mod['name']}] 可用于版本 [{version}] 的jar文件, 跳过.") continue if not FileInfo: print(f"warn: {mod['name']} 没有版本 {version} 可用的文件, 跳过") continue # 存储更新日期 ## 可能会找不到文件, touch it """ if not date_cache.exists(): date_cache.touch() date_cache.write_text(orjson.dumps({ "date": maya.now().rfc2822() }).decode('utf-8'), encoding="utf-8") """ date[str(mod['id'])] = maya.now().rfc2822() url = FileInfo["downloadUrl"] name = FileInfo["fileName"] length = FileInfo["fileLength"] if wait_a_little: await asyncio.sleep(3) wait_a_little = False await queue.put({"url": url, "name": name, "file-length": length}) done = True
async def fetch(session: aiohttp.ClientSession, url: str) -> Response: async with session.get(url) as response: print(f"request {url}") return response