Compare commits

...

33 Commits

Author SHA1 Message Date
b8b13616a8 fix broken api 2025-08-27 04:17:30 +03:00
8f5772b5dc fix broken api 2025-08-27 04:16:36 +03:00
5d11a1a3de fix broken api 2025-08-27 04:14:15 +03:00
abb4570958 update for new channel-helper 2025-08-09 23:06:18 +03:00
1b88ad2ac8 update for new channel-helper 2025-08-09 23:03:48 +03:00
f1684edef8 update for new channel-helper 2025-08-09 22:17:02 +03:00
3e275f54c4 update for new channel-helper 2025-08-09 22:08:58 +03:00
a9518259fb handle empty tags 2025-08-09 18:07:27 +03:00
2c2088d993 fix get subscriptions endpoint 2025-08-01 10:03:28 +03:00
69d0950037 fix get subscriptions endpoint 2025-08-01 09:59:57 +03:00
baea50eb73 init sanic-redis 2025-08-01 09:57:46 +03:00
a9c022b992 allow authorization header in cors 2025-08-01 09:51:25 +03:00
40644bcb4b add error http statuses 2025-08-01 09:44:23 +03:00
3723280e3d fix invalid auth response 2025-08-01 09:42:53 +03:00
cf9aad9aee fix invalid auth response 2025-08-01 09:34:54 +03:00
94184f8635 add cors to server 2025-08-01 05:09:25 +03:00
b52afa59cd fix resend after 2025-07-31 12:39:08 +03:00
15d4853402 fix resend after 2025-07-31 12:37:25 +03:00
ca194a8a2c fix resend after 2025-07-31 12:31:50 +03:00
687dabc354 fix resend after 2025-07-31 12:30:52 +03:00
af1479bc75 fix resend after 2025-07-31 12:29:41 +03:00
7bdebae54a fix resend after 2025-07-31 12:28:56 +03:00
c88e5e7ab1 fix resend after 2025-07-31 12:26:37 +03:00
973f432b53 fix server 2025-07-31 03:14:05 +03:00
1b3af70082 fix server auth 2025-07-31 03:05:05 +03:00
9b9197be0e fix handle send method returning image hash 2025-07-26 13:55:14 +03:00
fed68c0dfb update rpc response format 2025-07-26 02:45:36 +03:00
60c6448f72 handle send method returning image hash 2025-07-25 17:53:22 +03:00
01ba70c0ce fix tag alias filter 2025-07-15 03:34:57 +03:00
4c3b80fc5a do not update alias check progress on each iteration 2025-07-15 01:52:07 +03:00
2b6e9bd2df do not update alias check progress on each iteration 2025-07-14 17:39:16 +03:00
fab1798f7e validate tag alias status 2025-07-14 17:36:03 +03:00
92599a335f validate tag alias status 2025-07-14 17:35:49 +03:00
4 changed files with 65 additions and 41 deletions

View File

@@ -177,11 +177,11 @@ class E621:
})).json() })).json()
if 'success' in r: if 'success' in r:
return [] return []
return [E621PostVersion.from_dict(p) for p in r] return [E621PostVersion.from_dict(p) for p in r if p.get('tags') is not None]
async def get_tag_aliases(self, name: str) -> List[str]: async def get_tag_aliases(self, name: str) -> List[str]:
data = (await self.client.get('/tag_aliases.json', params={'search[antecedent_name]': name})).json() data = (await self.client.get('/tag_aliases.json', params={'search[antecedent_name]': name})).json()
logging.warning(f'{name}: {data}') logging.warning(f'{name}: {data}')
if 'tag_aliases' in data: if 'tag_aliases' in data:
return [] return []
return [alias['consequent_name'] for alias in data] return [alias['consequent_name'] for alias in data if alias['status'] == 'active']

View File

@@ -4,3 +4,4 @@ USERS=9893249151
AWS_ACCESS_KEY=AKIAUIXZQT AWS_ACCESS_KEY=AKIAUIXZQT
AWS_SECRET_KEY=QyBnXOhmlc AWS_SECRET_KEY=QyBnXOhmlc
AWS_S3_BUCKET=bucket AWS_S3_BUCKET=bucket
UPLOAD_KEY=123

53
main.py
View File

