物实换封面脚本

这玩意也是鸽了老长时间,一开始我发现怎么做的时候就随便写了个雏形,然后也没用几次,后来又去鼓捣别的玩意,就这样给搁置了
今天晚上把它随便完善了一下
使用Python编写,需要requests库
首先要用这个脚本获取一下登录参数(token和authcode)
请务必保管好自己的token和authcode,一旦泄露,任何得到它的人都可以通过直接调用API来使用你的账号!

import gzip
import json
import random
import requests
from requests import HTTPError


def random_device_id() -> str:
    """
    生成随机的设备ID(Device.Identifier),用于登录

    设备ID是40个十六进制数字(字母小写,没有0x前缀)
    :return: 随机设备ID字符串
    """
    return hex(random.randint(0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))[2:].rjust(40, '0')


headers = {
    'x-API-Version': '2411',
    'Accept-Encoding': 'gzip',
    'Content-Type': 'gzipped/json'
}


def login(email: str, password: str) -> requests.Response:
    data = {
        'Login': email,
        'Password': password,
        'Version': 2411,

        'Device': {
            'Identifier': random_device_id(),
            'Timezone': 'China Standard Time',
            'Language': 'Chinese'
        }
    }

    g = gzip.compress(json.dumps(data).encode('utf-8'))
    return requests.post('https://physics-api-cn.turtlesim.com/Users/Authenticate', headers=headers, data=g)


def main():
    email = input('请输入邮箱:')
    password = input('请输入密码:')
    resp = login(email, password)
    resp.raise_for_status()
    data = resp.json()
    if data['Status'] == 403:
        raise RuntimeError('邮箱或密码错误')
    elif data['Status'] != 200:
        raise RuntimeError(f'登录失败,错误{data["Status"]},信息:{data["Message"]}')

    token = data['Token']
    authcode = data['AuthCode']
    print(f'token:{token}')
    print(f'authcode:{authcode}')


if __name__ == '__main__':
    try:
        main()
    except HTTPError as e:
        print('网络或服务端错误:', e)
    except RuntimeError as e:
        print(e)

下面这个才是换封面脚本,登录成功得到token和authcode之后,把它以字符串的形式填到下面这个脚本的headers字典中,按提示操作即可

import gzip
import json
import os
import re

import requests
from requests import HTTPError

headers = {
    'x-API-Token': token,  # token,
    'x-API-AuthCode': authcode,  # authcode,
    'x-API-Version': '2411',
    'Accept-Encoding': 'gzip',
    'Content-Type': 'gzipped/json'
}


def json_gzip_encode(data: dict) -> bytes:
    """
    字典转JSON并用GZIP压缩
    :param data: 要转换的字典
    :return: 转换后的bytes
    """
    return gzip.compress(json.dumps(data).encode('utf-8'))


def get_summary(content_id: str, category: str) -> requests.Response:
    """
    获取实验介绍(没有电路)
    :param content_id: 实验序号
    :param category: 实验分区,实验区 = Experiment,黑洞区 = Discussion
    :return: 响应对象
    """
    data = {
        'ContentID': content_id,
        'Category': category
    }

    return requests.post('https://physics-api-cn.turtlesim.com/Contents/GetSummary',
                         headers=headers,
                         data=json_gzip_encode(data))


def submit_experiment(data: dict) -> requests.Response:
    """
    更新实验
    :param data: 实验的字典
    :return: 响应对象
    """
    return requests.post('https://physics-api-cn.turtlesim.com/Contents/SubmitExperiment',
                         headers=headers,
                         data=json_gzip_encode(data))


def upload(filename: str, policy: str, authorization: str) -> requests.Response:
    """
    上传实验图片
    :param filename: 图片文件名
    :param policy: 由更新实验请求得到的policy字段
    :param authorization: 由更新实验请求得到的authorization字段
    :return: 响应对象
    """
    with open(filename, 'rb') as f:
        data = {
            'policy': (None, policy, None),
            'authorization': (None, authorization, None),
            'file': ('temp.jpg', f, None)
        }
        return requests.post('http://v0.api.upyun.com/qphysics', files=data)


