实战|使用PyQt5制作BT种子转磁力链接工具

一、种子文件与磁力链接

经常下载各种资源和影视资料的小伙伴对于BT种子应该是不陌生的,通过小小的BT种子,我们可以下载到各种丰富的资源。而磁力链接相较于BT种子,则更加方便,直接通过一串代码式的链接就可以进行资源的下载。从而省去了BT种子的存储。既便于传播,也便于隐藏。

其实BT种子和磁力链接是有相关性的,通过BT种子可以转换为磁力链接,磁力链接也能索引到BT种子中记录的资源。(有关BT下载具体的原理,这里就不做介绍了,有兴趣的小伙伴可以自行搜索相关资料)下面我们通过Python剖析一个种子,来找到BT种子转磁力链接的关键。

我们在某电影资源网站上下载了一个电影《找到你》的BT种子,打开BT种子文件会自动调用迅雷进行识别(如果安装和迅雷软件),其显示了BT种子里面包含的如下图所示的资源:

里面有三个文件,其中一个MP4媒体文件,两个文本文件,迅雷是如何通过BT种子定位到这些文件资源的呢?下面我们继续研究。

二、使用Python转换种子文件为磁力链接

在上面,我们从网站上下载了一个电影资源的BT种子,通过迅雷验证了这个BT种子中的资源。下面,我们通过Python来读取一下这个BT种子的内容,看看BT种子中到底有些上面东西。

Python读取BT种子

BT种子是一个文件,当然不是纯的文本文件,所以,我们使用二进制的方式打开并读取它:

with open('找到你.torrent','rb') as f:
    ff = f.read()
    print(ff)

我们运行上述代码,其会通过二进制的方式打开“找到你.torrent”这个BT种子并将文件内容打印出来。结果如下:

打印出来的内容还是字节流,可以看到一些类似于URL链接的内容,我们使用bcoding模块利用Bencoding编码方式对它进行解码:

from bcoding import bdecode
with open('找到你.torrent','rb') as f:
    # ff = f.read()
    ff = bdecode(f)
    print(ff)

现在,打印出来的内容就是解码之后的BT种子的内容了,文件内容的结构比之前的清晰多了:

从中可以发现,BT种子解码之后的内容是多个字典的键值对,我们使用字典的items()方法,遍历BT种子的所有键值对内容并打印出来:

from bcoding import bencode,bdecode
with open('找到你.torrent','rb') as f:
    # ff = f.read()
    ff = bdecode(f)
    # print(ff)
    for i,k in ff.items():
        print(i,':',k)

这样呈现出来的结果就更加清晰了:

从中我们进而可以发现,BT种子中的内容分为7大块,分别是:

  • announce:表示Tracker的主服务器
  • announce-list:表示Tracker的服务器列表;
  • created by:表示BT种子通过什么方式进行的创建;
  • creation date:表示BT种子创建的时间,为一个时间戳;
  • encoding:表示BT种子的编码类型;
  • info:表示下载的文件的信息,包括多个子字段,根据下载的是单个文件还是多个文件,子字段的项目会不同;
  • nodes:节点的列表。

在这些内容中,我们需要重点关注info中的内容,因为这里面才是BT种子资源的最核心部分。可以发现info还是一个字典,我们继续对其进行遍历:

from bcoding import bencode,bdecode
with open('找到你.torrent','rb') as f:
    ff = bdecode(f)
    for i,k in ff['info'].items():
        print(i,':',k)

这样,我们又得到了BT种子info中的内容:

info里面的内容继续分为了9大块:

  • files:表示文件的信息,如果有多个文件,会用列表继续包裹;
  • name:推荐的文件夹名称;
  • name.utf-8:推荐的文件夹名称的utf-8编码;
  • piece length:文件块的大小;
  • pieces:文件块的特征信息,是BT种子最核心的字段;
  • publisher:发布者;
  • publisher-url:发布者的URL链接;
  • publisher-url.utf-8:发布者url链接的utf-8编码;
  • publisher.utf-8:发布者的utf-8编码;

