main.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. import os
  2. import re
  3. import magic
  4. import httpx
  5. import random, ssl
  6. import io
  7. import subprocess
  8. from urllib.parse import urlparse
  9. from PIL import Image, ExifTags,ImageDraw,ImageFont
  10. import pillow_avif
  11. from requests_toolbelt.multipart.encoder import MultipartEncoder
  12. import pyautogui # Replace pyperclip with pyautogui for sending keys
  13. import tornado.ioloop
  14. import tornado.web
  15. import tornado.gen
  16. import asyncio
  17. import logging
  18. import namegen as n
  19. # Configure logging
  20. logging.basicConfig(level=logging.DEBUG)
  21. # Define the upload folder path
  22. UPLOAD_FOLDER = './pics'
  23. ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}
  24. # Define an in-memory data store for storing the current room
  25. data_store = {'current_room': 'chad'}
  26. data_store_lock = asyncio.Lock()
  27. # Function to check if a file is allowed to be uploaded
  28. def allowed_file(filename):
  29. return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
  30. # Function to compress an image
  31. def compress_image(image):
  32. with io.BytesIO() as output:
  33. image = image.convert('RGB')
  34. image.save(output, format="JPEG", quality=40, optimize=True)
  35. contents = output.getvalue()
  36. return contents
  37. # Function to compress an image without resizing
  38. def compress_image_raw(image):
  39. with io.BytesIO() as output:
  40. image = image.convert('RGB')
  41. image.save(output, format="JPEG", quality=100, subsampling=0)
  42. contents = output.getvalue()
  43. return contents
  44. with open('sessionid.txt', 'r') as f:
  45. sessionid = f.read()
  46. async def upload_dickpic(slugstr, buffer_pic_data):
  47. async with httpx.AsyncClient(cookies={'agreeterms': '1', 'sessionid': sessionid}) as client:
  48. response = await client.get("https://chaturbate.com/emoticons/")
  49. pattern = r'name="csrfmiddlewaretoken" value="([a-zA-Z0-9]+)"'
  50. match = re.search(pattern, response.text)
  51. if not match:
  52. logging.error("CSRF token not found")
  53. return False
  54. csrfmiddlewaretoken = match.group(1)
  55. mime = magic.Magic(mime=True)
  56. mime_type = mime.from_buffer(buffer_pic_data)
  57. m = MultipartEncoder(
  58. fields={
  59. 'csrfmiddlewaretoken': csrfmiddlewaretoken,
  60. 'slug': slugstr,
  61. 'image': ('image.jpg', buffer_pic_data, mime_type),
  62. 'category': str(1),
  63. 'suggested_category': str()
  64. }
  65. )
  66. headers = {
  67. 'origin': 'https://chaturbate.com',
  68. 'content-type': m.content_type,
  69. 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
  70. }
  71. # Log headers and cookies for debugging
  72. logging.debug(f"Headers: {headers}")
  73. logging.debug(f"Cookies: {client.cookies}")
  74. r = await client.post('https://chaturbate.com/emoticons/', data=m.to_string(), headers=headers)
  75. if r.status_code == 302 and 'upload_complete' in r.headers.get('location', ''):
  76. print(slugstr)
  77. return True
  78. elif r.status_code == 403:
  79. logging.error("403 Forbidden: Check CSRF token, cookies, and headers.")
  80. logging.error(f"Response: {r.text}")
  81. return False
  82. else:
  83. logging.error(f"Unexpected status code: {r.status_code}")
  84. logging.error(f"Response: {r.text}")
  85. return False
  86. # Function to play a beep sound
  87. def play_beep(frequency=440, duration=1, volume=10):
  88. command = f'ffplay -nodisp -autoexit -f lavfi -i "sine=frequency={frequency}:duration={duration}" -volume {volume}'
  89. subprocess.Popen(command, shell=True)
  90. # Function to send a file extension
  91. def send_ext(filename):
  92. return filename.rsplit('.', 1)[1].lower()
  93. def addSexualComment(image): # Get image dimensions
  94. # Get image dimensions
  95. image_width, image_height = image.size
  96. # Initialize ImageDraw
  97. draw = ImageDraw.Draw(image)
  98. # Define your text components
  99. room_name = data_store.get('current_room')
  100. text_before_room = "Hopefully you like this @"
  101. text_after_room = room_name
  102. # Load a font
  103. font_size = int(image_height * 0.05) # Start with 5% of the image height
  104. font = ImageFont.truetype("ariel.otf", font_size) # Ensure the font path is correct
  105. # Calculate the size of each text component
  106. text_before_bbox = draw.textbbox((0, 0), text_before_room, font=font)
  107. text_before_width = text_before_bbox[2] - text_before_bbox[0]
  108. text_after_bbox = draw.textbbox((0, 0), text_after_room, font=font)
  109. text_after_width = text_after_bbox[2] - text_after_bbox[0]
  110. total_text_width = text_before_width + text_after_width
  111. total_text_height = max(text_before_bbox[3] - text_before_bbox[1], text_after_bbox[3] - text_after_bbox[1])
  112. # If the total text is too wide, reduce the font size until it fits
  113. while total_text_width > image_width - 20: # Leave some padding (20 pixels total)
  114. font_size -= 1
  115. font = ImageFont.truetype("ariel.otf", font_size) # Update font with new size
  116. text_before_bbox = draw.textbbox((0, 0), text_before_room, font=font)
  117. text_before_width = text_before_bbox[2] - text_before_bbox[0]
  118. text_after_bbox = draw.textbbox((0, 0), text_after_room, font=font)
  119. text_after_width = text_after_bbox[2] - text_after_bbox[0]
  120. total_text_width = text_before_width + text_after_width
  121. total_text_height = max(text_before_bbox[3] - text_before_bbox[1], text_after_bbox[3] - text_after_bbox[1])
  122. # Calculate position for the combined text
  123. x_position = (image_width - total_text_width) / 2
  124. y_position = image_height - total_text_height - 10
  125. # Draw the first part of the text in white
  126. draw.text((x_position, y_position), text_before_room, font=font, fill="white")
  127. # Draw the room name in red
  128. x_position += text_before_width
  129. draw.text((x_position, y_position), text_after_room, font=font, fill="red")
  130. return image
  131. # Function to handle EXIF orientation
  132. def handle_exif_orientation(image):
  133. exif = image._getexif()
  134. if exif is not None:
  135. orientation_tag = None
  136. for orientation in ExifTags.TAGS.keys():
  137. if ExifTags.TAGS[orientation] == 'Orientation':
  138. orientation_tag = orientation
  139. break
  140. if orientation_tag is not None and orientation_tag in exif:
  141. orientation = exif[orientation_tag]
  142. if orientation == 3:
  143. return image.rotate(180, expand=True)
  144. elif orientation == 6:
  145. return image.rotate(270, expand=True)
  146. elif orientation == 8:
  147. return image.rotate(90, expand=True)
  148. return image
  149. async def run_command(command):
  150. # subprocess.run(command, shell=True)
  151. await asyncio.to_thread(subprocess.run, command, shell=True)
  152. async def save_image_async(image, path, quality=60):
  153. await asyncio.to_thread(image.save, path, quality=quality)
  154. async def upload_image(image, slugstr):
  155. async with httpx.AsyncClient() as client:
  156. with io.BytesIO() as output:
  157. image.save(output, format="JPEG")
  158. contents = output.getvalue()
  159. files = {'file': ('image.jpg', contents, 'image/jpeg')}
  160. data = {'msg': f":{slugstr} @{data_store.get('current_room')}"}
  161. response = await client.post("http://opc:2525/upload_image", files=files, data=data)
  162. if response.status_code == 200:
  163. print("Image uploaded successfully!")
  164. class BeepHandler(tornado.web.RequestHandler):
  165. async def get(self):
  166. play_beep(duration=0.05, volume=60)
  167. self.write("")
  168. class WebRTCHandler(tornado.web.RequestHandler):
  169. async def get(self):
  170. self.set_header('Access-Control-Allow-Origin', '*')
  171. self.render('camtest.html', name="")
  172. class UploadFileHandler(tornado.web.RequestHandler):
  173. def options(self):
  174. self.set_status(204)
  175. self.set_header('Access-Control-Allow-Origin', '*')
  176. self.set_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
  177. self.set_header('Access-Control-Allow-Headers', 'Content-Type')
  178. self.finish()
  179. async def get(self):
  180. self.set_header('Access-Control-Allow-Origin', '*')
  181. self.render('index.html', name="")
  182. async def post(self):
  183. self.set_header('Access-Control-Allow-Origin', '*')
  184. async with data_store_lock:
  185. current_room = data_store.get('current_room')
  186. files = self.request.files.get('file')
  187. if not files:
  188. self.write("No file part")
  189. return
  190. file = files[0]
  191. if file.filename == '':
  192. self.write("No selected file")
  193. return
  194. if file and allowed_file(file.filename):
  195. slugstr = "penis" + "".join([n.generate_nickname() for _ in range(2)])
  196. filename = slugstr + "." + send_ext(file.filename)
  197. img = Image.open(io.BytesIO(file.body))
  198. img = handle_exif_orientation(img)
  199. # img = addSexualComment(img)
  200. tiny_img = compress_image(img)
  201. raw_img_flipped = Image.open(io.BytesIO(compress_image_raw(img)))
  202. results = await asyncio.gather(*[upload_dickpic(slugstr=slugstr, buffer_pic_data=tiny_img)])
  203. if any(results):
  204. filepath_to_save_avif = os.path.join(UPLOAD_FOLDER, slugstr + ".avif")
  205. # unused pyautogui.write(":" + slugstr + " ") # Use pyautogui to simulate keypress instead of pyperclip
  206. await run_command("echo 'type :"+slugstr+" ' | dotool")
  207. play_beep(duration=0.05, volume=60)
  208. # unused await run_command("echo key ctrl+v | dotool")
  209. await save_image_async(raw_img_flipped, filepath_to_save_avif)
  210. await upload_image(raw_img_flipped, slugstr)
  211. self.redirect("/" + slugstr, status=302)
  212. return
  213. self.render('index.html', name="")
  214. class ShowSlugHandler(tornado.web.RequestHandler):
  215. async def get(self, name):
  216. self.render('index.html', name=name)
  217. class SetRoomHandler(tornado.web.RequestHandler):
  218. async def post(self):
  219. data = tornado.escape.json_decode(self.request.body)
  220. parsed_url = urlparse(data['url'])
  221. if parsed_url.netloc != "chaturbate.com":
  222. self.write("not chaturbate")
  223. return
  224. path = parsed_url.path
  225. path_parts = path.strip('/').split('/')
  226. async with data_store_lock:
  227. data_store['current_room'] = path_parts[0]
  228. print(path_parts[0])
  229. self.write("")
  230. class DownloadFileHandler(tornado.web.RequestHandler):
  231. async def get(self, name):
  232. file_path = os.path.join(UPLOAD_FOLDER, name)
  233. if os.path.exists(file_path):
  234. self.set_header('Content-Type', 'application/octet-stream')
  235. self.set_header('Content-Disposition', f'attachment; filename={name}')
  236. with open(file_path, 'rb') as f:
  237. while chunk := f.read(1024 * 1024):
  238. self.write(chunk)
  239. self.finish()
  240. else:
  241. self.set_status(404)
  242. self.write("File not found")
  243. class SendKeysHandler(tornado.web.RequestHandler):
  244. async def get(self):
  245. command = "{ echo mouseto 0.5 0.5; echo click; echo click left; echo key enter; sleep 1; echo key left; } | dotool"
  246. await run_command(command)
  247. self.write("")
  248. class SendCockEnterKeysHandler(tornado.web.RequestHandler):
  249. async def get(self):
  250. command = "echo key enter | dotool"
  251. await run_command(command)
  252. referer = self.request.headers.get('Referer', '/')
  253. self.redirect(referer)
  254. class FaviconHandler(tornado.web.RequestHandler):
  255. async def get(self):
  256. self.set_status(204)
  257. self.finish()
  258. def make_app():
  259. return tornado.web.Application([
  260. (r"/a", BeepHandler),
  261. (r"/b", WebRTCHandler),
  262. (r"/sendkeys", SendKeysHandler),
  263. (r"/sendcock_enter", SendCockEnterKeysHandler),
  264. (r"/telemetry", SetRoomHandler),
  265. (r"/", UploadFileHandler),
  266. (r"/([a-zA-Z0-9]+)", ShowSlugHandler),
  267. (r"/uploads/(.*)", DownloadFileHandler),
  268. (r"/favicon.ico", FaviconHandler),
  269. ], template_path="templates", static_path="static")
  270. if __name__ == "__main__":
  271. app = make_app()
  272. app.listen(5000)
  273. tornado.ioloop.IOLoop.current().start()