Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 3 additions & 67 deletions bot/__init__.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,4 @@
import asyncio
import logging
import os
import sys
from logging import Logger, handlers
from pathlib import Path

import coloredlogs

TRACE_LEVEL = logging.TRACE = 5
logging.addLevelName(TRACE_LEVEL, "TRACE")


def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None:
"""
Log 'msg % args' with severity 'TRACE'.

To pass exception information, use the keyword argument exc_info with
a true value, e.g.

logger.trace("Houston, we have an %s", "interesting problem", exc_info=1)
"""
if self.isEnabledFor(TRACE_LEVEL):
self._log(TRACE_LEVEL, msg, args, **kwargs)


Logger.trace = monkeypatch_trace

DEBUG_MODE = 'local' in os.environ.get("SITE_URL", "local")

log_level = TRACE_LEVEL if DEBUG_MODE else logging.INFO
format_string = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
log_format = logging.Formatter(format_string)

log_file = Path("logs", "bot.log")
log_file.parent.mkdir(exist_ok=True)
file_handler = handlers.RotatingFileHandler(log_file, maxBytes=5242880, backupCount=7, encoding="utf8")
file_handler.setFormatter(log_format)

root_log = logging.getLogger()
root_log.setLevel(log_level)
root_log.addHandler(file_handler)

if "COLOREDLOGS_LEVEL_STYLES" not in os.environ:
coloredlogs.DEFAULT_LEVEL_STYLES = {
**coloredlogs.DEFAULT_LEVEL_STYLES,
"trace": {"color": 246},
"critical": {"background": "red"},
"debug": coloredlogs.DEFAULT_LEVEL_STYLES["info"]
}

if "COLOREDLOGS_LOG_FORMAT" not in os.environ:
coloredlogs.DEFAULT_LOG_FORMAT = format_string

if "COLOREDLOGS_LOG_LEVEL" not in os.environ:
coloredlogs.DEFAULT_LOG_LEVEL = log_level

coloredlogs.install(logger=root_log, stream=sys.stdout)

logging.getLogger("discord").setLevel(logging.WARNING)
logging.getLogger("websockets").setLevel(logging.WARNING)
logging.getLogger("chardet").setLevel(logging.WARNING)
logging.getLogger(__name__)


# On Windows, the selector event loop is required for aiodns.
if os.name == "nt":
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
print("Hi pythom")
int x = 7 y = 10
print(x+y)
86 changes: 3 additions & 83 deletions bot/__main__.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,4 @@
import logging

import discord
import sentry_sdk
from discord.ext.commands import when_mentioned_or
from sentry_sdk.integrations.aiohttp import AioHttpIntegration
from sentry_sdk.integrations.logging import LoggingIntegration
from sentry_sdk.integrations.redis import RedisIntegration

from bot import constants, patches
from bot.bot import Bot

sentry_logging = LoggingIntegration(
level=logging.DEBUG,
event_level=logging.WARNING
)

sentry_sdk.init(
dsn=constants.Bot.sentry_dsn,
integrations=[
sentry_logging,
AioHttpIntegration(),
RedisIntegration(),
]
)

allowed_roles = [discord.Object(id_) for id_ in constants.MODERATION_ROLES]
bot = Bot(
command_prefix=when_mentioned_or(constants.Bot.prefix),
activity=discord.Game(name="Commands: !help"),
case_insensitive=True,
max_messages=10_000,
allowed_mentions=discord.AllowedMentions(everyone=False, roles=allowed_roles)
)

# Internal/debug
bot.load_extension("bot.cogs.error_handler")
bot.load_extension("bot.cogs.filtering")
bot.load_extension("bot.cogs.logging")
bot.load_extension("bot.cogs.security")
bot.load_extension("bot.cogs.config_verifier")

# Commands, etc
bot.load_extension("bot.cogs.antimalware")
bot.load_extension("bot.cogs.antispam")
bot.load_extension("bot.cogs.bot")
bot.load_extension("bot.cogs.clean")
bot.load_extension("bot.cogs.extensions")
bot.load_extension("bot.cogs.help")