def confirm_experiment(summary_id: str, category: str, image: int) -> requests.Response:
    """
    实验图片上传完成后的确认
    :param summary_id: 实验序号
    :param category: 实验分区,实验区 = Experiment,黑洞区 = Discussion
    :param image: 图片序号
    :return: 响应对象
    """
    data = {
        'Category': category,
        'SummaryID': summary_id,
        'Image': image,
        'Extension': '.jpg'
    }
    return requests.post('https://physics-api-cn.turtlesim.com/Contents/ConfirmExperiment',
                         headers=headers,
                         data=json_gzip_encode(data))


def main():
    content_id = input('请输入实验序号:').strip()
    category_input = input('请输入实验分区,1=实验,2=黑洞:').strip()
    filename = input('请输入图片文件名:').strip('"')

    if not re.match('^[a-fA-F0-9]{24}$', content_id):
        raise RuntimeError('实验序号无效')
    categories = {'1': 'Experiment', '2': 'Discussion'}
    if category_input not in categories:
        raise RuntimeError('实验分区无效')
    category = categories[category_input]

    # ---------------------
    # 获取实验简介
    summary_resp = get_summary(content_id, category)
    summary_resp.raise_for_status()
    summary_json = summary_resp.json()

    status = summary_json['Status']
    message = summary_json['Message']

    match status:
        case 403:
            raise RuntimeError(f'登录失效,请重新获取token和authcode({message})')
        case 404:
            raise RuntimeError(f'实验不存在,也可能是分区选错了!({message})')
        case _ if status != 200:
            raise RuntimeError(f'获取实验信息失败,错误{status},信息:{message}')

    summary = summary_json['Data']
    if '$type' in summary:
        del summary['$type']

    print('获取实验信息成功')

    # ---------------------
    # 更新实验
    file_size = os.path.getsize(filename)
    submit = {
        'Request': {
            'FileSize': file_size,
            'Extension': '.jpg'
        },
        'Summary': summary
    }
    submit_resp = submit_experiment(submit)
    submit_resp.raise_for_status()
    submit_json = submit_resp.json()

    status = submit_json['Status']
    message = submit_json['Message']

    if status == 403:
        raise RuntimeError(f'这不是你的实验,无法更改封面({message})')
    elif status != 200:
        raise RuntimeError(f'更新实验(请求上传)失败,错误{status},信息:{message}')

    print('更新实验(请求上传)成功')

    # 上传
    policy = submit_json['Data']['Token']['Policy']
    authorization = submit_json['Data']['Token']['Authorization']
    upload(filename, policy, authorization)
    print('图片上传完成')

    # 确认
    confirm_experiment(summary_id=summary['ID'],
                       category=category,
                       image=summary['Image'] + 1)
    print('更新图片序号完成')
    print('更换封面成功')


if __name__ == '__main__':
    try:
        main()
    except HTTPError as e:
        print('网络或服务端错误:', e)
    except OSError as e:
        print('图片文件不存在', e)
    except RuntimeError as e:
        print(e)
1 个赞

有意思,以后我试试C++也搞一个…

过了一天就修改不了吗?
这玩意也是鸽了老长时间,一开始我发现怎么做的时候就随便写了个雏形,然后也没用几次,后来又去鼓捣别的玩意,就这样给搁置了
今天晚上把它随便完善了一下
使用Python编写,需要requests库(pip install requests)
作用是可以把你的实验封面换成任何图片,格式支持jpg、png、gif(不能动)、webp

1.登录脚本

首先要用这个脚本获取一下登录参数(token和authcode)
请务必保管好自己的token和authcode,一旦泄露,任何得到它的人都可以通过直接调用API来使用你的账号!

import gzip
import json
import random
import requests
from requests import HTTPError


def random_device_id() -> str:
    """
    生成随机的设备ID(Device.Identifier),用于登录

    设备ID是40个十六进制数字(字母小写,没有0x前缀)
    :return: 随机设备ID字符串
    """
    return hex(random.randint(0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))[2:].rjust(40, '0')


headers = {
    'x-API-Version': '2411',
    'Accept-Encoding': 'gzip',
    'Content-Type': 'gzipped/json'
}


