aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorclarkzjw <[email protected]>2023-02-28 23:28:20 -0800
committerclarkzjw <[email protected]>2023-02-28 23:28:20 -0800
commit9cf9a5b71890e4fdb3363e3709050ae6ce26b092 (patch)
tree5b82e2636985d3052aed39b22c6c752718600c2c
parente0a69eb031316ae145f1c40319bceb8ec520ad44 (diff)
parente1e8c54eafd61d31aa520593ece8a68d79dbdeeb (diff)
downloadphoto-9cf9a5b71890e4fdb3363e3709050ae6ce26b092.tar.gz
Merge branch 'feature/bot'
add python telegram bot to upload photos to cloudflare r2
-rw-r--r--.gitignore1
-rw-r--r--bot.py178
-rw-r--r--requirements.txt21
3 files changed, 200 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index fafa5b5..f157e65 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ _site/
3.DS_Store 3.DS_Store
4.ruby-version 4.ruby-version
5.jekyll-cache/ 5.jekyll-cache/
6.venv/
diff --git a/bot.py b/bot.py
new file mode 100644
index 0000000..20529c0
--- /dev/null
+++ b/bot.py
@@ -0,0 +1,178 @@
1import io
2import os
3import logging
4import traceback
5from PIL import Image
6import boto3
7from telegram import Update
8from telegram.constants import ParseMode
9from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
10from peewee import *
11from uuid import uuid4
12from datetime import datetime
13import json
14
15db = SqliteDatabase("database/photos.db")
16db.connect(reuse_if_open=True)
17
18
19class BaseModel(Model):
20 class Meta:
21 database = db
22
23
24class Photo(BaseModel):
25 guid = CharField(unique=True, primary_key=True)
26 fileId = CharField(max_length=256)
27 width = IntegerField()
28 height = IntegerField()
29 ratio = FloatField()
30 orientation = CharField(max_length=128)
31 path = CharField(max_length=256)
32 caption = CharField(max_length=256)
33 alt = CharField(max_length=256)
34 createdAt = DateTimeField()
35 uploadedAt = DateTimeField()
36
37
38with db.connection_context():
39 db.create_tables([Photo])
40
41logging.basicConfig(
42 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
43)
44
45logger = logging.getLogger(__name__)
46
47
48async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
49 await update.message.reply_text("This is a bot to output image in square shape")
50
51
52bucket_name = "pixel-jinwei-me"
53cf_account_id = os.getenv("CF_ACCOUNT_ID")
54aws_access_key_id = os.getenv("CF_R2_KEY_ID")
55aws_secret_access_key = os.getenv("CF_R2_ACCESS_KEY_SECRET")
56
57
58def write_json() -> bool:
59 with db.connection_context():
60 photos = Photo.select()
61 results = []
62 for photo in photos:
63 results.append({
64 "guid": photo.guid,
65 "fileId": photo.fileId,
66 "width": photo.width,
67 "height": photo.height,
68 "ratio": photo.ratio,
69 "orientation": photo.orientation,
70 "path": photo.path,
71 "caption": photo.caption,
72 "alt": photo.alt,
73 "createdAt": photo.createdAt.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
74 "uploadedAt": photo.uploadedAt.strftime("%Y-%m-%dT%H:%M:%S.000Z")
75 })
76 with open("database/photos.json", "w") as f:
77 f.write(json.dumps(results))
78 return True
79
80
81def upload_to_s3(key_name: str, file: bytes):
82 endpoint_ca_central = "https://{}.r2.cloudflarestorage.com".format(cf_account_id)
83
84 client = boto3.client("s3",
85 region_name="auto",
86 endpoint_url=endpoint_ca_central,
87 aws_access_key_id=aws_access_key_id,
88 aws_secret_access_key=aws_secret_access_key)
89
90 response = client.list_buckets()
91 buckets = [b["Name"] for b in response['Buckets']]
92
93 if bucket_name not in buckets:
94 print("{} doesn't exist".format(bucket_name))
95 print('Existing buckets:')
96 for bucket in response['Buckets']:
97 print(f' {bucket["Name"]}')
98 return False
99
100 response = client.put_object(Body=file, Bucket=bucket_name, Key=key_name, ContentType="image/webp")
101 status = response["ResponseMetadata"]["HTTPStatusCode"]
102 if status == 200:
103 print("upload {} to {} succeed".format(key_name, bucket_name))
104 return True
105 else:
106 print("upload {} to {} failed, status: {}".format(key_name, bucket_name, status))
107 return False
108
109
110async def process(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
111 chat_id = update.message.chat_id
112
113 if update.message.document is not None:
114 names = update.message.document.file_name.split(".")
115 file_ext = names[1]
116 filename = names[0]
117
118 if str.upper(file_ext) not in ("JPG", "JPEG", "PNG"):
119 await context.bot.send_message(chat_id, "Image extension `{}` not supported".format(file_ext),
120 parse_mode=ParseMode.MARKDOWN_V2)
121 return
122
123 file = await update.message.effective_attachment.get_file()
124 else:
125 return
126
127 await context.bot.send_message(chat_id, "Processing `{}`".format(filename), parse_mode=ParseMode.MARKDOWN_V2)
128
129 img = io.BytesIO()
130 await file.download_to_memory(img)
131
132 try:
133 im = Image.open(img)
134 output = io.BytesIO()
135 im.save(output, format="webp", lossless=True, quality=100)
136
137 now = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
138 key_name = "{}-{}.webp".format(now, filename)
139
140 with db.connection_context():
141 photo = Photo.create(guid=str.upper(str(uuid4())),
142 fileId=key_name,
143 width=im.width,
144 height=im.height,
145 ratio=im.width / im.height,
146 orientation="landscape" if im.width > im.height else "portrait",
147 path="https://pixelstatic.jinwei.me/{}".format(key_name),
148 caption="dsadas",
149 alt="dsada",
150 createdAt=datetime.now(),
151 uploadedAt=datetime.now())
152
153 output.seek(0)
154 upload_to_s3(photo.fileId, output.read())
155
156 write_json()
157 await update.message.reply_markdown_v2(text="Sending processed result")
158
159 await context.bot.send_document(chat_id=update.message.chat_id,
160 filename="{}-result.{}".format(filename, file_ext),
161 document=output.getvalue())
162
163 except Exception as e:
164 await update.message.reply_markdown_v2(text="Error:\n```{}```".format(traceback.format_exc()))
165
166
167def main() -> None:
168 tg_token = os.getenv("TG_TOKEN")
169 application = Application.builder().token(tg_token).build()
170
171 application.add_handler(CommandHandler("start", start))
172 application.add_handler(MessageHandler(filters.ATTACHMENT & ~filters.COMMAND, process))
173
174 application.run_polling()
175
176
177if __name__ == "__main__":
178 main()
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..788075c
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,21 @@
1anyio==3.6.2
2boto3==1.26.81
3botocore==1.29.81
4certifi==2022.12.7
5h11==0.14.0
6h2==4.1.0
7hpack==4.0.0
8httpcore==0.16.3
9httpx==0.23.3
10hyperframe==6.0.1
11idna==3.4
12jmespath==1.0.1
13peewee==3.16.0
14Pillow==9.4.0
15python-dateutil==2.8.2
16python-telegram-bot==20.1
17rfc3986==1.5.0
18s3transfer==0.6.0
19six==1.16.0
20sniffio==1.3.0
21urllib3==1.26.14
Powered by cgit v1.2.3 (git 2.41.0)