bot.load_extension("bot.cogs.doc")
bot.load_extension("bot.cogs.verification")

# Feature cogs
bot.load_extension("bot.cogs.alias")
bot.load_extension("bot.cogs.defcon")
bot.load_extension("bot.cogs.duck_pond")
bot.load_extension("bot.cogs.eval")
bot.load_extension("bot.cogs.information")
bot.load_extension("bot.cogs.jams")
bot.load_extension("bot.cogs.moderation")
bot.load_extension("bot.cogs.python_news")
bot.load_extension("bot.cogs.off_topic_names")
bot.load_extension("bot.cogs.reddit")
bot.load_extension("bot.cogs.reminders")
bot.load_extension("bot.cogs.site")
bot.load_extension("bot.cogs.snekbox")
bot.load_extension("bot.cogs.stats")
bot.load_extension("bot.cogs.sync")
bot.load_extension("bot.cogs.tags")
bot.load_extension("bot.cogs.token_remover")
bot.load_extension("bot.cogs.utils")
bot.load_extension("bot.cogs.watchchannels")
bot.load_extension("bot.cogs.webhook_remover")
bot.load_extension("bot.cogs.wolfram")

if constants.HelpChannels.enable:
bot.load_extension("bot.cogs.help_channels")

# Apply `message_edited_at` patch if discord.py did not yet release a bug fix.
if not hasattr(discord.message.Message, '_handle_edited_timestamp'):
patches.message_edited_at.apply_patch()

bot.run(constants.Bot.token)
print("Hi pythom")
int x = 7 y = 10
print(x+y)
158 changes: 3 additions & 155 deletions bot/api.py
Original file line number Diff line number Diff line change
@@ -1,156 +1,4 @@
import asyncio
import logging
from typing import Optional
from urllib.parse import quote as quote_url

import aiohttp

from .constants import Keys, URLs

log = logging.getLogger(__name__)


class ResponseCodeError(ValueError):
"""Raised when a non-OK HTTP response is received."""

def __init__(
self,
response: aiohttp.ClientResponse,
response_json: Optional[dict] = None,
response_text: str = ""
):
self.status = response.status
self.response_json = response_json or {}
self.response_text = response_text
self.response = response

def __str__(self):
response = self.response_json if self.response_json else self.response_text
return f"Status: {self.status} Response: {response}"


class APIClient:
"""Django Site API wrapper."""

# These are class attributes so they can be seen when being mocked for tests.
# See commit 22a55534ef13990815a6f69d361e2a12693075d5 for details.
session: Optional[aiohttp.ClientSession] = None
loop: asyncio.AbstractEventLoop = None

def __init__(self, loop: asyncio.AbstractEventLoop, **kwargs):
auth_headers = {
'Authorization': f"Token {Keys.site_api}"
}

if 'headers' in kwargs:
kwargs['headers'].update(auth_headers)
else:
kwargs['headers'] = auth_headers

self.session = None
self.loop = loop

self._ready = asyncio.Event(loop=loop)
self._creation_task = None
self._default_session_kwargs = kwargs

self.recreate()

@staticmethod
def _url_for(endpoint: str) -> str:
return f"{URLs.site_schema}{URLs.site_api}/{quote_url(endpoint)}"

async def _create_session(self, **session_kwargs) -> None:
"""
Create the aiohttp session with `session_kwargs` and set the ready event.

`session_kwargs` is merged with `_default_session_kwargs` and overwrites its values.
If an open session already exists, it will first be closed.
"""
await self.close()
self.session = aiohttp.ClientSession(**{**self._default_session_kwargs, **session_kwargs})
self._ready.set()

async def close(self) -> None:
"""Close the aiohttp session and unset the ready event."""
if self.session:
await self.session.close()

self._ready.clear()

def recreate(self, force: bool = False, **session_kwargs) -> None:
"""
Schedule the aiohttp session to be created with `session_kwargs` if it's been closed.

If `force` is True, the session will be recreated even if an open one exists. If a task to
create the session is pending, it will be cancelled.

`session_kwargs` is merged with the kwargs given when the `APIClient` was created and
overwrites those default kwargs.
"""
if force or self.session is None or self.session.closed:
if force and self._creation_task:
self._creation_task.cancel()

