Full Code of Koileo/ticket_for_allcpp for AI

main c37c2d65c71a cached
10 files
19.3 KB
5.0k tokens
12 symbols
1 requests
Download .txt
Repository: Koileo/ticket_for_allcpp
Branch: main
Commit: c37c2d65c71a
Files: 10
Total size: 19.3 KB

Directory structure:
gitextract_k9dgtw2k/

├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── config.txt
├── cookie.txt
├── main.py
├── requirements.txt
├── ticket_for_allcpp.spec
└── timentp.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/workflows/release.yml
================================================
name: Python Release

on:
  push:
    tags:
      - 'v*'

jobs:
    Windows-amd64:
        permissions: write-all
        runs-on: windows-latest
        name: Build Windows Binary
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Init Python 3.12
            uses: actions/setup-python@v4
            with:
                python-version: '3.12'
                cache: 'pip'
    
          - name: Install Dependent Packages
            run: |
                python -m pip install --upgrade pip
                pip install wheel pyinstaller
                pip install -r requirements.txt
            shell: pwsh
    
          - name: Pyinstaller
            run: |
                pyinstaller ticket_for_allcpp.spec
            shell: pwsh
    
          - name: Upload Windows File
            uses: actions/upload-artifact@v3
            with:
                name: ticket_for_allcpp-windows-amd64
                path: dist/ticket_for_allcpp.exe

    Linux-amd64:
        permissions: write-all
        runs-on: ubuntu-latest
        name: Build Linux Amd64
        steps:
          - name: Checkout
            uses: actions/checkout@v4
    
          - name: Init Python 3.12
            uses: actions/setup-python@v4
            with:
                python-version: '3.12'
                cache: 'pip'
    
          - name: Install Dependent Packages
            run: |
                python -m pip install --upgrade pip
                pip install wheel pyinstaller
                pip install -r requirements.txt

          - name: Pyinstaller
            run: |
                pyinstaller ticket_for_allcpp.spec
                mv dist/ticket_for_allcpp dist/ticket_for_allcpp-linux-amd64
    
          - name: Upload Linux File
            uses: actions/upload-artifact@v3
            with:
                name: ticket_for_allcpp-linux-amd64
                path: dist/ticket_for_allcpp-linux-amd64

    macos-amd64:
        permissions: write-all
        runs-on: macOS-latest
        name: Build macOS Amd64
        steps:
          - name: Checkout
            uses: actions/checkout@v4
            
          - name: Init Python 3.12
            uses: actions/setup-python@v4
            with:
                python-version: '3.12'
                cache: 'pip'
        
          - name: Install Dependent Packages
            run: |
                python -m pip install --upgrade pip
                pip install wheel pyinstaller
                pip install -r requirements.txt

          - name: Pyinstaller
            run: |
                pyinstaller ticket_for_allcpp.spec
                mv dist/ticket_for_allcpp dist/ticket_for_allcpp-macos-amd64
        
          - name: Upload macOS File
            uses: actions/upload-artifact@v3
            with:
                name: ticket_for_allcpp-macos-amd64
                path: dist/ticket_for_allcpp-macos-amd64

    Create-release:
        permissions: write-all
        runs-on: ubuntu-latest
        needs: [ Windows-amd64, Linux-amd64, macos-amd64]
        steps:
          - uses: actions/checkout@v4

          - name: Download Artifact
            uses: actions/download-artifact@v3

          - name: get release_informations
            shell: bash
            run: |
                mkdir releases
                mv ./ticket_for_allcpp-macos-amd64/ticket_for_allcpp-macos-amd64 ./releases/ticket_for_allcpp-macos-amd64
                mv ./ticket_for_allcpp-linux-amd64/ticket_for_allcpp-linux-amd64 ./releases/ticket_for_allcpp-linux-amd64
                mv ./ticket_for_allcpp-windows-amd64/ticket_for_allcpp.exe ./releases/ticket_for_allcpp-windows-amd64.exe
                cp config.txt ./releases/config.txt
                cp cookie.txt ./releases/cookie.txt

          - name: Create Release
            id: create_release
            uses: actions/create-release@latest
            env:
                GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
            with:
                tag_name: ${{ github.ref }}
                release_name: ${{ github.ref }}
                draft: false
                prerelease: false

          - name: Upload Release Asset
            uses: dwenegar/upload-release-assets@v1
            env:
                GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
            with:
                release_id: ${{ steps.create_release.outputs.id }}
                assets_path: |
                    ./releases/