之前我们通过迅雷打开BT种子,发现其中有3个文件,在此我们可以继续遍历files字段验证一下:

from bcoding import bencode,bdecode
with open('找到你.torrent','rb') as f:
    ff = bdecode(f)
    for i,k in ff['info'].items():
        print(i,':',k)
        if i == 'files':
            for kk in k:
                print(kk)

就得到了files字段下的内容:

{'ed2k': b's\r\x8a\x8b\xce\xdfL\xe6\xa2\xb2\xe6\xb5`8\xae\xa4', 'filehash': b'\x87\xea\xd7\x84\xc6D\x16\x0ca\x8c\x03\x06\x05\xde\xdbX\xcd\xf6LF', 'length': 10, 'path': ['btshoufa.com.txt'], 'path.utf-8': ['btshoufa.com.txt']}
{'length': 524278, 'path': ['_____padding_file_0_如果您看到此文件,请升级到BitComet(比特彗星)0.85或以上版本____'], 'path.utf-8': ['_____padding_file_0_如果您看到此文件,请升级到BitComet(比特彗星)0.85或以上版本____']}
{'ed2k': b'\x1a\xdc02\xeb \x9d\xa1\xde\xb4Hh\xb4A\xc9\xc5', 'filehash': b'\x03\xe6\x0cau\xb8x\x1b]3\x1a\x14fY5\xb2#\xf5\xd8\x0b', 'length': 4151889015, 'path': ['找到你.Lost.Found.2018.HD.2160P.X264.ACC-BTshoufa[国语中字].mp4'], 'path.utf-8': ['找到你.Lost.Found.2018.HD.2160P.X264.ACC-BTshoufa[国语中字].mp4']}
{'length': 471945, 'path': ['_____padding_file_1_如果您看到此文件,请升级到BitComet(比特彗星)0.85或以上版本____'], 'path.utf-8': ['_____padding_file_1_如果您看到此文件,请升级到BitComet(比特彗星)0.85或以上版本____']}
{'ed2k': b'-~\xd0h\xce\xdaH\xb8\xec."\xd3xSC\xfb', 'filehash': b'\xf6\x85\x84)j\x11\xbc\xbe\x9c\x14^\xf8G \x98B\xd5v\xb3\xa3', 'length': 13, 'path': ['本电影由BT首发论坛原创压制!.txt'], 'path.utf-8': ['本电影由BT首发论坛原创压制!.txt']}

Python生成磁力链接

在上面,我们对BT种子进行了解剖,其实,要生成BT种子的磁力链接,并不需要对BT种子解析那么多;因为磁力链接是通过文件内容的hash值来寻找文件,所以我们只需要获取到BT种子的info字段就可以了。

一个磁力链接的最基本结构如下所示:

magnet:?xt=urn:btih:D6ZCRFF4EDQLEPW44V7B7VTGRSCSE4K5

其中:

  • magnet:表示使用的协议;
  • xt:是英文Exact Topic的缩写,表示包含文件Hash值的统一资源名称;
  • btih:是英文BitTorrent Info Hash的缩写,表示采用了Hash方法名。btih还可以替换为ed2k、aich、sha1或md5等等 ,它表示的是这个文件唯一的标识符。

此三者,是磁力链接的最基本构造,还有一些注入ws、dn、tr的选填字段用于扩充磁力链接的信息,在此就不做介绍。

基于此,我们对BT种子进行解码获取到其info的内容,然后对其进行Hash加密,就得到了这个BT种子中的资源的磁力链接索引,再对链接进行字符串拼接,也就得到了BT种子的磁力链接。
所以我们使用Python对其的实现代码为:

from bcoding import bencode,bdecode
import hashlib
import base64

