Since I've set up a blog, I figured I could post some simple code snippets I've written here. They're all quite basic, so I don't plan to create a separate repository for each on GitHub.
I'll start by updating the "Bilibili Comics Check-in and Coupon Redemption" script. More scripts might follow when I have time.
Bilibili Comics grants users a certain number of points daily upon check-in, which can be redeemed for coupons usable in purchasing comics.
However, the most favorable 100-point coupon is only available in limited quantities at exactly 00:00 every day.
To address this, I developed two plugins based on NoneBot2 and bilibili-api, referencing SocialSisterYi/bilibili-API-collect. One handles daily check-ins for points, and the other automatically secures the 100-point coupon at 00:00. So far, both have proven highly stable.
NoneBot2 is fairly easy to use, and I've also built several plugins on it—some of which I may share later.
The results are as follows:

Seems like the success rate is too high—no need to push notifications
Still, better safe than sorry. I’ll keep the notification feature enabled.
Used to retrieve and refresh login information
The cookie_path refers to a JSON file containing the following five keys:
You can obtain these values by logging in via an InPrivate browser window and inspecting the cookies. See the bilibili-api documentation for details.
A simple NoneBot2 plugin that allows manual execution via /漫画签到 command sent to a QQ bot, and also runs automatically at a specified time each day, reporting results.
I didn’t make it general-purpose for all QQ bot friends—only administrators can use it. Of course, you could adapt it to run directly via cron, then handle message delivery differently (or skip it altogether).
A NoneBot2 plugin for redeeming coupons at 00:00 daily. In practice, it starts running at 23:59 for preprocessing, then waits until exactly 00:00 to execute the redemption. Ensure your system clock is accurate and synchronized regularly:
The logic is similar to the check-in plugin. Refer to the exchange_func function to understand the redemption process. The code is as follows:
from bilibili_api import Credential, sync
import json
import logging
def get_sessdata(cookie_path: str) -> str:
with open(cookie_path, 'r') as f:
cookie = json.load(f)
credential = Credential(**cookie)
if sync(credential.check_refresh()):
logging.info('Cookie needs to be refreshed.')
sync(credential.refresh())
cookie = credential.get_cookies()
cookie = {k.lower(): v for k, v in cookie.items()}
with open(cookie_path, 'w') as f:
json.dump(cookie, f, indent=4)
logging.info(f'Cookie refreshed.\n\t{cookie}')
return cookie['sessdata']
from nonebot import get_driver, on_command, get_bot, require
from nonebot.log import logger
from nonebot.adapters.onebot.v11 import Bot, Event
from nonebot.typing import T_State
from nonebot.permission import SUPERUSER
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler
import requests
import time
import traceback
from .get_sessdata import get_sessdata
checkin = on_command("漫画签到", priority=5, permission=SUPERUSER) # Only admins allowed
async def checkin_func():
url = "https://manga.bilibili.com/twirp/activity.v1.Activity/ClockIn?platform=android"
headers = {
'user-agent': "Dalvik/2.1.0 (Linux; U; Android 14; 23046RP50C Build/UKQ1.230804.001) 6.8.5 os/android model/23046RP50C mobi_app/android_comic build/36608060 channel/xiaomi innerVer/36608060 osVer/14 network/2",
'accept': 'application/json, text/plain, */*',
'accept-encoding': 'gzip, deflate, br',
}
# Cookie path hidden
headers.update({'cookie': f'SESSDATA={get_sessdata(config.cookie_path)}'})
logger.info(f'headers: {headers}')
try:
resp = requests.post(url, headers=headers)
resp.raise_for_status()
retdata = resp.json()
if retdata['code'] == 0:
msg = 'Bilibili comics check-in successful'
logger.success(msg)
else:
msg = 'Bilibili comics check-in failed, ' + retdata['msg']
logger.error(msg)
except Exception as e:
msg = 'Bilibili comics check-in failed, ' + str(e)
logger.error(traceback.format_exc())
return msg
@checkin.handle()
async def handle_checkin(bot: Bot, event: Event, state: T_State):
msg = await checkin_func()
await checkin.finish(msg)
# Set check-in time
@scheduler.scheduled_job('cron', hour=14, minute=57, id='manga_checkin')
async def handle_checkin_scheduler():
msg = await checkin_func()
bot = get_bot()
# Send scheduled messages only to admins
await bot.send_private_msg(user_id=next(iter(bot.config.superusers)), message=msg)
timedatectl set-ntp true
from nonebot import get_driver, on_command, get_bot, require
from nonebot.log import logger
from nonebot.adapters.onebot.v11 import Bot, Event
from nonebot.typing import T_State
from nonebot.permission import SUPERUSER
require("nonebot_plugin_apscheduler")
from nonebot_plugin_apscheduler import scheduler
import requests
import time
import traceback
from .get_sessdata import get_sessdata
exchange = on_command("漫画兑换", priority=5, permission=SUPERUSER) # Only admins allowed
async def exchange_func():
point_of_target = 100 # Redeem only 100-point coupons
type_of_target = 7 # Only redeem comic coupons, avoiding other product types
get_point_url = 'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/GetUserPoint'
list_url = 'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/ListProduct'
exchange_url = 'https://manga.bilibili.com/twirp/pointshop.v1.Pointshop/Exchange'
headers = {
'user-agent': "Dalvik/2.1.0 (Linux; U; Android 14; 23046RP50C Build/UKQ1.230804.001) 6.8.5 os/android model/23046RP50C mobi_app/android_comic build/36608060 channel/xiaomi innerVer/36608060 osVer/14 network/2",
'accept': 'application/json, text/plain, */*',
'accept-encoding': 'gzip, deflate, br',
}
# Cookie path hidden
headers.update({'cookie': f'SESSDATA={get_sessdata(config.cookie_path)}'})
logger.info(f'headers: {headers}')
def get_point(session):
try:
resp = session.post(get_point_url)
resp.raise_for_status()
return resp.json()['data']['point']
except Exception as e:
logger.error(traceback.format_exc())
return None
def get_target_id(session):
try:
resp = session.post(list_url)
resp.raise_for_status()
if resp.json()['code'] != 0:
raise RuntimeError('Failed to get product list.')
products = resp.json()['data']
for product in products:
if product['real_cost'] == point_of_target and product['type'] == type_of_target:
return product['id']
return -1
except Exception as e:
logger.error(traceback.format_exc())
return None
def exchange(session, product_id):
try:
resp = session.post(exchange_url, json={'product_id': product_id, 'product_num': 1, 'point': point_of_target})
resp.raise_for_status()
return resp.json()
except Exception as e:
logger.error(traceback.format_exc())
return None
session = requests.Session()
session.headers.update(headers)
# Get remaining points; exit if below target
point, retry = None, 10
while point is None and retry > 0:
point = get_point(session)
retry -= 1
if point is None:
msg = 'Failed to retrieve remaining points'
logger.error(msg)
return msg
point = int(point)
logger.info(f'Current remaining points: {point}')
if point < point_of_target:
msg = f'Remaining {point}, insufficient points'
logger.error(msg)
return msg
# Get target product ID
product_id, retry = None, 10
while product_id is None and retry > 0:
product_id = get_target_id(session)
retry -= 1
if product_id is None:
msg = 'Failed to retrieve target product ID'
logger.error(msg)
return msg
logger.info(f'Target product ID: {product_id}')
# Execute redemption
retry, success_times = 100, 0
while retry > 0:
if time.strftime("%H", time.localtime()) == '23':
while time.strftime("%H", time.localtime()) != '00':
pass # Wait until 00:00
resp = exchange(session, product_id)
if resp is None:
retry -= 1
continue
if resp['code'] == 2:
msg = 'Redemption failed, ' + resp['msg']
break
if resp['code'] == 1 and resp['msg'] == 'Insufficient points':
msg = 'Redemption failed, ' + resp['msg']
break
if resp['code'] != 0:
msg = 'Redemption failed, ' + resp['msg']
retry -= 1
continue
else:
success_times += 1
msg = f'Redemption successful {success_times} times'
time.sleep(0.05)
if success_times > 0:
msg = f'Redemption successful {success_times} times'
logger.info(msg)
return msg
@exchange.handle()
async def handle_exchange(bot: Bot, event: Event, state: T_State):
msg = await exchange_func()
await exchange.finish(msg)
@scheduler.scheduled_job('cron', hour='23', minute='59', id='manga_exchange')
async def handle_exchange_scheduler():
msg = await exchange_func()
bot = get_bot()
await bot.send_private_msg(user_id=next(iter(bot.config.superusers)), message=msg)