================================================
FILE: .gitignore
================================================
build/
dist/


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2024 Koileo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# ticket_for_allcpp

开源免费,简单易用,多线程暴力 CPP 抢票工具。

> [!NOTE]
> 本程序仅供学习交流, 不得用于商业用途
> 使用本程序进行违法操作产生的法律责任由操作者自行承担
> 本次cp30最好使用b站会员购进行抢票,cp30有可能加入验证码,由于之前从未出现,我没办法在开票前保证程序可用性!!!!!

## 安装教程

### 1. 快速安装

前往 [Releases](https://github.com/Koileo/ticket_for_allcpp/releases) 下载最新可执行文件直接命令行运行。

### 2. 源码运行

```shell
git clone https://github.com/Koileo/ticket_for_allcpp.git
cd ticket_for_allcpp
pip install -r requirements.txt
python main.py
```

## 使用说明

### cookie.txt 配置

- 第一行为账号`cookie`值,浏览器登入CPP直接F12获取并全部复制即可。
- 第二行为`ticketid`,同样也是F12查看 https://www.allcpp.cn/allcpp/ticket/getTicketTypeList.do?eventMainId=xxxx 的响应 一般为4位数字
- 以此类推,第三行第四行也是这样

### config.txt 配置文件:包括ntp服务器,间隔时长,线程数

- 本程序支持多线程,多账户(默认三线程)。
- 默认实名票全部按照购票人设置数量购买,你绑定几个人买几份票,即默认全选
- 同一账号支持同时购买不同票类,在第二行用“,"分开

## 其他可用脚本

| 链接                                                       | 主要特色                |
| --------------------------------------------------------- | ---------------------- |
| https://github.com/mikumifa/cppTickerBuy                      |图形化,对小白友好         |

## 未来功能

- [ ] 微信通知
- [x] 程序外部配置空隔时间和线程数
- [x] Linux 和 MacOS 打包
- [x] 时间校准

## 项目问题

反馈程序BUG或者提新功能建议:[点此链接向项目提出反馈BUG](https://github.com/Koileo/ticket_for_allcpp/issues)

## Star History

[![Star History Chart](https://api.star-history.com/svg?repos=Koileo/ticket_for_allcpp&type=Date)](https://star-history.com/#Koileo/ticket_for_allcpp&Date)


================================================
FILE: config.txt
================================================
[time]
ntp = ntp.aliyun.com

[ticket]
sleep = 1
num_thread = 3


================================================
FILE: cookie.txt
================================================
你的cookie
ticketid

================================================
FILE: main.py
================================================
# coding=utf-8
import requests
import json
import threading
import time
import secrets
import string
import hashlib
import ntplib
import configparser


# 定义一个全局锁用于线程同步
thread_dict = {}
cookie_file_path = 'cookie.txt'
config_file_path = 'config.txt'
headers = {
            'authority': 'www.allcpp.cn',
            'accept': 'application/json, text/plain, */*',
            'accept-language': 'zh-CN,zh;q=0.9',
            'content-type': 'application/json;charset=UTF-8',
            'origin': 'https://cp.allcpp.cn',
            'referer': 'https://cp.allcpp.cn/',
            'sec-ch-ua': '"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"',
            'sec-ch-ua-mobile': '?0',
            'sec-ch-ua-platform': '"Windows"',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-site',
            'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
        }


# 读取配置文件
def getConfig(filename, section, option):
    conf = configparser.ConfigParser()
    conf.read(filename)
    config = conf.get(section, option)
    return config


def timeconvey(ntp):
    chec = ntplib.NTPClient() 
    response = chec.request(ntp) 
    timestamp = response.tx_time
    timestamp_local = time.time()
    #print(timestamp_local)
    #print(timestamp)
    differ= timestamp - timestamp_local
    return differ


def sign_for_post(ticketid):
    timestamp = str(int(time.time())) ##"1682074579"
    ## 貌似并不校验 sign: a()(t + r + i + e + n)
    # nonce="jcFFFK4pPz2eNGBND3xDxTEyZ7PGCyzm" ## 32位随机值即可
    n = string.ascii_letters + string.digits
    nonce = ''.join(secrets.choice(n) for i in range(32))
    sign=hashlib.md5(f"2x052A0A1u222{timestamp}{nonce}{ticketid}2sFRs".encode('utf-8')).hexdigest()
    # print(f"ticket_type_id={ticket_type_id}")
    # print(f"nonce={nonce}")
    # print(f"timestamp={timestamp}")
    # print(f"sign={sign}")
    vital='nonce='+nonce+'&timeStamp='+timestamp+'&sign='+sign
    return vital


def cookie_string_to_dict(cookie_string):
    cookies = {}
    cookie_pairs = cookie_string.split("; ")
    
    for pair in cookie_pairs:
        key, value = pair.split("=", 1)
        cookies[key] = value
    
    return cookies


def read_cookies_and_tickets_from_file():
    cookies = []
    ticket_id = []
    ticket_ids = []
    try:
        with open(cookie_file_path, 'r',encoding='utf-8') as file:
            lines = file.readlines()
            i = 0
            while i < len(lines):
                cookies.append(lines[i].strip())  # 使用append方法将元素添加到列表
                i += 1
                if i < len(lines):
                    ticket_id.append(lines[i].strip())  # 使用append方法将元素添加到列表
                    i += 1
    except FileNotFoundError:
        print(f"File '{cookie_file_path}' not found.")
    for item in ticket_id:
    # 使用逗号分割字符串并添加到输出列表
        ticket_ids.append(item.split(','))
    return cookies, ticket_ids  # 返回两个列表作为一个元组


def getpurser(cookie_str):
    cookies = cookie_string_to_dict(cookie_str)
    pur = requests.get(
        url='https://www.allcpp.cn/allcpp/user/purchaser/getList.do',
        cookies=cookies,
        headers=headers,
    )
    purrer = pur.content.decode("utf-8")
    purrer_data = json.loads(purrer)
    return purrer_data


def check_success(cookies,ticketid):
    url = 'https://www.allcpp.cn/allcpp/user/getMyOrderList.do?pageindex=1&pagesize=10&enabled=0&orderby=0'
    list = requests.get(
        url=url,
        cookies=cookies,
        headers=headers,
    )
    listing = list.content.decode("utf-8")
    data = json.loads(listing)
    # 遍历订单信息
    for order in data['result']['list']:
        if order['ticketTypeId'] == ticketid:
            return True
        else:
            return False


def process_thread(ticketid,cookie_str):
    try:
        cookies = cookie_string_to_dict(cookie_str)
        try:
            pur = requests.get(
                url='https://www.allcpp.cn/allcpp/user/purchaser/getList.do',
                cookies=cookies,
                headers=headers,
            )

            purrer = pur.content.decode("utf-8")
            purrer_data = json.loads(purrer)
            print(purrer_data)
        except json.decoder.JSONDecodeError as e:
            pass
        ids = [str(item["id"]) for item in purrer_data]
        ids_str = ",".join(ids)
        id_count = len(ids)
        print(f"IDs for ticket {ticketid}: {ids_str}")
        json_data = {}


        retn_params = sign_for_post(ticketid)
        url = 'https://www.allcpp.cn/allcpp/ticket/buyTicketAliWapPay.do?ticketTypeId=' + str(ticketid) + '&count=' + str(
                id_count) + '&' + retn_params + '&purchaserIds=' + ids_str
        print(url)
        try:
            response = requests.post(
                    url=url,
                    cookies=cookies,
                    headers=headers,
                    json=json_data,
            )
            resp = response.content.decode("utf-8")
            parsed_resp = json.loads(resp)
        except json.decoder.JSONDecodeError as e:
            pass
        print(parsed_resp)
    except:
        pass
    
    i = 0
    if parsed_resp.get("isSuccess") == True:
        print(f"Thread for ticket {ticketid} succeeded")
        with open(f"output_ticket_{ticketid}_{ids_str}.txt", "a") as output_file:
            output_file.write(resp)
        print(f"Thread for ticket {ticketid} with cookies {cookies} succeeded, closing other two threads of the same type.")
        threads =[]
        threads_to_close = [thread for thread in threads if thread._target == process_thread and thread._args[0] == ticketid and thread._args[1] == cookies]
        for thread_to_close in threads_to_close[:2]:  # 关闭同类型的前两个线程
            thread_to_close.join()
        return True
    else:
        while i < 2:
            try:
                with open(f"output_ticket_{ticketid}_{ids_str}_attempt_{i}.txt", "a") as output_file:
                    output_file.write(resp)
                retn_params = sign_for_post(ticketid)
                url = 'https://www.allcpp.cn/allcpp/ticket/buyTicketAliWapPay.do?ticketTypeId=' + str(ticketid) + '&count=' + str(
                    id_count) + '&' + retn_params +'&purchaserIds=' + ids_str
                print(url)
                try:
                    response = requests.post(
                        url=url,
                        cookies=cookies,
                        headers=headers,
                        json=json_data,
                    )
                    resp = response.content.decode("utf-8")
                    parsed_resp = json.loads(resp)
                except json.decoder.JSONDecodeError as e:
                    pass
                print(parsed_resp)
                is_success = parsed_resp["isSuccess"]
                if is_success == True:
                    i = 3
                    print(f"Thread for ticket {ticketid} succeeded")
                    with open(f"output_ticket_{ticketid}_{ids_str}.txt", "a") as output_file:
                        output_file.write(resp)
                    threads_to_close = [thread for thread in threads if thread._target == process_thread and thread._args[0] == ticketid and thread._args[1] == cookies]
                    for thread_to_close in threads_to_close[:2]:  # 关闭同类型的前两个线程
                        thread_to_close.join()
                    return True
                else:
                    with open(f"output_ticket_{ticketid}_{ids_str}_attempt.txt", "a") as output_file:
                        output_file.write(resp)
                        url = 'https://www.allcpp.cn/allcpp/ticket/buyTicketAliWapPay.do?ticketTypeId=' + str(ticketid) + '&count=' + str(
                    id_count) + '&' + retn_params +'&purchaserIds=' + ids_str
                    print(url)
                    try:
                        response = requests.post(
                            url=url,
                            cookies=cookies,
                            headers=headers,
                            json=json_data,
                        )
                        resp = response.content.decode("utf-8")
                        parsed_resp = json.loads(resp)
                        is_success = parsed_resp["isSuccess"]
                    except json.decoder.JSONDecodeError as e:
                        pass
                    if is_success == True:
                            i = 3
                            print(f"Thread for ticket {ticketid} succeeded")
                            with open(f"output_ticket_{ticketid}_{ids_str}.txt", "a") as output_file:
                                output_file.write(resp)
                            threads_to_close = [thread for thread in threads if thread._target == process_thread and thread._args[0] == ticketid and thread._args[1] == cookies]
                            for thread_to_close in threads_to_close[:2]:  # 关闭同类型的前两个线程
                                thread_to_close.join()
                            return True
                    else:
                        with open(f"output_ticket_{ticketid}_{ids_str}_attempt.txt", "a") as output_file:
                            output_file.write(resp)
                        print(resp)
                        print(type(resp))
                        time.sleep(sleep_time)
            except:
                pass


def start(cookies, ticket_ids):
    thread_dict = {}

    for i in range(len(cookies)):
        for j in range(len(ticket_ids[i])):
             for _ in range(num_thread):
                ticket = ticket_ids[i][j]
                cook = cookies[i]
                thread = threading.Thread(target=process_thread, args=(ticket, cook))
                thread.start()


def schedule_script_at_timestamp(target_timestamp_ms,cookies, ticket_ids):
    current_timestamp_ms = int(time.time() * 1000)
    time_difference_ms = target_timestamp_ms - current_timestamp_ms

    if time_difference_ms <= 0:
        print("时间已经过去了主人")
    else:
        print(f"还有 {time_difference_ms / 1000} 秒喵~.")

        def delayed_execution():
            time.sleep(time_difference_ms / 1000)
            start(cookies, ticket_ids)
        t = threading.Thread(target=delayed_execution)
        t.start()


def main():
    ntp = getConfig("config.txt", 'time', 'ntp')
    global sleep_time
    sleep_time = float(getConfig("config.txt", 'ticket', 'sleep'))
    global num_thread
    num_thread = int(getConfig("config.txt", 'ticket', 'num_thread'))

    differ = timeconvey(ntp)
    if differ > 0 :
        print(f"\033[1;31;47m主人你的时间慢了{abs(differ)}秒\033[0m")
        print("\033[1;31;47m主人你的时间滞后了哦,请同步时间\033[0m")
    else:
        print(f"\033[1;31;47m主人你的时间快了{abs(differ)}秒\033[0m")
    cookies, ticket_ids = read_cookies_and_tickets_from_file()
    print(ticket_ids)
    i=0
    while i<len(cookies):
        ifn = getpurser(cookies[i])
        print(ifn)
        print(ticket_ids[i])
        i+=1
    if input('正确(回复T) or 错误(回复F) \n') == 'T':
        print("1.定时 2.捡漏\n")
        if input()== '1':
            target_timestamp_ms = int(input('输入开始时间戳(毫秒):'))
            schedule_script_at_timestamp(target_timestamp_ms,cookies, ticket_ids)
        else:
            start(cookies, ticket_ids)
    else: 
        exit


if __name__ == "__main__":
    main()


================================================
FILE: requirements.txt
================================================
requests
ntplib
configparser

================================================
FILE: ticket_for_allcpp.spec
================================================
# -*- mode: python ; coding: utf-8 -*-


a = Analysis(
    ['main.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[],
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    [],
    name='ticket_for_allcpp',
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    upx_exclude=[],
    runtime_tmpdir=None,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)


================================================
FILE: timentp.py
================================================
import ntplib
import time
def timeconvey():
    chec = ntplib.NTPClient() 
    response = chec.request('ntp.aliyun.com') 
    timestamp = response.tx_time
    timestamp_local = time.time()
    #print(timestamp_local)
    #print(timestamp)
    differ= timestamp - timestamp_local
    return differ
Download .txt
gitextract_k9dgtw2k/

├── .github/
│   └── workflows/
│       └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── config.txt
├── cookie.txt
├── main.py
├── requirements.txt
├── ticket_for_allcpp.spec
└── timentp.py
Download .txt
SYMBOL INDEX (12 symbols across 2 files)

FILE: main.py
  function getConfig (line 35) | def getConfig(filename, section, option):
  function timeconvey (line 42) | def timeconvey(ntp):
  function sign_for_post (line 53) | def sign_for_post(ticketid):
  function cookie_string_to_dict (line 68) | def cookie_string_to_dict(cookie_string):
  function read_cookies_and_tickets_from_file (line 79) | def read_cookies_and_tickets_from_file():
  function getpurser (line 101) | def getpurser(cookie_str):
  function check_success (line 113) | def check_success(cookies,ticketid):
  function process_thread (line 130) | def process_thread(ticketid,cookie_str):
  function start (line 250) | def start(cookies, ticket_ids):
  function schedule_script_at_timestamp (line 262) | def schedule_script_at_timestamp(target_timestamp_ms,cookies, ticket_ids):
  function main (line 278) | def main():

FILE: timentp.py
  function timeconvey (line 3) | def timeconvey():
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (22K chars).
[
  {
    "path": ".github/workflows/release.yml",
    "chars": 4495,
    "preview": "name: Python Release\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n    Windows-amd64:\n        permissions: write-all\n      "
  },
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "build/\ndist/\n"
  },
  {
    "path": "LICENSE",
    "chars": 1063,
    "preview": "MIT License\n\nCopyright (c) 2024 Koileo\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof "
  },
  {
    "path": "README.md",
    "chars": 1379,
    "preview": "# ticket_for_allcpp\n\n开源免费,简单易用,多线程暴力 CPP 抢票工具。\n\n> [!NOTE]\n> 本程序仅供学习交流, 不得用于商业用途\n> 使用本程序进行违法操作产生的法律责任由操作者自行承担\n> 本次cp30最好使"
  },
  {
    "path": "config.txt",
    "chars": 69,
    "preview": "[time]\r\nntp = ntp.aliyun.com\r\n\r\n[ticket]\r\nsleep = 1\r\nnum_thread = 3\r\n"
  },
  {
    "path": "cookie.txt",
    "chars": 18,
    "preview": "你的cookie\r\nticketid"
  },
  {
    "path": "main.py",
    "chars": 11724,
    "preview": "# coding=utf-8\r\nimport requests\r\nimport json\r\nimport threading\r\nimport time\r\nimport secrets\r\nimport string\r\nimport hashl"
  },
  {
    "path": "requirements.txt",
    "chars": 28,
    "preview": "requests\nntplib\nconfigparser"
  },
  {
    "path": "ticket_for_allcpp.spec",
    "chars": 707,
    "preview": "# -*- mode: python ; coding: utf-8 -*-\r\n\r\n\r\na = Analysis(\r\n    ['main.py'],\r\n    pathex=[],\r\n    binaries=[],\r\n    datas"
  },
  {
    "path": "timentp.py",
    "chars": 306,
    "preview": "import ntplib\r\nimport time\r\ndef timeconvey():\r\n    chec = ntplib.NTPClient() \r\n    response = chec.request('ntp.aliyun.c"
  }
]

About this extraction

This page contains the full source code of the Koileo/ticket_for_allcpp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (19.3 KB), approximately 5.0k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!