# Don't schedule a task if one is already in progress.
if force or self._creation_task is None or self._creation_task.done():
self._creation_task = self.loop.create_task(self._create_session(**session_kwargs))

async def maybe_raise_for_status(self, response: aiohttp.ClientResponse, should_raise: bool) -> None:
"""Raise ResponseCodeError for non-OK response if an exception should be raised."""
if should_raise and response.status >= 400:
try:
response_json = await response.json()
raise ResponseCodeError(response=response, response_json=response_json)
except aiohttp.ContentTypeError:
response_text = await response.text()
raise ResponseCodeError(response=response, response_text=response_text)

async def request(self, method: str, endpoint: str, *, raise_for_status: bool = True, **kwargs) -> dict:
"""Send an HTTP request to the site API and return the JSON response."""
await self._ready.wait()

async with self.session.request(method.upper(), self._url_for(endpoint), **kwargs) as resp:
await self.maybe_raise_for_status(resp, raise_for_status)
return await resp.json()

async def get(self, endpoint: str, *, raise_for_status: bool = True, **kwargs) -> dict:
"""Site API GET."""
return await self.request("GET", endpoint, raise_for_status=raise_for_status, **kwargs)

async def patch(self, endpoint: str, *, raise_for_status: bool = True, **kwargs) -> dict:
"""Site API PATCH."""
return await self.request("PATCH", endpoint, raise_for_status=raise_for_status, **kwargs)

async def post(self, endpoint: str, *, raise_for_status: bool = True, **kwargs) -> dict:
"""Site API POST."""
return await self.request("POST", endpoint, raise_for_status=raise_for_status, **kwargs)

async def put(self, endpoint: str, *, raise_for_status: bool = True, **kwargs) -> dict:
"""Site API PUT."""
return await self.request("PUT", endpoint, raise_for_status=raise_for_status, **kwargs)

async def delete(self, endpoint: str, *, raise_for_status: bool = True, **kwargs) -> Optional[dict]:
"""Site API DELETE."""
await self._ready.wait()

async with self.session.delete(self._url_for(endpoint), **kwargs) as resp:
if resp.status == 204:
return None

await self.maybe_raise_for_status(resp, raise_for_status)
return await resp.json()


def loop_is_running() -> bool:
"""
Determine if there is a running asyncio event loop.

This helps enable "call this when event loop is running" logic (see: Twisted's `callWhenRunning`),
which is currently not provided by asyncio.
"""
try:
asyncio.get_running_loop()
except RuntimeError:
return False
return True
print("Hi pythom")
int x = 7 y = 10
print(x+y)
41 changes: 3 additions & 38 deletions bot/async_stats.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,4 @@
import asyncio
import socket

from statsd.client.base import StatsClientBase


class AsyncStatsClient(StatsClientBase):
"""An async transport method for statsd communication."""

def __init__(
self,
loop: asyncio.AbstractEventLoop,
host: str = 'localhost',
port: int = 8125,
prefix: str = None
):
"""Create a new client."""
family, _, _, _, addr = socket.getaddrinfo(
host, port, socket.AF_INET, socket.SOCK_DGRAM)[0]
self._addr = addr
self._prefix = prefix
self._loop = loop
self._transport = None

async def create_socket(self) -> None:
"""Use the loop.create_datagram_endpoint method to create a socket."""
self._transport, _ = await self._loop.create_datagram_endpoint(
asyncio.DatagramProtocol,
family=socket.AF_INET,
remote_addr=self._addr
)

def _send(self, data: str) -> None:
"""Start an async task to send data to statsd."""
self._loop.create_task(self._async_send(data))

async def _async_send(self, data: str) -> None:
"""Send data to the statsd server using the async transport."""
self._transport.sendto(data.encode('ascii'), self._addr)
print("Hi pythom")
int x = 7 y = 10
print(x+y)
Loading