Использование tweepy с discord.py для публикации твитов на указанном c канале - PullRequest
1 голос
/ 01 мая 2020

Итак, я пытаюсь научить себя немного Python, используя комбинацию учебных пособий, документации и онлайн-примеров для создания бота Discord, который реализует некоторые функции одного или нескольких существующих ботов. Одной из таких функций является публикация (новых) твитов из определенного набора учетных записей Twitter на определенный канал c на моем сервере Discord. Я нашел несколько фрагментов кода, которые я собрал вместе для чтения из потока Twitter и «подправил» кое-что тут и там, чтобы попытаться выполнить sh this.

Однако я продолжайте сталкиваться с проблемой фактического получения кода on_status для правильного выполнения. Я пробовал разные способы заставить его работать, но все, что я пробовал, приводит к некоторой ошибке. Ниже приведен соответствующий код (отредактированный) из последней итерации, которую я тестировал:

import discord
import tweepy

from discord.ext import commands
from tweepy import Stream
from tweepy.streaming import StreamListener

class TweetListener(StreamListener):
    def on_status(self, status):
        if status.in_reply_to_status_id is None:
            TweetText = status.text

            for DGuild in MyBot.guilds:
                for DChannel in DGuild.text_channels:
                    if DChannel.name == 'testing':
                        TwitterEmbed = discord.Embed(title='New Tweet', description='New Tweet from my timeline.', color=0xFF0000)
                        TwitterEmbed.set_author(name='@TwitterHandle', icon_url=bot.user.default_avatar_url)
                        DChannel.send(TweetText, embed = TwitterEmbed)

DISCORD_TOKEN = 'dtoken'
TWITTER_CONSUMER_KEY = 'ckey'
TWITTER_CONSUMER_SECRET = 'csecret'
TWITTER_ACCESS_TOKEN = 'atoken'
TWITTER_ACCESS_SECRET = 'asecret'

MyBot = commands.Bot(command_prefix='!', description='This is a testing bot')
TwitterAuth = tweepy.OAuthHandler(TWITTER_CONSUMER_KEY, TWITTER_CONSUMER_SECRET)
TwitterAuth.set_access_token(TWITTER_ACCESS_TOKEN, TWITTER_ACCESS_SECRET)
TweetAPI = tweepy.API(TwitterAuth)
NewListener = TweetListener()
NewStream = tweepy.Stream(auth=TweetAPI.auth, listener=NewListener)
NewStream.filter(follow=['USER IDS HERE'], is_async=True)

@bot.event
async def on_ready():
    print(MyBot.user.name,'has successfully logged in ('+str(MyBot.user.id)+')')
    print('Ready to respond')

MyBot.run(DISCORD_TOKEN)

Метод channel.send() генерирует следующее сообщение, когда я публикую твит на временной шкале моей тестовой учетной записи:

RuntimeWarning: coroutine 'Messageable.send' was never awaited

Я понимаю сообщение - метод channel.send() является асинхронным методом, но обработчик события on_status() является синхронным методом, и я не могу await channel.send() внутри on_status() - но я могу не понимаю, как заставить это работать. Я пытался сделать метод on_status() асинхронным:

    async def on_status(self, status):
        <same code for checking the tweet and finding the channel>
        await DChannel.send(TweetText, embed = TwitterEmbed)

, но это всегда приводило к похожему предупреждению:

RuntimeWarning: coroutine 'TweetListener.on_status' was never awaited
if self.on_status(status) is False:

Я нашел вопрос, Как я могу asyn c on_status от tweepy? и следовал по ссылкам там, но я не не видел ничего, что подсказывало бы мне, какая бы ни была моя ошибка кодирования.

В ходе дополнительных исследований я также попытался использовать некоторые вызовы из библиотеки asyncio для совершения вызова:

    #channel.send(message, embed = TwitterEmbed)
    #---ASYNCIO TEST---
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(DChannel.send(embed=TwitterEmbed)))
    loop.close()

К сожалению, это приводит к необработанному исключению при попытке выполнить loop=asyncio.get_event_loop() с указанием There is no current event loop in thread 'Thread-6'. (таким образом, он даже не доберется до метода run_until_complete(), чтобы посмотреть, будет ли работать , хотя я не совсем оптимистичен c на данный момент).

Я понимаю, что существуют боты Discord, такие как MEE6, которые уже могут делать то, что я пытаюсь сделать здесь, но я хотел бы иметь возможность сделать этого бота "своим", хотя я немного учусь Python. Я, вероятно, упускаю из виду кое-что простое здесь, но я не смог найти ничего в документации API ни для tweepy, ни для discord.py, которая, кажется, указывает мне правильное направление, и мой Google- фу явно не так сильно. Все примеры, которые я смог найти до сих пор, кажутся устаревшими, так как они относятся к устаревшим методам и более старым версиям одной или обеих библиотек. Есть и другие вещи, которые мне еще нужно выяснить, как это сделать ( например, получить @TwitterHandle для правильного заполнения встраивания ), но я должен научиться читать и выводить sh твит, прежде чем я смогу беспокоиться о достижении этой точки.