with open('找到你.torrent','rb') as f:
    # ff = f.read()
    ff = bdecode(f) # 对种子文件进行解码
    content = ff['info'] # 选择info字段
    hashcontent = bencode(content) # 对info内容进行Bencoding编码
    digest = hashlib.sha1(hashcontent).digest() # 对Bencoding编码后的info内容进行hash加密
    b32hash = base64.b32encode(digest) # 对hash加密后的info内容进行base32编码
    magnet = b32hash.decode()

    magnetUrl = "magnet:?xt=urn:btih:{0}".format(magnet)
    print(magnetUrl)

运行代码,我们就得到了一个磁力链接:

magnet:?xt=urn:btih:D6ZCRFF4EDQLEPW44V7B7VTGRSCSE4K5

我们复制这个磁力链接,到迅雷上测试,和使用BT种子的效果是一样的:

三、使用PyQt5制作种子磁力链接转换图形界面

在通过Python代码实现了BT种子对磁力链接的转换之后,我们就可以使用PyQt5来对这个BT种子转磁力链接程序编写图形用户界面了,不然每次都手动设置种子文件路径怪不方便的。

创建一个基本的图形界面

首先来创建一个最基本的图形界面窗口,里面包含3个按钮和1个输入框:

import hashlib
import base64
from bcoding import bencode,bdecode
import traceback
import sys
from PyQt5 import QtCore,QtGui,QtWidgets

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("种子转磁力链接助手")
        self.setFixedSize(400,250)
        self.ini_ui()

    def ini_ui(self):
        # 设置图标
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setWindowIcon(icon)

        main_widget = QtWidgets.QWidget()
        main_layout = QtWidgets.QGridLayout()
        main_widget.setLayout(main_layout)
        self.select_file = QtWidgets.QPushButton("选择种子")
        self.file_path = QtWidgets.QLineEdit()
        self.convert_torrent = QtWidgets.QPushButton("转换")
        self.convert_torrent.setEnabled(False)
        self.maginfo_button = QtWidgets.QPushButton("")
        self.maginfo_button.setEnabled(False)

        main_layout.addWidget(self.select_file)
        main_layout.addWidget(self.file_path,)
        main_layout.addWidget(self.convert_torrent,)
        main_layout.addWidget(self.maginfo_button,)

        # 设置窗口主部件
        self.setCentralWidget(main_widget)

def main():
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

得到我们这个图形界面的形态:

创建种子选择方法

现在我们为MainUi()类创建一个子方法,用来在电脑中选择BT种子文件,并将BT种子的路径程序在文本输入框中,这个功能通过QtWidgets.QFileDialog()来实现:

    # 选择种子文件
    def select_file_clicked(self):
        try:
            self.filename = QtWidgets.QFileDialog.getOpenFileName(self, "OpenFile", ".","种子文件(*.torrent)")[0]
            if len(self.filename):
                self.file_path.setText(self.filename)
                self.convert_torrent.setEnabled(True)
                self.maginfo_button.setEnabled(False)
        except Exception as e:
            print(traceback.print_exc())

然后,将“选择种子”按钮的点击信号链接到这个方法上:

self.select_file.clicked.connect(self.select_file_clicked)

创建转换磁力链接方法

转换磁力链接方法包括了读取BT种子和对info信息进行哈希加密,是这个程序的最核心方法:

    # 转换为磁力链接
    def convert_magnet(self):
        if self.filename:
            with open(self.filename, 'rb') as f:
                # ff = f.read()
                ff = bdecode(f)  # 对种子文件进行解码
                content = ff['info']  # 选择info字段
                hashcontent = bencode(content)  # 对info内容进行Bencoding编码
                digest = hashlib.sha1(hashcontent).digest()  # 对Bencoding编码后的info内容进行hash加密
                b32hash = base64.b32encode(digest)  # 对hash加密后的info内容进行base32编码
                magnet = b32hash.decode()

                self.magnetUrl = "magnet:?xt=urn:btih:{0}".format(magnet)

            self.maginfo_button.setText('转换成功!点此复制')
            self.maginfo_button.setEnabled(True)

