用Python构建一个Reddit Autoposter Telegram Bot

853 阅读6分钟

用Python构建一个Reddit Autoposter Telegram Bot

Telegram是一个流行的、现代的跨平台应用,被广泛地用于消息传递。它非常快速、安全、基于云端,并且可以在所有设备上无缝工作。看起来很完美,不是吗?可能性还不止于此!整个代码是开源的,允许你建立你自己版本的Telegram!

Telegram对开发者也非常友好,提供完全免费的API。他们的开发者提供的服务可以分为两种类型。

  • 机器人API - 创建强大的机器人,在Telegram上运行。
  • Telegram APIs and TDLib - 建立你自己的定制Telegram客户端

在这篇文章中,我们将看看Telegram的Bot API,并深入研究如何建立一个强大的机器人,可以自动从subreddit上发帖。

以下教程的所有代码都可以在我的GitHub存储库中找到。你可以把它当做参考,或者把它当做明星,或者分叉它并提交一份PR

目录

动机

我们都喜欢狗,不是吗?😄

我喜欢在Reddit上滚动浏览狗的照片,有一段时间,我觉得我在Reddit上花了太多的时间。于是我有了一个想法。像一个真正的程序员一样,我决定用Python创建一个机器人,将Subreddits上的新帖子自动发布到Telegram频道上。这是一个让我更好地掌握Python的方法,同时也节省了每天的时间!

在下面的例子中,我们将从r/dogpictures子reddit中自动发帖。

获取机器人的API凭证

我们的第二步将是在Telegram上创建一个新的Bot,并为Bot获取API密钥。机器人管理是由Telegram上一个叫BotFather的机器人完成的。

现在,点击这个链接,打开BotFather。

Create a New Bot
通过输入命令创建一个新的机器人。\newbot

Bot Username
输入机器人的显示名称并点击回车。现在为该机器人选择一个用户名。

Bot Success
好了!你已经成功创建了机器人!API令牌和机器人的链接已经给出。把它安全地保存起来,因为我们很快就会需要它。

创建一个Telegram频道

在Telegram上创建一个频道是非常简单的。

  • 点击左边的汉堡包菜单,然后点击New Channel
  • 给这个频道起一个你选择的名字。
  • 将你创建的机器人添加到具有管理权限的频道中。

获取Reddit凭证

为了访问Reddit的帖子,我们将使用Reddit API和Python库PRAW(The Python Reddit API Wrapper)。

  • 最重要的一步是获得凭证。
  • 创建一个新的Reddit账户。
  • 进入应用首选项,并点击create app

Reddit Credentials

  • 给你的应用程序起个名字,并从单选按钮中选择子选项script
  • 输入一个简短的描述
  • 关于URI留空,在重定向URI字段中输入http://localhost:8080
  • 创建应用程序。
  • 你将被提供一个客户ID和一个客户秘密。安全地存储它们。

开始编写代码吧

必要的导入

from __future__ import unicode_literals

import telegram
import praw
import logging
import html
import sys
import os
import json

from time import sleep
from datetime import datetime

现在我们已经导入了必要的库,让我们来设置凭证和错误记录器。

凭证

credentials = {}

credentials["token"] = os.environ.get('TOKEN')
credentials["subreddit"] = os.environ.get('SUB')
credentials["channel"] = os.environ.get('CHANNEL')

token = credentials["token"]
channel = credentials["channel"]
sub = "dogpictures"
start_time = datetime.utcnow().timestamp()

在这里,我们正在使用环境变量的概念来隐藏我们的敏感信息。当我们将项目开源并发布到GitHub等代码库时,管理凭证就会更容易。

我们将看看如何在部署Python脚本之前设置环境变量。

异常和日志

ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
log.addHandler(ch)

if credentials["token"] == "":
    raise RuntimeError('Bot token not found 🙁! Put bot token🔐 in credentials.json!')
if credentials["subreddit"] == "":
    raise RuntimeError('Subreddit name not found 🙁! Enter the subreddit name📃 in credentials.json!')
if credentials["channel"] == "":
    raise RuntimeError('Telegram Channel name not found 🙁! Enter the channel name📰 in credentials.json!')

上面的代码用于在缺少任何环境变量时引发异常,同时也记录了这些记录。这使得代码的调试更加容易。

跟踪提交

def prev_submissions():
    try:
        with open('prev_submissions.id', 'r') as f:
            return f.read().strip()
    except:
        return None