@@ -36,6 +36,7 @@ e621 = E621()
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
bot = Bot(token=os.environ['BOT_TOKEN']) bot = Bot(token=os.environ['BOT_TOKEN'])
dp = Dispatcher() dp = Dispatcher()
upload_key = os.environ['UPLOAD_KEY']
ChatFilter = F.chat.id.in_(set(map(int, os.environ['USERS'].split(',')))) ChatFilter = F.chat.id.in_(set(map(int, os.environ['USERS'].split(','))))
@@ -183,27 +184,38 @@ async def check_updates():
@dp.message(filters.Command('resend_after'), ChatFilter) @dp.message(filters.Command('resend_after'), ChatFilter)
async def resend_after(msg: Message): async def resend_after(msg: Message):
args = msg.text.split()[1:]
try: try:
timestamp = int(msg.text.split()[1]) timestamp = int(args[0])
skip_to_sub = args[1] if len(args) > 1 else None
except: except:
traceback.print_exc() traceback.print_exc()
await msg.reply('Invalid timestamp or not provided') await msg.reply('Invalid timestamp or not provided')
return return
async with redis.lock(REDIS_LOCK_KEY): async with redis.lock(REDIS_LOCK_KEY):
tag_list = [tuple(t.decode().split()) for t in await redis.smembers(REDIS_SUBS_KEY)] tags = sorted(await redis.smembers(REDIS_SUBS_KEY))
if skip_to_sub is not None and skip_to_sub in tags:
tags = tags[tags.index(skip_to_sub):]
tag_list = [tuple(t.decode().split()) for t in tags]
for i, tag in enumerate(tag_list): for i, tag in enumerate(tag_list):
await msg.reply(f'Checking tag <b>{tag}</b> ({i+1}/{len(tag_list)})', parse_mode=ParseMode.HTML)
posts = [] posts = []
page = 1 page = 1
while True: while True:
page_posts = await e621.get_posts(tag, page) break_loop = False
if page > 10:
break
page_posts = await e621.get_posts(' '.join(tag), page)
if not page_posts: if not page_posts:
break break
for post in page_posts: for post in page_posts:
if datetime.datetime.fromisoformat(post.created_at).timestamp() < timestamp: post_created_at = datetime.datetime.fromisoformat(post.created_at)
if post_created_at.timestamp() < timestamp:
break_loop = True
break break
posts.append(post) posts.append(post)
if break_loop:
break
page += 1 page += 1
for post in posts[::-1]: for post in posts[::-1]:
await send_post(post, tag_list) await send_post(post, tag_list)
@@ -218,7 +230,10 @@ async def add_tag(msg: Message):
return return
for tag in args.split(): for tag in args.split():
posts = await e621.get_posts(tag) posts = await e621.get_posts(tag)
await redis.sadd(REDIS_SENT_KEY, *[post.id for post in posts]) if posts:
await redis.sadd(REDIS_SENT_KEY, *[post.id for post in posts])
else:
logging.warning(f'No posts found for tag {tag}')
await redis.sadd(REDIS_SUBS_KEY, tag) await redis.sadd(REDIS_SUBS_KEY, tag)
await msg.reply(f'Tags {args} added') await msg.reply(f'Tags {args} added')
@@ -311,11 +326,16 @@ async def check_aliases(msg: Message):
await resp.edit_text(f'Checking aliases {progress}/{len(tags)}\n\n{l}', parse_mode=ParseMode.HTML) await resp.edit_text(f'Checking aliases {progress}/{len(tags)}\n\n{l}', parse_mode=ParseMode.HTML)
for sub in tags: for sub in tags:
replaced_tags = False
for subtag in sub.split(): for subtag in sub.split():
if replacements := await e621.get_tag_aliases(subtag): if replacements := await e621.get_tag_aliases(subtag):
lines.append(f'- {subtag} -> {replacements[0]}, (<code>{sub}</code>)') lines.append(f'- {subtag} -> {replacements[0]}, (<code>{sub}</code>)')
replaced_tags = False
progress += 1 progress += 1
await send_progress() if replaced_tags:
await send_progress()
await send_progress()
@dp.message(filters.Command('update'), ChatFilter) @dp.message(filters.Command('update'), ChatFilter)
@@ -344,20 +364,19 @@ async def send_callback(cq: CallbackQuery):
img_bytes = BytesIO() img_bytes = BytesIO()
await bot.download(cq.message.photo[-1], img_bytes) await bot.download(cq.message.photo[-1], img_bytes)
img_bytes.seek(0) img_bytes.seek(0)
data = base64.b64encode(img_bytes.read()).decode()
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
r = await client.post(f'https://bots.bakatrouble.me/bots_rpc/{destination}/', json={ subdomain = 'ch' + ('sfw' if destination == 'pics' else 'nsfw')
"method": "post_photo", r = await client.post(f'https://{subdomain}.bakatrouble.me/{upload_key}/photo', files={'upload': img_bytes})
"params": [data, True], logging.info(r.text)
"jsonrpc": "2.0",
"id": 0,
})
resp = r.json() resp = r.json()
if 'result' in resp and resp['result'] == True: status = resp.get('status')
await cq.answer('Sent') if not status:
elif 'result' in resp and resp['result'] == 'duplicate': raise Exception(f'No result in response: {resp}')
elif status == 'duplicate':
await cq.answer('Duplicate') await cq.answer('Duplicate')
elif status == 'ok':
await cq.answer('Sent')
else: else:
raise Exception(resp) raise Exception(resp)
except: except:

View File