def login(email: str, password: str) -> requests.Response:
    data = {
        'Login': email,
        'Password': password,
        'Version': 2411,

        'Device': {
            'Identifier': random_device_id(),
            'Timezone': 'China Standard Time',
            'Language': 'Chinese'
        }
    }

    g = gzip.compress(json.dumps(data).encode('utf-8'))
    return requests.post('https://physics-api-cn.turtlesim.com/Users/Authenticate', headers=headers, data=g)


def main():
    email = input('请输入邮箱:')
    password = input('请输入密码:')
    resp = login(email, password)
    resp.raise_for_status()
    data = resp.json()
    if data['Status'] == 403:
        raise RuntimeError('邮箱或密码错误')
    elif data['Status'] != 200:
        raise RuntimeError(f'登录失败,错误{data["Status"]},信息:{data["Message"]}')

    token = data['Token']
    authcode = data['AuthCode']
    print(f'token:{token}')
    print(f'authcode:{authcode}')


if __name__ == '__main__':
    try:
        main()
    except HTTPError as e:
        print('网络或服务端错误:', e)
    except RuntimeError as e:
        print(e)

2.换封面脚本

下面这个是换封面脚本,登录成功得到token和authcode之后,把它以字符串的形式填到下面这个脚本的headers字典中,按提示操作即可
小窍门:windows系统按住shift右键点击文件,会有“复制文件地址”的选项,在要求输入图片文件名时粘贴进来就可以
也可以直接把文件拖进控制台(部分IDE或控制台不支持)

import gzip
import json
import os
import re

import requests
from requests import HTTPError

from token_authcode import token, authcode

headers = {
    'x-API-Token': token,  # token,
    'x-API-AuthCode': authcode,  # authcode,
    'x-API-Version': '2411',
    'Accept-Encoding': 'gzip',
    'Content-Type': 'gzipped/json'
}


def json_gzip_encode(data: dict) -> bytes:
    """
    字典转JSON并用GZIP压缩
    :param data: 要转换的字典
    :return: 转换后的bytes
    """
    return gzip.compress(json.dumps(data).encode('utf-8'))


def get_summary(content_id: str, category: str) -> requests.Response:
    """
    获取实验介绍(没有电路)
    :param content_id: 实验序号
    :param category: 实验分区,实验区 = Experiment,黑洞区 = Discussion
    :return: 响应对象
    """
    data = {
        'ContentID': content_id,
        'Category': category
    }

    return requests.post('https://physics-api-cn.turtlesim.com/Contents/GetSummary',
                         headers=headers,
                         data=json_gzip_encode(data))


def submit_experiment(data: dict) -> requests.Response:
    """
    更新实验
    :param data: 实验的字典
    :return: 响应对象
    """
    return requests.post('https://physics-api-cn.turtlesim.com/Contents/SubmitExperiment',
                         headers=headers,
                         data=json_gzip_encode(data))


def upload(filename: str, policy: str, authorization: str) -> requests.Response:
    """
    上传实验图片
    :param filename: 图片文件名
    :param policy: 由更新实验请求得到的policy字段
    :param authorization: 由更新实验请求得到的authorization字段
    :return: 响应对象
    """
    with open(filename, 'rb') as f:
        data = {
            'policy': (None, policy, None),
            'authorization': (None, authorization, None),
            'file': ('temp.jpg', f, None)
        }
        return requests.post('http://v0.api.upyun.com/qphysics', files=data)