def write_submissions(sub_id):
    try:
        with open('prev_submissions.id', 'w') as f:
            f.write(sub_id)
    except:
        log.expection("Error writing sub ID!")

在这里,我们写了两个函数。prev_submissions() 函数读取prev_submissions.id,这是一个指定机器人已经在telegram频道上发布的先前帖子的文件,以避免重复。

write_submissions() 将发送到频道的帖子写进文件。

设置变量和初始化PRAW和Bot API

post = False
last_sub_id = prev_submissions()

if not last_sub_id:
    log.info("Latest submission not found, starting all submissions!")
    post = True
else:
    log.info("Last posted submission is {}".format(last_sub_id))

r = praw.Reddit(user_agent="Dank Doggo by Harsha :D",
                client_id=os.environ.get('CLIENT_ID'),
                client_secret=os.environ.get('CLIENT_SECRET'),
                username=os.environ.get('RUSERNAME'),
                password=os.environ.get('RPASS'))
r.read_only = True
subreddit = r.subreddit(sub)

bot = telegram.Bot(token=token)

这里,由条件语句设置,if not last_sub_id ,如果subreddit上有新的帖子,就开始向频道发帖。

我们通过传入CLIENT_ID, CLIENT_SECRET 、Reddit用户名和Reddit密码作为参数来初始化Reddit实例。

Bot也是通过传入Bot API Token来初始化的。

主控制块

while True:
    try:
        for submission in subreddit.hot():
            try:
                link = "https://redd.it/{id}".format(id=submission.id)
                if not post and submission.created_utc < start_time:
                    log.info("Skipping {} --- latest submission not found!".format(submission.id))
                    if submission.id == last_sub_id:
                        post = True
                    continue
                image = html.escape(submission.url or '')
                title = html.escape(submission.title or '')
                user = html.escape(submission.author.name or '')

                template = "{title}\n{link}\nby {user}"
                message = template.format(title=title, link=link, user=user)

                log.info("Posting {}".format(link))
                bot.sendPhoto(chat_id=channel, photo=submission.url, caption=message)
                # bot.sendMessage(chat_id=channel, parse_mode=telegram.ParseMode.HTML, text=message)
                write_submissions(submission.id)
                sleep(600)
            except Exception as e:
                log.exception("Error parsing {}".format(link))
    except Exception as e:
        log.exception("Error fetching new submissions, restarting in 10 secs")
        sleep(10)

在这里,subreddit.hot() ,返回Subreddit中的所有热门帖子。如果脚本发现一个新的帖子还没有被写入prev_submissions.id文件(即还没有被发布到频道上),它将使用标记来格式化消息。最后,它通过使用bot.sendPhoto() API调用来发送消息,该调用将频道名称、照片URL和标题作为参数。

我们还将机器人设置为每10分钟只发送一条信息,这样它就不会产生垃圾邮件。这是通过使用sleep() 函数将脚本暂停600秒来实现的。

部署到Heroku

现在,我们已经成功地为我们的自动海报机器人编写了代码。

让我们把它部署到Heroku上,这样它就可以全天候运行了。

  • 首先,把所有的代码上传到GitHub仓库。(本文末尾有一个样本仓库的链接)。
  • 进入Heroku仪表板,创建一个新的账户。
  • 创建一个新的Heroku应用程序。

Heroku Dash

  • 进入部署标签,选择GitHub作为部署方式。
  • 现在搜索你的GitHub仓库并启用自动部署
  • 自动部署一旦检测到你的GitHub中的更新,就开始部署你的脚本。
  • 现在你需要添加环境变量。
  • 选择 "设置"标签,点击 "显示配置变量"选项。
  • 在这里,你需要添加以下配置变量。
    • CHANNEL: "@your-telegram-channel-name"
    • CLIENT_ID: "your-reddit-clientid"
    • CLIENT_SECRET: "your-reddit-client-secret" 。
    • RPASSWORD: "your-reddit-password"
    • 帐号:RUSERNAME:"your-reddit-username"
    • SUB: "subreddit-name"
    • TOKEN: "你的telegram-bot-api-token"
  • 一旦你保存,机器人就会自动部署!
  • 它将继续每10分钟发送一次新的帖子!

参考资料

编写Python脚本和机器人是提高你的编程技能的一个很好的方法看一下下面的文档,对代码有更深入的了解。