@@ -5,8 +5,8 @@ from functools import wraps
import dotenv import dotenv
import jwt import jwt
from sanic import Sanic, Unauthorized from sanic import Sanic, Unauthorized, json as jsonr
from sanic_ext import validate from sanic_ext import validate, Extend
from sanic_ext.extensions.openapi import openapi from sanic_ext.extensions.openapi import openapi
from sanic_ext.extensions.openapi.definitions import RequestBody from sanic_ext.extensions.openapi.definitions import RequestBody
from sanic_redis import SanicRedis from sanic_redis import SanicRedis
@@ -19,11 +19,16 @@ api_secret = os.environ['API_SECRET']
api_auth = json.loads(os.environ['API_AUTH']) api_auth = json.loads(os.environ['API_AUTH'])
app = Sanic('e621_bot_api') app = Sanic('e621_bot_api')
app.config.CORS_ORIGINS = '*'
app.config.CORS_HEADERS = 'Authorization, *'
app.config.update({ app.config.update({
'REDIS': 'redis://localhost', 'REDIS': 'redis://localhost',
}) })
Extend(app)
redis = SanicRedis() redis = SanicRedis()
redis.init_app(app)
async def get_subs(r): async def get_subs(r):
@@ -65,21 +70,22 @@ def protected(wrapped):
}) })
) )
@validate(json=LoginRequest) @validate(json=LoginRequest)
async def login(request): async def login(_, body: LoginRequest):
if pbkdf2_sha256(request.json['password']) != api_auth.get(request.json['username']): hash = api_auth.get(body.username)
return {'status': 'error', 'message': 'Invalid username or password'} if not hash or not pbkdf2_sha256(10000, salt=b'salt').verify(body.password, hash):
return { return jsonr({'status': 'error', 'message': 'Invalid username or password'}, 401)
return jsonr({
'token': jwt.encode({}, api_secret, algorithm='HS256'), 'token': jwt.encode({}, api_secret, algorithm='HS256'),
} })
@app.get('/api/subscriptions') @app.get('/api/subscriptions')
@protected @protected
async def get_subscriptions(request): async def get_subscriptions(_):
async with redis.conn as r: async with redis.conn as r:
return { return jsonr({
'subscriptions': await r.smembers(REDIS_SUBS_KEY), 'subscriptions': sorted(list(await get_subs(r))),
} })
@dataclass @dataclass
@@ -95,18 +101,17 @@ class UpdateSubscriptionRequest:
) )
@validate(json=UpdateSubscriptionRequest) @validate(json=UpdateSubscriptionRequest)
@protected @protected
async def delete_subscriptions(request): async def delete_subscriptions(_, body: UpdateSubscriptionRequest):
data = request.json requested_subs = {' '.join(sorted(sub.lower().split())) for sub in body.subs}
requested_subs = {' '.join(sorted(sub.lower().split())) for sub in data['subs']}
async with redis.conn as r: async with redis.conn as r:
subs = await get_subs(r) subs = await get_subs(r)
skipped = requested_subs - subs skipped = requested_subs - subs
if skipped: if skipped:
return {'status': 'error', 'message': 'Some subscriptions were not found', 'skipped': sorted(skipped)} return jsonr({'status': 'error', 'message': 'Some subscriptions were not found', 'skipped': sorted(skipped)}, 404)
await r.srem(REDIS_SUBS_KEY, *requested_subs) await r.srem(REDIS_SUBS_KEY, *requested_subs)
return {'status': 'ok', 'removed': sorted(requested_subs)} return jsonr({'status': 'ok', 'removed': sorted(requested_subs)})
@app.post('/api/subscriptions') @app.post('/api/subscriptions')
@@ -117,18 +122,17 @@ async def delete_subscriptions(request):
) )
@validate(json=UpdateSubscriptionRequest) @validate(json=UpdateSubscriptionRequest)
@protected @protected
async def add_subscriptions(request): async def add_subscriptions(_, body: UpdateSubscriptionRequest):
data = request.json requested_subs = {' '.join(sorted(sub.lower().split())) for sub in body.subs}
requested_subs = {' '.join(sorted(sub.lower().split())) for sub in data['subs']}
async with redis.conn as r: async with redis.conn as r:
subs = await get_subs(r) subs = await get_subs(r)
conflicts = requested_subs & subs conflicts = requested_subs & subs
if conflicts: if conflicts:
return {'status': 'error', 'message': 'Some subscriptions already exist', 'conflicts': sorted(conflicts)} return jsonr({'status': 'error', 'message': 'Some subscriptions already exist', 'conflicts': sorted(conflicts)}, 409)
await r.sadd(REDIS_SUBS_KEY, *data['subs']) await r.sadd(REDIS_SUBS_KEY, *body.subs)
return {'status': 'ok', 'added': sorted(requested_subs)} return jsonr({'status': 'ok', 'added': sorted(requested_subs)})
if __name__ == '__main__': if __name__ == '__main__':