def main():
    content_id = input('请输入实验序号:').strip()
    category_input = input('请输入实验分区,1=实验,2=黑洞:').strip()
    filename = input('请输入图片文件名:').strip('"')

    # 实验序号是24位16进制字符串
    if not re.match('^[a-fA-F0-9]{24}$', content_id):
        raise RuntimeError('实验序号无效')
    categories = {'1': 'Experiment', '2': 'Discussion'}
    if category_input not in categories:
        raise RuntimeError('实验分区无效')
    category = categories[category_input]

    # ---------------------
    # 获取实验简介
    summary_resp = get_summary(content_id, category)
    summary_resp.raise_for_status()
    summary_json = summary_resp.json()

    status = summary_json['Status']
    message = summary_json['Message']

    match status:
        case 403:
            raise RuntimeError(f'登录失效,请重新获取token和authcode({message})')
        case 404:
            raise RuntimeError(f'实验不存在,也可能是分区选错了!({message})')
        case _ if status != 200:
            raise RuntimeError(f'获取实验信息失败,错误{status},信息:{message}')

    summary = summary_json['Data']
    if '$type' in summary:
        del summary['$type']

    print('获取实验信息成功')

    # ---------------------
    # 更新实验
    file_size = os.path.getsize(filename)
    submit = {
        'Request': {
            'FileSize': file_size,
            'Extension': '.jpg'
        },
        'Summary': summary
    }
    submit_resp = submit_experiment(submit)
    submit_resp.raise_for_status()
    submit_json = submit_resp.json()

    status = submit_json['Status']
    message = submit_json['Message']

    if status == 403:
        raise RuntimeError(f'这不是你的实验,无法更改封面({message})')
    elif status != 200:
        raise RuntimeError(f'更新实验(请求上传)失败,错误{status},信息:{message}')

    print('更新实验(请求上传)成功')

    # ---------------------
    # 上传
    policy = submit_json['Data']['Token']['Policy']
    authorization = submit_json['Data']['Token']['Authorization']
    upload(filename, policy, authorization).raise_for_status()
    print('图片上传完成')

    # ---------------------
    # 更新图片序号
    # 改用/Contents/SubmitExperiment
    # 放弃/Contents/ConfirmExperiment是因为如果你是合作者就会报403
    summary['Image'] += 1
    update_image_resp = submit_experiment({'Summary': summary})
    update_image_resp.raise_for_status()
    update_image_json = update_image_resp.json()

    status = update_image_json['Status']
    message = update_image_json['Message']

    if status != 200:
        raise RuntimeError(f'更新图片序号失败,错误{status},信息:{message}')

    print('更新图片序号完成')
    print('更换封面成功')


if __name__ == '__main__':
    try:
        main()
    except HTTPError as e:
        print('网络或服务端错误:', e)
    except OSError as e:
        print('图片文件不存在', e)
    except RuntimeError as e:
        print(e)

两次SubmitExperiment会在发布实验的时候出问题

以及requests.post的json参数可以在很多时候代替你那个手动gzip

厚颜无耻贴个广告()

这个我一开始就没想让它能发布实验,只能修改,避免有人用来刷屏
还有好像不用gzip也能提交……
请求头Content-Type: application/json
Accept-Encoding去掉

刚才发现紫应该更新了api, 现在拿到token与auto_code也会爆Login.Required

1 个赞

紫:怕到时候又有一百多个水实验刷出来(

1 个赞

会过时的

实验发布频率是有限制的(应该不会在客户端限制

作者大大,你现在的脚本会炸掉
Traceback (most recent call last):
File “d:\projects\physicsLab\code\myTest.test.py”, line 155, in
main()
File “d:\projects\physicsLab\code\myTest.test.py”, line 129, in main
policy = submit_json[‘Data’][‘Token’][‘Policy’]
TypeError: ‘NoneType’ object is not subscriptable

我好像找到原因了,有些图片不行而有些图片可以,我怀疑和图片格式有关

我测试的结论是文件过大,有1MB(FileSize不能超过1048576)的限制
我手动把FileSize设置成1048576没问题,再加1就返回的Data.Token就是null了
这个问题在物实就有人提出过,而我当时无法复现,就是因为我用的都是一些截图,或者是随便下载的图片,本身就不是很大
我认为和格式没有关系,在调用/Contents/SubmitExperiment的时候,也仅仅只是写了个扩展名,实际图片还没传呢
顺便一提,这里的FileSize和实际图片大小必须一致,不然上传图床的时候就会403(message: file too large/small),不过把FileSize写成图片文件大小的相反数可以绕过这个限制

OK,我成功把一个13MB(13558055字节,FileSize:-13558055)的图片传上去了233

1 个赞

这是py?

有道理,我等会儿试一下(我目前碰到的部分情况也符合你的描述)