У меня проблема с адаптацией старого модуля для sopel для работы в Discord с python 3.7 - PullRequest
0 голосов
/ 14 июля 2020

Я работал над переносом модуля python 3.2 на 3.7, чтобы я мог перенести своего бота Sopel в Discord. Текущая проблема, с которой я столкнулся, связана с ошибкой RuntimeError: set_wakeup_fd only works in main thread в моей функции setup(bot).

Я пробовал создать asyncio l oop внутри функции, но только итерация через каналов связи, и больше ничего не сделал. Я знаю, что упускаю что-то очевидное, но ни одно из моих исследований за последние несколько дней не помогло решить эту проблему. Поскольку это не мой код, и я все еще довольно новенький python человек, некоторые из них мне не кажутся сопоставимыми, поэтому я не могу его исправить.

Вот код :

# -*- coding: utf-8 -*-
#pylint: disable = C0103, C0116, C0115, C0114, R0903

from __future__ import (
    unicode_literals,
    absolute_import,
    division,
    print_function
)

import asyncio
import threading
import re

from sopel import module
from sopel.config.types import (
    StaticSection, ValidatedAttribute, BaseValidated, NO_DEFAULT
)
from sopel.tools import get_input

import discord

import requests
from requests.exceptions import HTTPError

discord_api_url = 'https://discordapp.com/api'
client = discord.Client()


valid_message_pattern = r'^(?![.!?]\s*\w+)'


@client.event
async def on_ready():
    print('Logged into Discord as')
    print(client.user.name)
    print(client.user.id)
    print('----')


@client.event
async def on_message(message):
    content = message.clean_content
    if message.channel.id in client.channel_mappings \
            and not message.author.bot \
            and re.match(valid_message_pattern, content):
        irc_channel = client.channel_mappings[message.channel.id]
        content = re.sub(r'<(:\w+:)\d+>', r'\1', content)
        if message.attachments:
            extra = []
            if content:
                extra.append(content)
            for attachment in message.attachments:
                extra.append(attachment.get('url'))
            content = ' '.join(extra)
        content = content.replace('\n', ' ').strip()
        if content:
            if re.match(r'^_.+_$', content) and not message.attachments:
                # Discord uses markdown italics to denote /me action messages
                irc_message = '{} {}'.format(
                    message.author.name,
                    content[1:-1]
                )
                client.irc_bot.action(irc_message, irc_channel)
            else:
                irc_message = '<{}> {}'.format(message.author.name, content)
                client.irc_bot.msg(irc_channel, irc_message)

class DictAttribute(BaseValidated):
    '''Config attribute containing a list of key: value pairs.
    Key: value pairs are saved to the file as a comma-separated list.
    The spaces before and after each item are stripped.
    '''
    def __init__(self, name, default=None):
        default = default or {}
        super(DictAttribute, self).__init__(name, default=default)

    def parse(self, value):
        pairs = value.split(',')
        value = {}
        for item in pairs:
            k, v = item.split(':')
            value[k.strip()] = v.strip()
        return value

    def serialize(self, value):
        if not isinstance(value, dict):
            raise ValueError('DictAttribute value must be a dict')
        return ','.join(['{}:{}'.format(k, v) for k, v in value.items()])

    def configure(self, prompt, default, parent, section_name):
        each_prompt = '?'
        if isinstance(prompt, tuple):
            each_prompt = prompt[1]
            prompt = prompt[0]

            if default is not NO_DEFAULT:
                prompt = '{} [{}]'.format(prompt, default)
            else:
                default = ''
            values = []
            value = get_input(each_prompt + ' ') or default
            while value:
                values.append(value)
                value = get_input(each_prompt + ' ')
            return self.parse(','.join(values))


class DiscordSection(StaticSection):
    discord_token = ValidatedAttribute('discord_token')
    channel_mappings = DictAttribute('channel_mappings')