ИНФОРМАЦИЯ ОБ ОКРУЖАЮЩЕЙ СРЕДЕ

Visual Studio 2017 CE

Python 3.6

discord.py 1.3.3

tweepy 3.8.0


ОБНОВЛЕНИЕ: ДОПОЛНИТЕЛЬНЫЕ ИСПЫТАНИЯ

Итак, просто чтобы доказать себе, что мой TweetListener класс на самом деле работает, я продолжил и прокомментировал все взаимодействия с Discord в методе on_status ( все от for Guild in MyBot.guilds до конца метода ), затем добавил простой print(TweetText). Я снова запустил код, вошел в тестовую учетную запись Twitter, которая у меня есть для этого проекта, и опубликовал обновление. Проверяя консоль, она правильно выдает status.text моего твита, как я и ожидал. Как я и думал, единственная проблема, с которой я столкнулся здесь, - это попытка send этого текста в Discord.

Я также попытался инициализировать tweepy.Stream.filter в синхронном режиме вместо асинхронного - NewStream.filter(follow=['USER IDS']). Это только привело к тому, что мое приложение "зависало" при запуске. Я понял, что это связано с его положением в коде, поэтому я переместил все три строки инициализации TweetListener в метод события on_ready. Я оставил код Discord закомментированным и протестированным, и он снова «сработал» в том, что он напечатал status.text другого тестового твита на консоли. Тем не менее, бот не будет отвечать ни на что другое (все еще «ожидая» в потоке, я полагаю).

Несмотря на это, просто чтобы посмотреть, будет ли это иметь какое-то значение, я пошел дальше и раскомментировал Discord Код взаимодействия и попробовал еще раз. Результат был таким же, как у меня изначально, только в этот раз бот Discord не будет реагировать на активность в канале ( есть пара супер простых @bot.command объявлений в другом месте кода ), которые опять же, в основном, возвращает меня к исходному коду с той же проблемой.

Я уверен, что есть лучшие способы кодирования всего этого. Как я уже говорил выше, я только сейчас начинаю играть с Python и все еще изучаю правила синтаксиса и тому подобное. Несмотря на это, я все еще не уверен, что я делаю неправильно и / или упускаю из виду то, что мешает мне достичь того, что я думал, было бы довольно простой задачей.


БОЛЬШЕ ТЕСТИРОВАНИЯ

Я вернулся и нашел еще asyncio примеров и попробовал другую итерацию во взаимодействии Discord в on_status:

    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    result=loop.run_until_complete(DChannel.send(embed=TwitterEmbed))

На этот раз я получил другое необработанное исключение, чем Я получил раньше. Ошибка возникает в строке run_until_complete при получении нового твита:

Timeout context manager should be used inside a task

Я рассматриваю этот прогресс, но я, очевидно, все еще не совсем там, поэтому вернемся к Google, и я нашел информацию о библиотека asgiref с ее методами sync_to_async и async_to_sync, поэтому я решил попробовать.

    asgiref.sync.async_to_sync(DChannel.send)(embed=TwitterEmbed)

К сожалению, я получаю то же необработанное исключение, что и с asyncio версия. Я чувствую, что я на грани выяснения этого, но он просто еще не «щелкнул».


ДРУГОЙ КРУГЛЫЙ

Хорошо, так что после поиска информации Об исключении Timeout context manager, которое я получал выше, я наткнулся на другой ТАК вопрос, который дал мне крошечный проблеск надежды. Этот ответ на RuntimeError: Менеджер контекста времени ожидания должен использоваться внутри задачи еще раз возвращает меня к использованию asyncio и дает краткое, но наглядное объяснение причины проблемы ОП, прежде чем предоставить полезное предложение с помощью метод asyncio.run_coroutine_threadsafe. Глядя на рекомендованный код, стало понятно, что это может помочь мне эффективно начать взаимодействие с помощью метода syn c -> asyn c. Я реализовал предложенные изменения (создал глобальную переменную для объекта Thread, на которой запускается бот, добавил метод запуска, который сгенерировал бота в этом l oop, затем изменил взаимодействие Discord в on_status на свяжите все это вместе.

"Хорошая новость" в том, что я больше не получаю каких-либо ошибок при публикации твита. Кроме того, тестирование команды бота, кажется, работает нормально, так как хорошо. Плохая новость заключается в том, что он все еще не отправил сообщение на канал. Поскольку он не выдал никаких ошибок, невозможно сказать, где закончилось сообщение.

...