然后将“转换”按钮的点击信号链接到这个方法上:

self.convert_torrent.clicked.connect(self.convert_magnet)

创建复制磁力链接方法

磁力链接生成之后,我们需要点击按钮复制到粘贴板上:

    # 复制信息到粘贴板
    def copy_magnet(self):
        clipboard = QtWidgets.QApplication.clipboard() # 实例化一个粘贴板
        clipboard.setText(self.magnetUrl)
        QtWidgets.QMessageBox.information(self,'提示','复制成功')

然后,将“复制”按钮的点击信号链接到这个方法上:

self.maginfo_button.clicked.connect(self.copy_magnet)

测试程序运行:

最终,完整的程序代码如下所示:

import hashlib
import base64
from bcoding import bencode,bdecode
import traceback
import sys
from PyQt5 import QtCore,QtGui,QtWidgets

class MainUi(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("种子转磁力链接助手")
        self.setFixedSize(400,250)
        self.ini_ui()

    def ini_ui(self):
        # 设置图标
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("icon.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setWindowIcon(icon)

        main_widget = QtWidgets.QWidget()
        main_layout = QtWidgets.QGridLayout()
        main_widget.setLayout(main_layout)
        self.select_file = QtWidgets.QPushButton("选择种子")
        self.select_file.clicked.connect(self.select_file_clicked)
        self.file_path = QtWidgets.QLineEdit()
        self.convert_torrent = QtWidgets.QPushButton("转换")
        self.convert_torrent.setEnabled(False)
        self.convert_torrent.clicked.connect(self.convert_magnet)
        self.maginfo_button = QtWidgets.QPushButton("")
        self.maginfo_button.setEnabled(False)
        self.maginfo_button.clicked.connect(self.copy_magnet)

        main_layout.addWidget(self.select_file)
        main_layout.addWidget(self.file_path,)
        main_layout.addWidget(self.convert_torrent,)
        main_layout.addWidget(self.maginfo_button,)

        # 设置窗口主部件
        self.setCentralWidget(main_widget)

    # 选择种子文件
    def select_file_clicked(self):
        try:
            self.filename = QtWidgets.QFileDialog.getOpenFileName(self, "OpenFile", ".","种子文件(*.torrent)")[0]
            if len(self.filename):
                self.file_path.setText(self.filename)
                self.convert_torrent.setEnabled(True)
                self.maginfo_button.setEnabled(False)
        except Exception as e:
            print(traceback.print_exc())

    # 转换为磁力链接
    def convert_magnet(self):
        if self.filename:
            with open(self.filename, 'rb') as f:
                # ff = f.read()
                ff = bdecode(f)  # 对种子文件进行解码
                content = ff['info']  # 选择info字段
                hashcontent = bencode(content)  # 对info内容进行Bencoding编码
                digest = hashlib.sha1(hashcontent).digest()  # 对Bencoding编码后的info内容进行hash加密
                b32hash = base64.b32encode(digest)  # 对hash加密后的info内容进行base32编码
                magnet = b32hash.decode()

                self.magnetUrl = "magnet:?xt=urn:btih:{0}".format(magnet)

            self.maginfo_button.setText('转换成功!点此复制')
            self.maginfo_button.setEnabled(True)

    # 复制信息到粘贴板
    def copy_magnet(self):
        clipboard = QtWidgets.QApplication.clipboard() # 实例化一个粘贴板
        clipboard.setText(self.magnetUrl)
        QtWidgets.QMessageBox.information(self,'提示','复制成功')


def main():
    app = QtWidgets.QApplication(sys.argv)
    gui = MainUi()
    gui.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

运行代码,我们就可以使用这个图形界面的BT种子转磁力链接工具了:

使用另一个BT种子测试依然成功:

如有疑问,欢迎在知识星球交流!

猜你也喜欢

  1. 小治治说道:

    写得很好,对照源码还可以一步一步地实现,赞一个

发表评论

电子邮件地址不会被公开。