def _setup_webhooks(bot):
    bot.memory['webhooks'] = {}
    headers = {
        'Authorization': 'Bot {}'.format(bot.config.discord.discord_token)
    }
    for k, channel_id in bot.memory['channel_mappings'].items():
        try:
            r = requests.get(
                '{}/channels/{}/webhooks'.format(discord_api_url, channel_id),
                headers=headers
            )
            bot.memory['webhooks'][channel_id] = {}
            r.raise_for_status()
            for hook in r.json():
                if hook['name'] == 'discord-irc':
                    bot.memory['webhooks'][channel_id] = hook
            if not bot.memory['webhooks'][channel_id]:
                payload = {'name': 'discord-irc'}
                r = requests.post(
                    '{}/channels/{}/webhooks'.format(
                        discord_api_url,
                        channel_id
                    ),
                    headers=headers,
                    json=payload
                )
                r.raise_for_status()
                bot.memory['webhooks'][channel_id] = r.json()
        except HTTPError as e:
            print('Could not access webhook API for channel {}.'.format(
                channel_id))
            print('Make sure the bot user has the "Manage webhooks" permission'
                  'on the specified discord channel.')
            print(e)


def configure(config):
    config.define_section('discord', DiscordSection)
    config.discord.configure_setting(
        'discord_token',
        'Discord token for the app bot user'
    )
    config.discord.configure_setting(
        'channel_mappings',
        ('Comma-separated list of Discord channel to IRC channel mappings'
         ' (ex: #discord-channel1: #irc-channel1,'
         ' #discord-channel2: #irc-channel2)')
    )


def setup(bot):
    bot.config.define_section('discord', DiscordSection)
    client.irc_bot = bot
    client.channel_mappings = bot.config.discord.channel_mappings
    print(client.channel_mappings)
    # config order maps discord: IRC, invert the map for the IRC bot
    bot.memory['channel_mappings'] = {
        v: k for k, v in client.channel_mappings.items()
    }
    _setup_webhooks(bot)
    # only start the asyncio thread once (the discord thread can survive sopel
    # restarts)
    if not asyncio.get_event_loop().is_running():
        targs = (bot.config.discord.discord_token,)
        t = threading.Thread(target=client.run, args=targs)
        t.start()

# Match all messages except for those which start with common bot command
# prefixes
@module.require_chanmsg
@module.rule(valid_message_pattern)
def irc_message(bot, trigger):
    if not trigger.is_privmsg \
            and trigger.sender in bot.memory['channel_mappings']:
        discord_channel = bot.memory['channel_mappings'][trigger.sender]
        hook = bot.memory['webhooks'].get(discord_channel, {})
        if hook:
            headers = {
                'Authorization': 'Bot {}'.format(
                    bot.config.discord.discord_token)
            }
            content = trigger.match.string
            if trigger.tags.get('intent') == 'ACTION':
                content = '_{}_'.format(content)
            payload = {
                'content': content,
                'username': '{} (IRC)'.format(trigger.nick),
            }
            try:
                r = requests.post('{}/webhooks/{}/{}'.format(
                    discord_api_url, hook['id'], hook['token']
                    ),
                                  headers=headers,
                                  json=payload,
                                  )
                r.raise_for_status()
            except HTTPError as e:
                pass

Редактирование для добавления трассировки:

Traceback (most recent call last):
  File "/usr/lib/python3.7/asyncio/unix_events.py", line 92, in add_signal_handler
    signal.set_wakeup_fd(self._csock.fileno())
ValueError: set_wakeup_fd only works in main thread

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.7/threading.py", line 926, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.7/threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "/home/junya/.local/lib/python3.7/site-packages/discord/client.py", line 614, in run
    loop.add_signal_handler(signal.SIGINT, lambda: loop.stop())
  File "/usr/lib/python3.7/asyncio/unix_events.py", line 94, in add_signal_handler
    raise RuntimeError(str(exc))
RuntimeError: set_wakeup_fd only works in main thread

1 Ответ

0 голосов
/ 21 июля 2020

Я сдался и начал перекодирование с нуля. Попытка ввести ie бота сопел в раздор была слишком обременительной, и я уже закончил перезапись.

...