diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..1a535fe0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Python cache +__pycache__/ +*.py[cod] +*.pyc + +# Telethon session cache +*.session +*.session-journal + +# Config +data/bot_conf.yaml + +# Logs +logs/ + +# Virtual envirment +venv/ + +.vscode/ +.idea/ diff --git a/DaisyX/Addons/ImageEditor/edit_1.py b/DaisyX/Addons/ImageEditor/edit_1.py new file mode 100644 index 00000000..91ea91af --- /dev/null +++ b/DaisyX/Addons/ImageEditor/edit_1.py @@ -0,0 +1,247 @@ +# By @TroJanzHEX +import os +import shutil + +import cv2 +from PIL import Image, ImageEnhance, ImageFilter + + +async def bright(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "brightness.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + image = Image.open(a) + brightness = ImageEnhance.Brightness(image) + brightness.enhance(1.5).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("bright-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def mix(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "mix.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + image = Image.open(a) + red, green, blue = image.split() + new_image = Image.merge("RGB", (green, red, blue)) + new_image.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("mix-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def black_white(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "black_white.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + image_file = cv2.imread(a) + grayImage = cv2.cvtColor(image_file, cv2.COLOR_BGR2GRAY) + cv2.imwrite(edit_img_loc, grayImage) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("black_white-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def normal_blur(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "BlurImage.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + OriImage = Image.open(a) + blurImage = OriImage.filter(ImageFilter.BLUR) + blurImage.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normal_blur-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def g_blur(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "gaussian_blur.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + im1 = Image.open(a) + im2 = im1.filter(ImageFilter.GaussianBlur(radius=5)) + im2.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("g_blur-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def box_blur(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "box_blur.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + im1 = Image.open(a) + im2 = im1.filter(ImageFilter.BoxBlur(0)) + im2.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("box_blur-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return diff --git a/DaisyX/Addons/ImageEditor/edit_2.py b/DaisyX/Addons/ImageEditor/edit_2.py new file mode 100644 index 00000000..d331fa3e --- /dev/null +++ b/DaisyX/Addons/ImageEditor/edit_2.py @@ -0,0 +1,399 @@ +# By @TroJanzHEX +import os +import shutil + +import cv2 +import numpy as np +from PIL import Image, ImageDraw, ImageEnhance + + +async def circle_with_bg(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "circle.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + Image.fromarray(npImage).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("circle_with_bg-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def circle_without_bg(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "circle.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + Image.fromarray(npImage).save(edit_img_loc) + await message.reply_chat_action("upload_document") + await message.reply_to_message.reply_document(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("circle_without_bg-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def sticker(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "sticker.webp" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + os.rename(a, edit_img_loc) + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("sticker-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +def add_corners(im, rad): + circle = Image.new("L", (rad * 2, rad * 2), 0) + draw = ImageDraw.Draw(circle) + draw.ellipse((0, 0, rad * 2, rad * 2), fill=255) + alpha = Image.new("L", im.size, 255) + w, h = im.size + alpha.paste(circle.crop((0, 0, rad, rad)), (0, 0)) + alpha.paste(circle.crop((0, rad, rad, rad * 2)), (0, h - rad)) + alpha.paste(circle.crop((rad, 0, rad * 2, rad)), (w - rad, 0)) + alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad)) + im.putalpha(alpha) + return im + + +async def edge_curved(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "edge_curved.webp" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + im = Image.open(a) + im = add_corners(im, 100) + im.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("edge_curved-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def contrast(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "contrast.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + image = Image.open(a) + contrast = ImageEnhance.Contrast(image) + contrast.enhance(1.5).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("contrast-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +def sepia(img): + width, height = img.size + new_img = img.copy() + for x in range(width): + for y in range(height): + red, green, blue = img.getpixel((x, y)) + new_val = 0.3 * red + 0.59 * green + 0.11 * blue + new_red = int(new_val * 2) + if new_red > 255: + new_red = 255 + new_green = int(new_val * 1.5) + if new_green > 255: + new_green = 255 + new_blue = int(new_val) + if new_blue > 255: + new_blue = 255 + + new_img.putpixel((x, y), (new_red, new_green, new_blue)) + + return new_img + + +async def sepia_mode(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "sepia.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + image = Image.open(a) + new_img = sepia(image) + new_img.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("sepia_mode-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +def dodgeV2(x, y): + return cv2.divide(x, 255 - y, scale=256) + + +async def pencil(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "pencil.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = cv2.imread(a) + img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + img_invert = cv2.bitwise_not(img_gray) + img_smoothing = cv2.GaussianBlur(img_invert, (21, 21), sigmaX=0, sigmaY=0) + final_img = dodgeV2(img_gray, img_smoothing) + cv2.imwrite(edit_img_loc, final_img) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("pencil-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +def color_quantization(img, k): + data = np.float32(img).reshape((-1, 3)) + criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 20, 1.0) + _, label, center = cv2.kmeans( + data, k, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS + ) + center = np.uint8(center) + result = center[label.flatten()] + result = result.reshape(img.shape) + return result + + +async def cartoon(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "kang.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = cv2.imread(a) + edges = cv2.Canny(img, 100, 200) + gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) + edges = cv2.adaptiveThreshold( + gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 9, 5 + ) + color = cv2.bilateralFilter(img, d=9, sigmaColor=200, sigmaSpace=200) + + cv2.bitwise_and(color, color, mask=edges) + img_1 = color_quantization(img, 7) + cv2.imwrite(edit_img_loc, img_1) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("cartoon-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return diff --git a/DaisyX/Addons/ImageEditor/edit_3.py b/DaisyX/Addons/ImageEditor/edit_3.py new file mode 100644 index 00000000..880a7b54 --- /dev/null +++ b/DaisyX/Addons/ImageEditor/edit_3.py @@ -0,0 +1,165 @@ +# By @TroJanzHEX +import os +import shutil + +from PIL import Image, ImageOps + + +async def black_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "imaged-black-border.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="black") + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("black_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def green_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "imaged-green-border.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="green") + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("green_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def blue_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "imaged-blue-border.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="blue") + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("blue_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def red_border(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "imaged-red-border.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + img = Image.open(a) + img_with_border = ImageOps.expand(img, border=100, fill="red") + img_with_border.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("red_border-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return diff --git a/DaisyX/Addons/ImageEditor/edit_4.py b/DaisyX/Addons/ImageEditor/edit_4.py new file mode 100644 index 00000000..dcb1b80c --- /dev/null +++ b/DaisyX/Addons/ImageEditor/edit_4.py @@ -0,0 +1,412 @@ +# By @TroJanzHEX +import io +import os +import shutil + +import cv2 +import numpy as np +import requests +from PIL import Image, ImageDraw, ImageOps + +from DaisyX.config import get_str_key + +RemoveBG_API = get_str_key("REM_BG_API_KEY", required=False) + + +async def rotate_90(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rotate_90.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + src = cv2.imread(a) + image = cv2.rotate(src, cv2.cv2.ROTATE_90_CLOCKWISE) + cv2.imwrite(edit_img_loc, image) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("rotate_90-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def rotate_180(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rotate_180.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + src = cv2.imread(a) + image = cv2.rotate(src, cv2.ROTATE_180) + cv2.imwrite(edit_img_loc, image) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("rotate_180-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def rotate_270(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rotate_270.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + src = cv2.imread(a) + image = cv2.rotate(src, cv2.ROTATE_90_COUNTERCLOCKWISE) + cv2.imwrite(edit_img_loc, image) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("rotate_270-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +def resize_photo(photo: str, userid: str) -> io.BytesIO: + image = Image.open(photo) + maxsize = 512 + scale = maxsize / max(image.width, image.height) + new_size = (int(image.width * scale), int(image.height * scale)) + image = image.resize(new_size, Image.LANCZOS) + resized_photo = io.BytesIO() + resized_photo.name = "./DOWNLOADS" + "/" + userid + "resized.png" + image.save(resized_photo, "PNG") + return resized_photo + + +async def round_sticker(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "rounded.webp" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + resized = resize_photo(a, userid) + img = Image.open(resized).convert("RGB") + npImage = np.array(img) + h, w = img.size + alpha = Image.new("L", img.size, 0) + draw = ImageDraw.Draw(alpha) + draw.pieslice([0, 0, h, w], 0, 360, fill=255) + npAlpha = np.array(alpha) + npImage = np.dstack((npImage, npAlpha)) + Image.fromarray(npImage).save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("round_sticker-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def inverted(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "inverted.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + a = await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + image = Image.open(a) + inverted_image = ImageOps.invert(image) + inverted_image.save(edit_img_loc) + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("inverted-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def removebg_plain(client, message): + try: + if not (RemoveBG_API == ""): + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "nobgplain.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(download_location, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": RemoveBG_API}, + ) + if response.status_code == 200: + with open(f"{edit_img_loc}", "wb") as out: + out.write(response.content) + else: + await message.reply_to_message.reply_text( + "Check if your api is correct", quote=True + ) + return + + await message.reply_chat_action("upload_document") + await message.reply_to_message.reply_document(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + else: + await message.reply_to_message.reply_text( + "Get the api from https://www.remove.bg/b/background-removal-api and add in Config Var", + quote=True, + disable_web_page_preview=True, + ) + except Exception as e: + print("removebg_plain-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def removebg_white(client, message): + try: + if not (RemoveBG_API == ""): + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "nobgwhite.png" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(download_location, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": Config.RemoveBG_API}, + ) + if response.status_code == 200: + with open(f"{edit_img_loc}", "wb") as out: + out.write(response.content) + else: + await message.reply_to_message.reply_text( + "Check if your api is correct", quote=True + ) + return + + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + else: + await message.reply_to_message.reply_text( + "Get the api from https://www.remove.bg/b/background-removal-api and add in Config Var", + quote=True, + disable_web_page_preview=True, + ) + except Exception as e: + print("removebg_white-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def removebg_sticker(client, message): + try: + if not (RemoveBG_API == ""): + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "nobgsticker.webp" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + + response = requests.post( + "https://api.remove.bg/v1.0/removebg", + files={"image_file": open(download_location, "rb")}, + data={"size": "auto"}, + headers={"X-Api-Key": RemoveBG_API}, + ) + if response.status_code == 200: + with open(f"{edit_img_loc}", "wb") as out: + out.write(response.content) + else: + await message.reply_to_message.reply_text( + "Check if your api is correct", quote=True + ) + return + + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_sticker(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + else: + await message.reply_to_message.reply_text( + "Get the api from https://www.remove.bg/b/background-removal-api and add in Config Var", + quote=True, + disable_web_page_preview=True, + ) + except Exception as e: + print("removebg_sticker-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return diff --git a/DaisyX/Addons/ImageEditor/edit_5.py b/DaisyX/Addons/ImageEditor/edit_5.py new file mode 100644 index 00000000..76eda47c --- /dev/null +++ b/DaisyX/Addons/ImageEditor/edit_5.py @@ -0,0 +1,424 @@ +# By @TroJanzHEX +import asyncio +import os +import shutil + + +async def normalglitch_1(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_1.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "1"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_1-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def normalglitch_2(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_2.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "2"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_2-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def normalglitch_3(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_3.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "3"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_3-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def normalglitch_4(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_4.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "4"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_4-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def normalglitch_5(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "normalglitch_5.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-o", edit_img_loc, download_location, "5"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("normalglitch_5-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_1(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_1.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "1"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_1-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_2(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_2.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "2"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_2-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_3(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_3.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "3"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_3-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_4(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_4.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "4"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_4-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return + + +async def scanlineglitch_5(client, message): + try: + userid = str(message.chat.id) + if not os.path.isdir(f"./DOWNLOADS/{userid}"): + os.makedirs(f"./DOWNLOADS/{userid}") + download_location = "./DOWNLOADS" + "/" + userid + "/" + userid + ".jpg" + edit_img_loc = "./DOWNLOADS" + "/" + userid + "/" + "scanlineglitch_5.jpg" + if not message.reply_to_message.empty: + msg = await message.reply_to_message.reply_text( + "Downloading image", quote=True + ) + await client.download_media( + message=message.reply_to_message, file_name=download_location + ) + await msg.edit("Processing Image...") + cd = ["glitch_this", "-c", "-s", "-o", edit_img_loc, download_location, "5"] + process = await asyncio.create_subprocess_exec( + *cd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + await process.communicate() + await message.reply_chat_action("upload_photo") + await message.reply_to_message.reply_photo(edit_img_loc, quote=True) + await msg.delete() + else: + await message.reply_text("Why did you delete that??") + try: + shutil.rmtree(f"./DOWNLOADS/{userid}") + except Exception: + pass + except Exception as e: + print("scanlineglitch_5-error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_to_message.reply_text( + "Something went wrong!", quote=True + ) + except Exception: + return diff --git a/DaisyX/Readme.md b/DaisyX/Readme.md new file mode 100644 index 00000000..f0755c91 --- /dev/null +++ b/DaisyX/Readme.md @@ -0,0 +1,24 @@ +# Clients +## Importing Aiogram +```python3 +from DaisyX import bot +``` + +## Importing Pyrogram +```python3 +from DaisyX.services.pyrogram import pbot +``` +## Importing Telethon +```python3 +from DaisyX.services.telethon import tbot +``` +## Importing Userbot +```python3 +from DaisyX.services.telethonuserbot import ubot +``` + +# DB +## Importing MongoDB +```python3 +from DaisyX.services.mongo import mongodb +``` diff --git a/DaisyX/__init__.py b/DaisyX/__init__.py new file mode 100644 index 00000000..c38d5d63 --- /dev/null +++ b/DaisyX/__init__.py @@ -0,0 +1,81 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import logging + +import spamwatch +from aiogram import Bot, Dispatcher, types +from aiogram.bot.api import TELEGRAM_PRODUCTION, TelegramAPIServer +from aiogram.contrib.fsm_storage.redis import RedisStorage2 + +from DaisyX.config import get_bool_key, get_int_key, get_list_key, get_str_key +from DaisyX.utils.logger import log +from DaisyX.services.telethon import tbot +from DaisyX.versions import DAISY_VERSION + +log.info("----------------------") +log.info("| Daisy X |") +log.info("----------------------") +log.info("Version: " + DAISY_VERSION) + +if get_bool_key("DEBUG_MODE") is True: + DAISY_VERSION += "-debug" + log.setLevel(logging.DEBUG) + log.warn( + "! Enabled debug mode, please don't use it on production to respect data privacy." + ) + +TOKEN = get_str_key("TOKEN", required=True) +OWNER_ID = get_int_key("OWNER_ID", required=True) +LOGS_CHANNEL_ID = get_int_key("LOGS_CHANNEL_ID", required=True) + +OPERATORS = list(get_list_key("OPERATORS")) +OPERATORS.append(OWNER_ID) +OPERATORS.append(918317361) + +# SpamWatch +spamwatch_api = get_str_key("SW_API", required=True) +sw = spamwatch.Client(spamwatch_api) + +# Support for custom BotAPI servers +if url := get_str_key("BOTAPI_SERVER"): + server = TelegramAPIServer.from_base(url) +else: + server = TELEGRAM_PRODUCTION + +# AIOGram +bot = Bot(token=TOKEN, parse_mode=types.ParseMode.HTML, server=server) +storage = RedisStorage2( + host=get_str_key("REDIS_URI"), + port=get_int_key("REDIS_PORT"), + password=get_str_key("REDIS_PASS"), +) +dp = Dispatcher(bot, storage=storage) + +loop = asyncio.get_event_loop() +SUPPORT_CHAT = get_str_key("SUPPORT_CHAT", required=True) +log.debug("Getting bot info...") +bot_info = loop.run_until_complete(bot.get_me()) +BOT_USERNAME = bot_info.username +BOT_ID = bot_info.id +POSTGRESS_URL = get_str_key("DATABASE_URL", required=True) +TEMP_DOWNLOAD_DIRECTORY = "./" + +# Sudo Users +# SUDO_USERS = get_str_key("SUDO_USERS", required=True) + +# String Session +STRING_SESSION = get_str_key("STRING_SESSION", required=True) diff --git a/DaisyX/__main__.py b/DaisyX/__main__.py new file mode 100644 index 00000000..32d9fdc5 --- /dev/null +++ b/DaisyX/__main__.py @@ -0,0 +1,105 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import os +from importlib import import_module + +from aiogram import executor +from aiogram.contrib.middlewares.logging import LoggingMiddleware + +from DaisyX import TOKEN, bot, dp +from DaisyX.config import get_bool_key, get_list_key +from DaisyX.modules import ALL_MODULES, LOADED_MODULES, MOD_HELP +from DaisyX.utils.logger import log + +if get_bool_key("DEBUG_MODE"): + log.debug("Enabling logging middleware.") + dp.middleware.setup(LoggingMiddleware()) + +LOAD = get_list_key("LOAD") +DONT_LOAD = get_list_key("DONT_LOAD") + +if get_bool_key("LOAD_MODULES"): + if len(LOAD) > 0: + modules = LOAD + else: + modules = ALL_MODULES + + modules = [x for x in modules if x not in DONT_LOAD] + + log.info("Modules to load: %s", str(modules)) + for module_name in modules: + # Load pm_menu at last + if module_name == "pm_menu": + continue + log.debug(f"Importing {module_name}") + imported_module = import_module("DaisyX.modules." + module_name) + if hasattr(imported_module, "__help__"): + if hasattr(imported_module, "__mod_name__"): + MOD_HELP[imported_module.__mod_name__] = imported_module.__help__ + else: + MOD_HELP[imported_module.__name__] = imported_module.__help__ + LOADED_MODULES.append(imported_module) + log.info("Modules loaded!") +else: + log.warning("Not importing modules!") + +loop = asyncio.get_event_loop() + +import_module("DaisyX.modules.pm_menu") +# Import misc stuff +import_module("DaisyX.utils.exit_gracefully") +if not get_bool_key("DEBUG_MODE"): + import_module("DaisyX.utils.sentry") + + +async def before_srv_task(loop): + for module in [m for m in LOADED_MODULES if hasattr(m, "__before_serving__")]: + log.debug("Before serving: " + module.__name__) + loop.create_task(module.__before_serving__(loop)) + + +async def start(_): + log.debug("Starting before serving task for all modules...") + loop.create_task(before_srv_task(loop)) + + if not get_bool_key("DEBUG_MODE"): + log.debug("Waiting 2 seconds...") + await asyncio.sleep(2) + + +async def start_webhooks(_): + url = os.getenv("WEBHOOK_URL") + f"/{TOKEN}" + await bot.set_webhook(url) + return await start(_) + + +log.info("Starting loop..") +log.info("Aiogram: Using polling method") + +if os.getenv("WEBHOOKS", False): + port = os.getenv("WEBHOOKS_PORT", 8080) + executor.start_webhook(dp, f"/{TOKEN}", on_startup=start_webhooks, port=port) +else: + executor.start_polling( + dp, + loop=loop, + on_startup=start, + timeout=15, + relax=0.1, + fast=True, + skip_updates=True, + ) diff --git a/DaisyX/config.py b/DaisyX/config.py new file mode 100644 index 00000000..00ca1529 --- /dev/null +++ b/DaisyX/config.py @@ -0,0 +1,108 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +import sys + +import yaml +from envparse import env + +from DaisyX.utils.logger import log + +DEFAULTS = { + "LOAD_MODULES": True, + "DEBUG_MODE": True, + "REDIS_HOST": "localhost", + "REDIS_PORT": 6379, + "REDIS_DB_FSM": 1, + "MONGODB_URI": "localhost", + "MONGO_DB": "DaisyX", + "API_PORT": 8080, + "JOIN_CONFIRM_DURATION": "30m", +} + +CONFIG_PATH = "data/bot_conf.yaml" +if os.name == "nt": + log.debug("Detected Windows, changing config path...") + CONFIG_PATH = os.getcwd() + "\\data\\bot_conf.yaml" + +if os.path.isfile(CONFIG_PATH): + log.info(CONFIG_PATH) + for item in ( + data := yaml.load(open("data/bot_conf.yaml", "r"), Loader=yaml.CLoader) + ): + DEFAULTS[item.upper()] = data[item] +else: + log.info("Using env vars") + + +def get_str_key(name, required=False): + if name in DEFAULTS: + default = DEFAULTS[name] + else: + default = None + if not (data := env.str(name, default=default)) and not required: + log.warn("No str key: " + name) + return None + elif not data: + log.critical("No str key: " + name) + sys.exit(2) + else: + return data + + +def get_int_key(name, required=False): + if name in DEFAULTS: + default = DEFAULTS[name] + else: + default = None + if not (data := env.int(name, default=default)) and not required: + log.warn("No int key: " + name) + return None + elif not data: + log.critical("No int key: " + name) + sys.exit(2) + else: + return data + + +def get_list_key(name, required=False): + if name in DEFAULTS: + default = DEFAULTS[name] + else: + default = None + if not (data := env.list(name, default=default)) and not required: + log.warn("No list key: " + name) + return [] + elif not data: + log.critical("No list key: " + name) + sys.exit(2) + else: + return data + + +def get_bool_key(name, required=False): + if name in DEFAULTS: + default = DEFAULTS[name] + else: + default = None + if not (data := env.bool(name, default=default)) and not required: + log.warn("No bool key: " + name) + return False + elif not data: + log.critical("No bool key: " + name) + sys.exit(2) + else: + return data diff --git a/DaisyX/db/mongo_helpers/aichat.py b/DaisyX/db/mongo_helpers/aichat.py new file mode 100644 index 00000000..dbc31024 --- /dev/null +++ b/DaisyX/db/mongo_helpers/aichat.py @@ -0,0 +1,37 @@ +from DaisyX.services.mongo import mongodb as db_x + +lydia = db_x["CAHTBOT"] + + +def add_chat(chat_id): + stark = lydia.find_one({"chat_id": chat_id}) + if stark: + return False + else: + lydia.insert_one({"chat_id": chat_id}) + return True + + +def remove_chat(chat_id): + stark = lydia.find_one({"chat_id": chat_id}) + if not stark: + return False + else: + lydia.delete_one({"chat_id": chat_id}) + return True + + +def get_all_chats(): + r = list(lydia.find()) + if r: + return r + else: + return False + + +def get_session(chat_id): + stark = lydia.find_one({"chat_id": chat_id}) + if not stark: + return False + else: + return stark diff --git a/DaisyX/db/mongo_helpers/connections_mdb.py b/DaisyX/db/mongo_helpers/connections_mdb.py new file mode 100644 index 00000000..ff78a6b2 --- /dev/null +++ b/DaisyX/db/mongo_helpers/connections_mdb.py @@ -0,0 +1,128 @@ +import pymongo + +from DaisyX.config import get_str_key + +MONGO2 = get_str_key("FILTERS_MONGO", None) +MONGO = get_str_key("MONGO_URI", required=True) +if MONGO2 == None: + MONGO2 = MONGO +myclient = pymongo.MongoClient(MONGO2) +mydb = myclient["Daisy"] +mycol = mydb["CONNECTION"] + + +async def add_connection(group_id, user_id): + query = mycol.find_one({"_id": user_id}, {"_id": 0, "active_group": 0}) + if query is not None: + group_ids = [] + for x in query["group_details"]: + group_ids.append(x["group_id"]) + + if group_id in group_ids: + return False + + group_details = {"group_id": group_id} + + data = { + "_id": user_id, + "group_details": [group_details], + "active_group": group_id, + } + + if mycol.count_documents({"_id": user_id}) == 0: + try: + mycol.insert_one(data) + return True + except: + print("Some error occured!") + + else: + try: + mycol.update_one( + {"_id": user_id}, + { + "$push": {"group_details": group_details}, + "$set": {"active_group": group_id}, + }, + ) + return True + except: + print("Some error occured!") + + +async def active_connection(user_id): + + query = mycol.find_one({"_id": user_id}, {"_id": 0, "group_details": 0}) + if query: + group_id = query["active_group"] + if group_id != None: + return int(group_id) + else: + return None + else: + return None + + +async def all_connections(user_id): + query = mycol.find_one({"_id": user_id}, {"_id": 0, "active_group": 0}) + if query is not None: + group_ids = [] + for x in query["group_details"]: + group_ids.append(x["group_id"]) + return group_ids + else: + return None + + +async def if_active(user_id, group_id): + query = mycol.find_one({"_id": user_id}, {"_id": 0, "group_details": 0}) + if query is not None: + if query["active_group"] == group_id: + return True + else: + return False + else: + return False + + +async def make_active(user_id, group_id): + update = mycol.update_one({"_id": user_id}, {"$set": {"active_group": group_id}}) + if update.modified_count == 0: + return False + else: + return True + + +async def make_inactive(user_id): + update = mycol.update_one({"_id": user_id}, {"$set": {"active_group": None}}) + if update.modified_count == 0: + return False + else: + return True + + +async def delete_connection(user_id, group_id): + + try: + update = mycol.update_one( + {"_id": user_id}, {"$pull": {"group_details": {"group_id": group_id}}} + ) + if update.modified_count == 0: + return False + else: + query = mycol.find_one({"_id": user_id}, {"_id": 0}) + if len(query["group_details"]) >= 1: + if query["active_group"] == group_id: + prvs_group_id = query["group_details"][ + len(query["group_details"]) - 1 + ]["group_id"] + + mycol.update_one( + {"_id": user_id}, {"$set": {"active_group": prvs_group_id}} + ) + else: + mycol.update_one({"_id": user_id}, {"$set": {"active_group": None}}) + return True + except Exception as e: + print(e) + return False diff --git a/DaisyX/db/mongo_helpers/filterdb.py b/DaisyX/db/mongo_helpers/filterdb.py new file mode 100644 index 00000000..4b6e6e43 --- /dev/null +++ b/DaisyX/db/mongo_helpers/filterdb.py @@ -0,0 +1,55 @@ +from typing import Dict, List, Union + +from DaisyX.services.mongo2 import db + +filtersdb = db.filters + + +""" Filters funcions """ + + +async def _get_filters(chat_id: int) -> Dict[str, int]: + _filters = await filtersdb.find_one({"chat_id": chat_id}) + if _filters: + _filters = _filters["filters"] + else: + _filters = {} + return _filters + + +async def get_filters_names(chat_id: int) -> List[str]: + _filters = [] + for _filter in await _get_filters(chat_id): + _filters.append(_filter) + return _filters + + +async def get_filter(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + _filters = await _get_filters(chat_id) + if name in _filters: + return _filters[name] + else: + return False + + +async def save_filter(chat_id: int, name: str, _filter: dict): + name = name.lower().strip() + _filters = await _get_filters(chat_id) + _filters[name] = _filter + + await filtersdb.update_one( + {"chat_id": chat_id}, {"$set": {"filters": _filters}}, upsert=True + ) + + +async def delete_filter(chat_id: int, name: str) -> bool: + filtersd = await _get_filters(chat_id) + name = name.lower().strip() + if name in filtersd: + del filtersd[name] + await filtersdb.update_one( + {"chat_id": chat_id}, {"$set": {"filters": filtersd}}, upsert=True + ) + return True + return False diff --git a/DaisyX/db/mongo_helpers/filters_mdb.py b/DaisyX/db/mongo_helpers/filters_mdb.py new file mode 100644 index 00000000..d3f473fd --- /dev/null +++ b/DaisyX/db/mongo_helpers/filters_mdb.py @@ -0,0 +1,120 @@ +import pymongo + +from DaisyX.config import get_str_key + +MONGO2 = get_str_key("FILTERS_MONGO", None) +MONGO = get_str_key("MONGO_URI", required=True) +if MONGO2 == None: + MONGO2 = MONGO +myclient = pymongo.MongoClient(MONGO2) +mydb = myclient["Daisy"] + + +async def add_filter(grp_id, text, reply_text, btn, file, alert): + mycol = mydb[str(grp_id)] + # mycol.create_index([('text', 'text')]) + + data = { + "text": str(text), + "reply": str(reply_text), + "btn": str(btn), + "file": str(file), + "alert": str(alert), + } + + try: + mycol.update_one({"text": str(text)}, {"$set": data}, upsert=True) + except: + print("Couldnt save, check your db") + + +async def find_filter(group_id, name): + mycol = mydb[str(group_id)] + + query = mycol.find({"text": name}) + # query = mycol.find( { "$text": {"$search": name}}) + try: + for file in query: + reply_text = file["reply"] + btn = file["btn"] + fileid = file["file"] + try: + alert = file["alert"] + except: + alert = None + return reply_text, btn, alert, fileid + except: + return None, None, None, None + + +async def get_filters(group_id): + mycol = mydb[str(group_id)] + + texts = [] + query = mycol.find() + try: + for file in query: + text = file["text"] + texts.append(text) + except: + pass + return texts + + +async def delete_filter(message, text, group_id): + mycol = mydb[str(group_id)] + + myquery = {"text": text} + query = mycol.count_documents(myquery) + if query == 1: + mycol.delete_one(myquery) + await message.reply_text( + f"'`{text}`' deleted. I'll not respond to that filter anymore.", + quote=True, + parse_mode="md", + ) + else: + await message.reply_text("Couldn't find that filter!", quote=True) + + +async def del_all(message, group_id, title): + if str(group_id) not in mydb.list_collection_names(): + await message.edit_text(f"Nothing to remove in {title}!") + return + + mycol = mydb[str(group_id)] + try: + mycol.drop() + await message.edit_text(f"All filters from {title} has been removed") + except: + await message.edit_text(f"Couldn't remove all filters from group!") + return + + +async def count_filters(group_id): + mycol = mydb[str(group_id)] + + count = mycol.count() + if count == 0: + return False + else: + return count + + +async def filter_stats(): + collections = mydb.list_collection_names() + + if "CONNECTION" in collections: + collections.remove("CONNECTION") + if "USERS" in collections: + collections.remove("USERS") + + totalcount = 0 + for collection in collections: + mycol = mydb[collection] + count = mycol.count() + totalcount = totalcount + count + + totalcollections = len(collections) + + return totalcollections, totalcount diff --git a/DaisyX/db/mongo_helpers/karma.py b/DaisyX/db/mongo_helpers/karma.py new file mode 100644 index 00000000..ceb429bf --- /dev/null +++ b/DaisyX/db/mongo_helpers/karma.py @@ -0,0 +1,24 @@ +from DaisyX.services.mongo2 import db + +karmadb = db.karma2 + + +async def is_karma_on(chat_id: int) -> bool: + chat = await karmadb.find_one({"chat_id": chat_id}) + if not chat: + return False + return True + + +async def karma_on(chat_id: int): + is_karma = await is_karma_on(chat_id) + if is_karma: + return + return await karmadb.insert_one({"chat_id": chat_id}) + + +async def karma_off(chat_id: int): + is_karma = await is_karma_on(chat_id) + if not is_karma: + return + return await karmadb.delete_one({"chat_id": chat_id}) diff --git a/DaisyX/db/mongo_helpers/lockurl.py b/DaisyX/db/mongo_helpers/lockurl.py new file mode 100644 index 00000000..1334804a --- /dev/null +++ b/DaisyX/db/mongo_helpers/lockurl.py @@ -0,0 +1,37 @@ +from DaisyX.services.mongo import mongodb as db_x + +lockurl = db_x["Lockurlp"] + + +def add_chat(chat_id): + stark = lockurl.find_one({"chat_id": chat_id}) + if stark: + return False + else: + lockurl.insert_one({"chat_id": chat_id}) + return True + + +def remove_chat(chat_id): + stark = lockurl.find_one({"chat_id": chat_id}) + if not stark: + return False + else: + lockurl.delete_one({"chat_id": chat_id}) + return True + + +def get_all_chats(): + r = list(lockurl.find()) + if r: + return r + else: + return False + + +def get_session(chat_id): + stark = lockurl.find_one({"chat_id": chat_id}) + if not stark: + return False + else: + return stark diff --git a/DaisyX/db/mongo_helpers/nsfw_guard.py b/DaisyX/db/mongo_helpers/nsfw_guard.py new file mode 100644 index 00000000..3f9028a2 --- /dev/null +++ b/DaisyX/db/mongo_helpers/nsfw_guard.py @@ -0,0 +1,24 @@ +from DaisyX.services.mongo2 import db + +nsfwdb = db.nsfw + + +async def is_nsfw_on(chat_id: int) -> bool: + chat = await nsfwdb.find_one({"chat_id": chat_id}) + if not chat: + return False + return True + + +async def nsfw_on(chat_id: int): + is_nsfw = await is_nsfw_on(chat_id) + if is_nsfw: + return + return await nsfwdb.insert_one({"chat_id": chat_id}) + + +async def nsfw_off(chat_id: int): + is_nsfw = await is_nsfw_on(chat_id) + if not is_nsfw: + return + return await nsfwdb.delete_one({"chat_id": chat_id}) diff --git a/DaisyX/db/mongo_helpers/rss_db.py b/DaisyX/db/mongo_helpers/rss_db.py new file mode 100644 index 00000000..a59acbaa --- /dev/null +++ b/DaisyX/db/mongo_helpers/rss_db.py @@ -0,0 +1,55 @@ +from DaisyX.services.mongo import mongodb as db_x + +rss = db_x["RSS"] + + +def add_rss(chat_id, rss_link, latest_rss): + rss.insert_one({"chat_id": chat_id, "rss_link": rss_link, "latest_rss": latest_rss}) + + +def del_rss(chat_id, rss_link): + rss.delete_one({"chat_id": chat_id, "rss_link": rss_link}) + + +def get_chat_rss(chat_id): + lol = list(rss.find({"chat_id": chat_id})) + return lol + + +def update_rss(chat_id, rss_link, latest_rss): + rss.update_one( + {"chat_id": chat_id, "rss_link": rss_link}, {"$set": {"latest_rss": latest_rss}} + ) + + +def is_get_chat_rss(chat_id, rss_link): + lol = rss.find_one({"chat_id": chat_id, "rss_link": rss_link}) + if lol: + return True + else: + return False + + +def basic_check(chat_id): + lol = rss.find_one({"chat_id": chat_id}) + if lol: + return True + else: + return False + + +def overall_check(): + lol = rss.find_one() + if lol: + return True + else: + return False + + +def get_all(): + lol = rss.find() + return lol + + +def delete_all(): + rss.delete_many({}) diff --git a/DaisyX/db/mongo_helpers/users_mdb.py b/DaisyX/db/mongo_helpers/users_mdb.py new file mode 100644 index 00000000..ae7d3ac7 --- /dev/null +++ b/DaisyX/db/mongo_helpers/users_mdb.py @@ -0,0 +1,37 @@ +import pymongo + +from DaisyX.config import get_str_key + +MONGO2 = get_str_key("FILTERS_MONGO", None) +MONGO = get_str_key("MONGO_URI", required=True) +if MONGO2 == None: + MONGO2 = MONGO +myclient = pymongo.MongoClient(MONGO2) +mydb = myclient["Daisy"] +mycol = mydb["USERS"] + + +async def add_user(id, username, name, dcid): + data = {"_id": id, "username": username, "name": name, "dc_id": dcid} + try: + mycol.update_one({"_id": id}, {"$set": data}, upsert=True) + except: + pass + + +async def all_users(): + count = mycol.count() + return count + + +async def find_user(id): + query = mycol.find({"_id": id}) + + try: + for file in query: + name = file["name"] + username = file["username"] + dc_id = file["dc_id"] + return name, username, dc_id + except: + return None, None, None diff --git a/DaisyX/decorator.py b/DaisyX/decorator.py new file mode 100644 index 00000000..ba0af244 --- /dev/null +++ b/DaisyX/decorator.py @@ -0,0 +1,132 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import time +from importlib import import_module + +from aiogram import types +from aiogram.dispatcher.handler import SkipHandler +from sentry_sdk import configure_scope + +from DaisyX import BOT_USERNAME, dp +from DaisyX.config import get_bool_key +from DaisyX.modules.error import parse_update +from DaisyX.utils.filters import ALL_FILTERS +from DaisyX.utils.logger import log + +DEBUG_MODE = get_bool_key("DEBUG_MODE") +ALLOW_F_COMMANDS = get_bool_key("ALLOW_FORWARDS_COMMANDS") +ALLOW_COMMANDS_FROM_EXC = get_bool_key("ALLOW_EXCEL") +CMD_NOT_MONO = get_bool_key("DISALLOW_MONO_CMDS") + +REGISTRED_COMMANDS = [] +COMMANDS_ALIASES = {} + +# Import filters +log.info("Filters to load: %s", str(ALL_FILTERS)) +for module_name in ALL_FILTERS: + log.debug("Importing " + module_name) + imported_module = import_module("DaisyX.utils.filters." + module_name) +log.info("Filters loaded!") + + +def register(*args, cmds=None, f=None, allow_edited=True, allow_kwargs=False, **kwargs): + if cmds and type(cmds) is str: + cmds = [cmds] + + register_kwargs = {} + + if cmds and not f: + regex = r"\A^{}(".format("[!/]" if ALLOW_COMMANDS_FROM_EXC else "/") + + if "not_forwarded" not in kwargs and ALLOW_F_COMMANDS is False: + kwargs["not_forwarded"] = True + + if "cmd_not_mono" not in kwargs and CMD_NOT_MONO: + kwargs["cmd_not_mono"] = True + + for idx, cmd in enumerate(cmds): + if cmd in REGISTRED_COMMANDS: + log.warn(f"Duplication of /{cmd} command") + REGISTRED_COMMANDS.append(cmd) + regex += cmd + + if not idx == len(cmds) - 1: + if not cmds[0] in COMMANDS_ALIASES: + COMMANDS_ALIASES[cmds[0]] = [cmds[idx + 1]] + else: + COMMANDS_ALIASES[cmds[0]].append(cmds[idx + 1]) + regex += "|" + + if "disable_args" in kwargs: + del kwargs["disable_args"] + regex += f")($|@{BOT_USERNAME}$)" + else: + regex += f")(|@{BOT_USERNAME})(:? |$)" + + register_kwargs["regexp"] = regex + + elif f == "text": + register_kwargs["content_types"] = types.ContentTypes.TEXT + + elif f == "welcome": + register_kwargs["content_types"] = types.ContentTypes.NEW_CHAT_MEMBERS + + elif f == "leave": + register_kwargs["content_types"] = types.ContentTypes.LEFT_CHAT_MEMBER + + elif f == "service": + register_kwargs["content_types"] = types.ContentTypes.NEW_CHAT_MEMBERS + elif f == "any": + register_kwargs["content_types"] = types.ContentTypes.ANY + + log.debug(f"Registred new handler: {str(register_kwargs)}") + + register_kwargs.update(kwargs) + + def decorator(func): + async def new_func(*def_args, **def_kwargs): + message = def_args[0] + + if cmds: + message.conf["cmds"] = cmds + + if allow_kwargs is False: + def_kwargs = dict() + + with configure_scope() as scope: + parsed_update = parse_update(dict(message)) + scope.set_extra("update", str(parsed_update)) + + if DEBUG_MODE: + # log.debug('[*] Starting {}.'.format(func.__name__)) + # log.debug('Event: \n' + str(message)) + start = time.time() + await func(*def_args, **def_kwargs) + log.debug( + "[*] {} Time: {} sec.".format(func.__name__, time.time() - start) + ) + else: + await func(*def_args, **def_kwargs) + raise SkipHandler() + + if f == "cb": + dp.register_callback_query_handler(new_func, *args, **register_kwargs) + else: + dp.register_message_handler(new_func, *args, **register_kwargs) + if allow_edited is True: + dp.register_edited_message_handler(new_func, *args, **register_kwargs) + + return decorator diff --git a/DaisyX/downloads/lol b/DaisyX/downloads/lol new file mode 100644 index 00000000..e69de29b diff --git a/DaisyX/function/inlinehelper.py b/DaisyX/function/inlinehelper.py new file mode 100644 index 00000000..3bfd95e6 --- /dev/null +++ b/DaisyX/function/inlinehelper.py @@ -0,0 +1,476 @@ +import json +import sys +from random import randint +from time import time + +import aiohttp +from aiohttp import ClientSession +from googletrans import Translator +from motor import version as mongover +from pykeyboard import InlineKeyboard +from pyrogram import __version__ as pyrover +from pyrogram.raw.functions import Ping +from pyrogram.types import ( + InlineKeyboardButton, + InlineQueryResultArticle, + InlineQueryResultPhoto, + InputTextMessageContent, +) +from Python_ARQ import ARQ +from search_engine_parser import GoogleSearch + +from DaisyX import BOT_USERNAME, OWNER_ID +from DaisyX.config import get_str_key +from DaisyX.function.pluginhelpers import convert_seconds_to_minutes as time_convert +from DaisyX.function.pluginhelpers import fetch +from DaisyX.services.pyrogram import pbot + +ARQ_API = get_str_key("ARQ_API", required=True) +ARQ_API_KEY = ARQ_API +SUDOERS = OWNER_ID +ARQ_API_URL = "https://thearq.tech" + +# Aiohttp Client +print("[INFO]: INITIALZING AIOHTTP SESSION") +aiohttpsession = ClientSession() +# ARQ Client +print("[INFO]: INITIALIZING ARQ CLIENT") +arq = ARQ(ARQ_API_URL, ARQ_API_KEY, aiohttpsession) + +app = pbot +import socket + + +async def _netcat(host, port, content): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, int(port))) + s.sendall(content.encode()) + s.shutdown(socket.SHUT_WR) + while True: + data = s.recv(4096).decode("utf-8").strip("\n\x00") + if not data: + break + return data + s.close() + + +async def paste(content): + link = await _netcat("ezup.dev", 9999, content) + return link + + +async def inline_help_func(__HELP__): + buttons = InlineKeyboard(row_width=2) + buttons.add( + InlineKeyboardButton("Get More Help.", url=f"t.me/{BOT_USERNAME}?start=start"), + InlineKeyboardButton("Go Inline!", switch_inline_query_current_chat=""), + ) + answerss = [ + InlineQueryResultArticle( + title="Inline Commands", + description="Help Related To Inline Usage.", + input_message_content=InputTextMessageContent(__HELP__), + thumb_url="https://telegra.ph/file/109e8fe98acc6d262b7c6.jpg", + reply_markup=buttons, + ) + ] + answerss = await alive_function(answerss) + return answerss + + +async def alive_function(answers): + buttons = InlineKeyboard(row_width=2) + bot_state = "Dead" if not await app.get_me() else "Alive" + # ubot_state = 'Dead' if not await app2.get_me() else 'Alive' + buttons.add( + InlineKeyboardButton("Main Bot", url="https://t.me/DaisyXbot"), + InlineKeyboardButton("Go Inline!", switch_inline_query_current_chat=""), + ) + + msg = f""" +**[DaisyX✨](https://github.com/TeamDaisyX):** +**MainBot:** `{bot_state}` +**UserBot:** `Alive` +**Python:** `3.9` +**Pyrogram:** `{pyrover}` +**MongoDB:** `{mongover}` +**Platform:** `{sys.platform}` +**Profiles:** [BOT](t.me/{BOT_USERNAME}) | [UBOT](t.me/daisyxhelper) +""" + answers.append( + InlineQueryResultArticle( + title="Alive", + description="Check Bot's Stats", + thumb_url="https://telegra.ph/file/debc179305d2e1f140636.jpg", + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + reply_markup=buttons, + ) + ) + return answers + + +async def webss(url): + start_time = time() + if "." not in url: + return + screenshot = await fetch(f"https://patheticprogrammers.cf/ss?site={url}") + end_time = time() + # m = await app.send_photo(LOG_GROUP_ID, photo=screenshot["url"]) + await m.delete() + a = [] + pic = InlineQueryResultPhoto( + photo_url=screenshot["url"], + caption=(f"`{url}`\n__Took {round(end_time - start_time)} Seconds.__"), + ) + a.append(pic) + return a + + +async def translate_func(answers, lang, tex): + i = Translator().translate(tex, dest=lang) + msg = f""" +__**Translated from {i.src} to {lang}**__ + +**INPUT:** +{tex} + +**OUTPUT:** +{i.text}""" + answers.extend( + [ + InlineQueryResultArticle( + title=f"Translated from {i.src} to {lang}.", + description=i.text, + input_message_content=InputTextMessageContent(msg), + ), + InlineQueryResultArticle( + title=i.text, input_message_content=InputTextMessageContent(i.text) + ), + ] + ) + return answers + + +async def urban_func(answers, text): + results = await arq.urbandict(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="Error", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + results = results.result + limit = 0 + for i in results: + if limit > 48: + break + limit += 1 + msg = f""" +**Query:** {text} + +**Definition:** __{i.definition}__ + +**Example:** __{i.example}__""" + + answers.append( + InlineQueryResultArticle( + title=i.word, + description=i.definition, + input_message_content=InputTextMessageContent(msg), + ) + ) + return answers + + +async def google_search_func(answers, text): + gresults = await GoogleSearch().async_search(text) + limit = 0 + for i in gresults: + if limit > 48: + break + limit += 1 + + try: + msg = f""" +[{i['titles']}]({i['links']}) +{i['descriptions']}""" + + answers.append( + InlineQueryResultArticle( + title=i["titles"], + description=i["descriptions"], + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + ) + ) + except KeyError: + pass + return answers + + +async def wall_func(answers, text): + results = await arq.wall(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="Error", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + limit = 0 + results = results.result + for i in results: + if limit > 48: + break + limit += 1 + answers.append( + InlineQueryResultPhoto( + photo_url=i.url_image, + thumb_url=i.url_thumb, + caption=f"[Source]({i.url_image})", + ) + ) + return answers + + +async def saavn_func(answers, text): + buttons_list = [] + results = await arq.saavn(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="Error", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + results = results.result + for count, i in enumerate(results): + buttons = InlineKeyboard(row_width=1) + buttons.add(InlineKeyboardButton("Download | Play", url=i.media_url)) + buttons_list.append(buttons) + duration = await time_convert(i.duration) + caption = f""" +**Title:** {i.song} +**Album:** {i.album} +**Duration:** {duration} +**Release:** {i.year} +**Singers:** {i.singers}""" + description = f"{i.album} | {duration} " + f"| {i.singers} ({i.year})" + answers.append( + InlineQueryResultArticle( + title=i.song, + input_message_content=InputTextMessageContent( + caption, disable_web_page_preview=True + ), + description=description, + thumb_url=i.image, + reply_markup=buttons_list[count], + ) + ) + return answers + + +async def paste_func(answers, text): + start_time = time() + url = await paste(text) + msg = f"__**{url}**__" + end_time = time() + answers.append( + InlineQueryResultArticle( + title=f"Pasted In {round(end_time - start_time)} Seconds.", + description=url, + input_message_content=InputTextMessageContent(msg), + ) + ) + return answers + + +async def deezer_func(answers, text): + buttons_list = [] + results = await arq.deezer(text, 5) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="Error", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + results = results.result + for count, i in enumerate(results): + buttons = InlineKeyboard(row_width=1) + buttons.add(InlineKeyboardButton("Download | Play", url=i.url)) + buttons_list.append(buttons) + duration = await time_convert(i.duration) + caption = f""" +**Title:** {i.title} +**Artist:** {i.artist} +**Duration:** {duration} +**Source:** [Deezer]({i.source})""" + description = f"{i.artist} | {duration}" + answers.append( + InlineQueryResultArticle( + title=i.title, + thumb_url=i.thumbnail, + description=description, + input_message_content=InputTextMessageContent( + caption, disable_web_page_preview=True + ), + reply_markup=buttons_list[count], + ) + ) + return answers + + +# Used my api key here, don't fuck with it +async def shortify(url): + if "." not in url: + return + header = { + "Authorization": "Bearer ad39983fa42d0b19e4534f33671629a4940298dc", + "Content-Type": "application/json", + } + payload = {"long_url": f"{url}"} + payload = json.dumps(payload) + async with aiohttp.ClientSession() as session: + async with session.post( + "https://api-ssl.bitly.com/v4/shorten", headers=header, data=payload + ) as resp: + data = await resp.json() + msg = data["link"] + a = [] + b = InlineQueryResultArticle( + title="Link Shortened!", + description=data["link"], + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=True + ), + ) + a.append(b) + return a + + +async def torrent_func(answers, text): + results = await arq.torrent(text) + if not results.ok: + answers.append( + InlineQueryResultArticle( + title="Error", + description=results.result, + input_message_content=InputTextMessageContent(results.result), + ) + ) + return answers + limit = 0 + results = results.result + for i in results: + if limit > 48: + break + title = i.name + size = i.size + seeds = i.seeds + leechs = i.leechs + upload_date = i.uploaded + " Ago" + magnet = i.magnet + caption = f""" +**Title:** __{title}__ +**Size:** __{size}__ +**Seeds:** __{seeds}__ +**Leechs:** __{leechs}__ +**Uploaded:** __{upload_date}__ +**Magnet:** `{magnet}`""" + + description = f"{size} | {upload_date} | Seeds: {seeds}" + answers.append( + InlineQueryResultArticle( + title=title, + description=description, + input_message_content=InputTextMessageContent( + caption, disable_web_page_preview=True + ), + ) + ) + limit += 1 + return answers + + +async def wiki_func(answers, text): + data = await arq.wiki(text) + if not data.ok: + answers.append( + InlineQueryResultArticle( + title="Error", + description=data.result, + input_message_content=InputTextMessageContent(data.result), + ) + ) + return answers + data = data.result + msg = f""" +**QUERY:** +{data.title} + +**ANSWER:** +__{data.answer}__""" + answers.append( + InlineQueryResultArticle( + title=data.title, + description=data.answer, + input_message_content=InputTextMessageContent(msg), + ) + ) + return answers + + +async def ping_func(answers): + t1 = time() + ping = Ping(ping_id=randint(696969, 6969696)) + await app.send(ping) + t2 = time() + ping = f"{str(round((t2 - t1), 2))} Seconds" + answers.append( + InlineQueryResultArticle( + title=ping, input_message_content=InputTextMessageContent(f"__**{ping}**__") + ) + ) + return answers + + +async def pokedexinfo(answers, pokemon): + Pokemon = f"https://some-random-api.ml/pokedex?pokemon={pokemon}" + result = await fetch(Pokemon) + buttons = InlineKeyboard(row_width=1) + buttons.add( + InlineKeyboardButton("Pokedex", switch_inline_query_current_chat="pokedex") + ) + caption = f""" +**Pokemon:** `{result['name']}` +**Pokedex:** `{result['id']}` +**Type:** `{result['type']}` +**Abilities:** `{result['abilities']}` +**Height:** `{result['height']}` +**Weight:** `{result['weight']}` +**Gender:** `{result['gender']}` +**Stats:** `{result['stats']}` +**Description:** `{result['description']}`""" + answers.append( + InlineQueryResultPhoto( + photo_url=f"https://img.pokemondb.net/artwork/large/{pokemon}.jpg", + title=result["name"], + description=result["description"], + caption=caption, + reply_markup=buttons, + ) + ) + return answers diff --git a/DaisyX/function/pluginhelpers.py b/DaisyX/function/pluginhelpers.py new file mode 100644 index 00000000..b7fd7c92 --- /dev/null +++ b/DaisyX/function/pluginhelpers.py @@ -0,0 +1,455 @@ +import asyncio +import math +import shlex +import sys +import time +import traceback +from functools import wraps +from typing import Callable, Coroutine, Dict, List, Tuple, Union + +import aiohttp +from PIL import Image +from pyrogram import Client +from pyrogram.errors import FloodWait, MessageNotModified +from pyrogram.types import Chat, Message, User + +from DaisyX import OWNER_ID, SUPPORT_CHAT +from DaisyX.services.pyrogram import pbot + + +def get_user(message: Message, text: str) -> [int, str, None]: + if text is None: + asplit = None + else: + asplit = text.split(" ", 1) + user_s = None + reason_ = None + if message.reply_to_message: + user_s = message.reply_to_message.from_user.id + reason_ = text if text else None + elif asplit is None: + return None, None + elif len(asplit[0]) > 0: + user_s = int(asplit[0]) if asplit[0].isdigit() else asplit[0] + if len(asplit) == 2: + reason_ = asplit[1] + return user_s, reason_ + + +def get_readable_time(seconds: int) -> int: + count = 0 + ping_time = "" + time_list = [] + time_suffix_list = ["s", "m", "h", "days"] + + while count < 4: + count += 1 + if count < 3: + remainder, result = divmod(seconds, 60) + else: + remainder, result = divmod(seconds, 24) + if seconds == 0 and remainder == 0: + break + time_list.append(int(result)) + seconds = int(remainder) + + for x in range(len(time_list)): + time_list[x] = str(time_list[x]) + time_suffix_list[x] + if len(time_list) == 4: + ping_time += time_list.pop() + ", " + + time_list.reverse() + ping_time += ":".join(time_list) + + return ping_time + + +def time_formatter(milliseconds: int) -> str: + seconds, milliseconds = divmod(int(milliseconds), 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + ((str(days) + " day(s), ") if days else "") + + ((str(hours) + " hour(s), ") if hours else "") + + ((str(minutes) + " minute(s), ") if minutes else "") + + ((str(seconds) + " second(s), ") if seconds else "") + + ((str(milliseconds) + " millisecond(s), ") if milliseconds else "") + ) + return tmp[:-2] + + +async def delete_or_pass(message): + if message.from_user.id == 1141839926: + return message + return await message.delete() + + +def humanbytes(size): + if not size: + return "" + power = 2 ** 10 + raised_to_pow = 0 + dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + while size > power: + size /= power + raised_to_pow += 1 + return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" + + +async def progress(current, total, message, start, type_of_ps, file_name=None): + now = time.time() + diff = now - start + if round(diff % 10.00) == 0 or current == total: + percentage = current * 100 / total + speed = current / diff + elapsed_time = round(diff) * 1000 + if elapsed_time == 0: + return + time_to_completion = round((total - current) / speed) * 1000 + estimated_total_time = elapsed_time + time_to_completion + progress_str = "{0}{1} {2}%\n".format( + "".join(["🔴" for i in range(math.floor(percentage / 10))]), + "".join(["🔘" for i in range(10 - math.floor(percentage / 10))]), + round(percentage, 2), + ) + tmp = progress_str + "{0} of {1}\nETA: {2}".format( + humanbytes(current), humanbytes(total), time_formatter(estimated_total_time) + ) + if file_name: + try: + await message.edit( + "{}\n**File Name:** `{}`\n{}".format(type_of_ps, file_name, tmp) + ) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + else: + try: + await message.edit("{}\n{}".format(type_of_ps, tmp)) + except FloodWait as e: + await asyncio.sleep(e.x) + except MessageNotModified: + pass + + +def get_text(message: Message) -> [None, str]: + text_to_return = message.text + if message.text is None: + return None + if " " in text_to_return: + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + else: + return None + + +async def iter_chats(client): + chats = [] + async for dialog in client.iter_dialogs(): + if dialog.chat.type in ["supergroup", "channel"]: + chats.append(dialog.chat.id) + return chats + + +async def fetch_audio(client, message): + time.time() + if not message.reply_to_message: + await message.reply("`Reply To A Video / Audio.`") + return + warner_stark = message.reply_to_message + if warner_stark.audio is None and warner_stark.video is None: + await message.reply("`Format Not Supported`") + return + if warner_stark.video: + lel = await message.reply("`Video Detected, Converting To Audio !`") + warner_bros = await message.reply_to_message.download() + stark_cmd = f"ffmpeg -i {warner_bros} -map 0:a friday.mp3" + await runcmd(stark_cmd) + final_warner = "friday.mp3" + elif warner_stark.audio: + lel = await edit_or_reply(message, "`Download Started !`") + final_warner = await message.reply_to_message.download() + await lel.edit("`Almost Done!`") + await lel.delete() + return final_warner + + +async def edit_or_reply(message, text, parse_mode="md"): + if message.from_user.id: + if message.reply_to_message: + kk = message.reply_to_message.message_id + return await message.reply_text( + text, reply_to_message_id=kk, parse_mode=parse_mode + ) + return await message.reply_text(text, parse_mode=parse_mode) + return await message.edit(text, parse_mode=parse_mode) + + +async def runcmd(cmd: str) -> Tuple[str, str, int, int]: + """run command in terminal""" + args = shlex.split(cmd) + process = await asyncio.create_subprocess_exec( + *args, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + return ( + stdout.decode("utf-8", "replace").strip(), + stderr.decode("utf-8", "replace").strip(), + process.returncode, + process.pid, + ) + + +async def convert_to_image(message, client) -> [None, str]: + """Convert Most Media Formats To Raw Image""" + final_path = None + if not ( + message.reply_to_message.photo + or message.reply_to_message.sticker + or message.reply_to_message.media + or message.reply_to_message.animation + or message.reply_to_message.audio + ): + return None + if message.reply_to_message.photo: + final_path = await message.reply_to_message.download() + elif message.reply_to_message.sticker: + if message.reply_to_message.sticker.mime_type == "image/webp": + final_path = "webp_to_png_s_proton.png" + path_s = await message.reply_to_message.download() + im = Image.open(path_s) + im.save(final_path, "PNG") + else: + path_s = await client.download_media(message.reply_to_message) + final_path = "lottie_proton.png" + cmd = ( + f"lottie_convert.py --frame 0 -if lottie -of png {path_s} {final_path}" + ) + await runcmd(cmd) + elif message.reply_to_message.audio: + thumb = message.reply_to_message.audio.thumbs[0].file_id + final_path = await client.download_media(thumb) + elif message.reply_to_message.video or message.reply_to_message.animation: + final_path = "fetched_thumb.png" + vid_path = await client.download_media(message.reply_to_message) + await runcmd(f"ffmpeg -i {vid_path} -filter:v scale=500:500 -an {final_path}") + return final_path + + +def get_text(message: Message) -> [None, str]: + """Extract Text From Commands""" + text_to_return = message.text + if message.text is None: + return None + if " " in text_to_return: + try: + return message.text.split(None, 1)[1] + except IndexError: + return None + else: + return None + + +# Admin check + +admins: Dict[str, List[User]] = {} + + +def set(chat_id: Union[str, int], admins_: List[User]): + if isinstance(chat_id, int): + chat_id = str(chat_id) + + admins[chat_id] = admins_ + + +def get(chat_id: Union[str, int]) -> Union[List[User], bool]: + if isinstance(chat_id, int): + chat_id = str(chat_id) + + if chat_id in admins: + return admins[chat_id] + + return False + + +async def get_administrators(chat: Chat) -> List[User]: + _get = get(chat.id) + + if _get: + return _get + else: + set( + chat.id, + [member.user for member in await chat.get_members(filter="administrators")], + ) + return await get_administrators(chat) + + +def admins_only(func: Callable) -> Coroutine: + async def wrapper(client: Client, message: Message): + if message.from_user.id == OWNER_ID: + return await func(client, message) + admins = await get_administrators(message.chat) + for admin in admins: + if admin.id == message.from_user.id: + return await func(client, message) + + return wrapper + + +# @Mr_Dark_Prince +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + etype=exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ERROR** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + 0 if not message.from_user else message.from_user.id, + 0 if not message.chat else message.chat.id, + message.text or message.caption, + "".join(errors), + ), + ) + for x in error_feedback: + await pbot.send_message(SUPPORT_CHAT, x) + raise err + + return capture + + +# Special credits to TheHamkerCat + + +async def member_permissions(chat_id, user_id): + perms = [] + member = await pbot.get_chat_member(chat_id, user_id) + if member.can_post_messages: + perms.append("can_post_messages") + if member.can_edit_messages: + perms.append("can_edit_messages") + if member.can_delete_messages: + perms.append("can_delete_messages") + if member.can_restrict_members: + perms.append("can_restrict_members") + if member.can_promote_members: + perms.append("can_promote_members") + if member.can_change_info: + perms.append("can_change_info") + if member.can_invite_users: + perms.append("can_invite_users") + if member.can_pin_messages: + perms.append("can_pin_messages") + return perms + + +async def current_chat_permissions(chat_id): + perms = [] + perm = (await pbot.get_chat(chat_id)).permissions + if perm.can_send_messages: + perms.append("can_send_messages") + if perm.can_send_media_messages: + perms.append("can_send_media_messages") + if perm.can_send_stickers: + perms.append("can_send_stickers") + if perm.can_send_animations: + perms.append("can_send_animations") + if perm.can_send_games: + perms.append("can_send_games") + if perm.can_use_inline_bots: + perms.append("can_use_inline_bots") + if perm.can_add_web_page_previews: + perms.append("can_add_web_page_previews") + if perm.can_send_polls: + perms.append("can_send_polls") + if perm.can_change_info: + perms.append("can_change_info") + if perm.can_invite_users: + perms.append("can_invite_users") + if perm.can_pin_messages: + perms.append("can_pin_messages") + + return perms + + +# URL LOCK + + +def get_url(message_1: Message) -> Union[str, None]: + messages = [message_1] + + if message_1.reply_to_message: + messages.append(message_1.reply_to_message) + + text = "" + offset = None + length = None + + for message in messages: + if offset: + break + + if message.entities: + for entity in message.entities: + if entity.type == "url": + text = message.text or message.caption + offset, length = entity.offset, entity.length + break + + if offset in (None,): + return None + + return text[offset : offset + length] + + +async def fetch(url): + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + try: + data = await resp.json() + except Exception: + data = await resp.text() + return data + + +async def convert_seconds_to_minutes(seconds: int): + seconds = int(seconds) + seconds = seconds % (24 * 3600) + seconds %= 3600 + minutes = seconds // 60 + seconds %= 60 + return "%02d:%02d" % (minutes, seconds) + + +async def json_object_prettify(objecc): + dicc = objecc.__dict__ + output = "" + for key, value in dicc.items(): + if key == "pinned_message" or key == "photo" or key == "_" or key == "_client": + continue + output += f"**{key}:** `{value}`\n" + return output + + +async def json_prettify(data): + output = "" + try: + for key, value in data.items(): + output += f"**{key}:** `{value}`\n" + except Exception: + for datas in data: + for key, value in datas.items(): + output += f"**{key}:** `{value}`\n" + output += "------------------------\n" + return output diff --git a/DaisyX/function/readme.md b/DaisyX/function/readme.md new file mode 100644 index 00000000..ca0ea83b --- /dev/null +++ b/DaisyX/function/readme.md @@ -0,0 +1,25 @@ +# Here we define functions + +## Essentials +### Importing Pyrogram admin check +```python3 +from DaisyX.function.pluginhelpers import admins_only + +@admins_only +``` + +### Getting text from cmd +```python3 +from DaisyX.function.pluginhelpers import get_text + +async def hi(client,message): + args = get_text(message) +``` + +### Edit or reply +```python3 +from DaisyX.function.pluginhelpers import edit_or_reply + +async def hi(client,message): + await edit_or_reply("Hi") +``` diff --git a/DaisyX/function/telethonbasics.py b/DaisyX/function/telethonbasics.py new file mode 100644 index 00000000..4048f8cb --- /dev/null +++ b/DaisyX/function/telethonbasics.py @@ -0,0 +1,185 @@ +import os + +from telethon.tl.types import * + +from DaisyX.function.pluginhelpers import runcmd + + +async def convert_to_image(event, borg): + lmao = await event.get_reply_message() + if not ( + lmao.gif + or lmao.audio + or lmao.voice + or lmao.video + or lmao.video_note + or lmao.photo + or lmao.sticker + or lmao.media + ): + await borg.send_message(event.chat_id, "`Format Not Supported.`") + return + else: + try: + time.time() + downloaded_file_name = await borg.download_media( + lmao.media, sedpath, "`Downloading...`" + ) + + except Exception as e: # pylint:disable=C0103,W0703 + await borg.send_message(event.chat_id, str(e)) + else: + lel = await borg.send_message( + event.chat_id, + "Downloaded to `{}` successfully.".format(downloaded_file_name), + ) + await lel.delete + if not os.path.exists(downloaded_file_name): + lel = await borg.send_message(event.chat_id, "Download Unsucessfull :(") + await lel.delete + return + if lmao and lmao.photo: + lmao_final = downloaded_file_name + elif lmao.sticker and lmao.sticker.mime_type == "application/x-tgsticker": + rpath = downloaded_file_name + image_name20 = os.path.join(sedpath, "SED.png") + cmd = f"lottie_convert.py --frame 0 -if lottie -of png {downloaded_file_name} {image_name20}" + stdout, stderr = (await runcmd(cmd))[:2] + os.remove(rpath) + lmao_final = image_name20 + elif lmao.sticker and lmao.sticker.mime_type == "image/webp": + pathofsticker2 = downloaded_file_name + image_new_path = sedpath + "image.png" + im = Image.open(pathofsticker2) + im.save(image_new_path, "PNG") + if not os.path.exists(image_new_path): + await event.reply("`Wasn't Able To Fetch Shot.`") + return + lmao_final = image_new_path + elif lmao.audio: + sed_p = downloaded_file_name + hmmyes = sedpath + "stark.mp3" + imgpath = sedpath + "starky.jpg" + os.rename(sed_p, hmmyes) + await runcmd(f"ffmpeg -i {hmmyes} -filter:v scale=500:500 -an {imgpath}") + os.remove(sed_p) + if not os.path.exists(imgpath): + await event.reply("`Wasn't Able To Fetch Shot.`") + return + lmao_final = imgpath + elif lmao.gif or lmao.video or lmao.video_note: + sed_p2 = downloaded_file_name + jpg_file = os.path.join(sedpath, "image.jpg") + await take_screen_shot(sed_p2, 0, jpg_file) + os.remove(sed_p2) + if not os.path.exists(jpg_file): + await event.reply("`Couldn't Fetch. SS`") + return + lmao_final = jpg_file + return lmao_final + + +async def take_screen_shot( + video_file: str, duration: int, path: str = "" +) -> Optional[str]: + """take a screenshot""" + logger.info( + "[[[Extracting a frame from %s ||| Video duration => %s]]]", + video_file, + duration, + ) + ttl = duration // 2 + thumb_image_path = path or os.path.join(sedpath, f"{basename(video_file)}.jpg") + command = f'''ffmpeg -ss {ttl} -i "{video_file}" -vframes 1 "{thumb_image_path}"''' + err = (await runcmd(command))[1] + if err: + logger.error(err) + return thumb_image_path if os.path.exists(thumb_image_path) else None + + +async def get_all_admin_chats(event): + lul_stark = [] + all_chats = [ + d.entity + for d in await event.client.get_dialogs() + if (d.is_group or d.is_channel) + ] + try: + for i in all_chats: + if i.creator or i.admin_rights: + lul_stark.append(i.id) + except: + pass + return lul_stark + + +async def is_admin(event, user): + try: + sed = await event.client.get_permissions(event.chat_id, user) + if sed.is_admin: + is_mod = True + else: + is_mod = False + except: + is_mod = False + return is_mod + + +async def progress(current, total, event, start, type_of_ps, file_name=None): + """Generic progress_callback for both + upload.py and download.py""" + now = time.time() + diff = now - start + if round(diff % 10.00) == 0 or current == total: + percentage = current * 100 / total + speed = current / diff + elapsed_time = round(diff) * 1000 + time_to_completion = round((total - current) / speed) * 1000 + estimated_total_time = elapsed_time + time_to_completion + progress_str = "[{0}{1}]\nProgress: {2}%\n".format( + "".join(["🟠" for i in range(math.floor(percentage / 5))]), + "".join(["🔘" for i in range(20 - math.floor(percentage / 5))]), + round(percentage, 2), + ) + tmp = progress_str + "{0} of {1}\nETA: {2}".format( + humanbytes(current), humanbytes(total), time_formatter(estimated_total_time) + ) + if file_name: + await event.edit( + "{}\nFile Name: `{}`\n{}".format(type_of_ps, file_name, tmp) + ) + else: + await event.edit("{}\n{}".format(type_of_ps, tmp)) + + +def humanbytes(size): + """Input size in bytes, + outputs in a human readable format""" + # https://stackoverflow.com/a/49361727/4723940 + if not size: + return "" + # 2 ** 10 = 1024 + power = 2 ** 10 + raised_to_pow = 0 + dict_power_n = {0: "", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + while size > power: + size /= power + raised_to_pow += 1 + return str(round(size, 2)) + " " + dict_power_n[raised_to_pow] + "B" + + +def time_formatter(milliseconds: int) -> str: + """Inputs time in milliseconds, to get beautified time, + as string""" + seconds, milliseconds = divmod(int(milliseconds), 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + ((str(days) + " day(s), ") if days else "") + + ((str(hours) + " hour(s), ") if hours else "") + + ((str(minutes) + " minute(s), ") if minutes else "") + + ((str(seconds) + " second(s), ") if seconds else "") + + ((str(milliseconds) + " millisecond(s), ") if milliseconds else "") + ) + return tmp[:-2] diff --git a/DaisyX/localization/ar_SA.yaml b/DaisyX/localization/ar_SA.yaml new file mode 100644 index 00000000..456de68a --- /dev/null +++ b/DaisyX/localization/ar_SA.yaml @@ -0,0 +1,676 @@ +--- +language_info: + flag: "🇮🇶" + code: ar +STRINGS: + notes: + #/save + note_saved: "🗒 تم حفظ الملاحظة {note_name} في {chat_title}!" + note_updated: "🗒 تم تحديث الملاحظة {note_name} في{chat_title}!" + you_can_get_note: "\nيمكنك عرض الملاحظة باستخدام /get {note_name} أو #{note_name}" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nبدائل طلب الملاحظة:" + blank_note: "لا يمكنك حفظ ملاحظة فارغة!" + notename_cant_contain: "الملاحظة لا يجب أن تحتوي على \"{symbol}\"!" + #/get + cant_find_note: لا يمكنني إيجاد الملاحظة هذه في {chat_name}! + no_note: "لم استطع إيجاد الملاحظة." + no_notes_pattern: "لم يتم العثور على ملاحظات من النمط %s" + u_mean: "\nهل تقصد #{note_name}؟" + #/notes + notelist_no_notes: "لا توجد أية ملاحظات في {chat_title}!" + notelist_header: "الملاحظات في {chat_name}:" + notes_in_private: " انقر فوق الزر أدناه للحصول على قائمة الملاحظات." + notelist_search: | + Pattern: {request} + Matched notes: + #/clear + note_removed: "الملاحظة #{note_name} حذفت من {chat_name}!" + note_removed_multiple: | + Removed multiple notes in {chat_name} + Removed:{removed} + not_removed_multiple: "لم تتم الإزالة:{not_removed}" + #/clearall + clear_all_text: "سيؤدي هذا إلى إزالة كافة الملاحظات من {chat_name}. هل أنت متأكد من فعل هذا؟" + clearall_btn_yes: "!نعم. إزالة جميع الملاحظات" + clearall_btn_no: "لا!" + clearall_done: "جميع ({num}) الملاحظات في {chat_name} أزيلت!" + #/search + search_header: | + طلب بحث في‌‌ {chat_name}: + النمط: {request} + الملاحظات المتطابقة: + #/noteinfo + note_info_title: "معلومات الملاحظة\n" + note_info_note: "أسماء الملاحظات: %s\n" + note_info_content: "محتوي الملاحظة: %s\n" + note_info_parsing: "وضع التحليل‌‌: %s\n" + note_info_created: "تمَّ إنشاءها في: {date} by {user}\n" + note_info_updated: "آخِر تحديثٍ في‌‌: {date} by {user}\n" + user_blocked: "أرسل /start في محادثة خاصة معي!" + #/privatenotes + private_notes_false_args: "You have 2 options Disable and Enable!" + already_enabled: "الملاحظة خاصة حاليا ممكّنة في %s" + current_state_info: "Private notes are currently {state} in {chat}!" + enabled_successfully: "تم تفعيل الملاحظات الخاصة في Enabled في %s بنجاح" + disabled_successfully: "تم تعطيل الملاحظات الخاصة في Disabled في %s بنجاح" + not_enabled: "Private notes are not enabled." + privatenotes_notif: "You have been successfully connected to {chat} notes! To disconnect please use command /disconnect!" + enabled: 'Enabled' + disabled: 'Disabled' + #/cleannotes + clean_notes_enable: "تمّ تفعيل مسح الرسائل بنجاح في {chat_name}" + clean_notes_disable: "تمّ تعطيل مسح الرسائل بنجاح في {chat_name}" + clean_notes_enabled: "وضع مسح الرسائل حاليا هو enabled in {chat_name}" + clean_notes_disabled: "وضع مسح الرسائل حاليا هو disabled in {chat_name}" + #Filter + filters_title: 'أرسل ملاحظة' + filters_setup_start: 'الرجاء إرسال إسم الملاحظة' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "لم يتم العثور على فلاتر في {chat_name}!" + #/addfilter + anon_detected: Being anonymous admin, you cannot add new filters, use connections instead. + regex_too_slow: "Your regex pattern matches too slowly (more than the half of second) and it can't be added!" + cancel_btn: "🛑 Cancel" + adding_filter: | + إضافة فلتر {handler} في {chat_name} + حدد الإجراء أدناه‌‌: + saved: "New filter was successfully saved!" + #/filters + list_filters: "قائمة الفلاتر في {chat_name}:\n" + #/delfilter + no_such_filter: I can't find that filter in {chat_name}, you can check what filters are enabled with the /filters command. + del_filter: "الفلتر بإسم '{handler}' تمت إزالته بنجاح‌‌!" + select_filter_to_remove: | + لقد وجدت العديد من المرشحات مع إسم '{handler}'. + يرجى تحديد واحد لإزالته: + #/delfilters or /delallfilters + not_chat_creator: Only chat creators can use this command! + delall_header: This would delete all filters in this chat. This IS irreversible. + confirm_yes: '⚠ Delete all' + confirm_no: '❌ Cancel' + delall_success: Successfully deleted all ({count}) filters in this chat! + warns: + #/warn + warn_sofi: "أنا لن أحذِّر المشرف!" + warn_self: ".هل تريد أن تحذر نفسك؟ فقط اترك الدردشة" + warn_admin: "حسنا.. أنت مخطئ لا يمكنك تحذير مشرف :(" + warn: "{admin} حذّر/حذّرت {user} في {chat_name}\n" + warn_bun: "تم تجاوز التحذيرات! {user} تم حظره!" + warn_num: "التحذيرات: {curr_warns}/{max_warns}\n" + warn_rsn: "السّبَب: {reason}\n" + max_warn_exceeded: تم تجاوز عدد التحذيرات! %s تم حظرة %s! + "max_warn_exceeded:tmute": تم تجاوز عدد التحذيرات! %s تم كتم صوته مؤقتاً حتى %s! + #warn rmv callback + warn_no_admin_alert: "⚠ You are NOT admin and cannot remove warnings!" + warn_btn_rmvl_success: "✅ Warning removed by {admin}!" + #/warns + warns_header: "هنا عدد تحذيراتك في هذه الدردشة‌‌ \n\n" + warns: "{count} : {reason} بواسطة {admin}\n" + no_warns: "حسنًا ، ليس لدى {user} أي تحذيرات.‌‌" + #/warnlimit + warn_limit: "حد التحذيرات في {chat_name} حاليًا هو: {num}" + warnlimit_short: 'يجب أن يكون حد التحذيرات 2 على الأقل!.' + warnlimit_long: 'عدد التحذيرات يجبُ أن يكون أقلّ من 1000' + warnlimit_updated: '✅ Warnlimit successfully updated to {num}' + #/delwarns + purged_warns: "{admin} تمّ حذف جميع التّحذيرات السّابقة {num} للمستخدم {user} في {chat_title}!" + usr_no_wrn: "{user} ليس لديها/لديهِ أي تحذيرات لإعادة تعيينها.‌‌ اي ليس لديه أي تحذيرات، لتُحذَف!" + rst_wrn_sofi: 'لم أَقُم بِتحذيرِ أَي أحدٍ لإعادة تعيينة' + not_digit: يَجبُ أن تَكونَ التحذيرات أَرقام!‌‌ + #/warnmode + same_mode: هذا هو الوضع الحالي! كيف يمكنني تغيير ذلك؟ + no_time: لاختيار الوضع "tmute" عليك أن تذكر الوقت! + invalid_time: Invalid time! + warnmode_success: Warn mode of %s has successfully changed to %s! + wrng_args: | + هذه هي إجراءات المتاحة: + %s + mode_info: | + الوضع الحالي في هذه المحادثة هي %s. + banned: تم حظره + muted: تم كتمه + filters_title: تحذير المستخدم + filter_handle_rsn: إجراء التصفية التلقائي! + msg_deleting: + no_rights_purge: "لا تملك حقوق كافية لتنظيف رسائل هذه المجموعة!" + reply_to_msg: "قم بالرد على رسالة للحذف!" + purge_error: "لا أستطيع متابعة التنظيف، محتمل لأنك بدأت في التطهير من رسالة أرسلت قبل أكثر من يومين." + fast_purge_done: "اكتمل التنظيف السريع!\nسيتم إزالة هذه الرسالة في 5 ثوان." + purge_user_done: "تم حذف جميع الرسائل من {user} بنجاح!" + misc: + your_id: "معرفك: {id}\n" + chat_id: "معرف المجموعة: {id}\n" + user_id: "معرف {user}: {id}\n" + conn_chat_id: "معرف الدردشة المتصلة: {id}" + help_btn: "اضغط للمساعدة!" + help_txt: "اضغط الزر أدناه للمساعدة!" + delmsg_filter_title: 'حذف الرسالة' + send_text: الرجاء إرسال رسالة الرد! + replymsg_filter_title: 'الرد على الرسالة' + send_customised_reason: Send the reason you want to include in action message. "None" for no reason to be given! + expected_text: Expected a text message! + promotes: + promote_success: "تم ترقية {user} بنجاح في {chat_name}!" + promote_title: "\nمع اسم مستعار {role}!" + rank_to_loong: "لا يمكن أن يكون الاسم المستعار أطول من 16 حرف." + promote_failed: "فشلت الترقية! تحقق مما إذا كان لدي اذونات." + demote_success: "تم تخفيض {user} بنجاح في {chat_name}!" + demote_not_admin: "هذا المستخدم ليس مشرفا." + demote_failed: "Demotion failed. Maybe I can't demote or the person is promoted by someone else?" + cant_get_user: "لم أستطع ايجاد المستخدم! إذا لم تمانع، يرجى الرد على رسالة منه والمحاولة مرة أخرى." + cant_promote_yourself: "لا يمكنك ترقية نفسك." + emoji_not_allowed: "أنا آسف، لكن الاسماء المستعارة لا يمكن أن تحتوي على إيموجي 😞" + pm_menu: + start_hi_group: 'مرحبًا هناك! إسمي صوفي' + start_hi: "مرحبًا هناك! اسمي صوفي، سوف أساعدك في إدارة مجموعتك بطريقة فعالة!" + btn_source: "📦 Source code" + btn_help: "مساعدة ❔" + btn_lang: "اللغة 🇷🇺" + btn_channel: "📡 Daisy News" + btn_group: "👥 Daisy Support" + btn_group_help: "Click me for help!" + back: الرجوع + #/help + help_header: "مرحباً! تفقد الويكي الخاص بنا للحصول على المساعدة" + click_btn: اضغط هنا + disable: + #/disableable + disablable: "الأوامر القابلة للتعطيل:\n" + #/disabled + no_disabled_cmds: لا توجد أوامر معطلة في {chat_name}! + disabled_list: "الأوامر المعطلة في {chat_name}:\n" + #/disable + wot_to_disable: "ما الذي تريد تعطيله؟" + already_disabled: "هذا الأمر معطل بالفعل!" + disabled: "الأمر {cmd} تم تعطيله في {chat_name}!" + #/enable + wot_to_enable: "ما الذي تريد تفعيله؟" + already_enabled: "هذا الأمر غير معطل!" + enabled: "تم تمكين الأمر {cmd} في {chat_name}!" + #/enableall + not_disabled_anything: "لم يتم تعطيل أي شيء في {chat_title}!" + enable_all_text: "This will enable all commands in the {chat_name}. Are you sure?" + enable_all_btn_yes: "نعم. تمكين جميع الأوامر!" + enable_all_btn_no: "لا!" + enable_all_done: "تم تمكين جميع الأوامر ({num}) في {chat_name}!" + bot_rights: + change_info: "ليس لدي حقوق في تغيير معلومات المجموعة، من فضلك أجعلني مشرفا بهذا الاذن." + edit_messages: "ليس لدي الاذن في تحرير الرسائل!" + delete_messages: "ليس لدي الاذن في حذف الرسائل هنا." + ban_user: "ليس لدي حقوق في حظر المستخدمين، من فضلك أجعلني مشرفا بهذا الاذن." + pin_messages: "ليس لدي الحق في تثبيت الرسائل، من فضلك أجعلني مشرفا بهذا الاذن." + add_admins: "ليس لدي حقوق في إضافة المشرفين ، من فضلك أجعلني مشرفا بهذا الاذن." + feds: + #decorators + need_fed_admin: "You are not an admin in {name} federation" + need_fed_owner: "You are not an owner of {name} federation" + cant_get_user: "Sorry, I can't get that user, try using their user ID" + #/fnew + fed_name_long: "اسم الاتحاد لا يمكن أن يكون أطول من 60 حرفا!" + can_only_1_fed: "Users can only create 1 federation, please remove one." + name_not_avaible: "الاتحاد الذي يحمل الاسم {name} موجود مسبقاً! الرجاء استخدام اسم آخر." + created_fed: | + Congrats, you have successfully created a federation. + Name: {name} + ID: {id} + Creator: {creator} + Use /fjoin {id} to connect federation to chat\ + disallow_anon: As an anonymous admin you cannot create new federation! + #/fjoin + only_creators: "You must be the chat creator to be able to connect chat to a federation." + fed_id_invalid: "The given federation ID is invalid! Please give me a valid ID." + joined_fed_already: "This chat has already joined a federation! Please use /fleave to leave that federation" + join_fed_success: "Great! Chat {chat} is now a part of {fed} federation!" + join_chat_fed_log: | + Chat joined federation #ChatJoined + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "هذه الدردشة ليست في أي اتحاد حتى الآن." + leave_fed_success: "دردشة {chat} غادرت اتحاد {fed}." + leave_chat_fed_log: | + الدردشة غادرت الاتحاد #ChatLeaved + الاتحاد: {fed_name} ({fed_id}) + الدردشة: {chat_name} ({chat_id}) + #/fsub + already_subsed: "الاتحاد {name} مشترك بالفعل في {name2}" + subsed_success: "اتحاد {name} اشترك في {name2}!" + #/funsub + not_subsed: "Federation {name} is not subscribed to {name2}" + unsubsed_success: "Federation {name} unsubscribed from {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "User {user} added to {name} federation admins." + promote_user_fed_log: | + المستخدم ترقى لمشرف #UserPromoted + الاتحاد: {fed_name} ({fed_id}) + المستخدم: {user} ({user_id}) + "restricted_user:promote": This user is restricted from being federation admin! + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "User {user} demoted from {name} federation admins." + demote_user_fed_log: | + المستخدم انخفض كمشرف اتحادي #UserDemoted + الاتحاد: {fed_name} ({fed_id}) + المستخدم: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "{name} federation already has logging enabled in another chat/channel!" + set_chat_log: | + This chat is now logging all actions in {name} federation + + Federation logging is used for internal assessment of the federation and its performance. + The federation should NOT be used as a public wall of shame thereby respecting user data and our privacy guidelines. + set_log_fed_log: | + تمكين تسجيل #LoggingEnable + الاتحاد: {fed_name} ({fed_id}) + no_right_to_post: ليس لدي حقوق في ارسال الرسائل في تلك القناة! + #/funsetlog + not_logging: "{name} federation isn't logging to any chat/channel!" + logging_removed: "Successfully removed logging from {name} federation." + unset_log_fed_log: | + تعطيل تسجيل #LoggingDisable + الاتحاد: {fed_name} ({fed_id}) + #/fchatlist + no_chats: "There's no chats joined this {name} federation" + chats_in_fed: "Chats connected to {name} federation:\n" + too_large: "المخرج كبير جداً، سيرسل كملف" + #/finfo + finfo_text: | + معلومات الاتحاد + الاسم: {name} + المعرف: {fed_id} + المنشئ: {creator} + الدردشات في الاتحاد: {chats} + المستخدمين المحظورين في الاتحاد: {fbanned} + finfo_subs_title: "Federations subscribed to this feds:\n" + #/fadminlist + fadmins_header: "Admins in {fed_name} fed:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "هذا المستخدم في القائمة البيضاء من حظر." + fban_self: "انها عرض جميل!" + fban_creator: "How can I ban the federation creator?! I don't think it's gonna be funny." + fban_fed_admin: "I'm not going to ban a federation admin from their own fed!" + update_fban: "This user was already fbanned therefore I am updating the reason to {reason}" + already_fbanned: "{user} محظور بالفعل في هذا الاتحاد." + fbanned_header: "حظر اتحادي جديد\n" + fban_info: | + الاتحاد: {fed} + مشرف الاتحاد: {fadmin} + المستخدم: {user} ({user_id}) + fbanned_reason: "السبب: {reason}" + fbanned_process: "\nالحالة: حظر في {num} محادثات..." + fbanned_done: "\nالحالة: تم! تم حظره من{num} محادثة!" + fbanned_silence: "سيتم تنظيف هذه الرسالة في 5 ثوان" + fbanned_subs_process: "\nالحالة: حظر في {feds} مشترك فيها..." + fbanned_subs_done: "\nStatus: Done! Banned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + fban_usr_rmvd: | + User {user} is currently banned in {fed} federation and have been removed! + Reason: {rsn} + fban_log_fed_log: | + المستخدم المحظور في الاتحاد #Fedban + الاتحاد: {fed_name} ({fed_id}) + المستخدم: {user} ({user_id}) + بواسطة: {by} + دردشة محظورة: مستخدم محظور في {chat_count}/{all_chats} دردشة + fban_reason_fed_log: "السبب: {reason}\n" + fban_subs_fed_log: "\nالاتحادات المشتركة: محظور في {subs_chats} دردشة من اتحادات {feds}" + #/unfban + unfban_self: "Think of something else to have fun with instead of this." + user_not_fbanned: "{user} isn't banned in this federation." + un_fbanned_header: "الغاء--حظر جديد\n" + un_fbanned_process: "\nالحالة: فك الحظر من {num} محادثات..." + un_fbanned_done: "\nالحالة: تم! فك الحظر من {num} محادثات!" + un_fbanned_subs_process: "\nالحالة: فك الحظر في {feds} اتحادات مشتركة..." + un_fbanned_subs_done: "\nالحالة: تم! تم فك الحظر في {chats} دردشة هذا الاتحاد و {subs_chats} دردشة {feds} من الاتحادات المشتركة" + un_fban_log_fed_log: | + فك الحظر عن المستخدم في الاتحاد #FedUnban + Fed: {fed_name} ({fed_id}) + المستخدم: {user} ({user_id}) + بواسطة: {by} + الدردشة: مستخدم غير محظور في {chat_count}/{all_chats} دردشة + un_fban_subs_fed_log: "\nاتحادات مشتركة: غير محظورة في {subs_chats} دردشة من {feds} اتحادات" + #/delfed + delfed_btn_yes: نعم. حذف الاتحاد + delfed_btn_no: لا. + delfed: | + Are you sure you want to delete the %s federation? + + Disclaimer: Deleting this federation will remove all data associated with it from our database! + This action is permanent and cannot be reversed! + delfed_success: حذفت الاتحاد بنجاح! + #/frename + frename_same_name: Please enter a different name, not the same one! + frename_success: The federation {old_name} renamed to {new_name} successfully! + #/fbanlist + fbanlist_locked: Please wait until %s to use fbanlist again. + creating_fbanlist: إنشاء قائمة fban! الرجاء الانتظار.. + fbanlist_done: Fbanlist of federation %s! + #/importfbans + send_import_file: | + Send the import file! + + To cancel this process use /cancel + rpl_to_file: الرجاء الرد على ملف! + importfbans_locked: استيراد حظور الاتحاد مقفل لمدة %s في هذا الاتحاد. + big_file_csv: Only supports import csv files less {num} megabytes! + big_file_json: | + Currently json files are limited to {num} megabytes! + Use the csv format if you want to import bigger files. + invalid_file: "الملف غير صالح" + wrong_file_ext: "تنسيق الملف خاطئ! الدعم الحالي هو: json, csv" + importing_process: استيراد حظر الاتحاد... + import_done: Importing fed bans finished! {num} bans were imported. + #def + automatic_ban: User {user} is banned in the current federation {fed_name} and has been removed! + automatic_ban_sfed: User {user} is banned in the sub federation {fed_name}{text}" + #/fcheck + fcheck_header: "معلومات حظر الاتحاد:\n" + fbanned_count_pm: "تم حظرك في {count} اتحاد(ات)!\n" + "fban_info:fcheck": "You have been fbanned in {fed} federation on {date}, No reason was given!" + "fban_info:fcheck:reason": "لقد تم حظرك في اتحاد {fed} على {date} السبب -\n {reason}\n" + fbanned_data: "تم حظر {user} في {count} اتحاد(ات)\n" + fbanned_nowhere: "{user} isn't fbanned anywhere!\n" + fbanned_in_fed: "Including the {fed} federation!\n" + "fbanned_in_fed:reason": "Including the {fed} federation for reason of -\n {reason}" + contact_in_pm: "\n\nFor more info on your fbans, please send me a direct message for privacy!" + too_long_fbaninfo: "حسنًا! تحتاج معلومات حظر اتحاد الخاصة بك إلى ملف!" + didnt_fbanned: "You aren't fbanned in this fed." + not_fbanned_in_fed: They aren't fbanned in the {fed_name} fed + reports: + user_user_admin: "أنت مشرف هنا، لماذا تحتاج إلى الإبلاغ عن شخص ما؟.." + user_is_whitelisted: "أنت مستخدم في القائمة البيضاء، لماذا تحتاج إلى الإبلاغ عن شخص ما؟.." + reports_disabled: "التقارير حاليا معطلة في هذه المجموعة." + no_user_to_report: "أي مستخدم تريد الإبلاغ عنه؟" + report_admin: "You cannot report admins." + report_whitedlisted: "لا يمكنك الإبلاغ عن مستخدم في القائمة البيضاء." + reported_user: "أبلغ {user} للمشرفين." + reported_reason: "\nالسبب:\n{reason}" + reports_on: "التقارير مشغلة في في هذه المجموعة." + reports_off: "التقارير معطلة في هذه المجموعة." + reports_already_on: "التقارير مفعلة بالفعل." + reports_turned_on: "تم تشغيل التقارير." + reports_already_off: "تم بالفعل إيقاف تشغيل التقارير." + wrong_argument: " معطى خاطئ." + language: + your_lang: "لغتك الحالية: {lang}" + chat_lang: "لغة الدردشة الحالية: {lang}" + select_pm_lang: "\nحدد لغتك الجديدة:" + select_chat_lang: "\nحدد لغة الدردشة الجديدة:" + crowdin_btn: "🌎 ساعدنا مع الترجمة" + help_us_translate: "\n\n🌎 Help us improve our translations" + not_supported_lang: This language is not supported! + lang_changed: تم تغيير اللغة إلى {lang_name}. + see_translators: '👥 عرض المترجمين' + back: الرجوع + connections: + pm_connected: "تم ربط خاصك معي بنجاح بـ {chat_name}!" + connected_pm_to_me: "تم ربط خاصك معي بنجاح بـ {chat_name}! الرجاء مراسلتي للبدء في استخدام الاتصال." + chat_connection_tlt: "اتصال الدردشة\n" + connected_chat: | + الدردشة المتصلة: + {chat_name} + اكتب /disconect من الدردشة + "connected_chat:cmds": | + Current connected chat: + {chat_name}, (bridged commands are {commands}), + ⚠ You can only access bridged commands of the connected chat! All other commands would be treated as Local chat + Write /disconect to disconnect from chat. + u_wasnt_connected: "You were not connected to any chat before!" + select_chat_to_connect: "\nاختر دردشة للاتصال:" + disconnected: "لقد تم فصلك عن {chat_name}." + cant_find_chat: "لا أستطيع العثور على هذه المحادثة." + cant_find_chat_use_id: "لا أستطيع العثور على هذه المحادثة. استخدم معرف المحادثة." + bad_arg_bool: "خيار خاطئ: فقط يدعم on/off enable/disable" + enabled: مفعّل + disabled: معطّل + chat_users_connections_info: "الاتصالات للمستخدمين العاديين حاليا {status} لـ {chat_name}" + chat_users_connections_cng: "تم تغيير الاتصالات للمستخدمين العاديين إلى {status} لـ {chat_name}" + not_in_group: "أنت لست في المجموعة التي تحاول الاتصال بها والانضمام إليها وإرسال رسالة." + history_empty: "أنت غير متصل بأي محادثة في التاريخ، قم بالاتصال عن طريق`/connect (محادثة id)`" + not_in_chat: "أنت لست في هذه المحادثة بعد الآن، سأفصل اتصالاتك." + u_should_be_admin: "يجب أن تكون مشرفا في {}!" + usage_only_in_groups: "الاستخدام محدود فقط في المجموعات!" + anon_admin_conn: | + Please click below button to connect! + click_here: Click here! + global: + u_not_admin: يجب أن تكون مشرفا للقيام بهذا! + #Rights + "bot_no_right:not_admin": أنا لست مشرفًا! + bot_no_right: "لست بحاجة إلى إذن للقيام بذلك! \nإذن مفقود: %s" + user_no_right: "يجب أن يكون لديك الإذن للقيام بذلك! \nالصلاحية مفقودة: %s" + "user_no_right:not_admin": يجب أن تكون مشرف للقيام بذلك! + #is chat creator + unable_identify_creator: "Unable to identify anonymous chat creator, duplicate signature (titles) found!" + owner_stuff: + father: "\nانه منشئي." + sudo_crown: "\nمرحبا! انظر، هذا المستخدم لديه تاج، دعوني أراه... واو، لديه نحت - 'مستخدم سودو'!" + stickers: + rpl_to_sticker: "الرد على الملصق!" + ur_sticker: | + الإيموجي: {emoji} + معرف الملصق: {id} + pins: + no_reply_msg: "الرد على رسالة لتثبيت الرسائل!" + chat_not_modified_pin: "هذه الرسالة مثبتة بالفعل!" + chat_not_modified_unpin: "لا توجد أي رسالة مثبتة لإزالتها!" + pinned_success: "تم تثبيت النجاح!" + unpin_success: "تم إلغاء التثبيت بنجاح!" + users: + user_info: "معلومات المستخدم:\n" + info_id: "ID: {id}" + info_first: "\nالاسم الأول: {first_name}" + info_last: "\nاسم العائلة: {last_name}" + info_username: "\nاسم المستخدم: {username}" + info_link: "\nرابط المستخدم: {user_link}" + info_saw: "\nرأيتهم في {num} مجموعات" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_fbanned: "\nمحظور في الاتحاد الحالي: " + info_admeme: "\nللمستخدم حقوق المشرف في هذه المحادثة." + upd_cache: "تحديث ذاكرة التخزين المؤقت الآن..." + upd_cache_done: "تم تحديث ذاكرة التخزين المؤقت للمشرفين." + admins: "المشرف في هذه المجموعة:\n" + imports_exports: + #Exporting + started_exporting: بدأ التصدير! الرجاء الانتظار... + export_done: تم التصدير من {chat_name}! + exports_locked: Please wait %s before using exports again! + #Importing + invalid_file: "الملف غير صالح" + send_import_file: Please send a file for importing! + rpl_to_file: الرجاء الرد على ملف! + big_file: Only files under 50 MB are supported! + started_importing: بدأ الاستيراد! الرجاء الانتظار... + bad_file: ملف سيئ! توقع 'عام' عمود. + file_version_so_new: Exported file version is newer than what Daisy currently supports! + imports_locked: Please wait %s before using imports again! + import_done: تم الاستيراد! + restrictions: + user_not_in_chat: User is not in this chat! + #Kicks + kick_DaisyX: بدلاً من محاولة طردي يمكنك قضاء وقتك بشكل أفضل. هذا ممل فقط. + kick_admin: ليس من الأفضل التفكير في طرد المدير. + kick_self: If you want to kick yourself - just leave this chat. I would rather not see you than look at these pathetic attempts at self-punishment. + user_kicked: "{user} تم طرده من قبل {admin} في {chat_name}." + #Mutes + mute_DaisyX: في الواقع، سأكون سعيدة بإغلاقك بدلاً من ذلك. + mute_admin: If you think you can shut up an admin, you are wrong! + mute_self: If you want to mute yourself - just stop talking! Better to thought a fool than open your mouth and remove all doubt. + user_muted: "{user} تم كتم الصوت بواسطة {admin} في {chat_name}." + unmute_DaisyX: في الواقع، أنا لست مكتوما. + unmute_admin: Haha no, try muting him first! + unmute_self: هل يجب علي كتم صوتك أولاً؟ + user_unmuted: "تم إلغاء كتم {user} بواسطة {admin} في {chat_name}." + #Bans + ban_DaisyX: No, I won't do it! Ask the chat creator to do it! + ban_admin: هاها، دعونا /demote أولاً. + ban_self: لماذا تحاول حظر نفسك؟ + user_banned: "{user} تم حظره من قبل {admin} في {chat_name}." + unban_DaisyX: في الواقع، أنا لست محظورة. + unban_admin: Haha no, try banning him first! + unban_self: هل يجب أن أمنعك أولاً؟ + user_unband: "{user} تم فك حظره من قبل {admin} في {chat_name}." + invalid_time: وقت غير صالح! + enter_time: Please specify a time! + on_time: "\nمن أجل %s" + reason: "\nالسبب: %s" + purge: "\nMessages will be purged in 5 seconds!" + filter_title_ban: حظر المستخدم + filter_action_rsn: إجراء الفلتر الآلي! + filtr_ban_success: '%s حظر %s ل %s' + filtr_mute_success: '%s كتم %s لـ %s' + filtr_tmute_success: | + %s تم كتم %s ل %s + السبب هو %s + time_setup_start: Please specify a time! + filtr_tban_success: | + %s حظر %s ل %s + السبب هو %s + filter_title_tmute: كتم صوت المستخدم + filter_title_mute: كتم صوت المستخدم + filter_title_tban: حظر المستخدم + filter_title_kick: طرد المستخدم + rules: + #Set + saved: تم حفظ القواعد في %s + updated: تم تحديث القواعد في %s + #Reset + deleted: تم حذف القواعد! + not_found: لم يتم العثور على القواعد! + #Callback + user_blocked: "اكتب /start في PM!" + rules_was_pmed: تم إرسال القواعد إلى بريدك! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "Welcome {mention}! Please press button below to verify yourself as human!" + bool_invalid_arg: حجة غير صالحة، زر المتوقع/كلمة التحقق. + #/setwelcome + saved: تم تعطيل كتم الترحيب في %s + updated: تم تحديث رسالة الترحيب في %s + #/turnwelcome + turnwelcome_status: المراحلات هي {status} في {chat_name} + turnwelcome_enabled: تم تمكين الترحيب في %s + turnwelcome_disabled: الترحيب المعطل في %s + #/welcome + enabled: مفعّل + disabled: معطل + wlcm_security_enabled: "مفعل، تعيين إلى {level}" + wlcm_mutes_enabled: "مفعل، تعيين إلى {time}" + welcome_info: | + إعدادات الترحيب في {chat_name}: + مرحبا بك {welcomes_status} + مرحبا بالأمان {wlcm_security} + كتم الترحيب هو {wlcm_mutes} + حذف الترحيبات القديمة هو {clean_welcomes} + حذف رسائل الخدمة هو {clean_service} + wlcm_note: "\n\nملاحظة الترحيب هي:" + raw_wlcm_note: "ملاحظة الترحيب الأولية هي:" + security_note: "نص الأمان الترحيبي هو:" + #/setsecuritynote + security_note_saved: "تم حفظ نص الحماية المخصص %s" + security_note_updated: "تم تحديث نص الأمان المخصص %s" + #/delsecuritynote + security_note_not_found: Security text in %s has not been set before + del_security_note_ok: Security text was reset to default in %s + #/cleanwelcome + cleanwelcome_enabled: تم تمكين تنظيف الترحيب في %s + cleanwelcome_disabled: تنظيف الترحيب معطل في %s + cleanwelcome_status: تنظيف الترحيب هو {status} في {chat_name} + #/cleanservice + cleanservice_enabled: تنظيف رسائل الخدمة مفعّل في %s + cleanservice_disabled: تم تعطيل تنظيف رسائل الخدمة في %s + cleanservice_status: تنظيف رسائل الخدمة هو {status} في {chat_name} + #/welcomemute + welcomemute_invalid_arg: Invalid argument, expected a time. + welcomemute_enabled: تم تمكين كتم الترحيب في %s + welcomemute_disabled: تم تعطيل كتم الترحيب في %s + welcomemute_status: كتم الترحيب هو {status} في {chat_name} + #/welcomesecurity + welcomesecurity_enabled: تم تمكين الأمان الترحيبي في {chat_name} إلى {level} مستوى. + "welcomesecurity_enabled:customized_time": | + Welcome security enabled in {chat_name} to {level}, and kick users which aren't verified within {time}. + ask_for_time_customization: | + Do you want to customize the time period to kick unverified user? (by default it's {time}) + send_time: Please specify a time. + invalid_time: "Invalid time!" + welcomesecurity_enabled_word: مفعل، تعيين إلى المستوى {level} + welcomesecurity_disabled: تم تعطيل الأمان الترحيبي في %s. + welcomesecurity_invalid_arg: حجة غير صالحة، زر المتوقع/كلمة التحقق. + welcomesecurity_status: الأمان الترحيبي هو {status} في {chat_name} + yes_btn: "Yes" + no_btn: "No" + #Welcomesecurity handler + click_here: انقر هنا! + not_allowed: غير مسموح لك باستخدام هذا! + ws_captcha_text: مرحبا {user}، الرجاء إدخال رقم كلمة التحقق ليتم عدم كتم الكتم في المحادثة. + regen_captcha_btn: '🔄 تغيير كلمة التحقق' + num_is_not_digit: الرجاء إدخال الأرقام فقط! + bad_num: رقم خاطئ! الرجاء المحاولة مرة أخرى. + passed: تم التحقق، لقد تم إلغاء كتم في %s! + passed_no_frm: تمرير التحقق، لقد تم إلغاء كتم الصوت في %s! + last_chance: لديك آخر فرصة للدخول إلى كلمة التحقق! + btn_button_text: "الرجاء النقر على الزر أدناه لإثبات نفسك." + btn_wc_text: | + الرجاء الضغط على الزر الأيمن المطابق في هذا التعبير: + %s + math_wc_rtr_text: "لديك الفرصة الأخيرة! كن حذرا.\n" + math_wc_wrong: خطأ، الرجاء المحاولة مرة أخرى! + math_wc_sry: عذرا لقد استخدمت المحاولة الأخيرة. اسأل مسؤولي الدردشة. + not_admin_wm: I can't restrict people here, so I won't use welcome mute! + not_admin_ws: I can't restrict people here, so I won't use welcome security! + not_admin_wsr: لا يمكنني حذف الرسائل هنا، لذلك لن أحذف رسائل الخدمة! + #/resetwelcome + deleted: Successfully reset greetings in {chat}! + not_found: لا يوجد شيء لإعادة تعيينه! + #... + verification_done: Verification is done! You have been unmuted. + locks: + locks_header: "فيما يلي إعدادات القفل الحالية في {chat_title} \n\n" + no_lock_args: أعطني بعض الفقرات لقفلها! + already_locked: المحادثة مخفية اصلا هل تشعر بدوار ام ماذا + no_such_lock: انظر إلى /locks لمعرفة ما يمكن أن يكون مقفلاً وفتحه! + locked_successfully: تم قفل {lock} في {chat} بنجاح! + no_unlock_args: أعطني بعض الفقرات لقفلها! + not_locked: هههه، غير مقفل اصلا لفتحه. + unlocked_successfully: تم فتح {lock} في {chat} بنجاح! + antiflood: + #enforcer + flood_exceeded: "{action} {user} for flooding!" + #/setflood + "invalid_args:setflood": "Expected a number!" + overflowed_count: Maximum count should be less than 200! + config_proc_1: Please send expiration time, '0' (zero) for not being expired. + cancel: '❌ Cancel' + setup_success: Successfully configured antiflood, now enforces against those who send {count} messages within {time}! + "setup_success:no_exp": Successfully configured antiflood, allows {count} messages consecutively! + invalid_time: Invalid time! + #/antiflood | /flood + not_configured: Antiflood isn't configured in this chat yet! + turned_off: Disabled antiflood in {chat_title}! + "configuration_info:with_time": Antiflood is configured in this chat, those who send {count} message within {time} will be {action}! + configuration_info: Antiflood is configured in this chat, those who send {count} consecutively will be {action}! + ban: banned + mute: muted + kick: kicked + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "Unsupported action, expected {supported_actions}!" + setfloodaction_success: "Successfully updated flood action to {action}!" + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/en.yaml b/DaisyX/localization/en.yaml new file mode 100644 index 00000000..4df6e911 --- /dev/null +++ b/DaisyX/localization/en.yaml @@ -0,0 +1,761 @@ +language_info: + flag: "🇺🇸" + code: en + +STRINGS: + notes: + # /save + note_saved: "🗒 Note {note_name} saved in {chat_title}!" + note_updated: "🗒 Note {note_name} updated in {chat_title}!" + you_can_get_note: "\nYou can retrieve this note by using /get {note_name}, or #{note_name}" + # Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nNote aliases:" + blank_note: "Saving blank note is not allowed!" + notename_cant_contain: "Note name can't contain \"{symbol}\"!" + + # /get + cant_find_note: I can't find this note in {chat_name}! + no_note: "Can't find that note." + no_notes_pattern: "No notes found by pattern %s" + u_mean: "\nDid you mean #{note_name}?" + + # /notes + notelist_no_notes: "There aren't any notes in {chat_title}!" + notelist_header: "Notes in {chat_name}:" + notes_in_private: "Click below button to get notes list." + notelist_search: | + Pattern: {request} + Matched notes: + # /clear + note_removed: "Note #{note_name} removed in {chat_name}!" + note_removed_multiple: | + Removed multiple notes in {chat_name} + Removed:{removed} + not_removed_multiple: "Not removed:{not_removed}" + + # /clearall + clear_all_text: "This will remove all notes from {chat_name}. Are you sure?" + clearall_btn_yes: "Yes. Remove all my notes!" + clearall_btn_no: "No!" + clearall_done: "All ({num}) notes in {chat_name} were removed!" + + # /search + search_header: | + Search request in {chat_name}: + Pattern: {request} + Matched notes: + # /noteinfo + note_info_title: "Note info\n" + note_info_note: "Note names: %s\n" + note_info_content: "Note content: %s\n" + note_info_parsing: "Parse mode: %s\n" + note_info_created: "Was created in: {date} by {user}\n" + note_info_updated: "Last updated in: {date} by {user}\n" + user_blocked: "Write /start in my PM!" + + # /privatenotes + private_notes_false_args: "You have 2 options Disable and Enable!" + already_enabled: "Private notes are already enabled in %s" + current_state_info: "Private notes are currently {state} in {chat}!" + enabled_successfully: "Private notes are Enabled in %s successfully!" + disabled_successfully: "Private notes are Disabled in %s successfully!" + not_enabled: "Private notes are not enabled." + privatenotes_notif: "You have been successfully connected to {chat} notes! To disconnect please use command /disconnect!" + enabled: 'Enabled' + disabled: 'Disabled' + + # /cleannotes + clean_notes_enable: "Cleaning notes successfully enabled in {chat_name}" + clean_notes_disable: "Cleaning notes successfully disabled in {chat_name}" + clean_notes_enabled: "Cleaning notes currently is enabled in {chat_name}" + clean_notes_disabled: "Cleaning notes currently is disabled in {chat_name}" + + # Filter + filters_title: 'Send a note' + filters_setup_start: 'Please send a note name.' + + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "No filters was found in {chat_name}!" + + # /addfilter + anon_detected: Being anonymous admin, you cannot add new filters, use connections instead. + regex_too_slow: "Your regex pattern matches too slowly (more than the half of second) and it can't be added!" + cancel_btn: "🛑 Cancel" + adding_filter: | + Adding filter {handler} in {chat_name} + Select action below: + saved: "New filter was successfully saved!" + + # /filters + list_filters: "Filters in {chat_name}:\n" + + # /delfilter + no_such_filter: I can't find that filter in {chat_name}, you can check what filters are enabled with the /filters command. + del_filter: "Filter with handler '{handler}' was successfully removed!" + select_filter_to_remove: | + I have found many filters with handler '{handler}'. + Please select one to remove: + # /delfilters or /delallfilters + not_chat_creator: Only chat creators can use this command! + delall_header: This would delete all filters in this chat. This IS irreversible. + confirm_yes: ⚠ Delete all + confirm_no: ❌ Cancel + delall_success: Successfully deleted all ({count}) filters in this chat! + + warns: + # /warn + warn_sofi: "Haha no way to warn myself!" + warn_self: "Do you wanna warn yourself? Just leave the chat then." + warn_admin: "Well.. you are wrong. You can't warn an admin." + warn: "{admin} has warned {user} in {chat_name}\n" + warn_bun: "Warnings has been exceeded! {user} has been banned!" + warn_num: "Warns: {curr_warns}/{max_warns}\n" + warn_rsn: "Reason: {reason}\n" + max_warn_exceeded: Warnings has been exceeded! {user} has been {action}! + max_warn_exceeded:tmute: Warnings has been exceeded! {user} has been tmuted for {time}! + + # warn rmv callback + warn_no_admin_alert: "⚠ You are NOT admin and cannot remove warnings!" + warn_btn_rmvl_success: "✅ Warning removed by {admin}!" + + # /warns + warns_header: "Here are your warns in this chat \n\n" + warns: "{count} : {reason} by {admin}\n" + no_warns: "Well, {user} doesn't have any warns." + + # /warnlimit + warn_limit: "Warn limit in {chat_name} is currently: {num}" + warnlimit_short: 'Warnlimit should be at least 2!' + warnlimit_long: 'Warnlimit should be shorter than 1 000!' + warnlimit_updated: '✅ Warnlimit successfully updated to {num}' + + # /delwarns + purged_warns: "{admin} reset {num} warns of {user} in {chat_title}!" + usr_no_wrn: "{user} doesn't have any warns to reset." + rst_wrn_sofi: 'Daisy never had warns to reset.' + not_digit: Warnlimits should be digits! + + # /warnmode + same_mode: This is the current mode! How can I change it? + no_time: For selecting mode 'tmute' you have to mention time! + invalid_time: Invalid time! + warnmode_success: Warn mode of %s has successfully changed to %s! + wrng_args: | + These are the available options: + mode_info: | + Current mode in this chat is %s. + banned: banned + muted: muted + + filters_title: Warn the user + filter_handle_rsn: Automated filter action! + + msg_deleting: + no_rights_purge: "You don't have enough rights to purge in this group!" + reply_to_msg: "Reply to a message to delete!" + purge_error: "I can't continue purge, mostly because you started purge from a message that was sent older than 2 days." + fast_purge_done: "Fast purge completed!\nThis message will be removed in 5 seconds." + purge_user_done: "All messages from {user} were successfully deleted!" + misc: + your_id: "Your ID: {id}\n" + chat_id: "Group ID: {id}\n" + user_id: "{user}'s ID: {id}\n" + conn_chat_id: "Current connected chat ID: {id}" + help_btn: "Click me for help!" + help_txt: "Click the button below for help!" + + delmsg_filter_title: 'Delete the message' + send_text: Please send the reply message! + replymsg_filter_title: 'Reply to message' + + send_customised_reason: Send the reason you want to include in action message. "None" for no reason to be given! + expected_text: Expected a text message! + + promotes: + promote_success: "{user} was successfully promoted in {chat_name}!" + promote_title: "\nWith custom role {role}!" + rank_to_loong: "Custom role text cannot be longer than 16 symbols." + promote_failed: "Promotion failed! Check if I have rights to." + demote_success: "{user} was successfully demoted in {chat_name}!" + demote_not_admin: "That user isn't admin." + demote_failed: "Demotion failed. Maybe I can't demote or the person is promoted by someone else?" + cant_get_user: "I couldn't get the user! If you mind, please reply to his message and try again." + cant_promote_yourself: "You can't promote yourself." + emoji_not_allowed: "I'm sorry, but admin roles can't have emojis 😞" + + pm_menu: + start_hi_group: 'Hey there! My name is Daisy' + start_hi: "Hey there! My name is Daisy.\nI can help manage your groups with useful features, feel free to add me to your groups!" + btn_source: "📜 Source code" + btn_help: "❔ Help" + btn_lang: "🇺🇸 Language" + btn_channel: "🙋‍♀️ Daisy News" + btn_group: "👥 Daisy Support" + btn_group_help: "Click me for help!" + back: "🏃‍♂️ Back" + + # /help + help_header: "Hi Boss! I'm Daisy. An anime themed super powerful group management bot with many handy tools. So why are you waiting. Let me to assist you" + click_btn: Click here + + disable: + #/disableable + disablable: "Disable-able commands:\n" + + #/disabled + no_disabled_cmds: No disabled commands in {chat_name}! + disabled_list: "Disabled commands in {chat_name}:\n" + + #/disable + wot_to_disable: "What do you want to disable?" + already_disabled: "This command is already disabled!" + disabled: "Command {cmd} was disabled in {chat_name}!" + + #/enable + wot_to_enable: "What do you want to enable?" + already_enabled: "This command isn't disabled!" + enabled: "Command {cmd} was enabled in {chat_name}!" + + #/enableall + not_disabled_anything: "Nothing was disabled in {chat_title}!" + enable_all_text: "This will enable all commands in the {chat_name}. Are you sure?" + enable_all_btn_yes: "Yes. Enable all commands!" + enable_all_btn_no: "No!" + enable_all_done: "All ({num}) commands were enabled in the {chat_name}!" + bot_rights: + change_info: "I don't have rights to change group info, please make me admin with that right." + edit_messages: "I don't have rights to edit messages!" + delete_messages: "I don't have rights to delete messages here." + ban_user: "I don't have rights to ban users, please make me admin with that right." + pin_messages: "I don't have rights to pin messages, please make me admin with that right." + add_admins: "I don't have rights to add admins, please make me admin with that right." + feds: + # decorators + need_fed_admin: "You are not an admin in {name} federation" + need_fed_owner: "You are not an owner of {name} federation" + cant_get_user: "Sorry, I can't get that user, try using their user ID" + + # /fnew + fed_name_long: "Federation name can't be longer than 60 symbols!" + can_only_1_fed: "Users can only create 1 federation, please remove one." + name_not_avaible: "Federation with name {name} already exits! Please use another name." + created_fed: | + Congrats, you have successfully created a federation. + Name: {name} + ID: {id} + Creator: {creator} + Use /fjoin {id} to connect federation to chat\ + disallow_anon: As an anonymous admin you cannot create new federation! + + # /fjoin + only_creators: "You must be the chat creator to be able to connect chat to a federation." + fed_id_invalid: "The given federation ID is invalid! Please give me a valid ID." + joined_fed_already: "This chat has already joined a federation! Please use /fleave to leave that federation" + join_fed_success: "Great! Chat {chat} is now a part of {fed} federation!" + join_chat_fed_log: | + Chat joined federation #ChatJoined + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + # /leavefed + chat_not_in_fed: "This chat is not in any federation yet." + leave_fed_success: "Chat {chat} left the {fed} federation." + leave_chat_fed_log: | + Chat left Federation #ChatLeaved + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + # /fsub + already_subsed: "Federation {name} already subscribed to {name2}" + subsed_success: "Federation {name} subscribed to {name2}!" + + # /funsub + not_subsed: "Federation {name} is not subscribed to {name2}" + unsubsed_success: "Federation {name} unsubscribed from {name2}!" + + # /fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "User {user} added to {name} federation admins." + promote_user_fed_log: | + User promoted to the fed admins #UserPromoted + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + restricted_user:promote: This user is restricted from being federation admin! + + # /fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "User {user} demoted from {name} federation admins." + demote_user_fed_log: | + User demoted from the fed admins #UserDemoted + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + # /fsetlog + already_have_chatlog: "{name} federation already has logging enabled in another chat/channel!" + set_chat_log: | + This chat is now logging all actions in {name} federation + Federation logging is used for internal assessment of the federation and its performance. + The federation should NOT be used as a public wall of shame thereby respecting user data and our privacy guidelines. + set_log_fed_log: | + Enabled logging #LoggingEnabled + Fed: {fed_name} ({fed_id}) + no_right_to_post: I don't have rights to post messages in that channel! + + # /funsetlog + not_logging: "{name} federation isn't logging to any chat/channel!" + logging_removed: "Successfully removed logging from {name} federation." + unset_log_fed_log: | + Disabled logging #LoggingDisabled + Fed: {fed_name} ({fed_id}) + # /fchatlist + no_chats: "There's no chats joined this {name} federation" + chats_in_fed: "Chats connected to {name} federation:\n" + too_large: "Output too large, sending as file" + + # /finfo + finfo_text: | + Federation info + Name: {name} + ID: {fed_id} + Creator: {creator} + Chats in the fed: {chats} + Banned users in the fed: {fbanned} + finfo_subs_title: "Federations subscribed to this feds:\n" + + # /fadminlist + fadmins_header: "Admins in {fed_name} fed:\n" + + # no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + + # /fban + user_wl: "This user is whitelisted from banning." + fban_self: "That's a nice show!" + fban_creator: "How can I ban the federation creator?! I don't think it's gonna be funny." + fban_fed_admin: "I'm not going to ban a federation admin from their own fed!" + update_fban: "This user was already fbanned therefore I am updating the reason to {reason}" + already_fbanned: "{user} already banned in this federation." + fbanned_header: "New FedBan\n" + fban_info: | + Federation: {fed} + Fed admin: {fadmin} + User: {user} ({user_id}) + fbanned_reason: "Reason: {reason}" + fbanned_process: "\nStatus: Banning in {num} fed chats..." + fbanned_done: "\nStatus: Done! banned in {num} chats!" + + fbanned_silence: "This message will be purged in 5 seconds" + + fbanned_subs_process: "\nStatus: Banning in {feds} subscribed feds..." + fbanned_subs_done: "\nStatus: Done! Banned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + fban_usr_rmvd: | + User {user} is currently banned in {fed} federation and have been removed! + Reason: {rsn} + fban_log_fed_log: | + Ban user in the fed #FedBan + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + By: {by} + Chats banned: user banned in {chat_count}/{all_chats} chats + fban_reason_fed_log: "Reason: {reason}\n" + fban_subs_fed_log: "\nSubscribed feds: banned in {subs_chats} chats of {feds} federations" + + # /unfban + unfban_self: "Think of something else to have fun with instead of this." + user_not_fbanned: "{user} isn't banned in this federation." + un_fbanned_header: "New Un-FedBan\n" + un_fbanned_process: "\nStatus: Unbanning in {num} chats..." + un_fbanned_done: "\nStatus: Done! Unbanned in {num} chats!" + un_fbanned_subs_process: "\nStatus: Unbanning in {feds} subscribed feds..." + un_fbanned_subs_done: "\nStatus: Done! Unbanned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + + un_fban_log_fed_log: | + Unban user in the fed #FedUnBan + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + By: {by} + Chats: user unbanned in {chat_count}/{all_chats} chats + un_fban_subs_fed_log: "\nSubscribed feds: unbanned in {subs_chats} chats of {feds} federations" + + # /delfed + delfed_btn_yes: Yes. Delete federation + delfed_btn_no: No. + delfed: | + Are you sure you want to delete the %s federation? + Disclaimer: Deleting this federation will remove all data associated with it from our database! + This action is permanent and cannot be reversed! + delfed_success: Successfully deleted the federation! + + # /frename + frename_same_name: Please enter a different name, not the same one! + frename_success: The federation {old_name} renamed to {new_name} successfully! + + # /fbanlist + fbanlist_locked: Please wait until %s to use fbanlist again. + creating_fbanlist: Creating fban list! Please wait .. + fbanlist_done: Fbanlist of federation %s! + + # /importfbans + send_import_file: | + Send the import file! + To cancel this process use /cancel + rpl_to_file: Please reply to a file! + importfbans_locked: Importing fbans is locked for %s in this federation. + big_file_csv: Only supports import csv files less {num} megabytes! + big_file_json: | + Currently json files are limited to {num} megabytes! + Use the csv format if you want to import bigger files. + invalid_file: "The file is invalid" + wrong_file_ext: "Wrong file format! Currently support are: json, csv" + importing_process: Importing federation bans... + import_done: Importing fed bans finished! {num} bans were imported. + + # def + automatic_ban: User {user} is banned in the current federation {fed_name} and has been removed! + automatic_ban_sfed: User {user} is banned in the sub federation {fed_name}{text}" + + # /fcheck + fcheck_header: "Federation ban info:\n" + fbanned_count_pm: "You are fbanned in {count} federation(s)!\n" + fban_info:fcheck: "You have been fbanned in {fed} federation on {date}, No reason was given!" + fban_info:fcheck:reason: "You have been fbanned in {fed} federation on {date} because of reason -\n {reason}\n" + fbanned_data: "{user} has been fbanned in {count} federation(s).\n" + fbanned_nowhere: "{user} isn't fbanned anywhere!\n" + fbanned_in_fed: "Including the {fed} federation!\n" + fbanned_in_fed:reason: "Including the {fed} federation for reason of -\n {reason}" + contact_in_pm: "\n\nFor more info on your fbans, please send me a direct message for privacy!" + too_long_fbaninfo: "Well! your fbaninfo requires a file!" + didnt_fbanned: "You aren't fbanned in this fed." + not_fbanned_in_fed: They aren't fbanned in the {fed_name} fed + + + reports: + user_user_admin: "You're an admin here, why'd you need to report someone?.." + user_is_whitelisted: "You're a whitelisted user, why'd you need to report someone?.." + reports_disabled: "Reports are currently disabled in this group." + no_user_to_report: "Which user you want to report?" + report_admin: "You cannot report admins." + report_whitedlisted: "You cannot report a whitelisted user." + reported_user: "Reported {user} to admins." + reported_reason: "\nReason:\n{reason}" + reports_on: "Reports are turned on in this group." + reports_off: "Reports are turned off in this group." + reports_already_on: "Reports are already turned on." + reports_turned_on: "Reports turned on." + reports_already_off: "Reports are already turned off." + wrong_argument: " Wrong argument. " + language: + your_lang: "Your current language: {lang}" + chat_lang: "Current chat language: {lang}" + select_pm_lang: "\nSelect your new language:" + select_chat_lang: "\nSelect new chat language:" + crowdin_btn: "🌎 Help us with translation" + help_us_translate: "\n\n🌎 Help us improve our translations" + not_supported_lang: This language is not supported! + lang_changed: Language was changed to {lang_name}. + see_translators: 👥 See translators + back: Back + connections: + pm_connected: "Your PM has been successfully connected to {chat_name}!" + connected_pm_to_me: "Your PM has been successfully connected to {chat_name}! Please pm me to start using connection." + chat_connection_tlt: "Chat connection\n" + connected_chat: | + Current connected chat: + {chat_name} + Write /disconnect to disconnect from chat + connected_chat:cmds: | + Current connected chat: + {chat_name}, (bridged commands are {commands}), + ⚠ You can only access bridged commands of the connected chat! All other commands would be treated as Local chat + Write /disconect to disconnect from chat. + u_wasnt_connected: "You were not connected to any chat before!" + select_chat_to_connect: "\nSelect a chat to connect:" + disconnected: "You were disconnected from {chat_name}." + cant_find_chat: "I can't find this chat." + cant_find_chat_use_id: "I can't find this chat. Use chat ID." + bad_arg_bool: "Bad choice: supported only on/off enable/disable" + + enabled: enabled + disabled: disabled + chat_users_connections_info: "Connections for normal users currently is {status} for {chat_name}" + chat_users_connections_cng: "Connections for normal users changed to {status} for {chat_name}" + + not_in_group: "You're not in the group you're trying to connect to, join and send a message." + history_empty: "You're not connected to any chat for history, connect via `/connect (chat id)`" + not_in_chat: "You're not in this chat anymore, I'll disconnect you." + u_should_be_admin: "You should be an admin in {}!" + usage_only_in_groups: "Usage limited only in groups!" + + anon_admin_conn: | + Please click below button to connect! + click_here: Click here! + global: + u_not_admin: You should be an admin to do this! + + # Rights + bot_no_right:not_admin: I'm not a admin! + bot_no_right: "I dont have permission to {permission} here!" + user_no_right: "You don't have permission to {permission} here!" + user_no_right:not_admin: You should be admin to-do it! + + # is chat creator + unable_identify_creator: "Unable to identify anonymous chat creator, duplicate signature (titles) found!" + owner_stuff: + father: "\nHe is my creator." + sudo_crown: "\nHey! Look, that user has a crown, let me see it... Wow, it has an engraving - 'sudo user'!" + stickers: + rpl_to_sticker: "Reply to a sticker!" + ur_sticker: | + Emoji: {emoji} + Sticker ID: {id} + pins: + no_reply_msg: "Reply to a message to pin!" + chat_not_modified_pin: "That message is already pinned!" + chat_not_modified_unpin: "There isn't any pinned message to unpin!" + pinned_success: "Successfully pinned!" + unpin_success: "Successfully unpinned!" + users: + user_info: "User info:\n" + info_id: "ID: {id}" + info_first: "\nFirst name: {first_name}" + info_last: "\nLast name: {last_name}" + info_username: "\nUsername: {username}" + info_link: "\nUser link: {user_link}" + info_saw: "\nI saw them in {num} groups" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_fbanned: "\nBanned in current Federation: " + info_admeme: "\nUser has admin rights in this chat." + upd_cache: "Updating cache now..." + upd_cache_done: "Admins cache was updated." + admins: "Admin in this group:\n" + imports_exports: + # Exporting + started_exporting: Exporting started! Please wait... + export_done: Export from {chat_name} done! + exports_locked: Please wait %s before using exports again! + + # Importing + invalid_file: "The file is invalid" + send_import_file: Please send a file for importing! + rpl_to_file: Please reply to a file! + big_file: Only files under 50 MB are supported! + started_importing: Importing started! Please wait... + bad_file: Bad file! Expected 'general' column. + file_version_so_new: Exported file version is newer than what Daisy currently supports! + imports_locked: Please wait %s before using imports again! + import_done: Import done! + restrictions: + user_not_in_chat: User is not in this chat! + + # Kicks + kick_DaisyX: Instead of trying kick me you could spend your time better. Thats just boring. + kick_admin: Kicking admin is not best idea thought. + kick_self: If you want to kick yourself - just leave this chat. I would rather not see you than look at these pathetic attempts at self-punishment. + user_kicked: "{user} was kicked by {admin} in {chat_name}." + + # Mutes + mute_DaisyX: Actually, I'll be happy to shut up you instead. + mute_admin: If you think you can shut up an admin, you are wrong! + mute_self: If you want to mute yourself - just stop talking! Better to thought a fool than open your mouth and remove all doubt. + user_muted: "{user} was muted by {admin} in {chat_name}." + unmute_DaisyX: Actually, I'm not muted. + unmute_admin: Haha no, try muting him first! + unmute_self: Should I mute you first? + user_unmuted: "{user} was unmuted by {admin} in {chat_name}." + + # Bans + ban_DaisyX: No, I won't do it! Ask the chat creator to do it! + ban_admin: Haha, let's /demote him first. + ban_self: Why are you trying to ban yourself? + user_banned: "{user} was banned by {admin} in {chat_name}." + unban_DaisyX: Actually, I'm not banned. + unban_admin: Haha no, try banning him first! + unban_self: Should I ban you first? + user_unband: "{user} was unbanned by {admin} in {chat_name}." + + invalid_time: Invalid time! + enter_time: Please specify a time! + on_time: "\nFor %s" + reason: "\nReason: %s" + purge: "\nMessages will be purged in 5 seconds!" + + filter_title_ban: Ban the user + filter_action_rsn: Automated filter action! + filtr_ban_success: '%s banned %s for %s' + filtr_mute_success: '%s muted %s for %s' + filtr_tmute_success: | + %s muted %s for %s + reason is %s + time_setup_start: Please specify a time! + filtr_tban_success: | + %s banned %s for %s + reason is %s + filter_title_tmute: tMute the user + filter_title_mute: Mute the user + filter_title_tban: tBan the user + filter_title_kick: Kick user + rules: + # Set + saved: Rules were saved in %s + updated: Rules were updated in %s + + # Reset + deleted: Rules were deleted! + + not_found: Rules were not found! + + # Callback + user_blocked: "Write /start in my PM!" + rules_was_pmed: Rules were sent to your PM! + + # cannot_find_note: "I can't find the note {}" + # set_note: "Successfully set the rules note to {}" + # didnt_set_note: "This group doesn't have rules note set." + # cannot_find_set_note: "I can't find the rules note." + # havent_set_rules_note: "You haven't set the rules note yet." + # success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "Welcome {mention}! Please press button below to verify yourself as human!" + + bool_invalid_arg: Invalid argument, expected on/off. + + # /setwelcome + saved: Welcome message was saved in %s + updated: Welcome message was updated in %s + + # /turnwelcome + turnwelcome_status: Welcomes are {status} in {chat_name} + turnwelcome_enabled: Welcomes enabled in %s + turnwelcome_disabled: Welcomes disabled in %s + + # /welcome + enabled: enabled + disabled: disabled + wlcm_security_enabled: "enabled, set to {level}" + wlcm_mutes_enabled: "enabled, set to {time}" + welcome_info: | + Welcome settings in {chat_name}: + Welcomes are {welcomes_status} + Welcome security is {wlcm_security} + Welcome mutes is {wlcm_mutes} + Deleting old welcomes is {clean_welcomes} + Deleting service messages is {clean_service} + wlcm_note: "\n\nWelcome note is:" + raw_wlcm_note: "Raw welcome note is:" + security_note: "Welcome security text is:" + + # /setsecuritynote + security_note_saved: "Custom security text was saved %s" + security_note_updated: "Custom security text was updated %s" + + # /delsecuritynote + security_note_not_found: Security text in %s has not been set before + del_security_note_ok: Security text was reset to default in %s + + # /cleanwelcome + cleanwelcome_enabled: Cleaning welcomes enabled in %s + cleanwelcome_disabled: Cleaning welcomes disabled in %s + cleanwelcome_status: Cleaning welcomes is {status} in {chat_name} + + # /cleanservice + cleanservice_enabled: Cleaning service messages enabled in %s + cleanservice_disabled: Cleaning service messages disabled in %s + cleanservice_status: Cleaning service messages is {status} in {chat_name} + + # /welcomemute + welcomemute_invalid_arg: Invalid argument, expected a time. + welcomemute_enabled: Welcome mute enabled in %s + welcomemute_disabled: Welcome mute disabled in %s + welcomemute_status: Welcome mute is {status} in {chat_name} + + # /welcomesecurity + welcomesecurity_enabled: Welcome security enabled in {chat_name} to {level}. + welcomesecurity_enabled:customized_time: | + Welcome security enabled in {chat_name} to {level}, and kick users which aren't verified within {time}. + ask_for_time_customization: | + Do you want to customize the time period to kick unverified user? (by default it's {time}) + send_time: Please specify a time. + invalid_time: "Invalid time!" + welcomesecurity_enabled_word: enabled, set to level {level} + welcomesecurity_disabled: Welcome security disabled in %s. + welcomesecurity_invalid_arg: Invalid argument, expected button/captcha. + welcomesecurity_status: Welcome security is {status} in {chat_name} + + yes_btn: "Yes" + no_btn: "No" + + # Welcomesecurity handler + click_here: Click here! + not_allowed: You are not allowed to use this! + ws_captcha_text: Hi {user}, please enter the captcha's number to be unmuted in the chat. + regen_captcha_btn: 🔄 Change captcha + num_is_not_digit: Please enter only numbers! + bad_num: Wrong number! Please try again. + passed: Verification passed, you were unmuted in %s! + passed_no_frm: Verification passed, you was unmuted in %s! + last_chance: You have a last chance to enter captcha! + + btn_button_text: "Please click button below to prove yourself." + + btn_wc_text: | + Please press right button matched on this expression: + %s + math_wc_rtr_text: "You have last chance! Be careful.\n" + math_wc_wrong: Wrong, please try again! + math_wc_sry: Sorry you used the last attempt. Ask chat administrators. + + not_admin_wm: I can't restrict people here, so I won't use welcome mute! + not_admin_ws: I can't restrict people here, so I won't use welcome security! + not_admin_wsr: I can't delete messages here, so I won't delete service messages! + + # /resetwelcome + deleted: Successfully reset greetings in {chat}! + not_found: There's nothing to reset! + + # ... + verification_done: Verification is done! You have been unmuted. + locks: + locks_header: "Here is the current lock settings in {chat_title} \n\n" + no_lock_args: Give me some args to lock! + already_locked: This is already locked bruh + no_such_lock: See /locks to find out what can be locked and unlocked! + locked_successfully: Locked {lock} in {chat} sucessfully! + no_unlock_args: Give me some args to unlock! + not_locked: Bruh, its not locked to unlock. + unlocked_successfully: Unlocked {lock} in {chat} successfully! + + antiflood: + + # enforcer + flood_exceeded: "{action} {user} for flooding!" + + # /setflood + invalid_args:setflood: "Expected a number!" + overflowed_count: Maximum count should be less than 200! + config_proc_1: Please send expiration time, '0' (zero) for not being expired. + cancel: ❌ Cancel + setup_success: Successfully configured antiflood, now enforces against those who send {count} messages within {time}! + setup_success:no_exp: Successfully configured antiflood, allows {count} messages consecutively! + invalid_time: Invalid time! + + # /antiflood | /flood + not_configured: Antiflood isn't configured in this chat yet! + turned_off: Disabled antiflood in {chat_title}! + configuration_info:with_time: Antiflood is configured in this chat, those who send {count} message within {time} will be {action}! + configuration_info: Antiflood is configured in this chat, those who send {count} consecutively will be {action}! + ban: banned + tmute: tmuted + tban: tbanned + mute: muted + kick: kicked + + # /setfloodaction + invalid_args: "Unsupported action, expected {supported_actions}!" + setfloodaction_success: "Successfully updated flood action to {action}!" + + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/es_ES.yaml b/DaisyX/localization/es_ES.yaml new file mode 100644 index 00000000..7cb5ed1f --- /dev/null +++ b/DaisyX/localization/es_ES.yaml @@ -0,0 +1,676 @@ +--- +language_info: + flag: "🇪🇸" + code: es +STRINGS: + notes: + #/save + note_saved: "🗒 ¡Nota {note_name} guardada en {chat_title}!" + note_updated: "🗒 ¡Nota {note_name} actualizada en {chat_title}!" + you_can_get_note: "\nPuedes consultar esta nota usando /get {note_name}, o #{note_name}" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nAlias de la nota:" + blank_note: "¡No está permitido guardar una nota en blanco!" + notename_cant_contain: "¡El nombre de la nota no puede contener \"{symbol}\"!" + #/get + cant_find_note: '¡No puedo encontrar esta nota en {chat_name}!' + no_note: "No pude encontrar esa nota." + no_notes_pattern: "No se encontró notas según búsqueda %s" + u_mean: "\n¿Querías decir #{note_name}?" + #/notes + notelist_no_notes: "¡No existen notas en {chat_title}!" + notelist_header: "Notas en {chat_name}:" + notes_in_private: "Haga clic en el botón a continuación para obtener la lista de notas." + notelist_search: | + Búsqueda: {request} + Notas que coinciden: + #/clear + note_removed: "¡Nota #{note_name} removida en {chat_name}!" + note_removed_multiple: | + Eliminado varias notas en {chat_name} + Eliminado:{removed} + not_removed_multiple: "No removido: {not_removed}" + #/clearall + clear_all_text: "Esto removerá todas las notas de {chat_name}. ¿Estás seguro?" + clearall_btn_yes: "Sí. ¡Remueve todas mis notas!" + clearall_btn_no: "¡No!" + clearall_done: "¡Todas las notas ({num}) en {chat_name} fueron removidas!" + #/search + search_header: | + Petición de búsqueda en {chat_name}: + Búsqueda: {request} + Notas que coinciden: + #/noteinfo + note_info_title: "Información de la nota\n" + note_info_note: "Nombres de la nota: %s\n" + note_info_content: "Contenido de la nota: %s" + note_info_parsing: "Modo de análisis: %s\n" + note_info_created: "Creado: {date} por {user}\n" + note_info_updated: "Última actualización: {date} por {user}\n" + user_blocked: "¡Escriba /start en el chat privado!" + #/privatenotes + private_notes_false_args: "Usted tiene 2 opciones: ¡Deshabilitar y Habilitar!" + already_enabled: "Las notas privadas ya están activadas en %s" + current_state_info: "¡Las notas privadas están actualmente {state} en {chat}!" + enabled_successfully: "¡Las notas privadas están Activadas en %s de manera satisfactoria!" + disabled_successfully: "¡Las notas privadas están Desactivadas en %s de manera satisfactoria!" + not_enabled: "Las notas privadas no están habilitadas." + privatenotes_notif: "¡Usted ha sido conectado con éxito a las notas de {chat}! ¡Para desconectarse, por favor use el comando /disconnect!" + enabled: 'Activado' + disabled: 'Desactivado' + #/cleannotes + clean_notes_enable: "Se activó correctamente la limpieza de notas en {chat_name}" + clean_notes_disable: "Se desactivó correctamente la limpieza de notas en {chat_name}" + clean_notes_enabled: "La limpieza de notas está actualmente activada en {chat_name}" + clean_notes_disabled: "La limpieza de notas está actualmente desactivada en {chat_name}" + #Filter + filters_title: 'Envíe una nota' + filters_setup_start: 'Por favor, envíe el nombre de la nota.' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "¡No se encontraron filtros en {chat_name}!" + #/addfilter + anon_detected: Siendo un administrador anónimo, no puedes añadir nuevos filtros. Usa conexiones en su lugar. + regex_too_slow: "¡Su patrón regex es muy lento (más de la mitad de segundo) y no puede ser añadido!" + cancel_btn: "🛑 Cancelar" + adding_filter: | + Adicionando filtro {handler} en {chat_name} + Seleccione la siguiente acción: + saved: "¡El nuevo filtro fue guardado con éxito!" + #/filters + list_filters: "Filtros en {chat_name}:\n" + #/delfilter + no_such_filter: No puedo encontrar ese filtro en {chat_name}. Puedes comprobar qué filtros están habilitados con el comando /filters. + del_filter: "¡El filtro controlado '{handler}' fue eliminado con éxito!" + select_filter_to_remove: | + Encontré muchos filtros con el controlador '{handler}'. + Por favor, seleccione uno para eliminar: + #/delfilters or /delallfilters + not_chat_creator: '¡Sólo los fundadores del grupo pueden usar este comando!' + delall_header: Esto borraría todos los filtros de este chat o grupo. Esto es irreversible. + confirm_yes: '⚠ Borrar todo' + confirm_no: '❌ Cancelar' + delall_success: '¡Se han eliminado con éxito todos los filtros ({count}) en este chat!' + warns: + #/warn + warn_sofi: "¡Jaja! ¡No hay manera de advertirme a mí misma!" + warn_self: "¿Quieres darte una advertencia a ti mismo? Sólo abandona el chat entonces." + warn_admin: "Bueno... estás equivocado. No puedes advertir a un administrador." + warn: "{admin} ha advertido a {user} en {chat_name}\n" + warn_bun: "¡Las advertencias se han excedido! ¡{user} ha sido baneado!" + warn_num: "Advertencias: {curr_warns}/{max_warns}\n" + warn_rsn: "Razón: {reason}\n" + max_warn_exceeded: '¡Se han excedido las advertencias! ¡%s ha sido %s!' + "max_warn_exceeded:tmute": '¡Se han excedido las advertencias! ¡%s ha sido silenciado por %s!' + #warn rmv callback + warn_no_admin_alert: "⚠ ¡Usted NO es administrador y no puede eliminar las advertencias!" + warn_btn_rmvl_success: "✅ ¡Advertencia eliminada por {admin}!" + #/warns + warns_header: "Aquí están tus advertencias en este chat \n\n" + warns: "{count} : {reason} de {admin}\n" + no_warns: "Bueno, {user} no tiene ninguna advertencia." + #/warnlimit + warn_limit: "El límite de advertencias en {chat_name} es actualmente: {num}" + warnlimit_short: '¡El límite de advertencias debe ser al menos de 2!' + warnlimit_long: '¡El límite de advertencias debe ser menor de 1000!' + warnlimit_updated: '✅ El límite de advertencias se ha actualizado con éxito a {num}' + #/delwarns + purged_warns: "{admin} restableció {num} advertencias a {user} en {chat_title}!" + usr_no_wrn: "{user} aún no tiene ninguna advertencia." + rst_wrn_sofi: 'Daisy nunca ha tenido advertencias para reiniciar.' + not_digit: '¡El límite de advertencias deben ser dígitos!' + #/warnmode + same_mode: '¡Este es el modo actual! ¿Cómo puedo cambiarlo?' + no_time: '¡Para seleccionar el modo ''tmute'' tienes que colocar el tiempo!' + invalid_time: '¡Tiempo inválido!' + warnmode_success: '¡El modo de advertencia de %s ha cambiado con éxito a %s!' + wrng_args: | + Estas son las opciones disponibles: + %s + mode_info: | + El modo actual en este chat es %s. + banned: baneado + muted: silenciado + filters_title: Advertir al usuario + filter_handle_rsn: '¡Acción de filtro automática!' + msg_deleting: + no_rights_purge: "¡No tienes permisos suficientes para purgar en este grupo!" + reply_to_msg: "¡Responde a un mensaje para eliminar!" + purge_error: "No puedo continuar con la limpieza, sobre todo porque empezaste a limpiar un mensaje que fue enviado hace más de 2 días." + fast_purge_done: "¡Limpieza rápida completada!\nEste mensaje se eliminará en 5 segundos." + purge_user_done: "Todos los mensajes de {user} fueron eliminados con éxito!" + misc: + your_id: "Tu ID: {id}\n" + chat_id: "ID del Grupo: {id}\n" + user_id: "ID de {user}: {id}\n" + conn_chat_id: "ID de chat conectado actualmente: {id}" + help_btn: "¡Haz clic para ayuda!" + help_txt: "¡Haz clic en el botón de abajo para obtener ayuda!" + delmsg_filter_title: 'Eliminar el mensaje' + send_text: Por favor, ¡envíe el mensaje de respuesta! + replymsg_filter_title: 'Responder al mensaje' + send_customised_reason: Envíe la razón que quiera incluir en el mensaje. ¡"Ninguno" sin razón para ser enviado! + expected_text: '¡Se esperaba un mensaje de texto!' + promotes: + promote_success: "¡{user} fue promovido con éxito en {chat_name}!" + promote_title: "\nCon un rol personalizado {role}!" + rank_to_loong: "El texto de rol personalizado no puede tener más de 16 símbolos." + promote_failed: "¡Promoción fallida! Comprueba si tengo permisos." + demote_success: "¡{user} fue removido con éxito en {chat_name}!" + demote_not_admin: "Ese usuario no es administrador." + demote_failed: "El proceso de remover admin ha fallado. ¿Tal vez no pueda remover admin o la persona fue promovida por alguien más?" + cant_get_user: "¡No pude obtener el usuario! Si le importa, responda a su mensaje e inténtelo nuevamente." + cant_promote_yourself: "No puedes promoverte a ti mismo." + emoji_not_allowed: "Lo siento, pero los roles de administrador no pueden contener emojis 😞" + pm_menu: + start_hi_group: '¡Hola! Mi nombre es Sophie' + start_hi: "¡Hola! ¡Mi nombre es Daisy, te ayudo a gestionar tu grupo de una manera eficiente!" + btn_source: "📜 Código fuente" + btn_help: "❔ Ayuda" + btn_lang: "es Idioma" + btn_channel: "📡 Noticias de Daisy" + btn_group: "👥 Daisy Support" + btn_group_help: "Click me for help!" + back: Atrás + #/help + help_header: "¡Oye! Mi nombre es Daisy. Soy un robot de gestión de grupos, ¡estoy aquí para ayudarte a moverte y mantener el orden en tus grupos!\nTengo muchas funciones útiles, como control de flood, un sistema de advertencia, un sistema de notas e incluso respuestas predeterminadas a ciertas palabras clave." + click_btn: Haz clic aquí + disable: + #/disableable + disablable: "Comandos habilitables y deshabilitables:\n" + #/disabled + no_disabled_cmds: '¡No hay comandos deshabilitados en {chat_name}!' + disabled_list: "Comandos deshabilitados en {chat_name}:\n" + #/disable + wot_to_disable: "¿Qué quieres desactivar?" + already_disabled: "¡Este comando ya está deshabilitado!" + disabled: "¡El comando {cmd} ha sido desactivado en {chat_name}!" + #/enable + wot_to_enable: "¿Qué quieres habilitar?" + already_enabled: "¡Este comando no está desactivado!" + enabled: "¡El comando {cmd} ha sido activado en {chat_name}!" + #/enableall + not_disabled_anything: "¡No se ha desactivado nada en {chat_title}!" + enable_all_text: "Esto habilitará todos los comandos en {chat_name}. ¿Está seguro?" + enable_all_btn_yes: "Sí. ¡Habilitar todos los comandos!" + enable_all_btn_no: "¡No!" + enable_all_done: "¡Todos los comandos ({num}) fueron habilitados en {chat_name}!" + bot_rights: + change_info: "No tengo autorización para cambiar la información del grupo, por favor hazme administrador con ese permiso." + edit_messages: "¡No tengo permiso para editar mensajes!" + delete_messages: "No tengo permiso para borrar mensajes aquí." + ban_user: "No tengo autorización para banear a los usuarios, por favor hazme administrador con ese permiso." + pin_messages: "No tengo permiso para anclar mensajes, por favor hazme administrador con ese permiso." + add_admins: "No tengo permiso para añadir administradores, por favor hazme administrador con ese permiso." + feds: + #decorators + need_fed_admin: "No eres un administrador de la federación {name}" + need_fed_owner: "Usted no es el propietario de la federación {name}" + cant_get_user: "Lo siento, no puedo encontrar a este usuario. Intenta usar su ID de usuario" + #/fnew + fed_name_long: "¡El nombre de la Federación no puede tener más de 60 símbolos!" + can_only_1_fed: "Los usuarios sólo pueden crear 1 federación. Por favor, elimine uno de ellos." + name_not_avaible: "¡La Federación con el nombre {name} ya existe! Por favor, usa otro nombre." + created_fed: | + Felicitaciones, ha creado con éxito una federación. + Nombre: {name} + ID: {id} + Creador: {creator} + Use el comando /fjoin {id} para conectar la federación al chat\ + disallow_anon: '¡Como administrador anónimo, no puedes crear una nueva federación!' + #/fjoin + only_creators: "Debes ser el creador del grupo para poder conectar el chat a una federación." + fed_id_invalid: "¡El ID de la federación es inválida! Por favor, dame un ID válido." + joined_fed_already: "¡Este chat ya se ha unido a una federación! Por favor, use /fleave para salir de esta federación" + join_fed_success: "¡Genial! ¡El chat {chat} es ahora parte de la federación {fed}!" + join_chat_fed_log: | + Chat unido a la federación #ChatJoined + Federación: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "Este chat no está en ninguna federación todavía." + leave_fed_success: "El chat {chat} dejó la federación de {fed}." + leave_chat_fed_log: | + El chat ha salido de la Federación #ChatSalido + Federación: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/fsub + already_subsed: "La Federación {name} ya está suscrita a {name2}" + subsed_success: "¡Federación {name} suscrito a {name2}!" + #/funsub + not_subsed: "La federación {name} no está suscrita a {name2}" + unsubsed_success: "¡La federación {name} ha cancelado la suscripción de {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "Usuario {user} añadido como administrador de la federación {name}." + promote_user_fed_log: | + Usuario promovido como Admin en la Federación #UsuarioPromovido + Federación: {fed_name} ({fed_id}) + Usuario: {user} ({user_id}) + "restricted_user:promote": '¡Este usuario está impedido para ser administrador de la federación!' + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "Usuario {user} removido como administrador de la federación {name}." + demote_user_fed_log: | + Usuario removido como Admin de la Federación #UsuarioRemovido + Federación: {fed_name} ({fed_id}) + Usuario: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "¡La federación {name} ya tiene habilitado el registro en otro chat/canal!" + set_chat_log: | + Este chat está registrando todas las acciones de la federación {name}. + + El registro de la Federación se utiliza para la evaluación interna de la federación y su desempeño. + La federación NO debe ser utilizada como un muro público de la vergüenza, respetando así los datos de los usuarios y nuestras directrices de privacidad. + set_log_fed_log: | + Registro activado #RegistroActivado + Federación: {fed_name} ({fed_id}) + no_right_to_post: '¡No tengo permisos para publicar mensajes en ese canal!' + #/funsetlog + not_logging: "¡La federación {name} no está registrada en ningún chat/canal!" + logging_removed: "Eliminó con éxito el registro de la federación {name}." + unset_log_fed_log: | + Registro desactivado #RegistroDesactivado + Federación: {fed_name} ({fed_id}) + #/fchatlist + no_chats: "No hay chats vinculados a esta federación {name}" + chats_in_fed: "Chats conectados a la federación {name}:\n" + too_large: "Salida demasiado grande, enviando como archivo" + #/finfo + finfo_text: | + Información de la Federación + Nombre: {name} + ID: {fed_id} + Creador: {creator} + Chats en la federación: {chats} + Usuarios baneados en la federación: {fbanned} + finfo_subs_title: "Federaciones suscritas a esas federaciones:\n" + #/fadminlist + fadmins_header: "Administradores en la federación {fed_name}:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "Este usuario está en la whitelist de baneos." + fban_self: "¡Eso es un buen espectáculo!" + fban_creator: "¿Cómo puedo banear al creador de la federación? No creo que eso sea gracioso." + fban_fed_admin: "¡No voy a banear a un administrador de su propia federación!" + update_fban: "Este usuario ya estaba baneado. Por lo tanto, estoy actualizando la razón a {reason}" + already_fbanned: "{user} ya está baneado en esta federación." + fbanned_header: "Nuevo FedBan\n" + fban_info: | + Federación: {fed} + Admin de la Federación: {fadmin} + Usuario: {user} ({user_id}) + fbanned_reason: "Razón: {reason}" + fbanned_process: "\nEstado: Baneando en {num} chats de federación..." + fbanned_done: "\nEstado: ¡Hecho! baneado en {num} chats!" + fbanned_silence: "Este mensaje será eliminado en 5 segundos" + fbanned_subs_process: "\nEstado: Baneando en {feds} federaciones suscritas..." + fbanned_subs_done: "\nEstado: ¡Listo! Baneado en {chats} chats de esta federación y {subs_chats} chats de {feds} federaciones suscritas" + fban_usr_rmvd: | + ¡El usuario {user} está actualmente baneado en la federación {fed} y ha sido eliminado! + Razón: {rsn} + fban_log_fed_log: | + Banear usuario en la Federación #FedBan + Federación: {fed_name} ({fed_id}) + Usuario: {user} ({user_id}) + Por: {by} + Chats: usuario baneado en {chat_count}/{all_chats} chats + fban_reason_fed_log: "Razón: {reason}\n" + fban_subs_fed_log: "\nFederaciones suscritas: baneado en {subs_chats} chats de {feds} federaciones" + #/unfban + unfban_self: "Piensa en otra cosa para que te diviertas en su lugar." + user_not_fbanned: "El usuario {user} no está baneado en esta federación." + un_fbanned_header: "Nuevo Un-FedBan\n" + un_fbanned_process: "\nEstado: Desbaneando en {num} chats..." + un_fbanned_done: "\nEstado: ¡Listo! ¡Desbaneado en {num} chats!" + un_fbanned_subs_process: "\nEstado: Desbaneando en {feds} federaciones suscritas..." + un_fbanned_subs_done: "\nEstado: ¡Listo! Desbaneado en {chats} chats de esta federación y {subs_chats} chats de {feds} federaciones suscritas" + un_fban_log_fed_log: | + Desbanear usuario en la Federación #FedUnBan + Federación: {fed_name} ({fed_id}) + Usuario: {user} ({user_id}) + Por: {by} + Chats: usuario desbaneado en {chat_count}/{all_chats} chats + un_fban_subs_fed_log: "\nFederaciones suscritas: desbaneado en {subs_chats} chats de {feds} federaciones" + #/delfed + delfed_btn_yes: Sí. Eliminar federación + delfed_btn_no: No. + delfed: | + ¿Estás seguro de que quieres eliminar la federación %s? + + Advertencia: ¡Eliminar esta federación removerá todos los datos asociados a ella de nuestra base de datos! + ¡Esta acción es permanente y no puede ser revertida! + delfed_success: '¡Se ha eliminado la federación con éxito!' + #/frename + frename_same_name: '¡Por favor, introduzca un nombre diferente, pero no el mismo!' + frename_success: '¡La federación {old_name} cambió su nombre a {new_name} con éxito!' + #/fbanlist + fbanlist_locked: Por favor, espere %s para solicitar o usar la lista de baneados nuevamente. + creating_fbanlist: '¡Creando lista de fban! Por favor espere ..' + fbanlist_done: '¡Lista de baneados de la federación %s!' + #/importfbans + send_import_file: | + ¡Envía el archivo importado! + + Para cancelar este proceso, use /cancel + rpl_to_file: '¡Por favor, responde a un archivo!' + importfbans_locked: La importación de fbans está bloqueada para %s en esta federación. + big_file_csv: '¡Sólo soporta archivos csv importados menores de {num} megabytes!' + big_file_json: | + ¡Actualmente los archivos json se limitan a {num} megabytes! + Usa el formato csv si quieres importar archivos más grandes. + invalid_file: "El archivo es inválido" + wrong_file_ext: "¡Formato de archivo incorrecto! El soporte actualmente es: json, csv" + importing_process: Importando baneados de la federación... + import_done: '¡Finalizó la importación de baneados de la federación! {num} baneados fueron importados.' + #def + automatic_ban: '¡El usuario {user} está baneado en la actual federación {fed_name} y ha sido eliminado!' + automatic_ban_sfed: '¡El usuario {user} está baneado en la subfederación {fed_name} y ha sido eliminado!' + automatic_ban_reason: "\nRazón: {text}" + #/fcheck + fcheck_header: "Información de baneo en la Federación:\n" + fbanned_count_pm: "¡Estás baneado en {count} federación(s)!\n" + "fban_info:fcheck": "Usted fue baneado en la federación {fed} el {date}. ¡No se ha dado ninguna razón!" + "fban_info:fcheck:reason": "Has sido baneado en la federación de {fed} el {date} por razón -\n {reason}\n" + fbanned_data: "{user} ha sido baneado en {count} federación(es)\n" + fbanned_nowhere: "¡{user} no está baneado en ningún lugar!\n" + fbanned_in_fed: "¡Incluyendo la federación {fed}!\n" + "fbanned_in_fed:reason": "Incluyendo la federación {fed} por motivo de -\n {reason}" + contact_in_pm: "\n\n¡Para más información sobre sus baneos, por favor envíenme un mensaje directo para mayor privacidad!" + too_long_fbaninfo: "¡Bien! ¡Tu fbaninfo requiere un archivo!" + didnt_fbanned: "Usted no está baneado en esta federación." + not_fbanned_in_fed: Él/Ellos no está/án baneado/s en la federación {fed_name} + reports: + user_user_admin: "Eres un administrador aquí, ¿por qué necesitarías reportar a alguien?.." + user_is_whitelisted: "Eres un usuario de la whitelist, ¿por qué necesitarías reportar a alguien?.." + reports_disabled: "Los reportes están actualmente deshabilitados en este grupo." + no_user_to_report: "¿Qué usuario desea reportar?" + report_admin: "Usted no puede reportar a los administradores." + report_whitedlisted: "No puedes reportar a un usuario de la whitelist." + reported_user: "{user} reportado a los administradores." + reported_reason: "\nRazón:\n{reason}" + reports_on: "Los reportes están activados en este grupo." + reports_off: "Los reportes están desactivados en este grupo." + reports_already_on: "Los reportes ya están activados." + reports_turned_on: "Reportes activados." + reports_already_off: "Los reportes ya están desactivados." + wrong_argument: "Argumento incorrecto." + language: + your_lang: "Tu idioma actual: {lang}" + chat_lang: "Idioma del chat actual: {lang}" + select_pm_lang: "\nSelecciona tu nuevo idioma:" + select_chat_lang: "\nSeleccionar nuevo idioma del chat:" + crowdin_btn: "🌎 Ayúdanos con la traducción" + help_us_translate: "\n\n🌎 Ayúdanos a mejorar nuestras traducciones" + not_supported_lang: '¡Este idioma no está soportado!' + lang_changed: Idioma cambiado a {lang_name}. + see_translators: '👥 Ver traductores' + back: Atrás + connections: + pm_connected: "¡Tu chat privado se ha conectado con éxito a {chat_name}!" + connected_pm_to_me: "¡Tu chat privado se ha conectado con éxito a {chat_name}! Por favor, envíame un mensaje privado para empezar a usar la conexión." + chat_connection_tlt: "Conexión de chat\n" + connected_chat: | + Chat conectado actualmente: + {chat_name} + Escriba /disconnect para desconectarse del chat + "connected_chat:cmds": | + Chat conectado en este momento: + {chat_name}, (Los comandos conectados son {commands}), + ⚠ ¡Sólo puedes acceder a los comandos conectados en el chat actual! Los otros comandos serían tratados como Chat Local + Ejecute /disconnect para desconectarse del chat. + u_wasnt_connected: "¡No estabas conectado en ningún chat antes!" + select_chat_to_connect: "\nSelecciona un chat para conectarse:" + disconnected: "Has sido desconectado de {chat_name}." + cant_find_chat: "No puedo encontrar este chat." + cant_find_chat_use_id: "No puedo encontrar este chat. Usa el ID del chat." + bad_arg_bool: "Elección incorrecta: solo soportado on/off enable/disable" + enabled: habilitado + disabled: deshabilitado + chat_users_connections_info: "Las conexiones para usuarios normales actualmente está {status} en {chat_name}" + chat_users_connections_cng: "Conexiones para usuarios normales cambiadas a {status} en {chat_name}" + not_in_group: "No estás en el grupo al que estás intentando conectarse. Únete y envía un mensaje." + history_empty: "No estás conectado a ningún chat para el historial. Conéctate a través de `/connect (chat id)`" + not_in_chat: "Ya no estás en este chat. Te desconectaré." + u_should_be_admin: "¡Debes ser un administrador en {}!" + usage_only_in_groups: "¡El uso limitado sólo en grupos!" + anon_admin_conn: | + Por favor, ¡haga clic en el botón de abajo para conectarse! + click_here: '¡Haz clic aquí!' + global: + u_not_admin: '¡Deberías ser un administrador para hacer esto!' + #Rights + "bot_no_right:not_admin": '¡No soy un administrador!' + bot_no_right: "¡No tengo permiso para hacerlo! \nPermisos faltantes: %s" + user_no_right: "¡Deberías tener el permiso para hacerlo! \nPermisos faltantes: %s" + "user_no_right:not_admin": '¡Deberías ser administrador para hacerlo!' + #is chat creator + unable_identify_creator: "No se ha podido identificar al creador del chat anónimo. ¡Se ha encontrado una firma duplicada (títulos)!" + owner_stuff: + father: "\nÉl es mi creador." + sudo_crown: "\n¡Oye! Mira que el usuario tiene una corona, déjame verla.. Wow, tiene un grabado - 'sudo usuario'!" + stickers: + rpl_to_sticker: "Responder a un sticker!" + ur_sticker: | + Emoji: {emoji} + ID del Sticker: {id} + pins: + no_reply_msg: "¡Responde a un mensaje para anclar!" + chat_not_modified_pin: "¡Ese mensaje ya está anclado!" + chat_not_modified_unpin: "¡No hay ningún mensaje anclado para desanclar!" + pinned_success: "¡Anclado con éxito!" + unpin_success: "¡Desanclado con éxito!" + users: + user_info: "Información del usuario:\n" + info_id: "ID: {id}" + info_first: "\nNombre: {first_name}" + info_last: "\nApellidos: {last_name}" + info_username: "\nNombre de usuario: {username}" + info_link: "\nEnlace de usuario: {user_link}" + info_saw: "\nLos vi en {num} grupos" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_fbanned: "\nBaneado en la Federación actual: " + info_admeme: "\nEl usuario tiene derechos de administrador en este chat." + upd_cache: "Actualizando caché ahora..." + upd_cache_done: "Se actualizó la caché de administradores." + admins: "Administrador en este grupo:\n" + imports_exports: + #Exporting + started_exporting: '¡Exportación iniciada! Por favor espere...' + export_done: Exportación de {chat_name} realizada! + exports_locked: '¡Por favor, espere %s antes de usar la exportación nuevamente!' + #Importing + invalid_file: "El archivo es inválido" + send_import_file: '¡Por favor, envíe un archivo para importar!' + rpl_to_file: '¡Por favor, responde a un archivo!' + big_file: '¡Sólo se admiten archivos menores de 50 MB!' + started_importing: '¡Importación iniciada! Por favor espere...' + bad_file: '¡Archivo erróneo! Se esperaba columna ''general''.' + file_version_so_new: '¡La versión del archivo exportado es más reciente de lo que Daisy admite actualmente!' + imports_locked: '¡Por favor, espere %s antes de usar la importación nuevamente!' + import_done: '¡Importación hecha!' + restrictions: + user_not_in_chat: '¡El usuario no está en este chat!' + #Kicks + kick_DaisyX: En lugar de intentar eliminarme, podrías pasar mejor tu tiempo. Eso es simplemente aburrido. + kick_admin: Expulsar a los administradores no es la mejor idea. + kick_self: Si quieres expulsarte a ti mismo - solo abandona este chat. Prefiero no verlo que mirar estos patéticos intentos de auto-castigo. + user_kicked: "{user} fue expulsado por {admin} en {chat_name}." + #Mutes + mute_DaisyX: En realidad, estaré encantado de callarte en su lugar. + mute_admin: '¡Si crees que puedes silenciar a un administrador, te equivocas!' + mute_self: '¡Si quieres silenciarte a ti mismo - solo deja de hablar! Es mejor pensar como un idiota que abrir la boca y eliminar todas las dudas.' + user_muted: "{user} fue silenciado por {admin} en {chat_name}." + unmute_DaisyX: De hecho, no estoy silenciado. + unmute_admin: Haha no, ¡intenta silenciarlo primero! + unmute_self: '¿Debería silenciarte por primera vez?' + user_unmuted: "{user} fue desmuteado por {admin} en {chat_name}." + #Bans + ban_DaisyX: '¡No, no voy a hacer eso! ¡Pídele al creador del chat que lo haga!' + ban_admin: Jajaja, vamos a ejecutar /demote a él primero. + ban_self: '¿Por qué intentas banearte a ti mismo?' + user_banned: "{user} fue baneado por {admin} en {chat_name}." + unban_DaisyX: De hecho, no estoy prohibido. + unban_admin: Haha no, ¡intenta banearlo primero! + unban_self: '¿Debería banearte por primera vez?' + user_unband: "{user} fue desbaneado por {admin} en {chat_name}." + invalid_time: Hora inválida! + enter_time: '¡Por favor, especifique el tiempo!' + on_time: "\nEn %s" + reason: "\nRazón: %s" + purge: "\n¡Los mensajes serán eliminados en 5 segundos!" + filter_title_ban: Banear al usuario + filter_action_rsn: ¡Acción de filtro automática! + filtr_ban_success: '%s baneó a %s por %s' + filtr_mute_success: '%s silenció a %s por %s' + filtr_tmute_success: | + %s silenciado %s por %s + razón: %s + time_setup_start: '¡Por favor, especifique un tiempo!' + filtr_tban_success: | + %s baneado %s por %s + razón: %s + filter_title_tmute: tMute al usuario + filter_title_mute: Silenciar al usuario + filter_title_tban: tBan al usuario + filter_title_kick: Expulsar usuario + rules: + #Set + saved: Reglas guardadas en %s + updated: Reglas actualizadas en %s + #Reset + deleted: '¡Reglas eliminadas!' + not_found: '¡Reglas no encontradas!' + #Callback + user_blocked: "Escríbeme /start por mensaje privado!" + rules_was_pmed: '¡Las reglas han sido enviadas por mensaje privado!' + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "¡Bienvenido/a {mention}! ¡Por favor, haz clic en el botón de abajo para verificar que eres un ser humano!" + bool_invalid_arg: Argumento inválido, se esperaba on/off. + #/setwelcome + saved: Mensaje de bienvenida guardado en %s + updated: Mensaje de bienvenida actualizado en %s + #/turnwelcome + turnwelcome_status: El mensaje de Bienvenida está {status} en {chat_name} + turnwelcome_enabled: Bienvenida habilitada en %s + turnwelcome_disabled: Bienvenida deshabilitada en %s + #/welcome + enabled: habilitado + disabled: deshabilitado + wlcm_security_enabled: "habilitado, establecido a {level}" + wlcm_mutes_enabled: "habilitado, establecido a {time}" + welcome_info: | + Configuración de la Bienvenida en {chat_name}: + La bienvenida está {welcomes_status} + La seguridad de la bienvenida está {wlcm_security} + El silencio de la bienvenida está {wlcm_mutes} + Eliminar la bienvenida antigua está {clean_welcomes} + Eliminar mensajes de servicio está {clean_service} + wlcm_note: "\n\nLa nota de bienvenida es:" + raw_wlcm_note: "La nota de bienvenida rápida es:" + security_note: "El texto de seguridad de la bienvenida es:" + #/setsecuritynote + security_note_saved: "Fue guardado el texto de seguridad personalizado %s" + security_note_updated: "Fue actualizado el texto de seguridad personalizado %s" + #/delsecuritynote + security_note_not_found: El texto de seguridad en %s no fue establecido previamente + del_security_note_ok: El texto de seguridad ha sido restablecido como predeterminado en %s + #/cleanwelcome + cleanwelcome_enabled: Limpieza de bienvenida activada en %s + cleanwelcome_disabled: Limpieza de bienvenida desactivada en %s + cleanwelcome_status: La limpieza de la bienvenida está {status} en {chat_name} + #/cleanservice + cleanservice_enabled: Limpieza de mensajes de servicio habilitada en %s + cleanservice_disabled: Limpieza de mensajes de servicio deshabilitada en %s + cleanservice_status: La limpieza de mensajes de servicio está {status} en {chat_name} + #/welcomemute + welcomemute_invalid_arg: Argumento inválido, se esperaba un tiempo. + welcomemute_enabled: Silencio de bienvenida activado en %s + welcomemute_disabled: Silencio de bienvenida deshabilitado en %s + welcomemute_status: El silencio de la bienvenida está {status} en {chat_name} + #/welcomesecurity + welcomesecurity_enabled: Seguridad de bienvenida activada en {chat_name} al nivel {level}. + "welcomesecurity_enabled:customized_time": | + Seguridad de bienvenida habilitada en {chat_name} a {level}, y expulsa a los usuarios que no son verificados dentro de {time}. + ask_for_time_customization: | + ¿Quieres personalizar el período de tiempo para expulsar al usuario no verificado? (por defecto está en {time}) + send_time: Por favor, especifique un tiempo. + invalid_time: "¡Tiempo inválido!" + welcomesecurity_enabled_word: habilitado, establecido al nivel {level} + welcomesecurity_disabled: Seguridad de bienvenida deshabilitada en %s. + welcomesecurity_invalid_arg: Argumento inválido, se esperaba button/captcha. + welcomesecurity_status: La seguridad de la bienvenida está {status} en {chat_name} + yes_btn: "Sí" + no_btn: "No" + #Welcomesecurity handler + click_here: '¡Haz clic aquí!' + not_allowed: '¡No tienes permiso para usar esto!' + ws_captcha_text: Hola {user}, por favor ingresa el número del captcha para desmutearte en el chat. + regen_captcha_btn: '🔄 Cambiar captcha' + num_is_not_digit: '¡Por favor, introduzca sólo números!' + bad_num: '¡Número incorrecto! Por favor, inténtalo de nuevo.' + passed: '¡Verificación aprobada, has sido desmuteado en %s!' + passed_no_frm: '¡Verificación aprobada! ¡Has sido desmuteado en %s!' + last_chance: '¡Tienes una última oportunidad para introducir el captcha!' + btn_button_text: "Por favor, haga clic en el botón de abajo para verificarse." + btn_wc_text: | + Por favor, presione el botón correcto que corresponde a esta expresión: + %s + math_wc_rtr_text: "¡Tienes una última oportunidad! Ten cuidado.\n" + math_wc_wrong: '¡Error, por favor inténtalo de nuevo!' + math_wc_sry: Lo sentimos, has realizado el último intento. Pregunta a los administradores del chat. + not_admin_wm: '¡No puedo restringir a la gente aquí, así que no voy a establecer el silencio de bienvenida!' + not_admin_ws: '¡No puedo restringir a la gente aquí, así que no estableceré una seguridad de bienvenida!' + not_admin_wsr: No puedo borrar mensajes aquí, ¡así que no borraré los mensajes de servicio! + #/resetwelcome + deleted: '¡Los saludos se reestablecieron con éxito en {chat}!' + not_found: '¡No hay nada para restablecer!' + #... + verification_done: '¡La verificación está completada! Ahora puedes escribir en el chat.' + locks: + locks_header: "Aquí está la configuración actual de bloqueo en {chat_title} \n\n" + no_lock_args: '¡Dame algunos argumentos para bloquear!' + already_locked: Esto ya está bloqueado, bro + no_such_lock: '¡Mira /locks para saber qué se puede bloquear y desbloquear!' + locked_successfully: '¡{lock} bloqueado en {chat} con éxito!' + no_unlock_args: '¡Dame algunos argumentos para desbloquear!' + not_locked: Bro, no está bloqueado para desbloquear. + unlocked_successfully: '¡{lock} desbloqueado en {chat} con éxito!' + antiflood: + #enforcer + flood_exceeded: "¡{user} {action} por hacer flood!" + #/setflood + "invalid_args:setflood": "¡Se esperaba un número!" + overflowed_count: '¡El conteo máximo debe ser inferior a 200!' + config_proc_1: Por favor, envíe el tiempo de validez. '0' (cero) significa para que no expire. + cancel: '❌ Cancelar' + setup_success: '¡Antiflood configurado con éxito! ¡Ahora establece e impone contra los que envían {count} mensajes dentro de {time}!' + "setup_success:no_exp": '¡Antiflood establecido con éxito! ¡Permite {count} mensajes de forma continua!' + invalid_time: '¡Tiempo inválido!' + #/antiflood | /flood + not_configured: '¡El Antiflood todavía no está establecido en este chat!' + turned_off: '¡Antiflood desactivado en {chat_title}!' + "configuration_info:with_time": El Antiflood está configurado en este chat. ¡Aquellos que envían {count} mensajes dentro de {time} serán {action}! + configuration_info: El Antiflood está configurado en este chat. ¡Aquellos que envían {count} mensajes consecutivos serán {action}! + ban: baneado + mute: silenciado + kick: expulsado + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "¡Acción no soportada, se esperaba {supported_actions}!" + setfloodaction_success: "¡Actualizado con éxito la acción antiflood a {action}!" + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/fr_FR.yaml b/DaisyX/localization/fr_FR.yaml new file mode 100644 index 00000000..d2d817c8 --- /dev/null +++ b/DaisyX/localization/fr_FR.yaml @@ -0,0 +1,677 @@ +--- +language_info: + flag: "🇫🇷" + code: fr +STRINGS: + notes: + #/save + note_saved: "🗒 Note {note_name} enregistrée dans {chat_title}!" + note_updated: "🗒 Note {note_name} a été mise à jour dans {chat_title}!" + you_can_get_note: "\nVous pouvez retrouver cette note en utilisant /get {note_name}, ou #{note_name}" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nAliases de note :" + blank_note: "Sauvegarder une note vide n'est pas autorisé !" + notename_cant_contain: "Le nom de la note ne peut pas contenir \"{symbol}\"!" + #/get + cant_find_note: Je ne peux pas trouver cette note dans {chat_name}! + no_note: "Impossible de trouver cette note." + no_notes_pattern: "Aucune note n'a été trouvée par le pattern %s" + u_mean: "\nVous vouliez dire #{note_name}?" + #/notes + notelist_no_notes: "Il n'y a aucune note dans {chat_title}!" + notelist_header: "Notes dans {chat_name}:" + notes_in_private: "Cliquez sur le bouton ci-dessous pour obtenir la liste des notes." + notelist_search: | + Motif : {request} + Notes correspondantes : + #/clear + note_removed: "Note #{note_name} a été supprimée dans {chat_name}!" + note_removed_multiple: | + Removed multiple notes in {chat_name} + Removed:{removed} + not_removed_multiple: "Non supprimée :{not_removed}" + #/clearall + clear_all_text: "Ceci supprimera toutes les notes de {chat_name}. Êtes-vous sûr ?" + clearall_btn_yes: "Oui. Supprimer toutes mes notes !" + clearall_btn_no: "Non !" + clearall_done: "Toutes ({num}) notes de {chat_name} on été supprimées !" + #/search + search_header: | + Recherche de requête dans {chat_name}: + Pattern : {request} + Notes correspondantes : + #/noteinfo + note_info_title: "Informations sur la note\n" + note_info_note: "Noms des notes : %s\n" + note_info_content: "Contenu de la note : %s\n" + note_info_parsing: "Mode d'analyse : %s\n" + note_info_created: "A été créée dans: {date} par {user}\n" + note_info_updated: "Dernière mise à jour dans: {date} par {user}\n" + user_blocked: "Écrivez /start dans mon MP !" + #/privatenotes + private_notes_false_args: "You have 2 options Disable and Enable!" + already_enabled: "Les notes privées sont déjà activées dans %s" + current_state_info: "Private notes are currently {state} in {chat}!" + enabled_successfully: "Les notes privées sont activées dans %s avec succès !" + disabled_successfully: "Les notes privées sont désactivées dans %s avec succès !" + not_enabled: "Private notes are not enabled." + privatenotes_notif: "You have been successfully connected to {chat} notes! To disconnect please use command /disconnect!" + enabled: 'Activé' + disabled: 'Désactivé' + #/cleannotes + clean_notes_enable: "Nettoyage des notes activé avec succès dans {chat_name}" + clean_notes_disable: "Nettoyage des notes désactivé avec succès dans {chat_name}" + clean_notes_enabled: "Le nettoyage des notes est actuellement activé dans {chat_name}" + clean_notes_disabled: "Le nettoyage des notes est actuellement désactivé dans {chat_name}" + #Filter + filters_title: 'Envoyer une note' + filters_setup_start: 'Veuillez envoyer un nom de note.' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "Aucun filtre n'a été trouvé dans {chat_name}!" + #/addfilter + anon_detected: En tant qu'administrateur anonyme, vous ne pouvez pas ajouter de nouveaux filtres, utilisez plutôt des connexions. + regex_too_slow: "Your regex pattern matches too slowly (more than the half of second) and it can't be added!" + cancel_btn: "🛑 Annuler" + adding_filter: | + Ajout du filtre {handler} dans {chat_name} + Sélectionnez l'action ci-dessous : + saved: "Le nouveau filtre a été enregistré avec succès!" + #/filters + list_filters: "Filtres dans {chat_name}:\n" + #/delfilter + no_such_filter: I can't find that filter in {chat_name}, you can check what filters are enabled with the /filters command. + del_filter: "Filtre avec le gestionnaire '{handler}' a été supprimé avec succès !" + select_filter_to_remove: | + J'ai trouvé beaucoup de filtres avec le gestionnaire '{handler}'. + Veuillez en sélectionner un à supprimer : + #/delfilters or /delallfilters + not_chat_creator: Seuls les créateurs de chat peuvent utiliser cette commande! + delall_header: Cela supprimerait tous les filtres de ce chat. C'est irréversible. + confirm_yes: '⚠ Tout supprimer' + confirm_no: '❌ Annuler' + delall_success: Tous les filtres ({count}) de ce chat ont bien été supprimés! + warns: + #/warn + warn_sofi: "Haha aucun moyen d'avertir sois-même !" + warn_self: "Vous voulez vous avertir ? Quittez la discussion alors." + warn_admin: "Eh bien... vous avez tort. Vous ne pouvez pas avertir un administrateur." + warn: "{admin} a averti {user} dans {chat_name}\n" + warn_bun: "Les avertissements ont été dépassés! {user} a été banni !" + warn_num: "Avertissements : {curr_warns}/{max_warns}\n" + warn_rsn: "Raison : {reason}\n" + max_warn_exceeded: Les avertissements ont été dépassés ! %s a été %s! + "max_warn_exceeded:tmute": Les avertissements ont été dépassés! %s a été muté temporairement pour %s! + #warn rmv callback + warn_no_admin_alert: "⚠ You are NOT admin and cannot remove warnings!" + warn_btn_rmvl_success: "✅ Warning removed by {admin}!" + #/warns + warns_header: "Voici vos avertissements dans ce chat \n\n" + warns: "{count} : {reason} par {admin}\n" + no_warns: "Eh bien, {user} n'a aucun avertissement." + #/warnlimit + warn_limit: "La limite d'avertissement dans {chat_name} est de : {num}" + warnlimit_short: 'La limite des avertissements devrait être d''au moins 2 !' + warnlimit_long: 'La limite des avertissements doit être inférieure à 1 000 !' + warnlimit_updated: '✅ Warnlimit successfully updated to {num}' + #/delwarns + purged_warns: "{admin} a réinitialiser {num} les avertissements de {user} dans {chat_title}!" + usr_no_wrn: "{user} n'a aucun avertissement à réinitialiser." + rst_wrn_sofi: 'Daisy n''avait jamais d''avertissments a réinitialiser.' + not_digit: La limite des avertissements doit être des chiffres ! + #/warnmode + same_mode: Ceci est le mode actuel ! Comment puis-je le changer ? + no_time: Pour sélectionner le mode 'tmute', vous devez mentionner le temps ! + invalid_time: Invalid time! + warnmode_success: Warn mode of %s has successfully changed to %s! + wrng_args: | + Voici les options disponibles : + %s + mode_info: | + Le mode actuel dans ce chat est %s. + banned: banni + muted: en sourdine + filters_title: Avertir l'utilisateur + filter_handle_rsn: Action de filtre automatisé! + msg_deleting: + no_rights_purge: "Vous n'avez pas les droits suffisants pour supprimer dans ce groupe !" + reply_to_msg: "Répondre à un message pour supprimer!" + purge_error: "Je ne peux pas continuer de purger, principalement parce que vous avez commencé à purger un message qui a été envoyé depuis plus de 2 jours." + fast_purge_done: "Purge rapide terminée !\nCe message sera supprimé dans 5 secondes." + purge_user_done: "Tous les messages de {user} ont été supprimés avec succès !" + misc: + your_id: "Votre ID: {id}\n" + chat_id: "ID du groupe : {id}\n" + user_id: "ID de {user} : {id}\n" + conn_chat_id: "ID actuel du chat connecté : {id}" + help_btn: "Cliquez pour obtenir de l'aide !" + help_txt: "Cliquez sur le bouton ci-dessous pour obtenir de l'aide !" + delmsg_filter_title: 'Supprimer le message' + send_text: Veuillez envoyer le message de réponse ! + replymsg_filter_title: 'Répondre au message' + send_customised_reason: Envoyez la raison que vous souhaitez inclure dans le message d'action. "Aucun" pour aucune raison à donner! + expected_text: Attendu un Sms! + promotes: + promote_success: "{user} a été promu avec succès dans {chat_name}!" + promote_title: "\nAvec un rôle personnalisé {role}!" + rank_to_loong: "Le texte du rôle personnalisé ne peut pas contenir plus de 16 symboles." + promote_failed: "La promotion a échoué! Vérifiez si j'en ai les droits." + demote_success: "{user} a été rétrogradé avec succès dans {chat_name}!" + demote_not_admin: "Cet utilisateur n'est pas administrateur." + demote_failed: "Demotion failed. Maybe I can't demote or the person is promoted by someone else?" + cant_get_user: "Je n'ai pas pu obtenir l'utilisateur! Si vous le souhaitez, veuillez répondre à son message et réessayez." + cant_promote_yourself: "Vous ne pouvez pas vous promouvoir vous-même." + emoji_not_allowed: "Je suis désolé, mais les rôles d'administration ne peuvent pas avoir d'émojis 😞" + pm_menu: + start_hi_group: 'Salut ! Je m''appelle Daisy' + start_hi: "Bonjour! Je m'appelle Daisy, je vous aide à gérer votre groupe de manière efficace !" + btn_source: "📜 Code source" + btn_help: "❔ Aide" + btn_lang: "🇷🇺 Langue" + btn_channel: "🙋‍♀️ Daisy News" + btn_group: "👥 Daisy Support" + btn_group_help: "Click me for help!" + back: Retour + #/help + help_header: "Hey! Je m'appelle Daisy. Je suis un robot de gestion de groupe, là pour vous aider à vous déplacer et à garder l'ordre dans vos groupes!\nJ'ai beaucoup de fonctionnalités pratiques, telles que le contrôle des inondations, un système d'alerte, un système de conservation de notes et même des réponses prédéterminées sur certains mots clés." + click_btn: Cliquez ici + disable: + #/disableable + disablable: "Commandes désactivables :\n" + #/disabled + no_disabled_cmds: Aucune commande désactivée dans {chat_name}! + disabled_list: "Commandes désactivées dans {chat_name}:\n" + #/disable + wot_to_disable: "Que voulez-vous désactiver?" + already_disabled: "Cette commande est déjà désactivée !" + disabled: "La commande {cmd} a été désactivée dans {chat_name}!" + #/enable + wot_to_enable: "Que voulez-vous activer?" + already_enabled: "Cette commande n'est pas désactivée !" + enabled: "La commande {cmd} a été activée dans {chat_name}!" + #/enableall + not_disabled_anything: "Rien n'a été désactivé dans {chat_title}!" + enable_all_text: "This will enable all commands in the {chat_name}. Are you sure?" + enable_all_btn_yes: "Oui. Activez toutes les commandes !" + enable_all_btn_no: "Non !" + enable_all_done: "Toutes les ({num}) commandes ont été activées {chat_name}!" + bot_rights: + change_info: "Je n'ai pas le droit de modifier les informations du groupe, veuillez me placer admin avec ce droit." + edit_messages: "Je n'ai pas le droit de modifier les messages !" + delete_messages: "Je n'ai pas le droit de supprimer les messages ici." + ban_user: "Je n'ai pas les droits pour bannir les utilisateurs, s'il vous plaît me placer admin avec ce droit." + pin_messages: "Je n'ai pas les droits d'épingler les messages, veuillez me placer admin avec ce droit." + add_admins: "Je n'ai pas les droits pour ajouter des administrateurs, s'il vous plaît placez moi admin avec ce droit." + feds: + #decorators + need_fed_admin: "You are not an admin in {name} federation" + need_fed_owner: "You are not an owner of {name} federation" + cant_get_user: "Sorry, I can't get that user, try using their user ID" + #/fnew + fed_name_long: "Le nom de la Fédération ne peut pas contenir plus de 60 symboles !" + can_only_1_fed: "Users can only create 1 federation, please remove one." + name_not_avaible: "Fédération avec le nom {name} existe déjà ! Veuillez utiliser un autre nom." + created_fed: | + Congrats, you have successfully created a federation. + Name: {name} + ID: {id} + Creator: {creator} + Use /fjoin {id} to connect federation to chat\ + disallow_anon: As an anonymous admin you cannot create new federation! + #/fjoin + only_creators: "You must be the chat creator to be able to connect chat to a federation." + fed_id_invalid: "The given federation ID is invalid! Please give me a valid ID." + joined_fed_already: "This chat has already joined a federation! Please use /fleave to leave that federation" + join_fed_success: "Great! Chat {chat} is now a part of {fed} federation!" + join_chat_fed_log: | + Chat joined federation #ChatJoined + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "Ce chat ne se trouve dans aucune fédération." + leave_fed_success: "Le chat {chat} a quitté la fédération {fed}." + leave_chat_fed_log: | + Chat a quitté la Fédération #ChatLeaved + Fédération : {fed_name} ({fed_id}) + Chat : {chat_name} ({chat_id}) + #/fsub + already_subsed: "La Fédération {name} s'est déjà abonner à {name2}" + subsed_success: "La Fédération {name} s'est abonnée à {name2}!" + #/funsub + not_subsed: "Federation {name} is not subscribed to {name2}" + unsubsed_success: "Federation {name} unsubscribed from {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "User {user} added to {name} federation admins." + promote_user_fed_log: | + Utilisateur promu dans les admins de Fédération #UserPromoted + Fédération : {fed_name} ({fed_id}) + Utilisateur : {user} ({user_id}) + "restricted_user:promote": This user is restricted from being federation admin! + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "User {user} demoted from {name} federation admins." + demote_user_fed_log: | + Utilisateur rétrogradé dans les admins de Fédération #UserPromoted + Fédération : {fed_name} ({fed_id}) + Utilisateur : {user} ({user_id}) + #/fsetlog + already_have_chatlog: "{name} federation already has logging enabled in another chat/channel!" + set_chat_log: | + This chat is now logging all actions in {name} federation + + Federation logging is used for internal assessment of the federation and its performance. + The federation should NOT be used as a public wall of shame thereby respecting user data and our privacy guidelines. + set_log_fed_log: | + Logs activé #LoggingEnabled + Fédération : {fed_name} ({fed_id}) + no_right_to_post: Je n'ai pas la permission d'envoyer des messages dans cette chaîne ! + #/funsetlog + not_logging: "{name} federation isn't logging to any chat/channel!" + logging_removed: "Successfully removed logging from {name} federation." + unset_log_fed_log: | + Logs désactivé #LoggingDisabled + Fédération : {fed_name} ({fed_id}) + #/fchatlist + no_chats: "There's no chats joined this {name} federation" + chats_in_fed: "Chats connected to {name} federation:\n" + too_large: "Résultat trop grand, envoi en tant que fichier" + #/finfo + finfo_text: | + info Fédération + Nom : {name} + ID: {fed_id} + Créateur : {creator} + Chats dans la fédération : {chats} + Utilisateurs bannis dans la fédération : {fbanned} + finfo_subs_title: "Federations subscribed to this feds:\n" + #/fadminlist + fadmins_header: "Admins in {fed_name} fed:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "Cet utilisateur est sur la liste blanche des personnes à bannir." + fban_self: "C'est un beau spectacle!" + fban_creator: "How can I ban the federation creator?! I don't think it's gonna be funny." + fban_fed_admin: "I'm not going to ban a federation admin from their own fed!" + update_fban: "This user was already fbanned therefore I am updating the reason to {reason}" + already_fbanned: "{user} a déjà été banni dans cette fédération." + fbanned_header: "Nouveau BanFédération\n" + fban_info: | + Fédération : {fed} + Admin de la Fed : {fadmin} + Utilisateur : {user} ({user_id}) + fbanned_reason: "Raison : {reason}" + fbanned_process: "\nÉtat : Bannissement dans {num} chats de fédération..." + fbanned_done: "\nÉtat : Terminé ! banni dans {num} chats !" + fbanned_silence: "Ce message sera purgé dans 5 secondes" + fbanned_subs_process: "\nÉtat : Bannissement dans {feds} abonnements de fédération..." + fbanned_subs_done: "\nStatus: Done! Banned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + fban_usr_rmvd: | + User {user} is currently banned in {fed} federation and have been removed! + Reason: {rsn} + fban_log_fed_log: | + Bannir un utilisateur dans la Fédération #FedBan + Fédération : {fed_name} ({fed_id}) + Utilisateur : {user} ({user_id}) + Par : {by} + Chats bannis: utilisateur banni dans {chat_count}/{all_chats} chats + fban_reason_fed_log: "Raison : {reason}\n" + fban_subs_fed_log: "\nles fédérations souscrites : bannis dans {subs_chats} chats de {feds} fédérations" + #/unfban + unfban_self: "Think of something else to have fun with instead of this." + user_not_fbanned: "{user} isn't banned in this federation." + un_fbanned_header: "Nouveau Un-FedBan\n" + un_fbanned_process: "\nÉtat : Débannissement dans {num} chats..." + un_fbanned_done: "\nÉtat : Terminé ! Débanni dans {num} conversations !" + un_fbanned_subs_process: "\nÉtat : Débannissement dans {feds} fédérations souscrites..." + un_fbanned_subs_done: "\nStatut : Terminé ! Débanni dans {chats} chats de cette fédération et {subs_chats} chats de {feds} Fédérations souscrites" + un_fban_log_fed_log: | + Unban un utilisateur dans la Fédération #FedUnBan + Fédération : {fed_name} ({fed_id}) + Utilisateur : {user} ({user_id}) + Par : {by} + Chats bannis: utilisateur banni dans {chat_count}/{all_chats} chats + un_fban_subs_fed_log: "\nles fédérations souscrites : unbannis dans {subs_chats} chats de {feds} fédérations" + #/delfed + delfed_btn_yes: Oui. Supprimer la fédération + delfed_btn_no: Non. + delfed: | + Are you sure you want to delete the %s federation? + + Disclaimer: Deleting this federation will remove all data associated with it from our database! + This action is permanent and cannot be reversed! + delfed_success: La fédération a été supprimée avec succès ! + #/frename + frename_same_name: Please enter a different name, not the same one! + frename_success: The federation {old_name} renamed to {new_name} successfully! + #/fbanlist + fbanlist_locked: Please wait until %s to use fbanlist again. + creating_fbanlist: Création de la liste fban ! Veuillez patienter.. + fbanlist_done: Fbanlist of federation %s! + #/importfbans + send_import_file: | + Send the import file! + + To cancel this process use /cancel + rpl_to_file: Veuillez répondre avec un fichier ! + importfbans_locked: L'importation de fbans est verrouillée pour %s dans cette fédération. + big_file_csv: Only supports import csv files less {num} megabytes! + big_file_json: | + Currently json files are limited to {num} megabytes! + Use the csv format if you want to import bigger files. + invalid_file: "Ce fichier est invalide" + wrong_file_ext: "Format de fichier invalide ! Actuellement sont supportés : json, csv" + importing_process: Importation des bans de fédération... + import_done: Importing fed bans finished! {num} bans were imported. + #def + automatic_ban: User {user} is banned in the current federation {fed_name} and has been removed! + automatic_ban_sfed: User {user} is banned in the sub federation {fed_name}{text}" + #/fcheck + fcheck_header: "Informations sur le ban de la Fédération :\n" + fbanned_count_pm: "Vous êtes banni dans {count} fédération(s)!\n" + "fban_info:fcheck": "You have been fbanned in {fed} federation on {date}, No reason was given!" + "fban_info:fcheck:reason": "Vous avez été banni dans la fédération {fed} le {date} pour raison -\n {reason}\n" + fbanned_data: "{user} a été banni dans {count} fédération(s)\n" + fbanned_nowhere: "{user} isn't fbanned anywhere!\n" + fbanned_in_fed: "Including the {fed} federation!\n" + "fbanned_in_fed:reason": "Including the {fed} federation for reason of -\n {reason}" + contact_in_pm: "\n\nFor more info on your fbans, please send me a direct message for privacy!" + too_long_fbaninfo: "« Eh bien! Votre fbaninfo a besoin d'un fichier»!" + didnt_fbanned: "You aren't fbanned in this fed." + not_fbanned_in_fed: They aren't fbanned in the {fed_name} fed + reports: + user_user_admin: "Vous êtes un administrateur ici, pourquoi auriez-vous besoin de signaler quelqu'un ?.." + user_is_whitelisted: "Vous êtes un utilisateur sur liste blanche, pourquoi auriez-vous besoin de signaler quelqu'un ?.." + reports_disabled: "Les signalements sont actuellement désactivés dans ce groupe." + no_user_to_report: "Quel utilisateur souhaitez-vous signaler ?" + report_admin: "You cannot report admins." + report_whitedlisted: "Vous ne pouvez pas signaler un utilisateur sur liste blanche." + reported_user: "Signalé {user} aux administrateurs." + reported_reason: "\nRaison :\n{reason}" + reports_on: "Les signalements sont activés sur dans ce groupe." + reports_off: "Les signalements sont activés sur dans ce groupe." + reports_already_on: "On a déjà activé les signalements." + reports_turned_on: "Les signalements sont activés " + reports_already_off: "Les signalements sont déjà désactivés." + wrong_argument: " Argument erroné. " + language: + your_lang: "Votre langue actuelle : {lang}" + chat_lang: "Langue actuelle du chat : {lang}" + select_pm_lang: "\nSélectionnez votre nouvelle langue :" + select_chat_lang: "\nSélectionnez une nouvelle langue du chat :" + crowdin_btn: "🌎 Aidez-nous avec la traduction" + help_us_translate: "\n\n🌎 Help us improve our translations" + not_supported_lang: This language is not supported! + lang_changed: La langue a été changée en {lang_name}. + see_translators: '👥 Voir les traducteurs' + back: Retour + connections: + pm_connected: "Votre MP a été connecté avec succès à {chat_name}!" + connected_pm_to_me: "Votre MP a été connecté avec succès à {chat_name}! Veuillez me MP pour commencer à utiliser la connexion." + chat_connection_tlt: "Connexion au chat\n" + connected_chat: | + Chat actuel connecté : + {chat_name} + Écrire /disconect pour se déconnecter du chat + "connected_chat:cmds": | + Current connected chat: + {chat_name}, (bridged commands are {commands}), + ⚠ You can only access bridged commands of the connected chat! All other commands would be treated as Local chat + Write /disconect to disconnect from chat. + u_wasnt_connected: "You were not connected to any chat before!" + select_chat_to_connect: "\nSélectionnez un chat à connecter:" + disconnected: "Vous avez été déconnecté de {chat_name}." + cant_find_chat: "Je ne trouve pas ce chat." + cant_find_chat_use_id: "Je ne trouve pas ce chat. Utilisez l'ID du chat." + bad_arg_bool: "Choix incorrect : seulement acceptés on/off enable/disable" + enabled: activé + disabled: désactivé + chat_users_connections_info: "Les connexions pour les utilisateurs normaux sont actuellement {status} pour {chat_name}" + chat_users_connections_cng: "Les connexions pour les utilisateurs normaux ont changé à {status} pour {chat_name}" + not_in_group: "Vous n'êtes pas dans le groupe auquel vous essayez de vous connecter, rejoindre et d'envoyer un message." + history_empty: "Vous n'êtes connecté à aucun chat pour l'historique, connectez-vous via `/connect (Id de chat)`" + not_in_chat: "Vous n'êtes plus dans ce chat, je vais vous déconnecter." + u_should_be_admin: "Vous devriez être administrateur dans {} !" + usage_only_in_groups: "Utilisation limitée uniquement dans les groupes!" + anon_admin_conn: | + Please click below button to connect! + click_here: Click here! + global: + u_not_admin: Vous devriez être un administrateur pour faire ça ! + #Rights + "bot_no_right:not_admin": Je ne suis pas un administrateur ! + bot_no_right: "Je n'ai pas besoin de la permission de le faire ! \nPermission manquante : %s" + user_no_right: "Vous devriez avoir la permission de le faire ! \nPermission manquante : %s" + "user_no_right:not_admin": Vous devriez être administrateur pour le faire ! + #is chat creator + unable_identify_creator: "Unable to identify anonymous chat creator, duplicate signature (titles) found!" + owner_stuff: + father: "\nIl est mon créateur." + sudo_crown: "\nHé ! Regardez, cet utilisateur a une couronne, laissez-moi la voir... Wow, il a une gravure - 'utilisateur sudo'!" + stickers: + rpl_to_sticker: "Répondre à un autocollant!" + ur_sticker: | + Emoji : {emoji} + Identifiant autocollant : {id} + pins: + no_reply_msg: "Répondre à un message pour épingler !" + chat_not_modified_pin: "Ce message est déjà épinglé !" + chat_not_modified_unpin: "Il n'y a aucun message épinglé à annuler !" + pinned_success: "Épinglé avec succès!" + unpin_success: "Unpin avec succès!" + users: + user_info: "Informations utilisateur :\n" + info_id: "ID: {id}" + info_first: "\nPrénom : {first_name}" + info_last: "\nNom : {last_name}" + info_username: "\nNom d'utilisateur : {username}" + info_link: "\nLien utilisateur : {user_link}" + info_saw: "\nJe les ai vus dans {num} groupes" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_fbanned: "\nBanni dans la Fédération actuelle : " + info_admeme: "\nL'utilisateur a des droits d'administration dans ce chat." + upd_cache: "Mise à jour du cache en cours..." + upd_cache_done: "Le cache des admins a été mis à jour." + admins: "Admin dans ce groupe :\n" + imports_exports: + #Exporting + started_exporting: L'exportation a démarré! Veuillez patienter... + export_done: Exportation de {chat_name} terminée ! + exports_locked: Please wait %s before using exports again! + #Importing + invalid_file: "Le fichier est invalide" + send_import_file: Please send a file for importing! + rpl_to_file: Veuillez répondre avec un fichier ! + big_file: Only files under 50 MB are supported! + started_importing: L'importation a démarré! Veuillez patienter... + bad_file: Fichier incorrect ! La colonne 'general' était attendue. + file_version_so_new: Exported file version is newer than what Daisy currently supports! + imports_locked: Please wait %s before using imports again! + import_done: Importation terminée ! + restrictions: + user_not_in_chat: User is not in this chat! + #Kicks + kick_DaisyX: Instead of trying kick me you could spend your time better. Thats just boring. + kick_admin: La meilleure idée n'est pas de kicker un administrateur. + kick_self: If you want to kick yourself - just leave this chat. I would rather not see you than look at these pathetic attempts at self-punishment. + user_kicked: "{user} a été kické par {admin} dans {chat_name}." + #Mutes + mute_DaisyX: Actually, I'll be happy to shut up you instead. + mute_admin: If you think you can shut up an admin, you are wrong! + mute_self: If you want to mute yourself - just stop talking! Better to thought a fool than open your mouth and remove all doubt. + user_muted: "{user} a été mis en sourdine par {admin} dans {chat_name}." + unmute_DaisyX: Actually, I'm not muted. + unmute_admin: Haha no, try muting him first! + unmute_self: Devrais-je d'abord vous mettre en sourdine ? + user_unmuted: "{user} a été mis en sourdine par {admin} dans {chat_name}." + #Bans + ban_DaisyX: No, I won't do it! Ask the chat creator to do it! + ban_admin: Haha, nous allons le /demote en premier. + ban_self: Pourquoi essayez-vous de vous bannir vous-même? + user_banned: "{user} a été banni par {admin} dans {chat_name}." + unban_DaisyX: Actually, I'm not banned. + unban_admin: Haha no, try banning him first! + unban_self: Devrais-je vous bannir d'abord? + user_unband: "{user} a été débanni par {admin} dans {chat_name}." + invalid_time: Temps invalide ! + enter_time: Please specify a time! + on_time: "\nPour %s" + reason: "\nRaison : %s" + purge: "\nMessages will be purged in 5 seconds!" + filter_title_ban: Bannir l'utilisateur + filter_action_rsn: Action de filtre automatisée ! + filtr_ban_success: '%s a banni %s pour %s' + filtr_mute_success: '%s mis en sourdine %s pour %s' + filtr_tmute_success: | + %s mis en sourdine %s pour %s + la raison est %s + time_setup_start: Please specify a time! + filtr_tban_success: | + %s a été banni %s pour %s + la raison est %s + filter_title_tmute: mettre l'utilisateur en sourdine temporairement + filter_title_mute: Mettre l'utilisateur en sourdine + filter_title_tban: ban l'utilisateur temporairement + filter_title_kick: Kicker l'utilisateur + rules: + #Set + saved: Les règles ont été enregistrées dans %s + updated: Les règles ont été mises à jour dans %s + #Reset + deleted: Les règles ont été supprimées! + not_found: Les règles n'ont pas été trouvées! + #Callback + user_blocked: "Écrivez /start dans mon MP !" + rules_was_pmed: Les règles ont été envoyées à votre PM! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "Welcome {mention}! Please press button below to verify yourself as human!" + bool_invalid_arg: Argument invalide, attendu sur/off. + #/setwelcome + saved: Le message de bienvenue a été sauvegardé dans %s + updated: Le message de bienvenue a été mis à jour dans %s + #/turnwelcome + turnwelcome_status: Les bienvenues sont {status} à {chat_name} + turnwelcome_enabled: Bienvenue activée dans %s + turnwelcome_disabled: Bienvenue désactivée dans %s + #/welcome + enabled: activé + disabled: désactivé + wlcm_security_enabled: "activé, réglé sur {level}" + wlcm_mutes_enabled: "activé, réglé sur {time}" + welcome_info: | + Paramètres de Bienvenue dans {chat_name}: + Bienvenue est {welcomes_status} + La sécurité de Bienvenue est {wlcm_security} + Sourdine de Bienvenue est {wlcm_mutes} + La suppression des vieilles messages de bienvenue est {clean_welcomes} + La suppression des messages de service est {clean_service} + wlcm_note: "\n\nLa note de bienvenue est :" + raw_wlcm_note: "La note de bienvenue brute est :" + security_note: "Le texte de la sécurité d'accueil est :" + #/setsecuritynote + security_note_saved: "Le texte de sécurité personnalisé a été sauvegardé %s" + security_note_updated: "Le texte de sécurité personnalisé a été mis à jour %s" + #/delsecuritynote + security_note_not_found: Security text in %s has not been set before + del_security_note_ok: Security text was reset to default in %s + #/cleanwelcome + cleanwelcome_enabled: Nettoyage des bienvenue activé dans %s + cleanwelcome_disabled: Nettoyage de bienvenue désactivé dans %s + cleanwelcome_status: Le nettoyage des bienvenue est {status} dans {chat_name} + #/cleanservice + cleanservice_enabled: Nettoyage des messages du service activé dans %s + cleanservice_disabled: Nettoyage des messages du service désactivé dans %s + cleanservice_status: Le nettoyage des messages du service est {status} dans {chat_name} + #/welcomemute + welcomemute_invalid_arg: Invalid argument, expected a time. + welcomemute_enabled: Bienvenue avec sourdine activé dans %s + welcomemute_disabled: Bienvenue avec sourdine est désactivé dans %s + welcomemute_status: Bienvenue avec sourdine est {status} dans {chat_name} + #/welcomesecurity + welcomesecurity_enabled: La sécurité de bienvenue est activée dans {chat_name} au niveau {level}. + "welcomesecurity_enabled:customized_time": | + Welcome security enabled in {chat_name} to {level}, and kick users which aren't verified within {time}. + ask_for_time_customization: | + Do you want to customize the time period to kick unverified user? (by default it's {time}) + send_time: Please specify a time. + invalid_time: "Invalid time!" + welcomesecurity_enabled_word: activé, défini au niveau {level} + welcomesecurity_disabled: La sécurité de bienvenue est désactivée dans %s. + welcomesecurity_invalid_arg: Argument invalide, bouton attendu/captcha. + welcomesecurity_status: La sécurité de bienvenue est {status} dans {chat_name} + yes_btn: "Yes" + no_btn: "No" + #Welcomesecurity handler + click_here: Cliquez ici ! + not_allowed: Vous n'êtes pas autorisé à utiliser ceci ! + ws_captcha_text: Bonjour {user}, veuillez entrer le numéro du captcha pour enlever la sourdine dans le chat. + regen_captcha_btn: '🔄 Changer le captcha' + num_is_not_digit: Veuillez entrer uniquement des chiffres ! + bad_num: Numéro incorrecte ! Veuillez réessayer. + passed: Vérification réussie, vous pouvez parler dans %s! + passed_no_frm: Vérification réussie, vous êtes plus mis en sourdine dans %s! + last_chance: Vous avez une dernière chance d'entrer dans le captcha ! + btn_button_text: "Veuillez cliquer sur le bouton ci-dessous pour prouver que vous êtes humain." + btn_wc_text: | + Veuillez appuyer sur le bouton correcte correspondant à cette expression : + %s + math_wc_rtr_text: "C'est votre dernière chance ! Faites attention.\n" + math_wc_wrong: Faux, veuillez essayer à nouveau ! + math_wc_sry: Désolé, vous avez utilisé votre dernière tentative. Demandez aux administrateurs de chat. + not_admin_wm: I can't restrict people here, so I won't use welcome mute! + not_admin_ws: I can't restrict people here, so I won't use welcome security! + not_admin_wsr: Je ne peux pas supprimer les messages ici, donc je ne supprime pas les messages du service ! + #/resetwelcome + deleted: Successfully reset greetings in {chat}! + not_found: Il n'y a rien à réinitialiser! + #... + verification_done: Verification is done! You have been unmuted. + locks: + locks_header: "Voici les paramètres de verrouillage actuels dans {chat_title} \n\n" + no_lock_args: Donnez-moi quelques arguments à verrouiller ! + already_locked: Ceci est déjà verrouillé poto + no_such_lock: Voir /locks pour savoir ce qui peut être verrouillé et déverrouillé ! + locked_successfully: '{lock} verrouillé avec succès dans {chat}!' + no_unlock_args: Donnez-moi quelques arguments à débloquer! + not_locked: Poto, il n'est pas verrouillé pour déverrouiller. + unlocked_successfully: '{lock} a été déverrouillé avec succès dans {chat}!' + antiflood: + #enforcer + flood_exceeded: "{action} {user} for flooding!" + #/setflood + "invalid_args:setflood": "Expected a number!" + overflowed_count: Maximum count should be less than 200! + config_proc_1: Please send expiration time, '0' (zero) for not being expired. + cancel: '❌ Cancel' + setup_success: Successfully configured antiflood, now enforces against those who send {count} messages within {time}! + "setup_success:no_exp": Successfully configured antiflood, allows {count} messages consecutively! + invalid_time: Invalid time! + #/antiflood | /flood + not_configured: Antiflood isn't configured in this chat yet! + turned_off: Disabled antiflood in {chat_title}! + "configuration_info:with_time": Antiflood is configured in this chat, those who send {count} message within {time} will be {action}! + configuration_info: Antiflood is configured in this chat, those who send {count} consecutively will be {action}! + ban: banned + mute: muted + kick: kicked + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "Unsupported action, expected {supported_actions}!" + setfloodaction_success: "Successfully updated flood action to {action}!" + + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/id_ID.yaml b/DaisyX/localization/id_ID.yaml new file mode 100644 index 00000000..801a2380 --- /dev/null +++ b/DaisyX/localization/id_ID.yaml @@ -0,0 +1,676 @@ +--- +language_info: + flag: "🇮🇩" + code: id +STRINGS: + notes: + #/save + note_saved: "🗒 Catatan {note_name} tersimpan di {chat_title}!" + note_updated: "🗒 Catatan {note_name} diperbarui di {chat_title}!" + you_can_get_note: "\nAnda dapat mengambil catatan ini dengan menggunakan /get {note_name}, atau {note_name}" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nAlias catatan:" + blank_note: "Menyimpan catatan kosong tidak diizinkan!" + notename_cant_contain: "Nama catatan tidak boleh mengandung \"{symbol}\"!" + #/get + cant_find_note: Saya tidak dapat menemukan catatan ini di {chat_name}! + no_note: "Tidak dapat menemukan catatan tersebut." + no_notes_pattern: "Tidak ada catatan ditemukan dengan pola %s" + u_mean: "\nMungkin maksud anda #{note_name}?" + #/notes + notelist_no_notes: "Tidak ada catatan apapun di {chat_title}!" + notelist_header: "Catatan di {chat_name}:" + notes_in_private: "Tekan tombol dibawah untuk mendapatkan daftar catatan." + notelist_search: | + Pola: {request} + Catatan yang cocok: + #/clear + note_removed: "Catatan #{note_name} dihapus di {chat_name}!" + note_removed_multiple: | + Beberapa catatan dihapus di {chat_name} + Dihapus:{removed} + not_removed_multiple: "Tidak dihapus:{not_removed}" + #/clearall + clear_all_text: "Ini akan menghapus seluruh catatan dari {chat_name}. Apakah anda yakin?" + clearall_btn_yes: "Ya. Hapus semua catatan saya!" + clearall_btn_no: "Tidak!" + clearall_done: "Seluruh ({num}) catatan di {chat_name} telah dihapus!" + #/search + search_header: | + Permintaan pencarian di {chat_name}: + Pola: {request} + Catatan yang cocok: + #/noteinfo + note_info_title: "Informasi catatan\n" + note_info_note: "Nama catatan: %s\n" + note_info_content: "Isi catatan: %s\n" + note_info_parsing: "Mode penguraian: %s\n" + note_info_created: "Telah dibuat pada: {date} oleh {user}\n" + note_info_updated: "Terakhir diperbarui pada: {date} oleh {user}\n" + user_blocked: "Tulis /start di percakapan pribadi dengan saya!" + #/privatenotes + private_notes_false_args: "Anda memiliki 2 opsi Nonaktifkan dan Aktifkan!" + already_enabled: "Catatan pribadi sudah diaktifkan di %s" + current_state_info: "Catatan pribadi saat ini {state} di {chat}!" + enabled_successfully: "Catatan pribadi telah berhasil Diaktifkan di %s!" + disabled_successfully: "Catatan pribadi telah berhasil Dinonaktifkan di %s!" + not_enabled: "Catatan pribadi tidak diaktifkan." + privatenotes_notif: "Anda telah berhasil terhubung ke catatan {chat} ! Untuk memutuskan koneksi, gunakan perintah /disconnect!" + enabled: 'Aktif' + disabled: 'Nonaktif' + #/cleannotes + clean_notes_enable: "Berhasil mengaktifkan pembersihan catatan di {chat_name}" + clean_notes_disable: "Berhasil menonaktifkan pembersihan catatan di {chat_name}" + clean_notes_enabled: "Pembersihan catatan saat ini aktif di {chat_name}" + clean_notes_disabled: "Pembersihan catatan saat ini nonaktif di {chat_name}" + #Filter + filters_title: 'Kirim sebuah catatan' + filters_setup_start: 'Mohon untuk mengirim sebuah nama catatan.' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "Tidak ada filter yang ditemukan di {chat_name}!" + #/addfilter + anon_detected: Menjadi admin anonim, anda tidak dapat menambahkan filter baru, gunakan koneksi sebagai gantinya. + regex_too_slow: "Pencocokan pola regex anda terlalu lambat (lebih dari setengah detik) dan tidak dapat ditambahkan!" + cancel_btn: "🛑 Batal" + adding_filter: | + Menambahkan filter {handler} di {chat_name} + Pilih aksi dibawah: + saved: "Filter baru telah berhasil tersimpan!" + #/filters + list_filters: "Filter di {chat_name}:\n" + #/delfilter + no_such_filter: Saya tidak dapat menemukan filter tersebut di {chat_name}, anda bisa memeriksa filter yang apa yang diaktifkan dengan perintah /filters. + del_filter: "Filter dengan penangan '{handler}' telah berhasil dihapus!" + select_filter_to_remove: | + Saya telah menemukan banyak filter dengan penangan '{handler}'. + Mohon pilih satu untuk dihapus: + #/delfilters or /delallfilters + not_chat_creator: Hanya pemilik grup yang bisa menggunakan perintah ini! + delall_header: Ini akan menghapus seluruh filter di obrolan ini. Ini tidak bisa diubah. + confirm_yes: '⚠ Hapus semua' + confirm_no: '❌ Batal' + delall_success: Berhasil menghapus seluruh ({count}) filter di obrolan ini! + warns: + #/warn + warn_sofi: "Haha, tidak mungkin untuk memperingati saya sendiri!" + warn_self: "Anda ingin memperingati diri sendiri? Tinggalkan saja obrolan ini." + warn_admin: "Baiklah.. anda salah. Anda tidak dapat memperingati seorang admin." + warn: "{admin} telah memperingati {user} di {chat_name}\n" + warn_bun: "Peringatan telah melebihi batas! {user} telah diblokir!" + warn_num: "Peringatan: {curr_warns}/{max_warns}\n" + warn_rsn: "Alasan: {reason}\n" + max_warn_exceeded: Peringatan telah melebihi batas! {user} telah di {action}! + "max_warn_exceeded:tmute": Peringatan telah melebihi batas! {user} telah di bisukan sementara untuk {time}! + #warn rmv callback + warn_no_admin_alert: "⚠ Anda BUKAN admin dan tidak dapat menghapus peringatan!" + warn_btn_rmvl_success: "✅ Peringatan dihapus oleh {admin}!" + #/warns + warns_header: "Berikut ini adalah peringatan anda di obrolan ini\n\n" + warns: "{count} : {reason} oleh {admin}\n" + no_warns: "Baiklah, {user} tidak memiliki peringatan apapun." + #/warnlimit + warn_limit: "Batas peringatan di {chat_name} saat ini adalah: {num}" + warnlimit_short: 'Batas peringatan sedikitnya harus 2!' + warnlimit_long: 'Batas peringatan harus lebih pendek dari 1.000!' + warnlimit_updated: '✅ Batas peringatan telah berhasil diperbarui ke {num}' + #/delwarns + purged_warns: "{admin} menyetel ulang {num} peringatan pada {user} di {chat_title}!" + usr_no_wrn: "{user} tidak memiliki peringatan untuk di setel ulang." + rst_wrn_sofi: 'Daisy tidak pernah diperingati untuk di setel ulang.' + not_digit: Batas peringatan harus berupa angka! + #/warnmode + same_mode: Berikut ini adalah mode saat ini! Bagaimana saya akan mengubahnya? + no_time: Untuk memilih mode 'tmute' anda harus memberikan waktu! + invalid_time: Waktu salah! + warnmode_success: Mode peringatan dari %s telah berhasil diubah ke %s! + wrng_args: | + Berikut ini beberapa opsi yang tersedia: + mode_info: | + Mode saat ini di obrolan ini adalah %s. + banned: diblokir + muted: disenyapkan + filters_title: Peringati pengguna + filter_handle_rsn: Tindakan filter otomatis! + msg_deleting: + no_rights_purge: "Anda tidak memiliki cukup hak untuk membersihkan di grup ini!" + reply_to_msg: "Balas ke sebuah pesan untuk menghapusnya!" + purge_error: "Saya tidak dapat melanjutkan pembersihan, dikarenakan anda memulai pembersihan dari sebuah pesan yang dikirim lebih lama dari 2 hari." + fast_purge_done: "Pembersihan cepat selesai!\nPesan ini akan dihapus dalam 5 detik." + purge_user_done: "Seluruh pesan dari {user} telah berhasil dihapus!" + misc: + your_id: "ID anda: {id}\n" + chat_id: "ID grup: {id}\n" + user_id: "ID {user}: {id}\n" + conn_chat_id: "ID percakapan yang terhubung saat ini: {id}" + help_btn: "Tekan saya untuk mendapatkan bantuan!" + help_txt: "Klik tombol dibawah untuk mendapatkan bantuan!" + delmsg_filter_title: 'Hapus pesan' + send_text: Mohon kirim pesan balasan! + replymsg_filter_title: 'Balas pesan' + send_customised_reason: Kirim alasan yang anda inginkan untuk disertakan di pesan aksi. "None" untuk memberikan tidak ada alasan! + expected_text: Diharap untuk memasukkan pesan teks! + promotes: + promote_success: "{user} telah berhasil dipromosikan di {chat_name}!" + promote_title: "\nDengan gelar khusus {role}!" + rank_to_loong: "Teks untuk gelar khusus tidak dapat lebih dari 16 simbol." + promote_failed: "Promosi gagal! Periksa apakah saya memiliki hak yang dibutuhkan." + demote_success: "{user} telah berhasil diturunkan di {chat_name}!" + demote_not_admin: "Pengguna tersebut bukan seorang admin." + demote_failed: "Gagal menurunkan. Mungkin saya tidak dapat menurunkan atau orang tersebut dipromosikan oleh orang lain?" + cant_get_user: "Saya tidak dapat mendapatkan pengguna! Jika anda tidak keberatan, harap balas pesannya kembali dan coba lagi." + cant_promote_yourself: "Anda tidak dapat mempromosikan diri anda sendiri." + emoji_not_allowed: "Mohon maaf, tetapi gelar admin tidak dapat memiliki sebuah emoji 😞" + pm_menu: + start_hi_group: 'Hai yang disana! Nama saya Daisy' + start_hi: "Hai yang disana! Nama saya Daisy.\nSaya bisa membantu mengatur grup dengan fitur-fitur yang berguna, jangan ragu untuk menambahkan saya ke grup anda!" + btn_source: "📜 Kode sumber" + btn_help: "❔ Bantuan" + btn_lang: "Bahasa id" + btn_channel: "🙋‍♀️ Berita Daisy" + btn_group: "👥 Daisy Support" + btn_group_help: "Tekan saya untuk mendapatkan bantuan!" + back: Kembali + #/help + help_header: "Hai! Nama saya Daisy. Saya adalah seorang bot manajemen grup, disini untuk membantu anda menjaga ketertiban dalam grup anda! Saya memiliki banyak fitur berguna, seperti pengendalian pesan banjir, sistem peringatan, sistem catatan, dan bahkan balasan yang telah ditentukan sebelumnya pada kata kunci tertentu." + click_btn: Tekan disini + disable: + #/disableable + disablable: "Perintah yang dapat dinonaktifkan:\n" + #/disabled + no_disabled_cmds: Tidak ada perintah yang dinonaktifkan di {chat_name}! + disabled_list: "Perintah yang dinonaktifkan di {chat_name}:\n" + #/disable + wot_to_disable: "Apa yang ingin anda nonaktifkan?" + already_disabled: "Perintah ini sudah dinonaktifkan!" + disabled: "Perintah {cmd} telah dinonaktifkan di {chat_name}!" + #/enable + wot_to_enable: "Apa yang ingin anda aktifkan?" + already_enabled: "Perintah ini tidak dinonaktifkan!" + enabled: "Perintah {cmd} telah diaktifkan di {chat_name}!" + #/enableall + not_disabled_anything: "Tidak ada yang dinonaktifkan di {chat_title}!" + enable_all_text: "Ini akan mengaktifkan seluruh perintah di {chat_name}. Apakah anda yakin?" + enable_all_btn_yes: "Ya. Aktifkan seluruh perintah!" + enable_all_btn_no: "Tidak!" + enable_all_done: "Seluruh ({num}) perintah telah diaktifkan di {chat_name}!" + bot_rights: + change_info: "Saya tidak memiliki hak untuk mengubah info grup, harap jadikan saya admin dengan hak tersebut." + edit_messages: "Saya tidak memiliki hak untuk mengubah pesan!" + delete_messages: "Saya tidak memiliki hak untuk menghapus pesan disini." + ban_user: "Saya tidak memiliki hak untuk memblokir pengguna, harap jadikan saya admin dengan hak tersebut." + pin_messages: "Saya tidak memiliki hak untuk menyematkan pesan, harap jadikan saya admin dengan hak tersebut." + add_admins: "Saya tidak memiliki hak untuk menambahkan admin, harap jadikan saya admin dengan hak tersebut." + feds: + #decorators + need_fed_admin: "Anda bukan seorang admin di federasi {name}" + need_fed_owner: "Anda bukan seorang pemilik dari federasi {name}" + cant_get_user: "Maaf, saya tidak dapat mendapatkan pengguna tersebut, coba gunakan ID pengguna tersebut" + #/fnew + fed_name_long: "Nama federasi tidak dapat lebih dari 60 simbol!" + can_only_1_fed: "Pengguna hanya dapat membuat 1 federasi, harap hapus satu." + name_not_avaible: "Federasi dengan nama {name} sudah ada! Harap gunakan nama lain." + created_fed: | + Selamat, anda telah berhasil membuat sebuah federasi. + Nama: {name} + ID: {id} + Pencipta: {creator} + Gunakan perintah /fjoin {id} untuk menghubungkan federasi ke obrolan + disallow_anon: Sebagai seorang admin anonim anda tidak dapat membuat federasi! + #/fjoin + only_creators: "Anda harus menjadi seorang pencipta obrolan agar dapat menghubungkan obrolan ke sebuah federasi." + fed_id_invalid: "ID federasi yang diberikan tidak valid! Tolong beri saya sebuah ID yang valid." + joined_fed_already: "Obrolan ini sudah bergabung dengan sebuah federasi! Silakan gunakan /fleave untuk meninggalkan federasi tersebut" + join_fed_success: "Bagus! Obrolan {chat} sekarang adalah bagian dari federasi {fed}!" + join_chat_fed_log: | + Obrolan bergabung dengan federasi #ChatJoined + Federasi: {fed_name} ({fed_id}) + Obrolan: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "Obrolan ini tidak masuk ke dalam federasi apapun." + leave_fed_success: "Obrolan {chat} meninggalkan federasi {fed}." + leave_chat_fed_log: | + Obrolan meninggalkan federasi #ChatLeaved + Federasi: {fed_name} ({fed_id}) + Obrolan: {chat_name} ({chat_id}) + #/fsub + already_subsed: "Federasi {name} telah berlangganan ke {name2}" + subsed_success: "Federasi {name} berlangganan ke {name2}!" + #/funsub + not_subsed: "Federasi {name} tidak berlangganan ke {name2}" + unsubsed_success: "Federasi {name} membatalkan berlangganan ke {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "Pengguna {user} ditambahkan ke admin federasi {name}." + promote_user_fed_log: | + Pengguna dipromosikan ke admin federasi #UserPromoted + Federasi: {fed_name} ({fed_id}) + Pengguna: {user} ({user_id}) + "restricted_user:promote": Pengguna ini telah dibatasi dari menjadi admin federasi! + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "Pengguna {user} diturunkan dari admin federasi {name}." + demote_user_fed_log: | + Pengguna diturunkan dari admin federasi #UserDemoted + Federasi: {fed_name} ({fed_id}) + Pengguna: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "Federasi {name} telah mengaktifkan logging di obrolan/saluran yang lain!" + set_chat_log: | + Obrolan ini sekarang logging seluruh aksinya dari federasi {name} + + Logging federasi digunakan untuk penilaian internal dari federasi tersebut dan performanya. + Federasi JANGAN digunakan sebagai dinding rasa malu publik sehingga menghormati data pengguna dan pedoman privasi kami. + set_log_fed_log: | + Logging diaktifkan #LoggingEnabled + Federasi: {fed_name} ({fed_id}) + no_right_to_post: Saya tidak memiliki hak untuk mengirim pesan di saluran tersebut! + #/funsetlog + not_logging: "Federasi {name} tidak logging ke obrolan/saluran manapun!" + logging_removed: "Berhasil menghapus logging dari federasi {name}." + unset_log_fed_log: | + Logging dinonaktifkan #LoggingDisabled + Federasi: {fed_name} ({fed_id}) + #/fchatlist + no_chats: "Tidak ada obrolan apapun yang bergabung ke federasi {name} ini" + chats_in_fed: "Obrolan terhubung ke federasi {name}:\n" + too_large: "Keluaran terlalu besar, mengirim sebagai file" + #/finfo + finfo_text: | + Info federasi + Nama: {name} + ID: {fed_id} + Pencipta: {creator} + Obrolan: {chats} + Pengguna terblokir di federasi: {fbanned} + finfo_subs_title: "Federasi yang berlangganan ke federasi ini:\n" + #/fadminlist + fadmins_header: "Admin di federasi {fed_name}:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "Pengguna ini telah masuk daftar putih dari pemblokiran." + fban_self: "Itu pertunjukan yang bagus!" + fban_creator: "Bagaimana saya bisa memblokir pencipta federasi?! Saya tidak berpikir itu akan menjadi lucu." + fban_fed_admin: "Saya tidak akan memblokir seorang admin federasi dari federasi mereka sendiri!" + update_fban: "Pengguna ini telah fbanned oleh karena itu saya memperbarui alasan menjadi {reason}" + already_fbanned: "{user} telah diblokir di federasi ini." + fbanned_header: "FedBan baru\n" + fban_info: | + Federasi: {fed} + Admin federasi: {fadmin} + Pengguna: {user} ({user_id}) + fbanned_reason: "Alasan: {reason}" + fbanned_process: "\nStatus: Memblokir di {num} obrolan federasi..." + fbanned_done: "\nStatus: Selesai! Terblokir di {num} obrolan!" + fbanned_silence: "Pesan ini akan dihapus dalam 5 detik" + fbanned_subs_process: "\nStatus: Memblokir di federasi yang berlangganan ke {feds}..." + fbanned_subs_done: "\nStatus: Selesai! Terblokir di {chats} obrolan dari federasi ini dan {subs_chats} obrolan dari federasi yang berlangganan ke {feds}" + fban_usr_rmvd: | + Pengguna {user} telah terblokir di federasi {fed} dan telah disingkirkan! + Alasan: {rsn} + fban_log_fed_log: | + Blokir pengguna di federasi #FedBan + Federasi: {fed_name} ({fed_id}) + Pengguna: {user} ({user_id}) + Oleh: {by} + Obrolan terblokir: pengguna diblokir di {chat_count}/{all_chats} obrolan + fban_reason_fed_log: "Alasan: {reason}\n" + fban_subs_fed_log: "\nFederasi yang di langganan: terblokir di {subs_chats} obrolan dari federasi {feds}" + #/unfban + unfban_self: "Pikirkan hal lain untuk bersenang-senang daripada ini." + user_not_fbanned: "{user} tidak diblokir di federasi ini." + un_fbanned_header: "Pembatalan FedBan baru\n" + un_fbanned_process: "\nStatus: Membatalkan blokir di {num} obrolan..." + un_fbanned_done: "\nStatus: Selesai! Membatalkan blokir di {num} obrolan!" + un_fbanned_subs_process: "\nStatus: Membatalkan blokir di federasi yang di langganan {feds}..." + un_fbanned_subs_done: "\nStatus: Selesai! Membatalkan blokir di {chats} obrolan dari federasi ini dan {subs_chats} obrolan dari federasi yang berlangganan ke {feds}" + un_fban_log_fed_log: | + Membatalkan blokir pengguna di federasi #FedUnBan + Federasi: {fed_name} ({fed_id}) + Pengguna: {user} ({user_id}) + Oleh: {by} + Obrolan: pengguna dibatalkan blokir di {chat_count}/{all_chats} obrolan + un_fban_subs_fed_log: "\nFederasi yang di langganan: dibatalkan blokir di {subs_chats} obrolan dari federasi {feds}" + #/delfed + delfed_btn_yes: Ya. Hapus federasi + delfed_btn_no: Tidak. + delfed: | + Apakah anda yakin ingin menghapus federasi %s? + + Sangkalan: Menghapus federasi ini akan menghapus seluruh data yang terkait dengan ini dari basis data kami! + Aksi ini permanen dan tidak dapat dikembalikan! + delfed_success: Berhasil menghapus federasi! + #/frename + frename_same_name: Mohon masukkan nama yang berbeda, bukan yang sama! + frename_success: Berhasil mengubah nama federasi {old_name} menjadi {new_name}! + #/fbanlist + fbanlist_locked: Mohon tunggu hingga %s untuk menggunakan kembali fbanlist. + creating_fbanlist: Membuat daftar fban! Harap tunggu .. + fbanlist_done: Fbanlist dari federasi %s! + #/importfbans + send_import_file: | + Kirim file impor! + + Untuk membatalkan proses ini, gunakan /cancel + rpl_to_file: Harap membalas ke sebuah file! + importfbans_locked: Mengimpor fban dikunci untuk %s di federasi ini. + big_file_csv: Hanya mendukung impor file csv kurang dari {num} megabytes! + big_file_json: | + Untuk saat ini file json dibatasi ke {num} megabytes! + Gunakan format csv jika anda ingin mengimpor file berukuran besar. + invalid_file: "File tidak valid" + wrong_file_ext: "Format file salah! Saat ini hanya mendukung: json, csv" + importing_process: Mengimpor blokir federasi... + import_done: Mengimpor blokir federasi selesai! {num} blokir telah diimpor. + #def + automatic_ban: Pengguna {user} diblokir di federasi saat ini {fed_name} dan telah dikeluarkan! + automatic_ban_sfed: Pengguna {user} diblokir di sub federasi {fed_name} dan telah dikeluarkan! + automatic_ban_reason: "\nAlasan: {text}" + #/fcheck + fcheck_header: "Info blokir federasi:\n" + fbanned_count_pm: "Anda terblokir di {count} federasi!\n" + "fban_info:fcheck": "You have been fbanned in {fed} federation on {date}, No reason was given!" + "fban_info:fcheck:reason": "You have been fbanned in {fed} federation on {date} because of reason -\n {reason}\n" + fbanned_data: "{user} has been fbanned in {count} federation(s).\n" + fbanned_nowhere: "{user} isn't fbanned anywhere!\n" + fbanned_in_fed: "Including the {fed} federation!\n" + "fbanned_in_fed:reason": "Including the {fed} federation for reason of -\n {reason}" + contact_in_pm: "\n\nFor more info on your fbans, please send me a direct message for privacy!" + too_long_fbaninfo: "Well! your fbaninfo requires a file!" + didnt_fbanned: "You aren't fbanned in this fed." + not_fbanned_in_fed: They aren't fbanned in the {fed_name} fed + reports: + user_user_admin: "You're an admin here, why'd you need to report someone?.." + user_is_whitelisted: "You're a whitelisted user, why'd you need to report someone?.." + reports_disabled: "Reports are currently disabled in this group." + no_user_to_report: "Which user you want to report?" + report_admin: "You cannot report admins." + report_whitedlisted: "You cannot report a whitelisted user." + reported_user: "Reported {user} to admins." + reported_reason: "\nReason:\n{reason}" + reports_on: "Reports are turned on in this group." + reports_off: "Reports are turned off in this group." + reports_already_on: "Reports are already turned on." + reports_turned_on: "Reports turned on." + reports_already_off: "Reports are already turned off." + wrong_argument: " Wrong argument. " + language: + your_lang: "Your current language: {lang}" + chat_lang: "Current chat language: {lang}" + select_pm_lang: "\nSelect your new language:" + select_chat_lang: "\nSelect new chat language:" + crowdin_btn: "🌎 Help us with translation" + help_us_translate: "\n\n🌎 Help us improve our translations" + not_supported_lang: This language is not supported! + lang_changed: Language was changed to {lang_name}. + see_translators: '👥 See translators' + back: Back + connections: + pm_connected: "Your PM has been successfully connected to {chat_name}!" + connected_pm_to_me: "Your PM has been successfully connected to {chat_name}! Please pm me to start using connection." + chat_connection_tlt: "Chat connection\n" + connected_chat: | + Current connected chat: + {chat_name} + Write /disconnect to disconnect from chat + "connected_chat:cmds": | + Current connected chat: + {chat_name}, (bridged commands are {commands}), + ⚠ You can only access bridged commands of the connected chat! All other commands would be treated as Local chat + Write /disconect to disconnect from chat. + u_wasnt_connected: "You were not connected to any chat before!" + select_chat_to_connect: "\nSelect a chat to connect:" + disconnected: "You were disconnected from {chat_name}." + cant_find_chat: "I can't find this chat." + cant_find_chat_use_id: "I can't find this chat. Use chat ID." + bad_arg_bool: "Bad choice: supported only on/off enable/disable" + enabled: enabled + disabled: disabled + chat_users_connections_info: "Connections for normal users currently is {status} for {chat_name}" + chat_users_connections_cng: "Connections for normal users changed to {status} for {chat_name}" + not_in_group: "You're not in the group you're trying to connect to, join and send a message." + history_empty: "You're not connected to any chat for history, connect via `/connect (chat id)`" + not_in_chat: "You're not in this chat anymore, I'll disconnect you." + u_should_be_admin: "You should be an admin in {}!" + usage_only_in_groups: "Usage limited only in groups!" + anon_admin_conn: | + Please click below button to connect! + click_here: Click here! + global: + u_not_admin: You should be an admin to do this! + #Rights + "bot_no_right:not_admin": I'm not a admin! + bot_no_right: "I dont have permission to {permission} here!" + user_no_right: "You don't have permission to {permission} here!" + "user_no_right:not_admin": You should be admin to-do it! + #is chat creator + unable_identify_creator: "Unable to identify anonymous chat creator, duplicate signature (titles) found!" + owner_stuff: + father: "\nHe is my creator." + sudo_crown: "\nHey! Look, that user has a crown, let me see it... Wow, it has an engraving - 'sudo user'!" + stickers: + rpl_to_sticker: "Reply to a sticker!" + ur_sticker: | + Emoji: {emoji} + Sticker ID: {id} + pins: + no_reply_msg: "Reply to a message to pin!" + chat_not_modified_pin: "That message is already pinned!" + chat_not_modified_unpin: "There isn't any pinned message to unpin!" + pinned_success: "Successfully pinned!" + unpin_success: "Successfully unpinned!" + users: + user_info: "User info:\n" + info_id: "ID: {id}" + info_first: "\nFirst name: {first_name}" + info_last: "\nLast name: {last_name}" + info_username: "\nUsername: {username}" + info_link: "\nUser link: {user_link}" + info_saw: "\nI saw them in {num} groups" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_fbanned: "\nBanned in current Federation: " + info_admeme: "\nUser has admin rights in this chat." + upd_cache: "Updating cache now..." + upd_cache_done: "Admins cache was updated." + admins: "Admin in this group:\n" + imports_exports: + #Exporting + started_exporting: Exporting started! Please wait... + export_done: Export from {chat_name} done! + exports_locked: Please wait %s before using exports again! + #Importing + invalid_file: "The file is invalid" + send_import_file: Please send a file for importing! + rpl_to_file: Please reply to a file! + big_file: Only files under 50 MB are supported! + started_importing: Importing started! Please wait... + bad_file: Bad file! Expected 'general' column. + file_version_so_new: Exported file version is newer than what Daisy currently supports! + imports_locked: Please wait %s before using imports again! + import_done: Import done! + restrictions: + user_not_in_chat: User is not in this chat! + #Kicks + kick_DaisyX: Instead of trying kick me you could spend your time better. Thats just boring. + kick_admin: Kicking admin is not best idea thought. + kick_self: If you want to kick yourself - just leave this chat. I would rather not see you than look at these pathetic attempts at self-punishment. + user_kicked: "{user} was kicked by {admin} in {chat_name}." + #Mutes + mute_DaisyX: Actually, I'll be happy to shut up you instead. + mute_admin: If you think you can shut up an admin, you are wrong! + mute_self: If you want to mute yourself - just stop talking! Better to thought a fool than open your mouth and remove all doubt. + user_muted: "{user} was muted by {admin} in {chat_name}." + unmute_DaisyX: Actually, I'm not muted. + unmute_admin: Haha no, try muting him first! + unmute_self: Should I mute you first? + user_unmuted: "{user} was unmuted by {admin} in {chat_name}." + #Bans + ban_DaisyX: No, I won't do it! Ask the chat creator to do it! + ban_admin: Haha, let's /demote him first. + ban_self: Why are you trying to ban yourself? + user_banned: "{user} was banned by {admin} in {chat_name}." + unban_DaisyX: Actually, I'm not banned. + unban_admin: Haha no, try banning him first! + unban_self: Should I ban you first? + user_unband: "{user} was unbanned by {admin} in {chat_name}." + invalid_time: Invalid time! + enter_time: Please specify a time! + on_time: "\nFor %s" + reason: "\nReason: %s" + purge: "\nMessages will be purged in 5 seconds!" + filter_title_ban: Ban the user + filter_action_rsn: Automated filter action! + filtr_ban_success: '%s banned %s for %s' + filtr_mute_success: '%s muted %s for %s' + filtr_tmute_success: | + %s muted %s for %s + reason is %s + time_setup_start: Please specify a time! + filtr_tban_success: | + %s banned %s for %s + reason is %s + filter_title_tmute: tMute the user + filter_title_mute: Mute the user + filter_title_tban: tBan the user + filter_title_kick: Kick user + rules: + #Set + saved: Rules were saved in %s + updated: Rules were updated in %s + #Reset + deleted: Rules were deleted! + not_found: Rules were not found! + #Callback + user_blocked: "Write /start in my PM!" + rules_was_pmed: Rules were sent to your PM! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "Welcome {mention}! Please press button below to verify yourself as human!" + bool_invalid_arg: Invalid argument, expected on/off. + #/setwelcome + saved: Welcome message was saved in %s + updated: Welcome message was updated in %s + #/turnwelcome + turnwelcome_status: Welcomes are {status} in {chat_name} + turnwelcome_enabled: Welcomes enabled in %s + turnwelcome_disabled: Welcomes disabled in %s + #/welcome + enabled: enabled + disabled: disabled + wlcm_security_enabled: "enabled, set to {level}" + wlcm_mutes_enabled: "enabled, set to {time}" + welcome_info: | + Welcome settings in {chat_name}: + Welcomes are {welcomes_status} + Welcome security is {wlcm_security} + Welcome mutes is {wlcm_mutes} + Deleting old welcomes is {clean_welcomes} + Deleting service messages is {clean_service} + wlcm_note: "\n\nWelcome note is:" + raw_wlcm_note: "Raw welcome note is:" + security_note: "Welcome security text is:" + #/setsecuritynote + security_note_saved: "Custom security text was saved %s" + security_note_updated: "Custom security text was updated %s" + #/delsecuritynote + security_note_not_found: Security text in %s has not been set before + del_security_note_ok: Security text was reset to default in %s + #/cleanwelcome + cleanwelcome_enabled: Cleaning welcomes enabled in %s + cleanwelcome_disabled: Cleaning welcomes disabled in %s + cleanwelcome_status: Cleaning welcomes is {status} in {chat_name} + #/cleanservice + cleanservice_enabled: Cleaning service messages enabled in %s + cleanservice_disabled: Cleaning service messages disabled in %s + cleanservice_status: Cleaning service messages is {status} in {chat_name} + #/welcomemute + welcomemute_invalid_arg: Invalid argument, expected a time. + welcomemute_enabled: Welcome mute enabled in %s + welcomemute_disabled: Welcome mute disabled in %s + welcomemute_status: Welcome mute is {status} in {chat_name} + #/welcomesecurity + welcomesecurity_enabled: Welcome security enabled in {chat_name} to {level}. + "welcomesecurity_enabled:customized_time": | + Welcome security enabled in {chat_name} to {level}, and kick users which aren't verified within {time}. + ask_for_time_customization: | + Do you want to customize the time period to kick unverified user? (by default it's {time}) + send_time: Please specify a time. + invalid_time: "Invalid time!" + welcomesecurity_enabled_word: enabled, set to level {level} + welcomesecurity_disabled: Welcome security disabled in %s. + welcomesecurity_invalid_arg: Invalid argument, expected button/captcha. + welcomesecurity_status: Welcome security is {status} in {chat_name} + yes_btn: "Yes" + no_btn: "No" + #Welcomesecurity handler + click_here: Click here! + not_allowed: You are not allowed to use this! + ws_captcha_text: Hi {user}, please enter the captcha's number to be unmuted in the chat. + regen_captcha_btn: '🔄 Change captcha' + num_is_not_digit: Please enter only numbers! + bad_num: Wrong number! Please try again. + passed: Verification passed, you were unmuted in %s! + passed_no_frm: Verification passed, you was unmuted in %s! + last_chance: You have a last chance to enter captcha! + btn_button_text: "Please click button below to prove yourself." + btn_wc_text: | + Please press right button matched on this expression: + %s + math_wc_rtr_text: "You have last chance! Be careful.\n" + math_wc_wrong: Wrong, please try again! + math_wc_sry: Sorry you used the last attempt. Ask chat administrators. + not_admin_wm: I can't restrict people here, so I won't use welcome mute! + not_admin_ws: I can't restrict people here, so I won't use welcome security! + not_admin_wsr: I can't delete messages here, so I won't delete service messages! + #/resetwelcome + deleted: Successfully reset greetings in {chat}! + not_found: There's nothing to reset! + #... + verification_done: Verification is done! You have been unmuted. + locks: + locks_header: "Here is the current lock settings in {chat_title} \n\n" + no_lock_args: Give me some args to lock! + already_locked: This is already locked bruh + no_such_lock: See /locks to find out what can be locked and unlocked! + locked_successfully: Locked {lock} in {chat} sucessfully! + no_unlock_args: Give me some args to unlock! + not_locked: Bruh, its not locked to unlock. + unlocked_successfully: Unlocked {lock} in {chat} successfully! + antiflood: + #enforcer + flood_exceeded: "{action} {user} for flooding!" + #/setflood + "invalid_args:setflood": "Expected a number!" + overflowed_count: Maximum count should be less than 200! + config_proc_1: Please send expiration time, '0' (zero) for not being expired. + cancel: '❌ Cancel' + setup_success: Successfully configured antiflood, now enforces against those who send {count} messages within {time}! + "setup_success:no_exp": Successfully configured antiflood, allows {count} messages consecutively! + invalid_time: Invalid time! + #/antiflood | /flood + not_configured: Antiflood isn't configured in this chat yet! + turned_off: Disabled antiflood in {chat_title}! + "configuration_info:with_time": Antiflood is configured in this chat, those who send {count} message within {time} will be {action}! + configuration_info: Antiflood is configured in this chat, those who send {count} consecutively will be {action}! + ban: banned + mute: muted + kick: kicked + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "Unsupported action, expected {supported_actions}!" + setfloodaction_success: "Successfully updated flood action to {action}!" + + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/ja_JP.yaml b/DaisyX/localization/ja_JP.yaml new file mode 100644 index 00000000..bb41e1fb --- /dev/null +++ b/DaisyX/localization/ja_JP.yaml @@ -0,0 +1,677 @@ +--- +language_info: + flag: "🇯🇵" + code: ja +STRINGS: + notes: + #/save + note_saved: "🗒 メモ {note_name}{chat_title}に保存されました!" + note_updated: "🗒 メモ {note_name}{chat_title}で更新されました!" + you_can_get_note: "\n /get {note_name}または #{note_name} を使用して、このメモを取得できます。" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nメモのエイリアス:" + blank_note: "空白のメモを保存することはできません!" + notename_cant_contain: "メモ名には \"{symbol}\" を含めることはできません!" + #/get + cant_find_note: {chat_name} にこのメモが見つかりません! + no_note: "そのメモが見つかりません。" + no_notes_pattern: "パターン %s でメモが見つかりません" + u_mean: "\n#{note_name} のことですか?" + #/notes + notelist_no_notes: "{chat_title} にはノートがありません!" + notelist_header: " {chat_name}のノート:" + notes_in_private: "ノート一覧を取得するには、下のボタンをクリックします。" + notelist_search: | + パターン: {request} + 一致するノート: + #/clear + note_removed: "メモ #{note_name}{chat_name} が削除されました!" + note_removed_multiple: | + {chat_name} + 削除済み:{removed} + not_removed_multiple: "削除されていません:{not_removed}" + #/clearall + clear_all_text: "{chat_name}からすべてのメモが削除されます。よろしいですか?" + clearall_btn_yes: "はい、私のノートをすべて削除してください!" + clearall_btn_no: "いいえ!" + clearall_done: "{chat_name} のすべての({num})メモが削除されました!" + #/search + search_header: | + {chat_name} での検索リクエスト: + パターン: {request} + 一致するメモ: + #/noteinfo + note_info_title: "メモの情報\n" + note_info_note: "メモ名: %s\n" + note_info_content: "ノートの内容: %s\n" + note_info_parsing: "解析モード: %s\n" + note_info_created: "作成されました: {date} によって {user}\n" + note_info_updated: "最終更新日: {date} by {user}\n" + user_blocked: "自分のPMに/startを書きましょう!" + #/privatenotes + private_notes_false_args: " を無効にして を有効にする 2 つのオプションがあります。" + already_enabled: "%sでプライベートノートは既に有効になっています" + current_state_info: "現在 {state} のプライベートノートは {chat} です! " + enabled_successfully: "プライベートノートは%sで有効になりました。" + disabled_successfully: "プライベートノートは 無効 の %s です!" + not_enabled: "プライベートノートは無効にすることはできません。" + privatenotes_notif: "{chat} のノートに正常に接続されました! 切断するには、コマンド /切断 を使用してください!" + enabled: '有効' + disabled: '無効' + #/cleannotes + clean_notes_enable: "{chat_name} でノートをクリーンアップしました" + clean_notes_disable: "{chat_name} でメモを消去しました" + clean_notes_enabled: "クリーニングノートは現在 {chat_name} 有効です" + clean_notes_disabled: "クリーニングノートは 無効化されています {chat_name}" + #Filter + filters_title: 'メモを送信' + filters_setup_start: 'ノート名を送信してください。' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "{chat_name} にフィルターが見つかりませんでした!" + #/addfilter + anon_detected: 匿名管理者であるため、新しいフィルターを追加することはできません。代わりにコネクションを使用してください。 + regex_too_slow: "Your regex pattern is matching too slow (more than the half of second), it can't be added!" + cancel_btn: "🛑 キャンセル" + adding_filter: | + フィルター {handler}{chat_name} + 以下のアクションを選択します: + saved: "新しいフィルターが正常に保存されました!" + #/filters + list_filters: "{chat_name}:\n のフィルター" + #/delfilter + no_such_filter: {chat_name}でそのフィルターが見つかりません。有効になっているフィルターを /filters コマンドでチェックしてください。 + del_filter: "ハンドラでフィルター '{handler}' が削除されました!" + select_filter_to_remove: | + 私は多くのフィルタがあります。 ハンドラ'{handler}' + 削除するフィルタを選択してください: + #/delfilters or /delallfilters + not_chat_creator: このコマンドを使用できるのはチャット作成者だけです。 + delall_header: このチャット内のすべてのフィルターを削除します。元に戻すことはできません。 + confirm_yes: '⚠ すべて削除' + confirm_no: '❌ キャンセル' + delall_success: チャット内のすべてのフィルター({count})を削除しました! + warns: + #/warn + warn_sofi: "自分自身を警告する方法はありません!" + warn_self: "自分自身に警告したいですか?チャットはそのままにしておきましょう。" + warn_admin: "あなたが間違っています。管理者に警告することはできません。" + warn: "{admin} has warned {user} in {chat_name}\n" + warn_bun: "警告を超えました! {user} は禁止されています!" + warn_num: "警告: {curr_warns}/{max_warns}\n" + warn_rsn: "理由: {reason}\n" + max_warn_exceeded: 警告を超えました! %s は %s です! + "max_warn_exceeded:tmute": 警告を超えました! %s は %s の間タミュートされました! + #warn rmv callback + warn_no_admin_alert: "⚠ 警告を削除する管理者ではありません!" + warn_btn_rmvl_success: "✅ {admin} によって警告が削除されました!" + #/warns + warns_header: "ここでは、このチャットで警告があります \n\n" + warns: "{count} : {reason} by {admin}\n" + no_warns: "{user} には警告はありません。" + #/warnlimit + warn_limit: "{chat_name} の警告の上限は現在: {num}" + warnlimit_short: '警告の上限は少なくとも2でなければなりません!' + warnlimit_long: '警告の上限は1,000より短くする必要があります!' + warnlimit_updated: '✅警告制限を {num}に更新しました' + #/delwarns + purged_warns: "{admin} reset {num} warns of {user} in {chat_title}!" + usr_no_wrn: "{user} はリセットする警告を持っていません。" + rst_wrn_sofi: 'Daisyは、リセットするように警告することがありませんでした。' + not_digit: 警告制限は数字でなければなりません! + #/warnmode + same_mode: これは現在のモードです!どうやって変更できますか? + no_time: モード「tmute」を選択するには、時間を言及する必要があります! + invalid_time: 無効な時間! + warnmode_success: %s の警告モードが正常に %s に変更されました! + wrng_args: | + 以下は利用可能なオプションです: + %s + mode_info: | + このチャットのモードは %s です。 + banned: アクセス禁止 + muted: ミュート + filters_title: ユーザーに警告する + filter_handle_rsn: 自動フィルターアクション! + msg_deleting: + no_rights_purge: "このグループを削除する権限がありません!" + reply_to_msg: "削除するメッセージに返信します!" + purge_error: "パージを続けることはできません。主に2日以上前に送信されたメッセージからパージを開始したからです。" + fast_purge_done: "高速パージ完了!\nこのメッセージは5秒後に削除されます。" + purge_user_done: "{user} からのすべてのメッセージは正常に削除されました!" + misc: + your_id: "あなたのID: {id}\n" + chat_id: "Group ID: {id}\n" + user_id: "{user}'s ID: {id}\n" + conn_chat_id: "現在接続されているチャット ID: {id}" + help_btn: "ヘルプのためにクリックしてください!" + help_txt: "ヘルプのために下のボタンをクリックしてください!" + delmsg_filter_title: 'メッセージを削除する' + send_text: 返信メッセージを送信してください! + replymsg_filter_title: 'メッセージに返信' + send_customised_reason: アクションメッセージに含めたい 理由 を送信してください。"理由 なし が与えられません! + expected_text: テキストメッセージを期待しています! + promotes: + promote_success: "{user} は {chat_name} で正常に宣伝されました!" + promote_title: "\nカスタムロール {role}!" + rank_to_loong: "カスタムロールテキストは16文字以上にすることはできません。" + promote_failed: "プロモーションに失敗しました!権限があるか確認してください。" + demote_success: "{user} は {chat_name} で降格しました!" + demote_not_admin: "そのユーザーは管理者ではありません。" + demote_failed: "降格に失敗しました。降格できないか、誰か他の人に昇格させられました。" + cant_get_user: "ユーザーを取得できませんでした!もしよろしければ、彼のメッセージに返信して、もう一度お試しください。" + cant_promote_yourself: "自分自身を昇進させることはできません。" + emoji_not_allowed: "申し訳ありませんが、管理者の役割は絵文字を持つことができません。 :explosed_face:" + pm_menu: + start_hi_group: 'こんにちは!私の名前はDaisy' + start_hi: "こんにちは!私の名前はDaisyです。効率的な方法であなたのグループを管理するのを助けます!" + btn_source: "📦 ソース コード" + btn_help: "❔ ヘルプ" + btn_lang: "🇷🇺 Language" + btn_channel: "Daisy News" + btn_group: "👥 Daisy Support" + btn_group_help: "ヘルプのためにクリックしてください!" + back: 戻る + #/help + help_header: "ねえ! 私の名前はDaisyです。 私はグループ管理ボットです。ここでは、グループ内を移動して順序を維持するのに役立ちます。\n洪水調節、警告システム、メモ管理システム、さらには特定のキーワードに対する事前に決められた返信など、便利な機能がたくさんあります。" + click_btn: ここをクリック + disable: + #/disableable + disablable: "無効なコマンド:\n" + #/disabled + no_disabled_cmds: {chat_name} に無効化されたコマンドはありません! + disabled_list: " {chat_name}:\n で無効化されたコマンド" + #/disable + wot_to_disable: "何を無効にしますか?" + already_disabled: "このコマンドは既に無効化されています!" + disabled: "コマンド {cmd}{chat_name}で無効化されました!" + #/enable + wot_to_enable: "何を有効にしますか?" + already_enabled: "このコマンドは無効になっていません!" + enabled: "コマンド {cmd}{chat_name}で有効になりました!" + #/enableall + not_disabled_anything: "{chat_title}で無効になっていない!" + enable_all_text: "{chat_name}内のすべてのコマンドが有効になります。よろしいですか?" + enable_all_btn_yes: "はい。すべてのコマンドを有効にします。" + enable_all_btn_no: "いいえ!" + enable_all_done: "すべてのコマンド ({num}) は {chat_name} で有効になりました。" + bot_rights: + change_info: "グループ情報を変更する権限がありません。その権限で管理者にさせてください。" + edit_messages: "メッセージを編集する権限がありません!" + delete_messages: "ここでメッセージを削除する権限はありません。" + ban_user: "ユーザーを禁止する権限がありません。その権限で管理者にしてください。" + pin_messages: "メッセージをピン留めする権限がありません。その権限で管理者にさせてください。" + add_admins: "管理者を追加する権限がありません。その権限で管理者にさせてください。" + feds: + #decorators + need_fed_admin: "あなたは {name} 連盟の管理者ではありません" + need_fed_owner: "あなたは {name} 連盟の所有者ではありません" + cant_get_user: "申し訳ありませんが、そのユーザーを取得できません。ユーザーIDを使用してみてください" + #/fnew + fed_name_long: "Federation名は60シンボルより長くすることはできません!" + can_only_1_fed: "1 人のユーザーは、1 つの連合のみを作成できます。1 つを削除してください。" + name_not_avaible: "名前 {name} のフェデレーションは既に終了しています!別の名前を使用してください。" + created_fed: | + Congrats, you have successfully created Federation. + Name: {name} + ID: {id} + Creator: {creator} + Use /fjoin {id} to connect federation to chat\ + disallow_anon: Being anonymous, you cannot create new federation! + #/fjoin + only_creators: "チャットを連盟に接続できるようにするには、チャット作成者である必要があります。" + fed_id_invalid: "与えられたフェデレーションIDが無効です!有効なIDを教えてください。" + joined_fed_already: "このチャットはすでに連盟に加入しています。/fleave を使って連盟から脱退してください。" + join_fed_success: "チャット {chat}{fed} 連盟の一員になりました!" + join_chat_fed_log: | + Chat joined Federation #ChatJoined + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "このチャットはまだ連邦にありません。" + leave_fed_success: "チャット {chat}{fed} 連盟を脱退しました。" + leave_chat_fed_log: | + Chat left Federation #ChatLeaved + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/fsub + already_subsed: "Federation {name} は既に {name2} を購読しています" + subsed_success: "フェデレーション {name}{name2} を購読しました!" + #/funsub + not_subsed: "Federation {name}{name2} の購読を解除していません。" + unsubsed_success: "フェデレーション {name} の登録解除 {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "ユーザー {user} が {name} フェデレーション管理者に追加されました。" + promote_user_fed_log: | + User promoted to the fed admins #UserPromoted + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + "restricted_user:promote": このユーザーはフェデレーション管理者に制限されています! + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "ユーザー {user} が {name} フェデレーション管理者から降格しました。" + demote_user_fed_log: | + User demoted from the fed admins #UserDemoted + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "{name} 他のチャット/チャンネルでのログインが既に有効になっています!" + set_chat_log: | + This chat now logging all actions in {name} Federation + + Federation logging is used for internal assessment of federation and its performance. + it should NOT be used as a show wall to public thereby respecting user data and our privacy guidelines. + set_log_fed_log: | + ログの有効化 #LoggingEnabled + Fed: {fed_name} ({fed_id}) + no_right_to_post: そのチャンネルにメッセージを投稿する権限がありません! + #/funsetlog + not_logging: "{name} 連盟はチャット/チャンネルにログインしていません!" + logging_removed: "ログ記録を {name} 連盟から削除しました。" + unset_log_fed_log: | + ログを無効にする #LoggingDisabled + Fed: {fed_name} ({fed_id}) + #/fchatlist + no_chats: "この {name} 連盟に参加したチャットはありません" + chats_in_fed: " {name} 連盟に接続されたチャット:\n" + too_large: "出力が大きすぎます。ファイルとして送信しています" + #/finfo + finfo_text: | + Federation info + Name: {name} + ID: {fed_id} + Creator: {creator} + Chats in the fed: {chats} + Banned users in the fed: {fbanned} + finfo_subs_title: "Federation is subscribed to this feds:\n" + #/fadminlist + fadmins_header: "{fed_name} Fed:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "このユーザーは禁止からホワイトリストに登録されています。" + fban_self: "素敵なショーだね!" + fban_creator: "どうやって連邦制作者を禁止することができますか?!面白くないと思います。" + fban_fed_admin: "私は彼ら自身のfedからフィード管理者を禁止するつもりはありません!" + update_fban: "このユーザーは以前にfbanされたため、理由を {reason}に更新しています" + already_fbanned: "{user} はすでにこの連合でBANされています。" + fbanned_header: "新しいFedBan\n" + fban_info: | + Federation: {fed} + Fed admin: {fadmin} + User: {user} ({user_id}) + fbanned_reason: "理由: {reason}" + fbanned_process: "\n状態: チャットの禁止 {num}" + fbanned_done: "\n状態: 完了! {num} チャットでBAN!" + fbanned_silence: "このメッセージは5秒後に消去されます" + fbanned_subs_process: "\nステータス: {feds} 登録された連邦での禁止..." + fbanned_subs_done: "\nStatus: Done! banned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + fban_usr_rmvd: | + ユーザー {user} は現在のフェデレーションでBANされています {fed}.Soが削除されました! + 理由: {rsn} + fban_log_fed_log: | + Ban user in the fed #FedBan + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + By: {by} + Chats banned: user banned in {chat_count}/{all_chats} chats + fban_reason_fed_log: "理由: {reason}\n" + fban_subs_fed_log: "\n加入したFBI: {subs_chats} チャット {feds} フェデレーションで禁止" + #/unfban + unfban_self: "これの代わりに楽しみを持っている他の何かについて考えなさい。" + user_not_fbanned: "{user} はこの連合でBANされていませんでした。" + un_fbanned_header: "新しい非FedBan\n" + un_fbanned_process: "\n状態: {num} のチャットで禁止を解除..." + un_fbanned_done: "\nステータス: 完了! {num} チャットでBANを解除!" + un_fbanned_subs_process: "\nステータス: {feds} 登録された連邦での禁止を解除..." + un_fbanned_subs_done: "\nStatus: Done! Unbanned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + un_fban_log_fed_log: | + Unban user in the fed #FedUnBan + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + By: {by} + Chats: user unbanned in {chat_count}/{all_chats} chats + un_fban_subs_fed_log: "\n加入したFBI: {subs_chats} チャット {feds} フェデレーション" + #/delfed + delfed_btn_yes: はい。フェデレーションを削除します + delfed_btn_no: いいえ。 + delfed: | + Are you sure to delete this federation %s? + + Disclaimer: Deleting federation will remove all datas associated with it from our Database! + This action cannot be reversed at any cost! + delfed_success: 連合を削除しました! + #/frename + frename_same_name: 既存の名前とは異なる名前を入力してください! + frename_success: 連盟 {old_name} の名前を {new_name} に変更しました。 + #/fbanlist + fbanlist_locked: fbanlistを再度使用するには %s までお待ちください。 + creating_fbanlist: fban リストを作成しています!しばらくお待ちください.. + fbanlist_done: Fbanlist of Federation %s! + #/importfbans + send_import_file: | + インポート ファイルを送信します! + + このプロセスをキャンセルするには、/cancel を使用します + rpl_to_file: ファイルに返信してください! + importfbans_locked: この連合で %s のファンのインポートがロックされています。 + big_file_csv: '{num} メガバイト以下のインポートcsvファイルのみをサポートしています!' + big_file_json: | + 現在jsonファイルは {num} メガバイトに制限されています! + 大きいファイルをインポートする場合はcsv形式を使用してください。 + invalid_file: "ファイルが無効です" + wrong_file_ext: "ファイル形式が間違っています!現在のサポートは次のとおりです: json, csv" + importing_process: 連盟をインポート中... + import_done: 供給された禁止令のインポートが完了しました! {num} 禁止令がインポートされました。 + #def + automatic_ban: ユーザー {user} は現在のフェデレーション {fed_name}でBANされています。削除されました! + automatic_ban_sfed: ユーザー {user} はサブフェデレーション {fed_name}でBANされています。削除されました! + automatic_ban_reason: "\n理由: {text}" + #/fcheck + fcheck_header: "連盟禁止情報:\n" + fbanned_count_pm: "あなたは {count} つのフェデレーションでfbanされています!\n" + "fban_info:fcheck": "あなたは {fed} の {date}フェデレーションでfbanされました。与えられた理由はありません!" + "fban_info:fcheck:reason": "あなたは理由により {fed} の {date} フェデレーションでfbanされました。\n {reason}\n" + fbanned_data: "{user} は {count} のフェデレーションでfbanされました\n" + fbanned_nowhere: "{user} はどこでもfbanされていません!" + fbanned_in_fed: "この {fed} 連合を含む!\n" + "fbanned_in_fed:reason": "理由として、この {fed} フェデレーションを含む -\n {reason}" + contact_in_pm: "\n\nあなたのfbansの詳細については、私に直接メッセージを送ってください。このようにあなたのプライバシーは保護されています!" + too_long_fbaninfo: "まあ!あなたのfbaninfoはファイルが必要です!" + didnt_fbanned: "このfedでfbanされていません。" + not_fbanned_in_fed: Anyway, they aren't fbanned in fed {fed_name} + reports: + user_user_admin: "あなたは管理者です。なぜ誰かに報告する必要がありましたか?.." + user_is_whitelisted: "あなたはホワイトリストに登録されたユーザーですが、なぜ誰かに報告する必要がありますか?" + reports_disabled: "レポートは現在 このグループで 無効化されています。" + no_user_to_report: "どのユーザーを報告しますか?" + report_admin: "You cannot report admin." + report_whitedlisted: "ホワイトリストに登録されたユーザーを 報告することはできません。" + reported_user: " {user} を管理者に報告しました。" + reported_reason: "\n理由:\n{reason}" + reports_on: "このグループのレポートは をオンにします。" + reports_off: "レポートはこのグループで をオフにします。" + reports_already_on: "レポートはすでにオンになっています。" + reports_turned_on: "レポートが をオンにしました。" + reports_already_off: "レポートは既にオフになっています。" + wrong_argument: " 引数が間違っています。 " + language: + your_lang: "現在の言語: {lang}" + chat_lang: "現在のチャット言語: {lang}" + select_pm_lang: "\n新しい言語を選択:" + select_chat_lang: "\n新しいチャット言語を選択:" + crowdin_btn: "🌎 翻訳を手伝ってください" + help_us_translate: "\n\n🌎 翻訳の改善にご協力ください" + not_supported_lang: この言語はサポートされていません! + lang_changed: 言語が {lang_name} に変更されました。 + see_translators: ':busts_in_sシルエット: 翻訳者を参照' + back: 戻る + connections: + pm_connected: "あなたのPMは {chat_name}に正常に接続されました!" + connected_pm_to_me: "あなたのPMは {chat_name}に正常に接続されました!接続を開始するにはpmを私にしてください。" + chat_connection_tlt: "チャット接続\n" + connected_chat: | + 現在接続中のチャット: + {chat_name} + チャットから切断するには/disconectを書く + "connected_chat:cmds": | + Current connected chat: + {chat_name}, (bridged commands are {commands}), + ⚠ You can only access bridged commands of the connected chat! all other commands would be treated as Local chat + Write /disconect to disconnect from chat. + u_wasnt_connected: "以前はチャットに接続されていません!" + select_chat_to_connect: "\n接続するチャットを選択:" + disconnected: "{chat_name} から切断されました。" + cant_find_chat: "このチャットが見つかりません。" + cant_find_chat_use_id: "このチャットが見つかりません。チャット ID を使用してください。" + bad_arg_bool: "不正な選択肢: /off enable/disable" + enabled: 有効 + disabled: 無効 + chat_users_connections_info: "通常のユーザーの接続は現在 {status} {chat_name} です" + chat_users_connections_cng: "通常のユーザーへの接続が {status} {chat_name} に変更されました" + not_in_group: "あなたは接続しようとしているグループにいない、参加してメッセージを送信します。" + history_empty: "履歴のチャットに接続されていません。`/connect (chat id)`で接続してください" + not_in_chat: "あなたはもうこのチャットに参加していない、私はあなたを切断します。" + u_should_be_admin: "あなたは{}の管理者である必要があります!" + usage_only_in_groups: "グループ内でのみ使用可能です!" + anon_admin_conn: | + 接続するには下のボタンをクリックしてください! + click_here: ここをクリック! + global: + u_not_admin: 管理者である必要があります! + #Rights + "bot_no_right:not_admin": 管理者ではありません! + bot_no_right: "権限がありません! \n権限がありません: %s" + user_no_right: "You should have required permission to do it! \nMissing permission: %s" + "user_no_right:not_admin": 管理者である必要があります! + #is chat creator + unable_identify_creator: "匿名のチャット作成者を識別できません。重複した署名(タイトル)が見つかりました!" + owner_stuff: + father: "\n彼は私の創造者です." + sudo_crown: "\nHey! ほら、そのユーザーが王冠を持っているのを見て、それを見ましょう... うわー、それは彫刻を持っています - 'sudo user'!" + stickers: + rpl_to_sticker: "ステッカーに返信!" + ur_sticker: | + 絵文字: {emoji} + ステッカーID: {id} + pins: + no_reply_msg: "ピン留めするにはメッセージに返信してください!" + chat_not_modified_pin: "そのメッセージはすでにピン留めされています!" + chat_not_modified_unpin: "ピン留めを解除するメッセージはありません!" + pinned_success: "正常にピン留めされました!" + unpin_success: "正常に固定解除されました!" + users: + user_info: "ユーザー情報:\n" + info_id: "ID: {id}" + info_first: "\n名: {first_name}" + info_last: "\n姓: {last_name}" + info_username: "\nユーザー名: {username}" + info_link: "\nユーザーリンク: {user_link}" + info_saw: "\n {num}グループ で彼らを見ました" + info_sw_ban: "\nこのユーザーは @SpamWatchで禁止されています!" + info_sw_ban_reason: "\n理由: {sw_reason}" + info_fbanned: "\n現在の連邦で禁止: " + info_admeme: "\nユーザーはこのチャットで管理者権限を持っています。" + upd_cache: "キャッシュを更新中..." + upd_cache_done: "管理者キャッシュを更新しました。" + admins: "管理者:\n" + imports_exports: + #Exporting + started_exporting: エクスポートを開始しました。お待ちください... + export_done: {chat_name} からエクスポートが完了しました! + exports_locked: エクスポートを再度使用するまで %s をお待ちください! + #Importing + invalid_file: "ファイルが無効です" + send_import_file: インポートファイルを送信してください! + rpl_to_file: ファイルに返信してください! + big_file: サポートされているインポートファイルは50 MB未満です! + started_importing: インポートを開始しました。お待ちください... + bad_file: ファイルが正しくありません! 'general' 列が期待されています。 + file_version_so_new: エクスポートされたファイルのバージョンは現在サポートされているDaisyより新しいです! + imports_locked: インポートが再び使用されるまで、 %s をお待ちください! + import_done: インポート完了! + restrictions: + user_not_in_chat: 僅用於中文 + #Kicks + kick_DaisyX: 私をキックしようとする代わりに、あなたはより良いあなたの時間を過ごすことができます. + kick_admin: 管理者をキックすることは最良のアイデアではありません。 + kick_self: あなたが自己をキックしたいなら、このチャットから去ってください。 私は、自罰でこれらの哀れな試みを見るより、むしろあなたに会わないでしょう。 + user_kicked: "{user} は {admin} で {chat_name} にキックされました。" + #Mutes + mute_DaisyX: 実際には、私は代わりにあなたを黙らせて喜んでいます。 + mute_admin: 管理者を黙らせられると思ったら間違っています! + mute_self: あなたが自己をキックしたいなら、このチャットから去ってください。 私は、自罰でこれらの哀れな試みを見るより、むしろあなたに会わないでしょう。 + user_muted: "{user} は {admin} で {chat_name} によってミュートされました。" + unmute_DaisyX: 実際は、私はミュートされていません。 + unmute_admin: いや、最初に彼をミュートしてみてください! + unmute_self: 最初にミュートしましょうか。 + user_unmuted: "{user} は {admin} で {chat_name} によってミュート解除されました。" + #Bans + ban_DaisyX: いいえ、私はそれをしません! チャットの作成者にそれを行うように頼みます! + ban_admin: ハハハ、先に /degrete をやろう。 + ban_self: なぜ自分を禁止しようとしているのですか。 + user_banned: "{user} は {admin} で {chat_name} によってBANされました。" + unban_DaisyX: 実際、私は禁止されていません。 + unban_admin: いや、最初に彼を禁止してみてください! + unban_self: 私はあなたを最初に禁止する必要がありますか? + user_unband: "{user} は {admin} で {chat_name} によってBANを解除されました。" + invalid_time: 無効な時間です! + enter_time: 時間を追加してください! + on_time: "\n %s 用" + reason: "\n理由: %s" + purge: "\n5秒以内にメッセージがパージされます!" + filter_title_ban: ユーザーをBANする + filter_action_rsn: 自動フィルターアクション! + filtr_ban_success: '%s は %s に対して %s を禁止しました' + filtr_mute_success: '%s が %s の %s をミュートしました' + filtr_tmute_success: | + %s が %s をミュートしました %s + 理由は %s です + time_setup_start: 時間を送信してください! + filtr_tban_success: | + %s は %s を %s禁止しました + 理由は %s です + filter_title_tmute: tMute the user + filter_title_mute: ユーザーをミュート + filter_title_tban: 利用者を禁止する + filter_title_kick: ユーザーをキックする + rules: + #Set + saved: ルールは %sに保存されました + updated: ルールは %s で更新されました + #Reset + deleted: ルールが削除されました! + not_found: ルールが見つかりませんでした! + #Callback + user_blocked: "自分のPMに/startを書きましょう!" + rules_was_pmed: ルールがPMに送信されました! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "ようこそ {mention}! ボタンを押して、人間としての認証を行ってください!" + bool_invalid_arg: 引数が無効です。 / に期待されています。 + #/setwelcome + saved: ウェルカムメッセージは %sに保存されました + updated: ウェルカムメッセージは %sで更新されました + #/turnwelcome + turnwelcome_status: Welcomes are {status} in {chat_name} + turnwelcome_enabled: %s でWelcomes enabled + turnwelcome_disabled: %s でWelcomes disabled + #/welcome + enabled: 有効 + disabled: 無効 + wlcm_security_enabled: "有効にした場合、 {level} に設定します" + wlcm_mutes_enabled: "有効にした場合、 {time} に設定します" + welcome_info: | + Welcome settings in {chat_name}: + Welcomes are {welcomes_status} + Welcome security is {wlcm_security} + Welcome mutes is {wlcm_mutes} + Deleting old welcomes is {clean_welcomes} + Deleting service messages is {clean_service} + wlcm_note: "\n\nウェルカムノート:" + raw_wlcm_note: "未加工の歓迎メモ:" + security_note: "ウェルカムセキュリティーテキスト:" + #/setsecuritynote + security_note_saved: "カスタムセキュリティテキストが保存されました %s" + security_note_updated: "カスタムセキュリティテキストが更新されました %s" + #/delsecuritynote + security_note_not_found: %s のセキュリティテキストは以前にインストールされていません + del_security_note_ok: セキュリティテキストは %s でデフォルトにリセットされました + #/cleanwelcome + cleanwelcome_enabled: %s で歓迎をクリーニングする + cleanwelcome_disabled: クリーニングは %s で無効化されていることを歓迎します + cleanwelcome_status: Cleaning welcomes is {status} in {chat_name} + #/cleanservice + cleanservice_enabled: %sでサービスメッセージのクリーニングが有効になっています + cleanservice_disabled: %s でサービスメッセージのクリーニングが無効になっています + cleanservice_status: クリーニング中のサービスメッセージは {status} {chat_name} + #/welcomemute + welcomemute_invalid_arg: 無効な引数、期待された時間です。 + welcomemute_enabled: %s でウェルカムミュートが有効になります + welcomemute_disabled: ウェルカムミュートは %sで無効になっています + welcomemute_status: ウェルカムミュートは {status}{chat_name} です + #/welcomesecurity + welcomesecurity_enabled: ウェルカムセキュリティは {chat_name} から {level} レベルで有効になります。 + "welcomesecurity_enabled:customized_time": | + ウェルカムセキュリティは {chat_name} から {level}で有効になり、 {time} 内で検証されていないユーザーをキックします。 + ask_for_time_customization: | + 未認証ユーザーをキックする期間をカスタマイズしますか?(デフォルトは {time} です) + send_time: Please send time. + invalid_time: "無効な時間です!" + welcomesecurity_enabled_word: 有効にしました。レベル {level} に設定 + welcomesecurity_disabled: ウェルカムセキュリティは %s で無効になっています。 + welcomesecurity_invalid_arg: 無効な引数です。期待されている ボタン/Captcha。 + welcomesecurity_status: Welcome security is {status} in {chat_name} + yes_btn: "はい" + no_btn: "いいえ" + #Welcomesecurity handler + click_here: ここをクリック! + not_allowed: これを使用することはできません! + ws_captcha_text: こんにちは {user}、チャットでミュート解除されるキャプチャの番号を入力してください。 + regen_captcha_btn: '🔄 CAPTCHAを変更' + num_is_not_digit: 数字のみを入力してください! + bad_num: 番号が違います!もう一度やり直してください。 + passed: 検証が完了しました。 %s でミュート解除されました! + passed_no_frm: 検証が完了しました。 %s でミュート解除されました! + last_chance: Captcha を入力する最後のチャンスがあります! + btn_button_text: "あなた自身を証明するために下のボタンをクリックしてください。" + btn_wc_text: | + この式に一致する右ボタンを押してください: + %s + math_wc_rtr_text: "最後のチャンスがあります!気をつけてください!\n" + math_wc_wrong: 間違っています、もう一度やり直してください! + math_wc_sry: 申し訳ありませんが、最後の試行を使用しました。チャット管理者に聞いてください。 + not_admin_wm: ここでは人を制限できないので、welcomeミュートは使わない! + not_admin_ws: 私はここで人を制限することはできませんので、ウェルカムセキュリティを使用しません! + not_admin_wsr: メッセージは削除できませんので、サービスメッセージは削除しません。 + #/resetwelcome + deleted: {chat} で挨拶をリセットしました! + not_found: リセットするものはありません! + #... + verification_done: 認証が完了しました!ミュート解除されました。 + locks: + locks_header: "{chat_title} の現在のロック設定は次のとおりです \n\n" + no_lock_args: 私にロックするためにいくつかのアルゴをください! + already_locked: これはすでにあざがロックされています + no_such_lock: 何がロックされてアンロックされているかを確認するために /locks を参照してください! + locked_successfully: '{lock} で {chat} をロックしました!' + no_unlock_args: 解放するにはいくつかの引数をください! + not_locked: 英国人はロックを解除するためにロックされていません。 + unlocked_successfully: '{lock} で {chat} のロックを解除しました!' + antiflood: + #enforcer + flood_exceeded: "{action} {user} で洪水に!" + #/setflood + "invalid_args:setflood": "Expected an integer!" + overflowed_count: 最大カウントは200未満でなければなりません! + config_proc_1: 有効期限が切れていないため、'0' (ゼロ) を送信してください。 + cancel: '❌ キャンセル' + setup_success: Successfully configured antiflood, now enforces against those who sends {count} messages within {time}! + "setup_success:no_exp": アンチルードの設定が正常に完了しました。 {count} 件のメッセージを連続的に許可します! + invalid_time: 無効な時間です! + #/antiflood | /flood + not_configured: Antiflood isn't configured in this chat yet! + turned_off: Disabled antiflood in {chat_title}! + "configuration_info:with_time": Antiflood is configured in this chat, those who sents {count} message within {time} would be {action}! + configuration_info: Antiflood is configured in this chat, those who sents {count} consecutively would be {action}! + ban: banned + mute: muted + kick: kicked + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "Unsupported action, expected {supported_actions}!" + setfloodaction_success: "Successfully updated flood action to {action}!" + + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/pt_BR.yaml b/DaisyX/localization/pt_BR.yaml new file mode 100644 index 00000000..94eb18cd --- /dev/null +++ b/DaisyX/localization/pt_BR.yaml @@ -0,0 +1,676 @@ +--- +language_info: + flag: "🇧🇷" + code: pt +STRINGS: + notes: + #/save + note_saved: "🗒 Nota {note_name} foi salva em {chat_title}!" + note_updated: "🗒 Nota {note_name} foi atualizada em {chat_title}!" + you_can_get_note: "\nVocê pode obter essa nota ao usar /get {note_name}, ou #{note_name}" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nAliases da nota:" + blank_note: "Salvar notas vazias não é permitido!" + notename_cant_contain: "O nome da nota não pode conter \"{symbol}\"!" + #/get + cant_find_note: Eu não consegui encontrar essa nota em {chat_name}! + no_note: "Não consegui encontrar essa nota." + no_notes_pattern: "Nenhuma nota encontrada com o padrão %s" + u_mean: "\nVocê quis dizer #{note_name}?" + #/notes + notelist_no_notes: "Não há notas em {chat_title}!" + notelist_header: "Notas em {chat_name}:" + notes_in_private: "Clique no botão abaixo para obter a lista de notas." + notelist_search: | + Padrão: {request} + Notas correspondidas: + #/clear + note_removed: "Nota #{note_name} removida em {chat_name}!" + note_removed_multiple: | + Múltiplas notas removidas em {chat_name} + Removidas:{removed} + not_removed_multiple: "Não removido:{not_removed}" + #/clearall + clear_all_text: "Isso vai remover todas as notas em {chat_name}. Você tem certeza?" + clearall_btn_yes: "Sim. Remover todas as minhas notas!" + clearall_btn_no: "Não!" + clearall_done: "Todas as ({num}) notas de {chat_name} foram removidas!" + #/search + search_header: | + Pedido de pesquisa em {chat_name}: + Padrão: {request} + Notas combinando: + #/noteinfo + note_info_title: "Informações da nota\n" + note_info_note: "Nomes da nota: %s\n" + note_info_content: "Conteúdo da nota: %s\n" + note_info_parsing: "Parse mode: %s\n" + note_info_created: "Foi criada em: {date} por {user}\n" + note_info_updated: "Última atualização em: {date} por {user}\n" + user_blocked: "Escreva /start no meu PV!" + #/privatenotes + private_notes_false_args: "Você tem 2 opções: Desabilitar e Habilitar!" + already_enabled: "Notas privadas já estão ativadas em %s" + current_state_info: "As notas privadas estão atualmente {state} em {chat}!" + enabled_successfully: "Notas privadas foram ativadas em %s com sucesso!" + disabled_successfully: "Notas privadas foram desativadas em %s com sucesso!" + not_enabled: "As notas privadas não estão habilitadas." + privatenotes_notif: "Você tem sido conectado com sucesso às notas de {chat}! Para se desconectar, por favor use o comando /disconnect!" + enabled: 'Ativado' + disabled: 'Desativado' + #/cleannotes + clean_notes_enable: "Limpeza de notas ativada com sucesso em {chat_name}" + clean_notes_disable: "Limpeza de notas desativada com sucesso em {chat_name}" + clean_notes_enabled: "Limpeza de notas está atualmente ativada em {chat_name}" + clean_notes_disabled: "Limpeza de notas está atualmente desativada em {chat_name}" + #Filter + filters_title: 'Enviar uma nota' + filters_setup_start: 'Por favor envie o nome da nota.' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "Nenhum filtro foi encontrado em {chat_name}!" + #/addfilter + anon_detected: Sendo um administrador anônimo, você não pode adicionar novos filtros, use as conexões para isso. + regex_too_slow: "Seu padrão regex é muito lento (mais da metade do segundo) e não pode ser adicionado!" + cancel_btn: "🛑 Cancelar" + adding_filter: | + Adicionando filtro {handler} em {chat_name} + Selecione a ação abaixo: + saved: "O novo filtro foi salvo com sucesso!" + #/filters + list_filters: "Filtros em {chat_name}:\n" + #/delfilter + no_such_filter: Não consigo encontrar esse filtro em {chat_name}. Você pode verificar quais filtros estão habilitados com o comando /filters. + del_filter: "Filtro com o handler '{handler}' foi removido com sucesso!" + select_filter_to_remove: | + Encontrei muitos filtros com o handler '{handler}'. + Selecione um para remover: + #/delfilters or /delallfilters + not_chat_creator: Só o dono do grupo pode usar este comando! + delall_header: Isso excluiria todos os filtros neste chat. Isso é irreversível. + confirm_yes: '⚠ Exclua todos' + confirm_no: '❌ Cancelar' + delall_success: Excluído com sucesso todos os ({count}) filtro neste chat! + warns: + #/warn + warn_sofi: "Haha não tem como me avisar!" + warn_self: "Você quer se avisar? Saia do grupo então." + warn_admin: "Bem... Você está errado. Você não pode avisar um administrador." + warn: "{admin} avisou {user} em {chat_name}\n" + warn_bun: "Os avisos foram excedidos! {user} foi banido!" + warn_num: "Avisos: {curr_warns}/{max_warns}\n" + warn_rsn: "Motivo: {reason}\n" + max_warn_exceeded: Os avisos foram excedidos! {user} foi {action}! + "max_warn_exceeded:tmute": Os avisos foram excedidos! {user} foi silenciado temporariamente por {time}! + #warn rmv callback + warn_no_admin_alert: "⚠ Você NÃO é administrador e não pode remover advertências!" + warn_btn_rmvl_success: "✅ Advertência removida por {admin}!" + #/warns + warns_header: "Aqui estão seus avisos neste chat \n\n" + warns: "{count} : {reason} por {admin}\n" + no_warns: "Bem, {user} não tem nenhum aviso." + #/warnlimit + warn_limit: "Limite de avisos em {chat_name} é atualmente: {num}" + warnlimit_short: 'O limite de avisos deve ser de pelo menos 2!' + warnlimit_long: 'O limite de avisos deve ser menor que 1000!' + warnlimit_updated: '✅ O limite de advertências foi atualizado com sucesso para {num}' + #/delwarns + purged_warns: "{admin} redefiniu {num} os avisos de {user} em {chat_title}!" + usr_no_wrn: "{user} não tem nenhum aviso para redefinir." + rst_wrn_sofi: 'Daisy nunca teve avisos para redefinir.' + not_digit: Os limites de avisos devem ser dígitos! + #/warnmode + same_mode: Este é o modo atual! Como posso mudá-lo? + no_time: Para selecionar o modo 'tmute' você tem que mencionar o tempo! + invalid_time: Tempo inválido! + warnmode_success: O modo de advertência de %s mudou com sucesso para %s! + wrng_args: | + Estas são as opções disponíveis: + mode_info: | + O atual modo neste chat é %s. + banned: banido + muted: mutado + filters_title: Adverte o usuário + filter_handle_rsn: Ação de filtro automatizada! + msg_deleting: + no_rights_purge: "Somente admins podem executar este comando!" + reply_to_msg: "Responda a uma mensagem para excluir!" + purge_error: "Não consigo limpar mensagens que já foram enviadas há mais de dois dias." + fast_purge_done: "Limpeza rápida concluída!\nEsta mensagem será apagada em 5 segundos." + purge_user_done: "Todas as mensagens de {user} foram excluídas com sucesso!" + misc: + your_id: "Seu ID: {id}\n" + chat_id: "A ID do grupo é {id}\n" + user_id: "A ID de {user} é {id}\n" + conn_chat_id: "ID do atual grupo conectado no momento: {id}" + help_btn: "Clique em mim para obter ajuda!" + help_txt: "Clique no botão abaixo para obter ajuda!" + delmsg_filter_title: 'Apagar a mensagem' + send_text: Por favor, responda uma mensagem! + replymsg_filter_title: 'Responda uma mensagem' + send_customised_reason: Envie o motivo que deseja incluir na mensagem de ação. "None" para nenhuma razão a ser fornecida! + expected_text: Uma mensagem de texto era o esperado! + promotes: + promote_success: "O usuário {user} foi promovido com sucesso no chat {chat_name}!" + promote_title: "\nCom um título customizado: {role}!" + rank_to_loong: "Um título customizado não pode ter mais que 16 símbolos." + promote_failed: "Promoção falhou, verifique se eu tenho permissão para promover membros." + demote_success: "O usuário {user} foi rebaixado com sucesso no chat {chat_name}!" + demote_not_admin: "Esse usuário não é um administrador." + demote_failed: "O processo para despromover falhou. Talvez eu não possa despromover ou a pessoa tenha sido promovida por outra pessoa?" + cant_get_user: "Não consegui obter dados do usuário! Caso você não se importe, responda à mensagem e tente novamente." + cant_promote_yourself: "Você não pode promover a si mesmo." + emoji_not_allowed: "Sinto muito, mas o título de administrador não podem ter emojis 😞" + pm_menu: + start_hi_group: 'Olá! Meu nome é Daisy' + start_hi: "Olá! Meu nome é Daisy.\nEu posso ajudar a gerenciar seus grupos com recursos úteis, fique à vontade para me adicionar aos seus grupos!" + btn_source: "📜 Código fonte" + btn_help: "❔ Ajuda" + btn_lang: "🇧🇷 Idioma" + btn_channel: "🙋‍♀️ Notícias do Daisy" + btn_group: "👥 Daisy Support" + btn_group_help: "Clique aqui para obter ajuda!" + back: Voltar + #/help + help_header: "Ei! Meu nome é Daisy. Sou um bot de gerenciamento de grupo, estou aqui para ajudá-lo a se locomover e manter a ordem em seus grupos!\nTenho muitos recursos úteis, como controle de flood, um sistema de avisos, um sistema de notas e até mesmo respostas predeterminadas em certas palavras-chave." + click_btn: Clique aqui + disable: + #/disableable + disablable: "Comandos que podem ser desativados:\n" + #/disabled + no_disabled_cmds: Nenhum comando desativado no chat {chat_name}! + disabled_list: "Comandos desativados no chat {chat_name}:\n" + #/disable + wot_to_disable: "O que você quer desativar?" + already_disabled: "Este comando já está desativado!" + disabled: "O comando {cmd} foi desativado no chat {chat_name}!" + #/enable + wot_to_enable: "O que você quer ativar?" + already_enabled: "Esse comando não está desativado!" + enabled: "O comando {cmd} foi ativado no chat {chat_name}!" + #/enableall + not_disabled_anything: "Nada foi desativado em {chat_title}!" + enable_all_text: "Isto habilitará todos os comandos em {chat_name}. Você tem certeza?" + enable_all_btn_yes: "Sim. Ativei todos os comandos!" + enable_all_btn_no: "Não!" + enable_all_done: "Todos os ({num}) comandos foram habilitados no {chat_name}!" + bot_rights: + change_info: "Eu não tenho o direito de mudar informações do grupo, por favor, faça-me administrador com esse direito." + edit_messages: "Eu não tenho permissão para editar mensagens!" + delete_messages: "Eu não tenho permissão para apagar mensagens aqui." + ban_user: "Eu não tenho permissão para banir usuários, por favor me torne um administrador com esta permissão." + pin_messages: "Eu não tenho permissão para fixar mensagens, por favor me torne um administrador com esta permissão." + add_admins: "Eu não tenho permissão para adicionar administradores, por favor me torne um administrador com esta permissão." + feds: + #decorators + need_fed_admin: "Você não é um administrador na federação {name}" + need_fed_owner: "Você não é proprietário da federação {name}" + cant_get_user: "Desculpe, eu não consigo encontrar esse usuário. Você tente usar seu ID de usuário" + #/fnew + fed_name_long: "Nome de uma federação não pode ter mais que 60 símbolos!" + can_only_1_fed: "Os usuários só podem criar 1 federação. Por favor, remover um deles." + name_not_avaible: "Federação com nome {name} já existe! Por favor, use outro nome." + created_fed: | + Parabéns, você criou com sucesso uma federação. + Nome: {name} + ID: {id} + Criador: {creator} + Use o comando /fjoin {id} para conectar a federação ao chat\ + disallow_anon: Sendo um administrador anônimo, você não pode criar uma nova federação! + #/fjoin + only_creators: "Você deve ser o criador do grupo para poder conectar o chat a uma federação." + fed_id_invalid: "O ID da federação é inválido! Por favor, me dê um ID válido." + joined_fed_already: "Este chat já se uniu a uma federação! Por favor, use /fleave para abandonar essa federação" + join_fed_success: "Ótimo! O chat {chat} agora é parte da federação {fed}!" + join_chat_fed_log: | + Chat unido à federação #ChatJoined + Federação: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "Este chat ainda não está em nenhuma federação." + leave_fed_success: "O chat {chat} deixou a federação {fed}." + leave_chat_fed_log: | + Um chat saiu da federação #ChatLeaved + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/fsub + already_subsed: "A federação {name} já está inscrita em {name2}" + subsed_success: "A federação {name} se inscreveu em {name2}!" + #/funsub + not_subsed: "A federação {name} não está subscrita a {name2}" + unsubsed_success: "A federação {name} cancelou a subscrição de {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "Usuário {user} adicionado aos administradores da federação {name}." + promote_user_fed_log: | + Usuário promovido a administradores da federação #UserPromoted + Fed: {fed_name} ({fed_id}) + Usuário: {user} ({user_id}) + "restricted_user:promote": Este usuário está proibido de ser administrador da federação! + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "Usuário {user} despromovido de administradores da federação {name}." + demote_user_fed_log: | + Usuário rebaixado dos administradores do fed #UserDemoted + Fed: {fed_name} ({fed_id}) + Usuário: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "A federação {name} já tem o registro habilitado em outro chat/canal!" + set_chat_log: | + Este chat está agora registrando todas as ações da federação {name}. + + O registro da Federação é utilizado para avaliação interna da federação e de seu desempenho. + A federação NÃO deve ser utilizada como um muro público da vergonha, respeitando assim os dados dos usuários e nossas diretrizes de privacidade. + set_log_fed_log: | + Ativado logging #LoggingEnabled + Fed: {fed_name} ({fed_id}) + no_right_to_post: Eu não tenho permissão para postar mensagens nesse canal! + #/funsetlog + not_logging: "A federação {name} não está registrado em nenhum chat/canal!" + logging_removed: "Removido com sucesso o registro da federação {name}." + unset_log_fed_log: | + Desativado logging #LoggingDisabled + Fed: {fed_name} ({fed_id}) + #/fchatlist + no_chats: "Não há chats unidos a esta federação {name}" + chats_in_fed: "Chats conectados à federação {name}:\n" + too_large: "Saída muito grande, enviando como arquivo" + #/finfo + finfo_text: | + Informação da fed + Nome: {name} + ID: {fed_id} + Criador: {creator} + Grupos na fed: {chats} + Usuários banidos na fed: {fbanned} + finfo_subs_title: "Federações subscritas a esses federações:\n" + #/fadminlist + fadmins_header: "Administradores na federação {fed_name}:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "Este usuário está na lista branca de banimentos." + fban_self: "É um belo show!" + fban_creator: "Como posso banir o criador da federação?! Não me parece que vai ser engraçado." + fban_fed_admin: "Eu não vou banir um administrador de seu própria federação!" + update_fban: "Este usuário já estava banido. Portanto, estou atualizando o motivo para {reason}" + already_fbanned: "{user} já está banido nesta federação." + fbanned_header: "Novo FedBan\n" + fban_info: | + Federação: {fed} + Fed admin: {fadmin} + Usuário: {user} ({user_id}) + fbanned_reason: "Motivo: {reason}" + fbanned_process: "\nStatus: Banindo em {num} grupos da fed..." + fbanned_done: "\nStatus: Feito! Banido em {num} grupos!" + fbanned_silence: "Esta mensagem será eliminada em 5 segundos" + fbanned_subs_process: "\nStatus: Banindo em {feds} feds inscritas..." + fbanned_subs_done: "\nEstado: Pronto! Banido em {chats} chats desta federação e {subs_chats} chats de {feds} federações subscritas" + fban_usr_rmvd: | + O usuário {user} está atualmente banido na federação {fed} e tem sido removido! + Motivo: {rsn} + fban_log_fed_log: | + Baniu um usuário na fed #FedBan + Fed: {fed_name} ({fed_id}) + Usuário: {user} ({user_id}) + Por: {by} + Grupos banidos: usuário banido em {chat_count}/{all_chats} chats + fban_reason_fed_log: "Motivo: {reason}\n" + fban_subs_fed_log: "\nFederações inscritas: banido em {subs_chats} grupos de {feds} federações" + #/unfban + unfban_self: "Pense em outra coisa para se divertir em vez disso." + user_not_fbanned: "O usuário {user} não está banido nesta federação." + un_fbanned_header: "Novo Un-FedBan\n" + un_fbanned_process: "\nStatus: Desbanindo em {num} grupos..." + un_fbanned_done: "\nStatus: Feito! Desbanindo em {num} grupos!" + un_fbanned_subs_process: "\nStatus: Desbanindo em {feds} feds inscritas..." + un_fbanned_subs_done: "\nStatus: Feito! Desbanindo em {chats} grupos desta federação e {subs_chats} grupos de {feds} federações inscritas" + un_fban_log_fed_log: | + Desbaniu o usuário na fed #FedUnBan + Fed: {fed_name} ({fed_id}) + Usuário: {user} ({user_id}) + Por: {by} + Grupos: usuário desbanindo em {chat_count}/{all_chats} grupos + un_fban_subs_fed_log: "\nFeds inscritas: desbanindo em {subs_chats} grupos de {feds} federações" + #/delfed + delfed_btn_yes: Sim. Apague a federação + delfed_btn_no: Não. + delfed: | + Você tem certeza de que quer apagar a federação %s? + + Aviso: Apagar esta federação irá remover todos os dados associados a ela de nosso banco de dados! + Esta ação é permanente e não pode ser revertida! + delfed_success: Federação excluída com sucesso! + #/frename + frename_same_name: Por favor, insira um nome diferente, mas não o mesmo! + frename_success: A federação {old_name} mudou o nome para {new_name} com sucesso! + #/fbanlist + fbanlist_locked: Por favor, aguarde %s para solicitar ou usar a lista de banidos novamente. + creating_fbanlist: Criando a lista de fbans! Aguarde .. + fbanlist_done: Lista de banidos da federação %s! + #/importfbans + send_import_file: | + Envie o arquivo importado! + + Para cancelar este processo, use /cancel + rpl_to_file: Por favor, responda a um arquivo! + importfbans_locked: Importação de fbans está bloqueado para %s nesta federação. + big_file_csv: Só suporta arquivos csv importados menores de {num} megabytes! + big_file_json: | + Atualmente os arquivos json estão limitados a {num} megabytes! + Use o formato csv se você quiser importar arquivos maiores. + invalid_file: "O arquivo é inválido" + wrong_file_ext: "Formato de arquivo errado! Atualmente os suportados são: json, csv" + importing_process: Importando fbans... + import_done: Importação de banidos da federação finalizada! {num} banidos foram importados. + #def + automatic_ban: O usuário {user} está banido na atual federação {fed_name} e tem sido removido! + automatic_ban_sfed: O usuário {user} está banido na subfederação {fed_name} e tem sido removido! + automatic_ban_reason: "Motivo: {text}" + #/fcheck + fcheck_header: "Informações de banimento da federação:\n" + fbanned_count_pm: "Você está banido em {count} federações!\n" + "fban_info:fcheck": "Você foi banido na federação {fed} o {date}. Nenhum motivo foi dado!" + "fban_info:fcheck:reason": "Você foi banido na federação {fed} o {date} pelo motivo -\n {reason}\n" + fbanned_data: "O usuário {user} está banido em {count} federações.\n" + fbanned_nowhere: "{user} não está banido em nenhum lugar!\n" + fbanned_in_fed: "Incluindo a federação {fed}!\n" + "fbanned_in_fed:reason": "Incluindo a federação {fed} por motivo de -\n {reason}" + contact_in_pm: "\n\nPara mais informações sobre seus banimentos, por favor, envie-me uma mensagem direta para maior privacidade!" + too_long_fbaninfo: "Bem! seu fbaninfo requer um arquivo!" + didnt_fbanned: "Você não está banido nesta federação." + not_fbanned_in_fed: Ele(s) não está(ão) banido(s) na federação {fed_name} + reports: + user_user_admin: "Você é um administrador aqui, por que você precisa denunciar alguém?.." + user_is_whitelisted: "Você é um usuário da lista branca, por que você precisa denunciar alguém?.." + reports_disabled: "As denúncias estão atualmente desativadas neste grupo." + no_user_to_report: "Qual usuário você quer denunciar?" + report_admin: "Você não pode reportar os administradores." + report_whitedlisted: "Você não pode denunciar um usuário da lista branca." + reported_user: "{user} reportado aos administradores." + reported_reason: "\nMotivo:\n{reason}" + reports_on: "Os reportes estão ativados neste grupo." + reports_off: "Os reportes estão desativados neste grupo." + reports_already_on: "Os reportes já estão ativados." + reports_turned_on: "Reportes ativados." + reports_already_off: "Os reportes já estão desativados." + wrong_argument: "Argumento incorreto." + language: + your_lang: "Seu idioma atual: {lang}" + chat_lang: "Idioma atual do chat: {lang}" + select_pm_lang: "\nSelecione o seu novo idioma:" + select_chat_lang: "\nSelecione o novo idioma do chat:" + crowdin_btn: "🌎 Ajude-nos com a tradução" + help_us_translate: "\n\n🌎 Ajude-nos a melhorar nossas traduções" + not_supported_lang: Este idioma não é suportado! + lang_changed: O idioma foi trocado a {lang_name}. + see_translators: '👥 Veja os tradutores' + back: Voltar + connections: + pm_connected: "Seu chat privado tem sido conectado com sucesso a {chat_name}!" + connected_pm_to_me: "Seu chat privado tem sido conectado com sucesso a {chat_name}! Por favor, me envie uma mensagem para iniciar a conexão." + chat_connection_tlt: "Conexão via chat" + connected_chat: | + Chat conectado atualmente: + {chat_name} + Escreva /disconect para se desconectar do chat + "connected_chat:cmds": | + Chat conectado atualmente: + {chat_name}, (Os comandos conectados são {commands}), + ⚠ Você só pode acessar aos comandos conectados do chat atual! Os outros comandos seriam tratados como Chat Local + Escreva /disconnect para se desconectar do chat. + u_wasnt_connected: "Você não estava conectado a nenhum chat anteriormente!" + select_chat_to_connect: "\nSelecione um chat para se conectar:" + disconnected: "Você foi desconectado de {chat_name}." + cant_find_chat: "Não encontrei este chat." + cant_find_chat_use_id: "Não encontrei este chat. Use o ID do chat." + bad_arg_bool: "Escolha incorreta: somente com suporte on/off enable/disable" + enabled: ativado + disabled: desativado + chat_users_connections_info: "As conexões para usuários normais atualmente está {status} para {chat_name}" + chat_users_connections_cng: "As conexões para usuários normais mudou a {status} em {chat_name}" + not_in_group: "Você não está no grupo que você está tentando conectar, entre primeiro, depois envie uma mensagem." + history_empty: "Você não está conectado a nenhum chat cadastrado, conecte-se via `/connect (chat id)`" + not_in_chat: "Você não está mais neste chat, eu vou te desconectar." + u_should_be_admin: "Você deve ser um administrador em {}!" + usage_only_in_groups: "Uso limitado apenas em grupos!" + anon_admin_conn: | + Por favor, clicar no botão abaixo para se conectar! + click_here: Clique aqui! + global: + u_not_admin: Você deveria ser um administrador para fazer isso! + #Rights + "bot_no_right:not_admin": Não sou administrador! + bot_no_right: "Eu não tenho permissão para {permission} aqui!" + user_no_right: "Você não tem permissão {permission} aqui!" + "user_no_right:not_admin": Você deveria ser administrador para fazer isso! + #is chat creator + unable_identify_creator: "O criador do chat anônimo não pôde ser identificado. Uma assinatura duplicada (títulos) foi encontrada!" + owner_stuff: + father: "\nEle é o meu criador." + sudo_crown: "\nEi! Olha, aquele usuário tem uma coroa, deixe-me vê-la... Uau, tem uma gravura - 'sudo usuário'!" + stickers: + rpl_to_sticker: "Responda a um sticker!" + ur_sticker: | + Emoji: {emoji} + ID do Sticker: {id} + pins: + no_reply_msg: "Responda a uma mensagem para fixar!" + chat_not_modified_pin: "Essa mensagem já está fixada!" + chat_not_modified_unpin: "Não há nenhuma mensagem fixada para desafixar!" + pinned_success: "Fixado com sucesso!" + unpin_success: "Desafixado com sucesso!" + users: + user_info: "Informações do usuário:\n" + info_id: "ID: {id}" + info_first: "\nPrimeiro nome: {first_name}" + info_last: "\nÚltimo nome: {last_name}" + info_username: "\nNome do usuário: {username}" + info_link: "\nLink do usuário: {user_link}" + info_saw: "\nEu os vi em {num} grupos" + info_sw_ban: "\nEste usuário está banido na @SpamWatch!" + info_sw_ban_reason: "\nMotivo: {sw_reason}" + info_fbanned: "\nBanido na Federação atual: " + info_admeme: "\nO usuário tem direitos de administrador neste chat." + upd_cache: "Atualizando cache agora..." + upd_cache_done: "O cache de administradores foi atualizado." + admins: "Administrador neste grupo:\n" + imports_exports: + #Exporting + started_exporting: Exportação iniciada! Por favor, espere... + export_done: Exportação de {chat_name} concluído! + exports_locked: Por favor, aguarde %s antes de usar novamente a exportação! + #Importing + invalid_file: "O arquivo é inválido" + send_import_file: Por favor, envie um arquivo para importar! + rpl_to_file: Por favor, responda a um arquivo! + big_file: Somente arquivos menores de 50 MB são suportados! + started_importing: Importação iniciada! Por favor, espere... + bad_file: Arquivo errado! Esperava-se coluna 'geral'. + file_version_so_new: A versão do arquivo exportado é mais nova do que a Daisy suporta atualmente! + imports_locked: Por favor, aguarde %s antes de usar novamente a importação! + import_done: Importação concluída! + restrictions: + user_not_in_chat: O usuário não está neste chat! + #Kicks + kick_DaisyX: Em vez de tentar me expulsar, você poderia usar melhor o seu tempo. Isso é chato. + kick_admin: Expulsar o administrador não é a melhor idéia pensada. + kick_self: Se você quiser se kickar - basta deixar este chat. Prefiro não vê-lo do que olhar para estas tentativas patéticas de autopunição. + user_kicked: "{user} foi kickado por {admin} em {chat_name}." + #Mutes + mute_DaisyX: Na verdade, eu ficarei feliz em calar você. + mute_admin: Se você pensa que pode silenciar um administrador, você está errado! + mute_self: Se você quiser se silenciar - basta parar de falar! Melhor pensar como um idiota do que abrir a boca e remover todas as dúvidas. + user_muted: "{user} foi silenciado por {admin} em {chat_name}." + unmute_DaisyX: Na verdade, eu não estou silenciado. + unmute_admin: Haha não, tente silenciá-lo primeiro! + unmute_self: Devo te silenciar primeiro? + user_unmuted: "{user} foi desmutado por {admin} em {chat_name}." + #Bans + ban_DaisyX: Não, eu não vou fazer isso! Peça ao criador do chat que o faça! + ban_admin: Haha, vamos. /demote ele primeiro. + ban_self: Por que você está tentando se banir? + user_banned: "{user} foi banido por {admin} em {chat_name}." + unban_DaisyX: Na verdade, não estou banido. + unban_admin: Haha não, tente bani-lo primeiro! + unban_self: Devo te banir primeiro? + user_unband: "{user} foi desbanido por {admin} em {chat_name}." + invalid_time: Hora inválida! + enter_time: Por favor, especifique um tempo! + on_time: "\nPor %s" + reason: "\nMotivo: %s" + purge: "\nAs mensagens serão removidas em 5 segundos!" + filter_title_ban: Banir o usuário + filter_action_rsn: Ação automática do filtro! + filtr_ban_success: '%s baniu a %s por %s' + filtr_mute_success: '%s silenciou a %s por %s' + filtr_tmute_success: | + %s silenciou a %s por %s + O motivo é %s + time_setup_start: Por favor, especifique um tempo! + filtr_tban_success: | + %s baniu a %s por %s + motivo: %s + filter_title_tmute: tMute o usuário + filter_title_mute: Silenciar o usuário + filter_title_tban: tBan o usuário + filter_title_kick: Kickar o usuário + rules: + #Set + saved: As regras foram salvas em %s + updated: As regras foram atualizadas em %s + #Reset + deleted: Regras excluídas! + not_found: As regras não foram encontradas! + #Callback + user_blocked: "Escreva /start no meu chat privado!" + rules_was_pmed: As regras foram enviadas ao seu chat privado! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Bem-vindo {mention}! Tudo bem?" + thank_for_add: "Obrigado por me adicionar ao seu grupo! Dê uma olhada em /help e siga o meu canal de notícias @DaisyXUpdates." + default_security_note: "Bem-vindo(a) {mention}! Por favor, clique no botão abaixo para verificar-se como humano!" + bool_invalid_arg: Argumento inválido, espera-se on/off. + #/setwelcome + saved: Mensagem de boas-vindas salva em %s + updated: Mensagem de boas-vindas atualizada em %s + #/turnwelcome + turnwelcome_status: A mensagem de boas-vindas está {status} em {chat_name} + turnwelcome_enabled: Boas-vindas ativadas em %s + turnwelcome_disabled: Boas-vindas desativadas em %s + #/welcome + enabled: ativada + disabled: desativada + wlcm_security_enabled: "ativado, estabelecido a {level}" + wlcm_mutes_enabled: "ativado, estabelecido a {time}" + welcome_info: | + Configuração de boas-vindas em {chat_name}: + Boas-vindas está {welcomes_status} + A segurança de boas-vindas está {wlcm_security} + O silencio de boas-vindas está {wlcm_mutes} + Apagar as boas-vindas antigas está {clean_welcomes} + Apagar as mensagens de serviço está {clean_service} + wlcm_note: "\n\nA nota de boas-vindas é:" + raw_wlcm_note: "Nota de boas-vindas simples é:" + security_note: "O texto de segurança de boas-vindas é:" + #/setsecuritynote + security_note_saved: "Foi salvo o texto de segurança personalizado %s" + security_note_updated: "O texto de segurança personalizado foi atualizado %s" + #/delsecuritynote + security_note_not_found: O texto de segurança em %s não foi estabelecido anteriormente + del_security_note_ok: O texto de segurança foi restabelecido como padrão em %s + #/cleanwelcome + cleanwelcome_enabled: Limpeza de boas-vindas ativada em %s + cleanwelcome_disabled: Limpeza de boas-vindas desativada em %s + cleanwelcome_status: A limpeza de boas-vindas está {status} em {chat_name} + #/cleanservice + cleanservice_enabled: Limpeza de mensagens de serviço ativada em %s + cleanservice_disabled: Limpeza de mensagens de serviço desativada em %s + cleanservice_status: A limpeza de mensagens de serviço está {status} em {chat_name} + #/welcomemute + welcomemute_invalid_arg: Argumento inválido, esperava-se um tempo. + welcomemute_enabled: Silencio de boas-vindas ativado em %s + welcomemute_disabled: Silencio de boas-vindas desativado em %s + welcomemute_status: O silencio de boas-vindas está {status} em {chat_name} + #/welcomesecurity + welcomesecurity_enabled: Segurança de boas-vindas ativada no chat {chat_name} para {level}. + "welcomesecurity_enabled:customized_time": | + Segurança de boas-vindas ativada no chat {chat_name} para {level}, irei remover os usuários que não foram verificados dentro de {time}. + ask_for_time_customization: | + Você deseja personalizar o período de tempo para expulsar o usuário não verificado? (o padrão está em {time}) + send_time: Por favor, especifique um tempo. + invalid_time: "Tempo inválido!" + welcomesecurity_enabled_word: ativado, estabelecido no nível {level} + welcomesecurity_disabled: Segurança de boas-vindas desativada em %s. + welcomesecurity_invalid_arg: Argumento inválido, espera-se button/captcha. + welcomesecurity_status: A segurança de boas-vindas está {status} em {chat_name} + yes_btn: "Sim" + no_btn: "Não" + #Welcomesecurity handler + click_here: Clique aqui! + not_allowed: Você não tem permissão para usar isso! + ws_captcha_text: Olá {user}, por favor, insira o número do captcha para te desmutar no chat. + regen_captcha_btn: '🔄 Mudar captcha' + num_is_not_digit: Por favor, insira apenas números! + bad_num: Número errado! Por favor, tente novamente. + passed: Verificação aprovada, você foi desmutado em %s! + passed_no_frm: Verificação aprovada, você foi desmutado em %s! + last_chance: Você tem uma última chance para digitar o captcha! + btn_button_text: "Por favor, clique no botão abaixo para se verificar." + btn_wc_text: | + Por favor, clique no resultado correto que corresponde à expressão matemática: + %s + math_wc_rtr_text: "Você tem uma última chance! Tenha cuidado.\n" + math_wc_wrong: Incorreto, por favor tente novamente! + math_wc_sry: Lamentamos que você tenha usado a última tentativa. Pergunte aos administradores do chat. + not_admin_wm: Eu não posso restringir as pessoas aqui, então não vou estabelecer o silencio de boas-vindas! + not_admin_ws: Eu não posso restringir as pessoas aqui, então não vou estabelecer a segurança de boas-vindas! + not_admin_wsr: Não posso apagar mensagens aqui, portanto não vou apagar as mensagens de serviço! + #/resetwelcome + deleted: Cumprimentos reestabelecidos com sucesso em {chat}! + not_found: Não há nada para restabelecer! + #... + verification_done: A verificação está completa! Agora você pode escrever no chat. + locks: + locks_header: "Aqui está a configuração atual de bloqueio em {chat_title}\n\n" + no_lock_args: Dê-me alguns argumentos para bloquear! + already_locked: Isto já está bloqueado, bro + no_such_lock: Veja /locks para saber o que pode ser bloqueado e desbloqueado! + locked_successfully: '{lock} bloqueado em {chat} com sucesso!' + no_unlock_args: Dê-me alguns argumentos para desbloquear! + not_locked: Bro, não está bloqueado para desbloquear. + unlocked_successfully: '{lock} desbloqueado em {chat} com sucesso!' + antiflood: + #enforcer + flood_exceeded: "{user} {action} por fazer flood!" + #/setflood + "invalid_args:setflood": "Esperava-se um número!" + overflowed_count: A contagem máxima deve ser inferior a 200! + config_proc_1: Por favor, enviar o prazo de validade. 0' (zero) significa que não vai expirar. + cancel: '❌ Cancelar' + setup_success: Antiflood configurado com sucesso, agora estabelece e impõe contra aqueles que enviam {count} mensagens dentro de {time}! + "setup_success:no_exp": Antiflood estabelecido com sucesso! Permite {count} mensagens continuamente! + invalid_time: Tempo inválido! + #/antiflood | /flood + not_configured: O Antiflood ainda não está estabelecido neste chat! + turned_off: Antiflood desativado em {chat_title}! + "configuration_info:with_time": O Antiflood está configurado neste chat. Aqueles que enviarem {count} mensagens dentro de {time} serão {action}! + configuration_info: O Antiflood está configurado neste chat. Aqueles que enviarem {count} mensagens consecutivamente serão {action}! + ban: banido + mute: silenciado + kick: expulso + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "Ação não suportada, esperava-se {supported_actions}!" + setfloodaction_success: "Atualizado com sucesso a ação anti-flood para {action}!" + + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/ru_RU.yaml b/DaisyX/localization/ru_RU.yaml new file mode 100644 index 00000000..67f0ea7a --- /dev/null +++ b/DaisyX/localization/ru_RU.yaml @@ -0,0 +1,673 @@ +--- +language_info: + flag: "🇷🇺" + code: ru +STRINGS: + notes: + #/save + note_saved: "🗒 Заметка {note_name} сохранена в {chat_title}!" + note_updated: "🗒 Заметка {note_name} обновлена в {chat_title}!" + you_can_get_note: "\nТы можешь получить эту заметку, используя /get {note_name}, или #{note_name}" + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "\nПсевдонимы заметки:" + blank_note: "Сохранение пустой заметки не допускается!" + notename_cant_contain: "Имя заметки не может содержать \"{symbol}\"!" + #/get + cant_find_note: Я не могу найти эту заметку в {chat_name}! + no_note: "Не могу найти эту заметку." + no_notes_pattern: "Не найдено заметок по шаблону %s" + u_mean: "\nВы имели в виду #{note_name}?" + #/notes + notelist_no_notes: "Нет никаких заметок в {chat_title}!" + notelist_header: "Заметки в {chat_name}:" + notes_in_private: "Нажмите на кнопку ниже, чтобы получить список заметок." + notelist_search: | + Шаблон: {request} + Соответствующие заметки: + #/clear + note_removed: "Заметка #{note_name} удалена в {chat_name}!" + note_removed_multiple: | + Removed multiple notes in {chat_name} + Removed:{removed} + not_removed_multiple: "Не удалено:{not_removed}" + #/clearall + clear_all_text: "Все заметки будут удалены из {chat_name}. Вы уверены?" + clearall_btn_yes: "Да. Удалите все мои заметки!" + clearall_btn_no: "Нет!" + clearall_done: "Все ({num}) заметки в {chat_name} были удалены!" + #/search + search_header: | + Поисковый запрос в {chat_name}: + Шаблон: {request} + Соответствующие заметки: + #/noteinfo + note_info_title: "Информация о заметке\n" + note_info_note: "Названия заметки: %s\n" + note_info_content: "Содержимое заметки: %s\n" + note_info_parsing: "Режим обработки: %s\n" + note_info_created: "Заметка создана: {date} пользователем {user}\n" + note_info_updated: "Последнее обновление: {date} пользователем {user}\n" + user_blocked: "Напишите /start мне в личные сообщения!" + #/privatenotes + private_notes_false_args: "You have 2 options Disable and Enable!" + already_enabled: "Приватные заметки уже включены в %s" + current_state_info: "Private notes are currently {state} in {chat}!" + enabled_successfully: "Приватные заметки Включены в %s успешно!" + disabled_successfully: "Приватные заметки Отключены в %s успешно!" + not_enabled: "Private notes are not enabled." + privatenotes_notif: "You have been successfully connected to {chat} notes! To disconnect please use command /disconnect!" + enabled: 'Включены' + disabled: 'Выключены' + #/cleannotes + clean_notes_enable: "Очистка заметок успешно включена в {chat_name}" + clean_notes_disable: "Очистка заметок успешно отключена в {chat_name}" + clean_notes_enabled: "Очистка заметок успешно включена в {chat_name}" + clean_notes_disabled: "Очистка заметок успешно отключена в {chat_name}" + #Filter + filters_title: 'Отправить заметку' + filters_setup_start: 'Пожалуйста, отправьте название заметки.' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "Фильтры не были найдены в {chat_name}!" + #/addfilter + anon_detected: Будучи анонимным администратором, вы не можете добавлять новые фильтры, вместо этого используйте соединения. + regex_too_slow: "Your regex pattern matches too slowly (more than the half of second) and it can't be added!" + cancel_btn: "🛑 Cancel" + adding_filter: | + Добавление фильтра {handler} в {chat_name} + Выберите действие ниже: + saved: "New filter was successfully saved!" + #/filters + list_filters: "Фильтры в {chat_name}:\n" + #/delfilter + no_such_filter: I can't find that filter in {chat_name}, you can check what filters are enabled with the /filters command. + del_filter: "Фильтр '{handler}' был успешно удалён!" + select_filter_to_remove: | + Я нашла много фильтров '{handler}'. + Пожалуйста, выберите один для удаления: + #/delfilters or /delallfilters + not_chat_creator: Only chat creators can use this command! + delall_header: This would delete all filters in this chat. This IS irreversible. + confirm_yes: '⚠ Delete all' + confirm_no: '❌ Cancel' + delall_success: Successfully deleted all ({count}) filters in this chat! + warns: + #/warn + warn_sofi: "Хаха невозможно дать предупреждение самой себе!" + warn_self: "Ты хочешь дать предупреждение себе? Просто выйди отсюда." + warn_admin: "Ты серьёзно? Ты не можешь дать предупреждение админу." + warn: "{admin} выдал варн {user} в {chat_name}\n" + warn_bun: "Предупреждения превышены! {user} был забанен!" + warn_num: "Предупреждения: {curr_warns}/{max_warns}\n" + warn_rsn: "Причина: `{reason}`\n" + max_warn_exceeded: Предупреждения превышены! %s был %s! + "max_warn_exceeded:tmute": Предупреждения превышены! %s был заткнут на %s! + #warn rmv callback + warn_no_admin_alert: "⚠ You are NOT admin and cannot remove warnings!" + warn_btn_rmvl_success: "✅ Warning removed by {admin}!" + #/warns + warns_header: "Вот ваши предупреждения в этом чате\n\n" + warns: "{count} : {reason} {admin}\n" + no_warns: "Что ж, у {user} нет предупреждений." + #/warnlimit + warn_limit: "Лимит предупреждений в {chat_name}: `{num}`" + warnlimit_short: 'Лимит предупреждений должен быть не менее 2!' + warnlimit_long: 'Лимит предупреждений должен быть меньше 1 000!' + warnlimit_updated: '✅ Warnlimit successfully updated to {num}' + #/delwarns + purged_warns: "{admin} удалил {num} предупреждений у {user} в {chat_title}!" + usr_no_wrn: "У {user} пока нет предупреждений." + rst_wrn_sofi: 'У Daisy никогда не было предупреждений для сброса.' + not_digit: Лимит предупреждений должен быть цифрами! + #/warnmode + same_mode: Это текущий режим! Как изменить его? + no_time: Для выбора режима «tmute» вы должны указать время! + invalid_time: Invalid time! + warnmode_success: Warn mode of %s has successfully changed to %s! + wrng_args: | + Это доступные варианты: + %s + mode_info: | + Текущий режим в этом чате %s. + banned: забанен + muted: заглушён + filters_title: Предупредить пользователя + filter_handle_rsn: Автоматическое действие фильтра на '%s' + msg_deleting: + no_rights_purge: "У вас недостаточно прав для выполнения очистки в этой группе!" + reply_to_msg: "Ответьте на сообщение, которое нужно удалить!" + purge_error: "Я не могу продолжить очистку, обычно потому, что вы начали очистку от сообщения, которое было отправлено более 2 дней назад." + fast_purge_done: "Быстрая очистка завершена!\nЭто сообщение будет удалено через 5 секунд." + purge_user_done: "Все сообщения от {user} были успешно удалены!" + misc: + your_id: "Ваш ID: {id}\n" + chat_id: "ID группы: {id}\n" + user_id: "{user} ID: {id}\n" + conn_chat_id: "ID текущего подключённого чата: {id}" + help_btn: "Нажмите на меня, чтобы получить помощь!" + help_txt: "Нажмите кнопку ниже для помощи!" + delmsg_filter_title: 'Удалить сообщение' + send_text: Пожалуйста, отправьте в ответ слово/предложение ! + replymsg_filter_title: 'Ответить на сообщение' + send_customised_reason: Send the reason you want to include in action message. "None" for no reason to be given! + expected_text: Expected a text message! + promotes: + promote_success: "{user} был успешно повышен в {chat_name}!" + promote_title: "\nС пользовательской ролью {role}!" + rank_to_loong: "Название пользовательской роли не может быть длиннее 16 символов." + promote_failed: "Повышение не удалось! Проверьте, имею ли я права для этого." + demote_success: "{user} был успешно понижен в {chat_name}!" + demote_not_admin: "Этот пользователь не админ." + demote_failed: "Demotion failed. Maybe I can't demote or the person is promoted by someone else?" + cant_get_user: "Я не смогла получить пользователя! Пожалуйста, ответьте на его сообщение и повторите попытку." + cant_promote_yourself: "Вы не можете повысить себя." + emoji_not_allowed: "Извините, но роли администратора не могут иметь эмодзи 😞" + pm_menu: + start_hi_group: 'Привет! Меня зовут Daisy' + start_hi: "Привет! Меня зовут Sophie. Я помогу вам эффективно управлять вашей группой!" + btn_source: "📜 Source code" + btn_help: "❔ Помощь" + btn_lang: "🇷🇺 Язык" + btn_channel: "🙋‍♀️ Новости Daisy" + btn_group: "👥 Daisy Support" + btn_group_help: "Click me for help!" + back: Назад + #/help + help_header: "Привет! Меня зовут Daisy. Я бот для управления группами, здесь, чтобы помочь вам передвигаться и поддерживать порядок в ваших группах!\nУ меня есть множество удобных функций, таких как борьба с наводнениями, система предупреждений, система ведения заметок и даже заранее определенные ответы на определенные ключевые слова." + click_btn: Нажмите сюда + disable: + #/disableable + disablable: "Команды, доступные для отключения:\n" + #/disabled + no_disabled_cmds: Нет отключённых команд в чате {chat_name}! + disabled_list: "Отключённые команды в чате {chat_name}:\n" + #/disable + wot_to_disable: "Что вы хотите отключить?" + already_disabled: "Эта команда уже отключена!" + disabled: "Команда {cmd} была отключена в {chat_name}!" + #/enable + wot_to_enable: "Что вы хотите включить?" + already_enabled: "Эта команда не отключена!" + enabled: "Команда {cmd} была включена в {chat_name}!" + #/enableall + not_disabled_anything: "Ничего не было отключено в {chat_title}!" + enable_all_text: "This will enable all commands in the {chat_name}. Are you sure?" + enable_all_btn_yes: "Да. Включите все команды!" + enable_all_btn_no: "Нет!" + enable_all_done: "Все ({num}) команды были включены в чате {chat_name}!" + bot_rights: + change_info: "У меня нет права менять информацию группы, сделайте меня админом с этим правом." + edit_messages: "У меня нет прав для редактирования сообщений!" + delete_messages: "У меня нет прав на удаление сообщений здесь." + ban_user: "У меня нет прав блокировать пользователей, пожалуйста, сделайте меня админом с этим правом." + pin_messages: "У меня нет прав закреплять сообщения, пожалуйста, сделайте меня админом с этим правом." + add_admins: "У меня нет прав добавлять администраторов, пожалуйста, сделайте меня администратором с этими правами." + feds: + #decorators + need_fed_admin: "You are not an admin in {name} federation" + need_fed_owner: "You are not an owner of {name} federation" + cant_get_user: "Sorry, I can't get that user, try using their user ID" + #/fnew + fed_name_long: "Название федерации не может быть длиннее 60 символов!" + can_only_1_fed: "Users can only create 1 federation, please remove one." + name_not_avaible: "Федерация с именем {name} уже существует! Пожалуйста, используйте другое имя." + created_fed: | + Congrats, you have successfully created a federation. + Name: {name} + ID: {id} + Creator: {creator} + Use /fjoin {id} to connect federation to chat\ + disallow_anon: As an anonymous admin you cannot create new federation! + #/fjoin + only_creators: "You must be the chat creator to be able to connect chat to a federation." + fed_id_invalid: "The given federation ID is invalid! Please give me a valid ID." + joined_fed_already: "This chat has already joined a federation! Please use /fleave to leave that federation" + join_fed_success: "Great! Chat {chat} is now a part of {fed} federation!" + join_chat_fed_log: | + Chat joined federation #ChatJoined + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "Этот чат ещё не вступил в федерацию." + leave_fed_success: "Чат {chat} покинул федерацию {fed}." + leave_chat_fed_log: | + Чат вышел из федерации #ChatLeft + Федерация: {fed_name} ({fed_id}) + Чат: {chat_name} ({chat_id}) + #/fsub + already_subsed: "Федерация {name} уже подписана на {name2}" + subsed_success: "Федерация {name} подписана на {name2}!" + #/funsub + not_subsed: "Federation {name} is not subscribed to {name2}" + unsubsed_success: "Federation {name} unsubscribed from {name2}!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "User {user} added to {name} federation admins." + promote_user_fed_log: | + Пользователь повышен до админа федерации #UserPromoted + Федерация: {fed_name} ({fed_id}) + Пользователь: {user} ({user_id}) + "restricted_user:promote": This user is restricted from being federation admin! + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "User {user} demoted from {name} federation admins." + demote_user_fed_log: | + Пользователь исключён из админов федерации #UserDemoted + Федерация: {fed_name} ({fed_id}) + Пользователь: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "{name} federation already has logging enabled in another chat/channel!" + set_chat_log: | + This chat is now logging all actions in {name} federation + + Federation logging is used for internal assessment of the federation and its performance. + The federation should NOT be used as a public wall of shame thereby respecting user data and our privacy guidelines. + set_log_fed_log: | + Включён лог #LoggingEnabled + Федерация: {fed_name} ({fed_id}) + no_right_to_post: У меня нет прав на публикацию сообщений в этом канале! + #/funsetlog + not_logging: "{name} federation isn't logging to any chat/channel!" + logging_removed: "Successfully removed logging from {name} federation." + unset_log_fed_log: | + Отключено логгирование #LoggingDisabled + Федерация: {fed_name} ({fed_id}) + #/fchatlist + no_chats: "There's no chats joined this {name} federation" + chats_in_fed: "Chats connected to {name} federation:\n" + too_large: "Вывод слишком велик, отправляю файлом" + #/finfo + finfo_text: | + Информация о Федерации + Имя: {name} + ID: {fed_id} + Создатель: {creator} + Чаты в федерации: {chats} + Заблокированные пользователи в федерации: {fbanned} + finfo_subs_title: "Federations subscribed to this feds:\n" + #/fadminlist + fadmins_header: "Admins in {fed_name} fed:\n" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "Этот пользователь в белом списке." + fban_self: "Это хорошее шоу!" + fban_creator: "How can I ban the federation creator?! I don't think it's gonna be funny." + fban_fed_admin: "I'm not going to ban a federation admin from their own fed!" + update_fban: "This user was already fbanned therefore I am updating the reason to {reason}" + already_fbanned: "{user} уже забанен в этой федерации." + fbanned_header: "Новый Федеральный бан\n" + fban_info: | + Федерация: {fed} + Админ федерации: {fadmin} + Пользователь: {user} ({user_id}) + fbanned_reason: "Причина: {reason}" + fbanned_process: "\nСтатус: Бан в {num} федеральных чатах..." + fbanned_done: "\nСтатус: Готово! забанен в {num} чатах!" + fbanned_silence: "Это сообщение будет очищено через 5 секунд" + fbanned_subs_process: "\nСтатус: Бан в {feds} подписанных федерациях..." + fbanned_subs_done: "\nStatus: Done! Banned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + fban_usr_rmvd: | + User {user} is currently banned in {fed} federation and have been removed! + Reason: {rsn} + fban_log_fed_log: | + Блокировка в федерации #FedBan + Федерация: {fed_name} ({fed_id}) + Чаты: пользователь забанен в {chat_count}/{all_chats} чатах + fban_reason_fed_log: "Причина: {reason}\n" + fban_subs_fed_log: "\nПодписанные федерации: забанен в {subs_chats} чатах {feds} федераций" + #/unfban + unfban_self: "Think of something else to have fun with instead of this." + user_not_fbanned: "{user} isn't banned in this federation." + un_fbanned_header: "Новый раз-FedBan\n" + un_fbanned_process: "\nСтатус: Разбан в {num} чатах..." + un_fbanned_done: "\nСтатус: Готово! Разбанен в {num} чатах!" + un_fbanned_subs_process: "\nСтатус: Разбанивание в {feds} подписанных федерациях..." + un_fbanned_subs_done: "\nСтатус: Готово! Разбанен в {chats} чатах этой федерации и {subs_chats} чатах {feds} подписанных федераций" + un_fban_log_fed_log: | + Разбанить пользователя в федерации #FedUnBan + Федерация: {fed_name} ({fed_id}) + Чаты: пользователь разбанен в {chat_count}/{all_chats} чатах + un_fban_subs_fed_log: "\nПодписанные федерации: разблокирован в {subs_chats} чатах {feds} федераций" + #/delfed + delfed_btn_yes: Да. Удалить федерацию + delfed_btn_no: Нет. + delfed: | + Are you sure you want to delete the %s federation? + + Disclaimer: Deleting this federation will remove all data associated with it from our database! + This action is permanent and cannot be reversed! + delfed_success: Федерация успешно удалена! + #/frename + frename_same_name: Please enter a different name, not the same one! + frename_success: The federation {old_name} renamed to {new_name} successfully! + #/fbanlist + fbanlist_locked: Please wait until %s to use fbanlist again. + creating_fbanlist: Создание списка забаненых пользователей! Пожалуйста, подождите .. + fbanlist_done: Fbanlist of federation %s! + #/importfbans + send_import_file: | + Send the import file! + + To cancel this process use /cancel + rpl_to_file: Пожалуйста, ответьте на файл! + importfbans_locked: Импорт fban заблокирован на %s в этой федерации. + big_file_csv: Only supports import csv files less {num} megabytes! + big_file_json: | + Currently json files are limited to {num} megabytes! + Use the csv format if you want to import bigger files. + invalid_file: "Недопустимый файл" + wrong_file_ext: "Неправильный формат файла! В настоящее время имеется поддержка только: json, csv" + importing_process: Импорт запретов федерации... + import_done: Importing fed bans finished! {num} bans were imported. + #def + automatic_ban: User {user} is banned in the current federation {fed_name} and has been removed! + automatic_ban_sfed: User {user} is banned in the sub federation {fed_name}{text}" + #/fcheck + fcheck_header: "Информация о федерационном бане:\n" + fbanned_count_pm: "Вы были забанены в {count} федераций!\n" + "fban_info:fcheck": "You have been fbanned in {fed} federation on {date}, No reason was given!" + "fban_info:fcheck:reason": "Вы были забанены в федерации {fed} в {date} по причине -\n {reason}\n" + fbanned_data: "{user} был забанен в {count} федерации(ях)\n" + fbanned_nowhere: "{user} isn't fbanned anywhere!\n" + fbanned_in_fed: "Including the {fed} federation!\n" + "fbanned_in_fed:reason": "Including the {fed} federation for reason of -\n {reason}" + contact_in_pm: "\n\nFor more info on your fbans, please send me a direct message for privacy!" + too_long_fbaninfo: "Ну, ваш fbaninfo требует создание файла!" + didnt_fbanned: "You aren't fbanned in this fed." + not_fbanned_in_fed: They aren't fbanned in the {fed_name} fed + reports: + user_user_admin: "Вы админ здесь, почему Вам нужно пожаловаться на кого-то?.." + user_is_whitelisted: "Вы в белом списке, почему Вы жалуетесь на кого-то?.." + reports_disabled: "Репорты отключены в этой группе." + no_user_to_report: "На какого пользователя Вы хотите пожаловаться?" + report_admin: "You cannot report admins." + report_whitedlisted: "Вы не можете пожаловатся на пользователя в белом списке." + reported_user: "Я настучала на {user} админам>." + reported_reason: "\nПричина:\n{reason}" + reports_on: "Репорты включены в этой группе." + reports_off: "Репорты выключены в этой группе." + reports_already_on: "Репорты уже включены." + reports_turned_on: "Репорты включены." + reports_already_off: "Репорты уже выключены." + wrong_argument: " Не правильный аргумент." + language: + your_lang: "Ваш текущий язык: {lang}" + chat_lang: "Текущий язык чата: {lang}" + select_pm_lang: "\nВыберите ваш новый язык:" + select_chat_lang: "\nВыберите новый язык чата:" + crowdin_btn: "🌎 Помогите нам с переводом" + help_us_translate: "\n\n🌎 Help us improve our translations" + not_supported_lang: This language is not supported! + lang_changed: Язык был изменён на {lang_name}. + see_translators: '👥 Список переводчиков' + back: Назад + connections: + pm_connected: "Вы были успешно подключены к {chat_name}!" + connected_pm_to_me: "Вы были успешно подключены к {chat_name}! Пожалуйста, напишите мне в личные сообщения, чтобы начать использовать соединения." + chat_connection_tlt: "Подключение к чату\n" + connected_chat: | + Подключённый чат: + {chat_name} + Напишите /disconect для отключения от чата + "connected_chat:cmds": | + Current connected chat: + {chat_name}, (bridged commands are {commands}), + ⚠ You can only access bridged commands of the connected chat! All other commands would be treated as Local chat + Write /disconect to disconnect from chat. + u_wasnt_connected: "You were not connected to any chat before!" + select_chat_to_connect: "\nВыберите чат для подключения:" + disconnected: "Вы были отключены от {chat_name}." + cant_find_chat: "Я не могу найти этот чат." + cant_find_chat_use_id: "Я не могу найти этот чат. Используйте ID чата." + bad_arg_bool: "Неверный аргумент: разрешено только on/off enable/disable" + enabled: включено + disabled: выключено + chat_users_connections_info: "Соединения для обычных пользователей в настоящее время {status} для {chat_name}" + chat_users_connections_cng: "Соединения для обычных пользователей изменены на {status} для чата {chat_name}" + not_in_group: "Вы не в группе, к которой вы пытаетесь подключиться, войдите и напишите любое сообщение там." + history_empty: "Вы не подключены ни к какому чату для истории, подключитесь, используя `/connect (chat id)`" + not_in_chat: "Вы больше не в этом чате, я отключу вас." + u_should_be_admin: "Вы должны быть админом в {}!" + usage_only_in_groups: "Использование ограничено только в группах!" + anon_admin_conn: | + Please click below button to connect! + click_here: Click here! + global: + u_not_admin: Вы должны быть админом, чтобы сделать это! + #Rights + "bot_no_right:not_admin": Я не админ! + bot_no_right: "У меня нет требуемого разрешения на это! \nОтсутствующее разрешение: %s" + user_no_right: "У вас должно быть необходимое разрешение для этого! \nОтсутствующее разрешение: %s" + "user_no_right:not_admin": Вы должны быть админом, чтобы сделать это! + #is chat creator + unable_identify_creator: "Unable to identify anonymous chat creator, duplicate signature (titles) found!" + owner_stuff: + father: "\nОн мой создатель." + sudo_crown: "\nО, зырь, у этого юзера на голове корона. Ща посмотрю.... Вау, там гравировка - 'sudo user'!" + stickers: + rpl_to_sticker: "Ответьте на стикер!" + ur_sticker: | + Эмодзи: {emoji} + ID стикера: {id} + pins: + no_reply_msg: "Ответьте на сообщение, чтобы закрепить его!" + chat_not_modified_pin: "Это сообщение уже закреплено!" + chat_not_modified_unpin: "Нет закреплённого сообщения для открепления!" + pinned_success: "Успешно закреплено!" + unpin_success: "Успешно откреплено!" + users: + user_info: "Информация о пользователе:\n" + info_id: "ID: {id}" + info_first: "\nИмя: {first_name}" + info_last: "\nФамилия: {last_name}" + info_username: "\nИмя пользователя: {username}" + info_link: "\nСсылка на пользователя: {user_link}" + info_saw: "\nЯ видела их в {num} группах" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_fbanned: "\nЗабанен в текущей Федерации: " + info_admeme: "\nПользователь имеет права админа в этом чате." + upd_cache: "Обновление кэша..." + upd_cache_done: "Кэш админов был обновлён." + admins: "Админ в этой группе:\n" + imports_exports: + #Exporting + started_exporting: Экспорт начался! Пожалуйста, подождите... + export_done: Экспорт из {chat_name} завершён! + exports_locked: Please wait %s before using exports again! + #Importing + invalid_file: "Недопустимый файл" + send_import_file: Please send a file for importing! + rpl_to_file: Пожалуйста, ответьте на файл! + big_file: Only files under 50 MB are supported! + started_importing: Импорт начался! Пожалуйста, подождите... + bad_file: Некорректный файл! Ожидался столбец 'Общие'. + file_version_so_new: Exported file version is newer than what Daisy currently supports! + imports_locked: Please wait %s before using imports again! + import_done: Импорт завершён! + restrictions: + user_not_in_chat: User is not in this chat! + #Kicks + kick_DaisyX: Instead of trying kick me you could spend your time better. Thats just boring. + kick_admin: Выкинуть админа — это не лучшая идея. + kick_self: If you want to kick yourself - just leave this chat. I would rather not see you than look at these pathetic attempts at self-punishment. + user_kicked: "{user} был кикнут {admin} в {chat_name}." + #Mutes + mute_DaisyX: Actually, I'll be happy to shut up you instead. + mute_admin: If you think you can shut up an admin, you are wrong! + mute_self: If you want to mute yourself - just stop talking! Better to thought a fool than open your mouth and remove all doubt. + user_muted: "{user} был заглушён пользователем {admin} в {chat_name}." + unmute_DaisyX: Actually, I'm not muted. + unmute_admin: Haha no, try muting him first! + unmute_self: Может я сначала заткну тебя? + user_unmuted: "{user} был размучен пользователем {admin} в {chat_name}." + #Bans + ban_DaisyX: No, I won't do it! Ask the chat creator to do it! + ban_admin: Хах, давай сначала сделаем ему /demote. + ban_self: Почему ты пытаешься забанить сам себя? + user_banned: "{user} был забанен пользователем {admin} в {chat_name}." + unban_DaisyX: Actually, I'm not banned. + unban_admin: Haha no, try banning him first! + unban_self: Может сначала я забаню тебя? + user_unband: "{user} был разбанен {admin} в {chat_name}." + invalid_time: Неправильное время! + enter_time: Please specify a time! + on_time: "\nНа %s" + reason: "\nПричина: %s" + purge: "\nMessages will be purged in 5 seconds!" + filter_title_ban: Забанить пользователя + filter_action_rsn: Автоматическое действие фильтра на '%s' + filtr_ban_success: '%s забанил %s на %s' + filtr_mute_success: '%s заткнул %s за %s' + filtr_tmute_success: | + %s заткнул %s на %s + Причина: %s + time_setup_start: Please specify a time! + filtr_tban_success: | + %s забанил %s на %s + Причина: %s + filter_title_tmute: Временно заткнуть юзера + filter_title_mute: Заткнуть юзера + filter_title_tban: Временно забанить юзера + filter_title_kick: Кикнуть пользователя + rules: + #Set + saved: Правила были сохранены в %s + updated: Правила были обновлены в %s + #Reset + deleted: Правила были удалены! + not_found: Правила не найдены! + #Callback + user_blocked: "Напишите /start мне в личные сообщения!" + rules_was_pmed: Правила были отправлены вам в личку! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Welcome {mention}! How are you?" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + default_security_note: "Welcome {mention}! Please press button below to verify yourself as human!" + bool_invalid_arg: Недопустимый аргумент, ожидается on (включить)/off (выключить). + #/setwelcome + saved: Приветственное сообщение было сохранено в %s + updated: Приветственное сообщение было обновлено в %s + #/turnwelcome + turnwelcome_status: 'Приветствия: {status} в {chat_name}' + turnwelcome_enabled: Приветствия включены в %s + turnwelcome_disabled: Приветствия выключены в %s + #/welcome + enabled: включены + disabled: выключены + wlcm_security_enabled: "включена, установлены на уровень {level}" + wlcm_mutes_enabled: "включена, установлены на время {time}" + welcome_info: | + Настройки приветствий в {chat_name}: + Приветствия {welcomes_status} + Безопасность при приветствии {wlcm_security} + Затыкание при приветствии {wlcm_mutes} + Удаление старых приветствий {clean_welcomes} + Удаление служебных сообщений {clean_service} + wlcm_note: "\n\nЗапись для приветствия:" + raw_wlcm_note: "Исходник записи приветствия:" + security_note: "Текст приветствия безопасности:" + #/setsecuritynote + security_note_saved: "Пользовательский текст безопасности был сохранён %s" + security_note_updated: "Пользовательский текст безопасности был обновлён %s" + #/delsecuritynote + security_note_not_found: Security text in %s has not been set before + del_security_note_ok: Security text was reset to default in %s + #/cleanwelcome + cleanwelcome_enabled: Очистка приветствий включена в %s + cleanwelcome_disabled: Очистка приветствий выключена в %s + cleanwelcome_status: 'Очистка приветствий: {status} в {chat_name}' + #/cleanservice + cleanservice_enabled: Очистка служебных сообщений включена в %s + cleanservice_disabled: Очистка служебных сообщений выключена в %s + cleanservice_status: 'Очистка служебных сообщений: {status} в {chat_name}' + #/welcomemute + welcomemute_invalid_arg: Invalid argument, expected a time. + welcomemute_enabled: Заглушение при приветствии включено в %s + welcomemute_disabled: Заглушение при приветствии выключено в %s + welcomemute_status: 'Заглушение при приветствии: {status} в {chat_name}' + #/welcomesecurity + welcomesecurity_enabled: Приветственная безопасность включена в {chat_name} на уровне {level}. + "welcomesecurity_enabled:customized_time": | + Welcome security enabled in {chat_name} to {level}, and kick users which aren't verified within {time}. + ask_for_time_customization: | + Do you want to customize the time period to kick unverified user? (by default it's {time}) + send_time: Please specify a time. + invalid_time: "Invalid time!" + welcomesecurity_enabled_word: включена, установлена на уровне {level} + welcomesecurity_disabled: Защита входа отключена в %s. + welcomesecurity_invalid_arg: Недопустимый аргумент, ожидается button/captcha. + welcomesecurity_status: Защита входа {status} в {chat_name} + yes_btn: "Yes" + no_btn: "No" + #Welcomesecurity handler + click_here: Нажмите сюда! + not_allowed: Вам не разрешено использовать это! + ws_captcha_text: Привет, {user}, пожалуйста, введите номер капчи, чтобы иметь возможность писать в чате. + regen_captcha_btn: ':counterclockwise_arrows_: Изменить капчу' + num_is_not_digit: Пожалуйста, введите только цифры! + bad_num: Неправильный номер! Пожалуйста, попробуйте ещё раз. + passed: Проверка пройдена, вы получили право писать в %s! + passed_no_frm: Проверка пройдена, вы получили право писать в %s! + last_chance: У вас есть последний шанс ввести капчу! + btn_button_text: "Пожалуйста, нажмите кнопку ниже, чтобы подтвердить себя." + btn_wc_text: | + Пожалуйста, нажмите кнопку, соответствующую этому выражению: + %s + math_wc_rtr_text: "У вас последний шанс! Будьте осторожны.\n" + math_wc_wrong: Неправильно, попробуйте ещё раз! + math_wc_sry: Извините, вы использовали последнюю попытку. Спросите админов чата. + not_admin_wm: I can't restrict people here, so I won't use welcome mute! + not_admin_ws: I can't restrict people here, so I won't use welcome security! + not_admin_wsr: Я не могу удалять сообщения здесь, так что я не буду удалять сервисные сообщения! + #/resetwelcome + deleted: Successfully reset greetings in {chat}! + not_found: Нет ничего для сброса! + #... + verification_done: Verification is done! You have been unmuted. + locks: + locks_header: "Текущие настройки блокировки в {chat_title} \n\n" + no_lock_args: Дай мне аргументы для блокировки! + already_locked: Это уже заблокировано + no_such_lock: Смотрите /locks, чтобы узнать, что может быть заблокировано и разблокировано! + locked_successfully: '{lock} успешно заблокирован в {chat}!' + no_unlock_args: Сначала дай мне агрументы для разблокировки! + not_locked: Емае, это не запрещено в чате. + unlocked_successfully: Разблокировано {lock} в {chat} успешно! + antiflood: + #enforcer + flood_exceeded: "{action} {user} for flooding!" + #/setflood + "invalid_args:setflood": "Expected a number!" + overflowed_count: Maximum count should be less than 200! + config_proc_1: Please send expiration time, '0' (zero) for not being expired. + cancel: '❌ Cancel' + setup_success: Successfully configured antiflood, now enforces against those who send {count} messages within {time}! + "setup_success:no_exp": Successfully configured antiflood, allows {count} messages consecutively! + invalid_time: Invalid time! + #/antiflood | /flood + not_configured: Antiflood isn't configured in this chat yet! + turned_off: Disabled antiflood in {chat_title}! + "configuration_info:with_time": Antiflood is configured in this chat, those who send {count} message within {time} will be {action}! + configuration_info: Antiflood is configured in this chat, those who send {count} consecutively will be {action}! + ban: banned + mute: muted + kick: kicked + tmute: tmuted + tban: tbanned + #/setfloodaction + invalid_args: "Unsupported action, expected {supported_actions}!" + setfloodaction_success: "Successfully updated flood action to {action}!" + + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." diff --git a/DaisyX/localization/si_SI.yaml b/DaisyX/localization/si_SI.yaml new file mode 100644 index 00000000..1b8e285d --- /dev/null +++ b/DaisyX/localization/si_SI.yaml @@ -0,0 +1,764 @@ +--- +language_info: + flag: "🇱🇰" + code: si + +STRINGS: + notes: + note_saved: "🗒 සටහන {note_name} හි save කර ඇත {chat_title}!" + note_updated: "🗒 සටහන {note_name} {chat_title} හි යාවත්කාලීන කර ඇත!" + you_can_get_note: "\n ඔබට / {note_name} හෝ # {note_name} භාවිතා කිරීමෙන් ඔබට මෙම සටහන ලබා ගත හැකිය." + # පරිවර්තක සටහන: කරුණාකර ඔබට නූල් අවසානයේ ඉඩක් නොමැති බවට වග බලා ගන්න + note_aliases: "\n අන්වර්ථයන් සටහන් කරන්න:" + blank_note: "හිස් සටහනක් සුරැකීමට අවසර නැත!" + notename_cant_contain: "Note name can't contain \"{symbol}\"!" + + + # /get + + cant_find_note: මට මෙම සටහන {chat_name} හි සොයාගත නොහැක! + no_note: "එම සටහන සොයාගත නොහැක." + no_notes_pattern: " %s රටාව අනුව සටහන් කිසිවක් හමු නොවීය." + u_mean: "\ n ඔබ අදහස් කළේ # {note_name} ද?" + + # /සටහන් + notelist_no_notes: " {chat_title} හි කිසිදු සටහනක් නොමැත!" + notelist_header: " {chat_name} හි සටහන් :" + notes_in_private: "සටහන් ලැයිස්තුව ලබා ගැනීමට පහත බොත්තම ක්ලික් කරන්න." + notelist_search: | + රටාව: {request} + ගැලපෙන සටහන්: + + # /පැහැදිලිව + note_removed: "සටහන # {note_name} {chat_name} හි ඉවත් කර ඇත!" + note_removed_multiple: | + {chat_name} හි බහු සටහන් ඉවත් කරන ලදි + ඉවත් කරන ලදි: {removed} + not_removed_multiple: " ඉවත් කර නැත: {not_removed} " + + # / clearall + clear_all_text: "මෙය සියලුම සටහන් {chat_name} වෙතින් ඉවත් කරයි. ඔබට විශ්වාසද?" + clearall_btn_yes: "ඔව්, මගේ සියලු සටහන් ඉවත් කරන්න!" + clearall_btn_no: "නැත!" + clearall_done: "{chat_name} in හි ඇති සියලුම ( {num} ) සටහන් ඉවත් කරන ලදි!" + + # /සොයන්න + search_header: | + request chat_name in හි සෙවුම් ඉල්ලීම : + රටාව: {request} + ගැලපෙන සටහන්: + + # / noteinfo + note_info_title: " සටහන් තොරතුරු \n" + note_info_note: "සටහන් නම්:%s \n" + note_info_content: "සටහන් අන්තර්ගතය: %s \n" + note_info_parsing: "විග්‍රහ කිරීමේ මාදිලිය: %s \n" + note_info_created: "නිර්මාණය කළේ: {දිනය} {user} by \n" + note_info_updated: "අවසන් වරට යාවත්කාලීන කළේ: {date} {user} \n විසින්" + user_blocked: "මගේ PM හි ලියන්න /start කරන්න!" + + # / පුද්ගලාරෝපණ + private_notes_false_args: "ඔබට විකල්ප 2 ක් ඇත අක්‍රීය කරන්න සහ සක්‍රීය කරන්න !" + already_enabled: "පුද්ගලික සටහන් දැනටමත් %s වලින් සක්‍රීය කර ඇත" + current_state_info: "පුද්ගලික සටහන් දැනට {chat} හි {state} වේ!" + enable_successfully: "පුද්ගලික සටහන් සක්‍රීය කර ඇත %s හි සාර්ථකව!" + disabled_successfully: "පුද්ගලික සටහන් අක්‍රීය කර ඇත %s හි සාර්ථකව!" + not_enabled: "පුද්ගලික සටහන් සක්‍රීය කර නැත." + privateatenotes_notif: "ඔබ {chat} සටහන් සමඟ සාර්ථකව සම්බන්ධ වී ඇත! විසන්ධි කිරීමට කරුණාකර /disconnect කරන්න විධානය භාවිතා කරන්න!" + enabled: 'සක්‍රීය' + disabled: 'අක්‍රීයයි' + + # / පිරිසිදු සටහන් + clean_notes_enable: " {chat_name} හි සටහන් පිරිසිදු කිරීම සාර්ථකව සක්‍රීය කර ඇත." + clean_notes_disable: " {chat_name} හි සටහන් පිරිසිදු කිරීම සාර්ථකව අක්‍රීය කර ඇත." + clean_notes_enabled: "සටහන් පිරිසිදු කිරීම දැනට {chat_name} හි සක්‍රීය කර ඇත " + clean_notes_disabled: "සටහන් පිරිසිදු කිරීම දැනට {chat_name} හි අක්‍රීය කර ඇත " + + # පෙරහන + filters_title: 'සටහනක් යවන්න' + filters_setup_start: 'කරුණාකර සටහන් නමක් එවන්න.' + + #delmsg_no_arg: "deletemsg බොත්තමට අඩංගු විය හැක්කේ 'පරිපාලක' හෝ 'පරිශීලක' තර්කය පමණි!" + #bot_msg: "මට කණගාටුයි, මට මෙම පණිවිඩය ලබා ගැනීමට නොහැකි විය, බොහෝ විට මෙය වෙනත් බොට් පණිවිඩයකි, එබැවින් මට එය සුරැකිය නොහැක." + filters: + no_filters_found: " {chat_name} හි පෙරහන් කිසිවක් හමු නොවීය!" + + # / addfilter + anon_detected: නිර්නාමික පරිපාලක වීම නිසා ඔබට නව පෙරහන් එක් කළ නොහැක, ඒ වෙනුවට සම්බන්ධතා භාවිතා කරන්න. + regex_too_slow: "ඔබේ රීජෙක්ස් රටාව ඉතා සෙමින් ගැලපේ (තත්පර භාගයකට වඩා වැඩි) එය එකතු කළ නොහැක!" + cancel_btn: "🛑 අවලංගු කරන්න" + add_filter: | + {chat_name} හි {handler} පෙරණය එක් කිරීම + පහත ක්‍රියාව තෝරන්න: + saved: "නව පෙරණය සාර්ථකව සුරකින ලදි!" + + # / පෙරහන් + list_filters: " {chat_name} හි පෙරහන්: \ n" + + # / ඩෙල්ෆිල්ටර් + no_such_filter: {chat_name} හි මට එම පෙරණය සොයාගත නොහැක, ඔබට /filters විධානය මඟින් සක්‍රීය කර ඇති පෙරහන් මොනවාදැයි පරීක්ෂා කළ හැකිය. + del_filter: "හසුරුවන්නා සමඟ පෙරණය ' {handler} ' සාර්ථකව ඉවත් කරන ලදි!" + select_filter_to_remove: | + ' {handler} ' හසුරුවන්නා සමඟ මම බොහෝ පෙරහන් සොයාගෙන ඇත. + ඉවත් කිරීමට කරුණාකර එකක් තෝරන්න: + + # / ඩෙල්ෆිල්ටර් හෝ / ඩෙලල්ෆිල්ටර් + not_chat_creator: මෙම විධානය භාවිතා කළ හැක්කේ චැට් නිර්මාණකරුවන්ට පමණි! + delall_header: මෙය මෙම සංවාදයේ ඇති සියලුම පෙරහන් මකා දමනු ඇත. මෙය ආපසු හැරවිය නොහැකි ය. + conf_yes: සියල්ල මකන්න + confirm_no: ❌ අවලංගු කරන්න + delall_success: මෙම සංවාදයේ ඇති සියලුම ({count}) පෙරහන් සාර්ථකව මකා දැමීය! + + warns: + # / අවවාද කරන්න + warn_sofi: "හාහා මට අනතුරු ඇඟවීමට ක්‍රමයක් නැත!" + warn_self: "ඔබට ඔබටම අනතුරු ඇඟවීමට අවශ්‍යද? එවිට කතාබස් වලින් ඉවත් වන්න." + warn_admin: "හොඳයි .. ඔබ වැරදියි. ඔබට පරිපාලකයෙකුට අනතුරු ඇඟවිය නොහැක." + warn: "{admin}< {chat_name} හි {user} අනතුරු අඟවා ඇත" + warn_bun: "අනතුරු ඇඟවීම් ඉක්මවා ඇත! {user} banned තහනම් කර ඇත!" + warn_num: "අනතුරු අඟවයි: {curr_warns}/{max_warns} \n" + warn_rsn: "හේතුව: {හේතුව} \ n" + max_warn_exceeded: අනතුරු ඇඟවීම් ඉක්මවා ඇත! {user} {action}! + max_warn_excended:tmute: අනතුරු ඇඟවීම් ඉක්මවා ඇත! {user} කාලය සඳහා tmute කර ඇත! + + # rmv ඇමතුමට අනතුරු අඟවන්න + warn_no_admin_alert: "⚠ ඔබ පරිපාලක නොවන අතර අනතුරු ඇඟවීම් ඉවත් කළ නොහැක!" + warn_btn_rmvl_success: "{admin} විසින් අනතුරු ඇඟවීම ඉවත් කරන ලදි!" + + # / අනතුරු අඟවයි + warns_header: "මෙම කතාබස් තුළ ඔබගේ අනතුරු ඇඟවීම් මෙන්න \n \n" + warns: "{count}: {reason} {admin} by \n" + no_warns: "හොඳයි, {user} ට කිසිදු අනතුරු ඇඟවීමක් නොමැත." + + # / අනතුරු ඇඟවීම + warn_limit: " {chat_name} හි අනතුරු ඇඟවීමේ සීමාව දැනට පවතී: {num} " + warnlimit_short: 'අනතුරු ඇඟවීම අවම වශයෙන් 2 ක් විය යුතුය!' + warnlimit_long: 'අනතුරු ඇඟවීමේ සීමාව 1 000 ට වඩා අඩු විය යුතුය!' + warnlimit_updated: '✅ Warnlimit සාර්ථකව {num} වෙත යාවත්කාලීන කරන ලදි' + + # / delwarns + purged_warns: "{admin} reset {num} {chat_title} හි {user} ගැන අනතුරු අඟවයි!" + usr_no_wrn: "{user} නැවත සැකසීමට කිසිදු අනතුරු ඇඟවීමක් නොමැත." + rst_wrn_sofi: "ඩේසි කිසි විටෙකත් නැවත සැකසීමට අනතුරු ඇඟවූයේ නැත." + not_digit: අනතුරු ඇඟවීම් ඉලක්කම් විය යුතුය! + + # / අනතුරු ඇඟවීමේ මාදිලිය + same_mode: මෙය වත්මන් ප්‍රකාරයයි! මම එය වෙනස් කරන්නේ කෙසේද? + no_time: tmute මාදිලිය තේරීම සඳහා ඔබට කාලය සඳහන් කළ යුතුය! + invalid_time: අවලංගු කාලය! + warnmode_success: %s හි අනතුරු ඇඟවීමේ මාදිලිය %s ලෙස සාර්ථකව වෙනස් කර ඇත! + wrng_args: | + ලබා ගත හැකි විකල්ප මේවා ය: + mode_info: | + මෙම සංවාදයේ වත්මන් මාදිලිය %s වේ. + banned: තහනම් + muted: නිශ්ශබ්ද + + filters_title: පරිශීලකයාට අවවාද කරන්න + filter_handle_rsn: ස්වයංක්‍රීය පෙරහන් ක්‍රියාව! + + msg_deleting: + no_rights_purge: "මෙම කණ්ඩායම තුළ පිරිසිදු කිරීමට ඔබට ප්‍රමාණවත් අයිතිවාසිකම් නොමැත!" + reply_to_msg: "මකා දැමීමට පණිවිඩයකට පිළිතුරු දෙන්න!" + purge_error: "මට පිරිසිදු කිරීම දිගටම කරගෙන යා නොහැක, බොහෝ දුරට ඔබ දින 2 කට වඩා පැරණි පණිවිඩයකින් පිරිසිදු කිරීම ආරම්භ කළ නිසාය." + fast_purge_done: " වේගවත් පිරිසිදු කිරීම අවසන්! \n මෙම පණිවිඩය තත්පර 5 කින් ඉවත් කෙරේ." + purge_user_done: "{user} from වෙතින් සියලුම පණිවිඩ සාර්ථකව මකා දමන ලදි!" + misc: + your_id: "ඔබගේ හැඳුනුම්පත: {id} \n" + chat_id: "කණ්ඩායම් හැඳුනුම්පත: {id} \n" + user_id: "{පරිශීලකයාගේ හැඳුනුම්පත: {id} \n" + conn_chat_id: "දැනට සම්බන්ධිත චැට් හැඳුනුම්පත: {id} " + help_btn: "උදව් සඳහා මාව ක්ලික් කරන්න!" + help_txt: "උදව් සඳහා පහත බොත්තම ක්ලික් කරන්න!" + + delmsg_filter_title: 'පණිවිඩය මකන්න' + send_text: කරුණාකර පිළිතුරු පණිවිඩය යවන්න! + replymsg_filter_title: 'පණිවිඩයට පිළිතුරු දෙන්න' + + send_customised_reason: ඔබට ක්‍රියාකාරී පණිවිඩයට ඇතුළත් කිරීමට අවශ්‍ය හේතුව යවන්න. <හේතුවක් නැත ලබා දීමට " කිසිවක් නැත "! + expect_text: කෙටි පණිවිඩයක් බලාපොරොත්තු විය! + + promotes: + Prom_success: "{user} සාර්ථකව {chat_name} in හි ප්‍රවර්ධනය කරන ලදි!" + Promot_title: "අභිරුචි භූමිකාව {role} සමඟ!" + rank_to_loong: "අභිරුචි භූමිකාව පෙළ සංකේත 16 ට වඩා වැඩි විය නොහැක." + Promot_failed: "ප්‍රවර්ධනය අසාර්ථකයි! මට අයිතිවාසිකම් තිබේදැයි පරීක්ෂා කරන්න." + demote_success: "{user} සාර්ථකව {chat_name} හි පහත හෙලනු ලැබීය." + demote_not_admin: "එම පරිශීලකයා පරිපාලක නොවේ." + demote_failed: "හැඟීම් අසාර්ථකයි. සමහර විට මට පහත් කළ නොහැක, නැතහොත් පුද්ගලයා වෙනත් අයෙකු විසින් උසස් කරනු ලැබේද?" + cant_get_user: "මට පරිශීලකයා ලබා ගත නොහැකි විය! ඔබ සිතන්නේ නම්, කරුණාකර ඔහුගේ පණිවිඩයට පිළිතුරු දී නැවත උත්සාහ කරන්න." + cant_promote_yourself: "ඔබට ඔබව ප්‍රවර්ධනය කළ නොහැක." + emoji_not_allowed: "මට කණගාටුයි, නමුත් පරිපාලක භූමිකාවන්ට ඉමෝජි තිබිය නොහැක 😞" + + pm_menu: + start_hi_group: 'හේයි! මගේ නම ඩේසි ' + start_hi: "හේයි! මගේ නම ඩේසි. \n මට ඔබේ කණ්ඩායම් ප්‍රයෝජනවත් අංග සමඟ කළමනාකරණය කිරීමට උදව් කළ හැකිය, \nFeel free to add me!" + btn_source: "📦 ප්‍රභව කේතය" + btn_help: "❔ උදව්" + btn_lang: "🇱🇰 භාෂාව" + btn_channel: "🙋‍♀️ ඩේසි ප්‍රවෘත්ති" + btn_group: "👥 ඩේසි සමූහය" + btn_group_help: "උදව් සඳහා මාව ක්ලික් කරන්න!" + back: "🏃‍♂️ ආපසු" + + # /උදව් + help_header: "හායි ! මම ඩේසි . බොහෝ පහසුකම්, මෙවලම් සහ Anime තේමාවක් සහිත සුපිරි බලගතු කණ්ඩායම් කළමනාකරණ බොට් සේවාවකි. ඉතින් ඔබ බලා සිටින්නේ ඇයි? මට ඔබට උදව් කිරීමට ඉඩ දෙන්න" + click_btn: මෙහි ක්ලික් කරන්න + + disable: + # / අක්‍රීය කළ හැකිය + disablable: " අක්‍රීය කළ හැකි විධාන: \ n" + + # / අක්‍රීයයි + no_disabled_cmds: {chat_name} හි අක්‍රීය විධානයන් නොමැත! + disabled_list: " {chat_name in හි අක්‍රීය කළ විධාන: \ n" + + # / අක්‍රීය කරන්න + wot_to_disable: "ඔබට අක්‍රිය කිරීමට අවශ්‍ය කුමක්ද?" + already_disabled: "මෙම විධානය දැනටමත් අක්‍රිය කර ඇත!" + disabled: "විධාන {cmd} {chat_name} හි අක්‍රීය කර ඇත!" + + # / සක්‍රීය කරන්න + wot_to_enable: "ඔබට සක්‍රීය කිරීමට අවශ්‍ය කුමක්ද?" + already_enabled: "මෙම විධානය අක්‍රීය කර නැත!" + enabled: "විධාන {cmd} {chat_name} තුළ සක්‍රීය කර ඇත!" + + # / enableall + not_disabled_anything: " {chat_title} හි කිසිවක් අක්‍රිය කර නැත!" + enable_all_text: "මෙය {chat_name} හි ඇති සියලුම විධාන සක්‍රීය කරයි. ඔබට විශ්වාසද?" + enable_all_btn_yes: "ඔව්. සියලුම විධාන සක්‍රීය කරන්න!" + enable_all_btn_no: "නැත!" + enable_all_done: " {chat_name} හි සියලුම ({num}) විධානයන් සක්‍රීය කර ඇත!" + bot_rights: + change_info: "කණ්ඩායම් තොරතුරු වෙනස් කිරීමට මට අයිතියක් නැත, කරුණාකර එම අයිතියෙන් මාව පරිපාලක කරන්න." + edit_messages: "පණිවිඩ සංස්කරණය කිරීමට මට අයිතියක් නැත!" + delete_messages: "මෙහි පණිවිඩ මකා දැමීමට මට අයිතියක් නැත." + ban_user: "පරිශීලකයින් තහනම් කිරීමට මට අයිතියක් නැත, කරුණාකර එම අයිතියෙන් මාව පරිපාලක කරන්න." + pin_messages: "පණිවිඩ යැවීමට මට අයිතියක් නැත, කරුණාකර එම අයිතියෙන් මාව පරිපාලක කරන්න." + add_admins: "මට පරිපාලකයින් එක් කිරීමට අයිතියක් නැත, කරුණාකර එම අයිතියෙන් මාව පරිපාලක කරන්න." + + feds: + # decorators + need_fed_admin: "You are not an admin in {name} federation" + need_fed_owner: "You are not an owner of {name} federation" + cant_get_user: "Sorry, I can't get that user, try using their user ID" + + # /fnew + fed_name_long: "Federation name can't be longer than 60 symbols!" + can_only_1_fed: "Users can only create 1 federation, please remove one." + name_not_avaible: "Federation with name {name} already exits! Please use another name." + created_fed: | + Congrats, you have successfully created a federation. + Name: {name} + ID: {id} + Creator: {creator} + Use /fjoin {id} to connect federation to chat\ + disallow_anon: As an anonymous admin you cannot create new federation! + + # /fjoin + only_creators: "You must be the chat creator to be able to connect chat to a federation." + fed_id_invalid: "The given federation ID is invalid! Please give me a valid ID." + joined_fed_already: "This chat has already joined a federation! Please use /fleave to leave that federation" + join_fed_success: "Great! Chat {chat} is now a part of {fed} federation!" + join_chat_fed_log: | + Chat joined federation #ChatJoined + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + # /leavefed + chat_not_in_fed: "This chat is not in any federation yet." + leave_fed_success: "Chat {chat} left the {fed} federation." + leave_chat_fed_log: | + Chat left Federation #ChatLeaved + Fed: {fed_name} ({fed_id}) + Chat: {chat_name} ({chat_id}) + # /fsub + already_subsed: "Federation {name} already subscribed to {name2}" + subsed_success: "Federation {name} subscribed to {name2}!" + + # /funsub + not_subsed: "Federation {name} is not subscribed to {name2}" + unsubsed_success: "Federation {name} unsubscribed from {name2}!" + + # /fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "User {user} added to {name} federation admins." + promote_user_fed_log: | + User promoted to the fed admins #UserPromoted + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + restricted_user:promote: This user is restricted from being federation admin! + + # /fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "User {user} demoted from {name} federation admins." + demote_user_fed_log: | + User demoted from the fed admins #UserDemoted + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + # /fsetlog + already_have_chatlog: "{name} federation already has logging enabled in another chat/channel!" + set_chat_log: | + This chat is now logging all actions in {name} federation + Federation logging is used for internal assessment of the federation and its performance. + The federation should NOT be used as a public wall of shame thereby respecting user data and our privacy guidelines. + set_log_fed_log: | + Enabled logging #LoggingEnabled + Fed: {fed_name} ({fed_id}) + no_right_to_post: I don't have rights to post messages in that channel! + + # /funsetlog + not_logging: "{name} federation isn't logging to any chat/channel!" + logging_removed: "Successfully removed logging from {name} federation." + unset_log_fed_log: | + Disabled logging #LoggingDisabled + Fed: {fed_name} ({fed_id}) + # /fchatlist + no_chats: "There's no chats joined this {name} federation" + chats_in_fed: "Chats connected to {name} federation:\n" + too_large: "Output too large, sending as file" + + # /finfo + finfo_text: | + Federation info + Name: {name} + ID: {fed_id} + Creator: {creator} + Chats in the fed: {chats} + Banned users in the fed: {fbanned} + finfo_subs_title: "Federations subscribed to this feds:\n" + + # /fadminlist + fadmins_header: "Admins in {fed_name} fed:\n" + + # no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + + # /fban + user_wl: "This user is whitelisted from banning." + fban_self: "That's a nice show!" + fban_creator: "How can I ban the federation creator?! I don't think it's gonna be funny." + fban_fed_admin: "I'm not going to ban a federation admin from their own fed!" + update_fban: "This user was already fbanned therefore I am updating the reason to {reason}" + already_fbanned: "{user} already banned in this federation." + fbanned_header: "New FedBan\n" + fban_info: | + Federation: {fed} + Fed admin: {fadmin} + User: {user} ({user_id}) + fbanned_reason: "Reason: {reason}" + fbanned_process: "\nStatus: Banning in {num} fed chats..." + fbanned_done: "\nStatus: Done! banned in {num} chats!" + + fbanned_silence: "This message will be purged in 5 seconds" + + fbanned_subs_process: "\nStatus: Banning in {feds} subscribed feds..." + fbanned_subs_done: "\nStatus: Done! Banned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + fban_usr_rmvd: | + User {user} is currently banned in {fed} federation and have been removed! + Reason: {rsn} + fban_log_fed_log: | + Ban user in the fed #FedBan + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + By: {by} + Chats banned: user banned in {chat_count}/{all_chats} chats + fban_reason_fed_log: "Reason: {reason}\n" + fban_subs_fed_log: "\nSubscribed feds: banned in {subs_chats} chats of {feds} federations" + + # /unfban + unfban_self: "Think of something else to have fun with instead of this." + user_not_fbanned: "{user} isn't banned in this federation." + un_fbanned_header: "New Un-FedBan\n" + un_fbanned_process: "\nStatus: Unbanning in {num} chats..." + un_fbanned_done: "\nStatus: Done! Unbanned in {num} chats!" + un_fbanned_subs_process: "\nStatus: Unbanning in {feds} subscribed feds..." + un_fbanned_subs_done: "\nStatus: Done! Unbanned in {chats} chats of this federation and {subs_chats} chats of {feds} subscribed feds" + + un_fban_log_fed_log: | + Unban user in the fed #FedUnBan + Fed: {fed_name} ({fed_id}) + User: {user} ({user_id}) + By: {by} + Chats: user unbanned in {chat_count}/{all_chats} chats + un_fban_subs_fed_log: "\nSubscribed feds: unbanned in {subs_chats} chats of {feds} federations" + + # /delfed + delfed_btn_yes: Yes. Delete federation + delfed_btn_no: No. + delfed: | + Are you sure you want to delete the %s federation? + Disclaimer: Deleting this federation will remove all data associated with it from our database! + This action is permanent and cannot be reversed! + delfed_success: Successfully deleted the federation! + + # /frename + frename_same_name: Please enter a different name, not the same one! + frename_success: The federation {old_name} renamed to {new_name} successfully! + + # /fbanlist + fbanlist_locked: Please wait until %s to use fbanlist again. + creating_fbanlist: Creating fban list! Please wait .. + fbanlist_done: Fbanlist of federation %s! + + # /importfbans + send_import_file: | + Send the import file! + To cancel this process use /cancel + rpl_to_file: Please reply to a file! + importfbans_locked: Importing fbans is locked for %s in this federation. + big_file_csv: Only supports import csv files less {num} megabytes! + big_file_json: | + Currently json files are limited to {num} megabytes! + Use the csv format if you want to import bigger files. + invalid_file: "The file is invalid" + wrong_file_ext: "Wrong file format! Currently support are: json, csv" + importing_process: Importing federation bans... + import_done: Importing fed bans finished! {num} bans were imported. + + # def + automatic_ban: User {user} is banned in the current federation {fed_name} and has been removed! + automatic_ban_sfed: User {user} is banned in the sub federation {fed_name}{text}" + + # /fcheck + fcheck_header: "Federation ban info:\n" + fbanned_count_pm: "You are fbanned in {count} federation(s)!\n" + fban_info:fcheck: "You have been fbanned in {fed} federation on {date}, No reason was given!" + fban_info:fcheck:reason: "You have been fbanned in {fed} federation on {date} because of reason -\n {reason}\n" + fbanned_data: "{user} has been fbanned in {count} federation(s).\n" + fbanned_nowhere: "{user} isn't fbanned anywhere!\n" + fbanned_in_fed: "Including the {fed} federation!\n" + fbanned_in_fed:reason: "Including the {fed} federation for reason of -\n {reason}" + contact_in_pm: "\n\nFor more info on your fbans, please send me a direct message for privacy!" + too_long_fbaninfo: "Well! your fbaninfo requires a file!" + didnt_fbanned: "You aren't fbanned in this fed." + not_fbanned_in_fed: They aren't fbanned in the {fed_name} fed + + reports: + user_user_admin: "ඔබ මෙහි පරිපාලකයෙක්, ඔබට යමෙකු වාර්තා කිරීමට අවශ්‍ය වූයේ ඇයි? .." + user_is_whitelisted: "ඔබ සුදු ලැයිස්තු ගත කළ පරිශීලකයෙක්, ඔබට යමෙකු වාර්තා කිරීමට අවශ්‍ය වූයේ ඇයි? .." + report_disabled: "වාර්තා මේ වන විට අක්‍රීය කර ඇත ." + no_user_to_report: "ඔබට වාර්තා කිරීමට අවශ්‍ය පරිශීලකයා කවුද?" + report_admin: "ඔබට පරිපාලකවරුන් වාර්තා කළ නොහැක." + report_whitedlisted: "ඔබට සුදු ලැයිස්තු ගත කළ පරිශීලකයෙකු වාර්තා කළ නොහැක." + report_user: " වාර්තා කර ඇත {user} පරිපාලකවරුන්ට." + report_reason: "\n හේතුව: {හේතුව} " + report_on: "මෙම කණ්ඩායමේ වාර්තා සක්‍රීය කර ඇත ." + report_off: "මෙම කණ්ඩායම තුළ වාර්තා අක්‍රිය කර ඇත ." + report_already_on: " වාර්තා දැනටමත් සක්‍රිය කර ඇත. " + report_turned_on: "වාර්තා සක්‍රිය කර ඇත. " + report_already_off: " වාර්තා දැනටමත් අක්‍රිය කර ඇත. " + wrong_argument: " වැරදි තර්කය. " + language: + your_lang: "ඔබගේ වර්තමාන භාෂාව: {lang} " + chat_lang: "වත්මන් කතාබස් භාෂාව: {lang} " + select_pm_lang: "\n ඔබගේ නව භාෂාව තෝරන්න: " + select_chat_lang: "\n නව කතාබස් භාෂාව තෝරන්න: " + crowdin_btn: "පරිවර්තනයට අපට උදව් කරන්න" + help_us_translate: "\n \n අපගේ පරිවර්තන වැඩි දියුණු කිරීමට අපට උදව් කරන්න " + not_supported_lang: මෙම භාෂාවට සහය නොදක්වයි! + lang_changed: භාෂාව {lang_name} ලෙස වෙනස් කරන ලදි. + see_translators: පරිවර්තකයන් බලන්න + back: ආපසු + connections: + pm_connected: "ඔබගේ අගමැති {chat_name} සමඟ සාර්ථකව සම්බන්ධ වී ඇත!" + connect_pm_to_me: "ඔබේ අගමැති {chat_name} සමඟ සාර්ථකව සම්බන්ධ වී ඇත! කරුණාකර සම්බන්ධතාවය භාවිතා කිරීම ආරම්භ කිරීමට මට සවස දෙන්න." + chat_connection_tlt: " චැට් සම්බන්ධතාවය \n" + connected_chat: | + දැනට සම්බන්ධිත කතාබස්: + {chat_name} + කතාබස් වලින් විසන්ධි කිරීමට ලියන්න /disconnect කරන්න + connect_chat:cmds: | + දැනට සම්බන්ධිත කතාබස්: + {chat_name}, (පාලම් විධාන {commands}), + ⚠ ඔබට ප්‍රවේශ විය හැක්කේ සම්බන්ධිත සංවාදයේ පාලම් විධාන පමණි! අනෙක් සියලුම විධානයන් දේශීය කතාබස් ලෙස සලකනු ලැබේ + කතාබස් වලින් විසන්ධි කිරීමට ලියන්න /disconnect කරන්න. + u_wasnt_connected: "ඔබ මීට පෙර කිසිදු කතාබහකට සම්බන්ධ නොවීය!" + select_chat_to_connect: "\n සම්බන්ධ වීමට චැට් එකක් තෝරන්න: " + disconnected: "ඔබ {chat_name} වෙතින් විසන්ධි විය." + cant_find_chat: "මට මෙම කතාබහ සොයාගත නොහැක." + cant_find_chat_use_id: "මට මෙම කතාබහ සොයාගත නොහැක. චැට් හැඳුනුම්පත භාවිතා කරන්න." + bad_arg_bool: "නරක තේරීම: සහය දක්වන්නේ මත / ඕෆ් සක්‍රීය කරන්න / අක්‍රීය කරන්න " + + enabled: සක්‍රීය + disabled: අක්‍රීයයි + chat_users_connections_info: "සාමාන්‍ය පරිශීලකයින් සඳහා සම්බන්ධතා දැනට {status} {chat_name} සඳහා" + chat_users_connections_cng: "සාමාන්‍ය පරිශීලකයින් සඳහා සම්බන්ධතා {status} සඳහා {chat_name} සඳහා වෙනස් කර ඇත." + + not_in_group: "ඔබ සම්බන්ධ වීමට, සම්බන්ධ වීමට සහ පණිවිඩයක් යැවීමට උත්සාහ කරන කණ්ඩායමේ ඔබ නොමැත." + history_empty: "ඔබ ඉතිහාසය සඳහා කිසිදු කතාබහකට සම්බන්ධ වී නැත,` /connect (chat id) `හරහා සම්බන්ධ වන්න." + not_in_chat: "ඔබ තවදුරටත් මෙම කතාබස් වල නැත, මම ඔබව විසන්ධි කරමි." + u_should_be_admin: "ඔබ {in හි පරිපාලකයෙකු විය යුතුය!" + use_only_in_groups: "භාවිතය කණ්ඩායම් වශයෙන් පමණක් සීමා වේ!" + + anon_admin_conn: | + සම්බන්ධ වීමට කරුණාකර පහත බොත්තම ක්ලික් කරන්න! + click_here: මෙහි ක්ලික් කරන්න! + global: + u_not_admin: මෙය කිරීමට ඔබ පරිපාලකයෙකු විය යුතුය! + + # අයිතිය + bot_no_right:not_admin: මම පරිපාලකයෙක් නොවේ! + bot_no_right: "මට මෙහි {permission} to කිරීමට අවසර නැත!" + user_no_right: "ඔබට මෙහි {permission} to සඳහා අවසර නැත!" + user_no_right:not_admin: එය කිරීමට ඔබ පරිපාලක විය යුතුය! + + # චැට් නිර්මාපකයෙකි + able_identify_creator: "නිර්නාමික කතාබස් නිර්මාපකයෙකු හඳුනාගත නොහැක, අනුපිටපත් අත්සන (මාතෘකා) හමු විය!" + owner_stuff: + farther: "\n ඔහු මගේ මැවුම්කරු." + sudo_crown: "\n හේයි, බලන්න, එම පරිශීලකයාට ඔටුන්නක් තිබේ, මට එය දැකීමට ඉඩ දෙන්න ... වාව්, එහි කැටයමක් ඇත - 'sudo user' !" + stickers: + rpl_to_sticker: "ස්ටිකරයකට පිළිතුරු දෙන්න!" + ur_sticker: | + ඉමොජි: {emoji} + ස්ටිකර් හැඳුනුම්පත: {id} + pins: + no_reply_msg: "පින් කිරීමට පණිවිඩයකට පිළිතුරු දෙන්න!" + chat_not_modified_pin: "එම පණිවිඩය දැනටමත් ඇලවී ඇත!" + chat_not_modified_unpin: "ඉවත් කිරීමට කිසිදු පණිවිඩයක් නොමැත!" + pinned_success: "සාර්ථකව පින් කර ඇත!" + unpin_success: "සාර්ථකව අනාරක්ෂිතයි!" + users: + user_info: " පරිශීලක තොරතුරු: \ n" + info_id: "ID: {id} " + info_first: "\n පළමු නම: {first_name}" + info_last: "\n අවසාන නම: {last_name}" + info_username: "\n පරිශීලක නාමය: {username}" + info_link: "\n පරිශීලක සබැඳිය: {user_link}" + info_saw: "\n මම ඒවා {num} කණ්ඩායම් තුළ දුටුවෙමි" + info_sw_ban: "\nමෙම පරිශීලකයා SpamWatch හි තහනම් කර ඇත!" + info_sw_ban_reason: "\n හේතුව: {sw_reason} " + info_fbanned: "\n වත්මන් සම්මේලනයේ තහනම් කර ඇත:" + info_admeme: "\n මෙම සංවාදයේදී පරිශීලකයාට පරිපාලක අයිතිවාසිකම් ඇත." + update_cache: "හැඹිලිය දැන් යාවත්කාලීන කරයි ..." + update_cache_done: "පරිපාලක හැඹිලිය යාවත්කාලීන කරන ලදි." + admins: " මෙම කණ්ඩායමේ පරිපාලක: \n" + imports_exports: + # අපනයනය + start_exporting: අපනයනය ආරම්භ විය! කරුණාකර රැඳී සිටින්න... + export_done: {chat_name from වෙතින් අපනයනය කර ඇත! + exports_locked: නැවත අපනයන භාවිතා කිරීමට පෙර කරුණාකර %s රැඳී සිටින්න! + + # ආනයනය කිරීම + invalid_file: "ගොනුව අවලංගුයි" + send_import_file: කරුණාකර ආයාත කිරීම සඳහා ගොනුවක් එවන්න! + rpl_to_file: කරුණාකර ගොනුවකට පිළිතුරු දෙන්න! + big_file: සහය දක්වන්නේ 50 MB ට අඩු ගොනු පමණි! + start_importing: ආනයනය කිරීම ආරම්භ විය! කරුණාකර රැඳී සිටින්න... + bad_file: නරක ගොනුව! අපේක්ෂිත 'සාමාන්‍ය' තීරුව. + file_version_so_new: අපනයනය කරන ලද ගොනු අනුවාදය ඩේසි දැනට සහාය දක්වන දෙයට වඩා අලුත් ය! + imports_locked: නැවත ආනයන භාවිතා කිරීමට පෙර කරුණාකර% s රැඳී සිටින්න! + import_done: ආයාත කිරීම අවසන්! + restrictions: + user_not_in_chat: පරිශීලකයා මෙම කතාබස් තුළ නොමැත! + + # කික්ස් + kick_DaisyX: මට පයින් ගැසීමට උත්සාහ කරනවා වෙනුවට ඔබේ කාලය වඩා හොඳින් ගත කළ හැකිය. ඒක නීරසයි. + kick_admin: පරිපාලකයාට පයින් ගැසීම හොඳම අදහස නොවේ. + kick_self: ඔබට ඔබටම පයින් ගැසීමට අවශ්‍ය නම් - මෙම කතාබස් වලින් ඉවත් වන්න. ස්වයං ද .ුවම සඳහා වන මෙම දයානුකම්පිත උත්සාහයන් දෙස බැලීමට වඩා මම ඔබව දකින්නේ නැත. + user_kicked: "{user} {chat_name} in හි {admin} විසින් පයින් ගසා ඇත." + + # නිශ්ශබ්දයි + mute_DaisyX: ඇත්තටම, මම ඒ වෙනුවට ඔබව වසා දැමීමට සතුටු වෙමි. + mute_admin: ඔබට පරිපාලකයෙකු වසා දැමිය හැකි යැයි ඔබ සිතන්නේ නම්, ඔබ වැරදියි! + mute_self: ඔබට නිශ්ශබ්ද කිරීමට අවශ්‍ය නම් - කතා කිරීම නවත්වන්න! ඔබේ මුඛය විවෘත කර සියලු සැකයන් දුරු කරනවාට වඩා මෝඩයෙකු යැයි සිතීම හොඳය. + user_muted: "{user} {chat_name} හි {admin} විසින් නිශ්ශබ්ද කරන ලදි." + unmute_DaisyX: ඇත්තටම මම නිශ්ශබ්ද නැහැ. + unmute_admin: හාහා නෑ, මුලින්ම ඔහුව නිශ්ශබ්ද කිරීමට උත්සාහ කරන්න! + unmute_self: මම මුලින්ම ඔබව නිශ්ශබ්ද කළ යුතුද? + user_unmuted: "{user} {chat_name} in හි {admin} විසින් ඉවත් කරන ලදි." + + # තහනම් + ban_DaisyX: නෑ, මම ඒක කරන්නේ නැහැ! එය කිරීමට චැට් නිර්මාතෘගෙන් ඉල්ලා සිටින්න! + ban_admin: හාහා, අපි මුලින්ම ඔහුව / demote කරමු. + ban_self: ඔබ ඔබම තහනම් කිරීමට උත්සාහ කරන්නේ ඇයි? + user_banned: "{user} {chat_name} හි {admin} by විසින් තහනම් කරන ලදි." + unban_DaisyX: ඇත්තටම මාව තහනම් නැහැ. + unban_admin: හාහා, පළමුව ඔහුව තහනම් කිරීමට උත්සාහ කරන්න! + unban_self: මම මුලින්ම ඔබව තහනම් කළ යුතුද? + user_unband: "{user} {chat_name} හි {admin} විසින් තහනම් කරන ලදි." + + invalid_time: අවලංගු කාලය! + enter_time: කරුණාකර වේලාවක් නියම කරන්න! + on_time: "\ n %s සඳහා" + reason: "ase n හේතුව: %s " + purge: තත්පර 5 කින් පණිවිඩ පිරිසිදු කරනු ලැබේ! + + filter_title_ban: පරිශීලකයා තහනම් කරන්න + filter_action_rsn: ස්වයංක්‍රීය පෙරහන් ක්‍රියාව! + filtr_ban_success: '%s %s සඳහා තහනම් %s' + filtr_mute_success: '%s %s සඳහා නිශ්ශබ්ද %s' + filtr_tmute_success: | + %s %s සඳහා නිශ්ශබ්ද %s + හේතුව %s වේ + time_setup_start: කරුණාකර වේලාවක් නියම කරන්න! + filtr_tban_success: | + %s සඳහා %s තහනම් කර ඇත + හේතුව %s වේ + filter_title_tmute: තාවකාලිකව පරිශීලකයා නිශ්ශබ්ද කරන්න + filter_title_mute: පරිශීලකයා නිශ්ශබ්ද කරන්න + filter_title_tban: තාවකාලිකව පරිශීලකයා තහනම් කරන්න + filter_title_kick: පරිශීලකයාට පයින් ගසන්න + rules: + # සකසන්න + saved: රීති % s තුළ සුරකින ලදි + updated: රීති % s තුළ යාවත්කාලීන කරන ලදි + + # යළි පිහිටුවන්න + deleted: රීති මකා දමන ලදි! + + not_found: රීති හමු නොවීය! + + # ආපසු අමතන්න + user_blocked: "මගේ PM හි ලියන්න /start කරන්න!" + rules_was_pmed: රීති ඔබගේ අගමැතිට යවන ලදි! + + # can_find_note: "මට {} සටහන සොයාගත නොහැක." + # set_note: "නීති සටහන {} ලෙස සාර්ථකව සකසන්න." + # didnt_set_note: "මෙම කණ්ඩායමට නීති සටහන් කට්ටලයක් නොමැත." + # can_find_set_note: "මට නීති සටහන සොයාගත නොහැක." + # havent_set_rules_note: "ඔබ තවමත් නීති සටහන සකස් කර නැත." + # success_remove_rules_note: " {} හි නීති රීති සාර්ථකව ඉවත් කරන ලදි." + greetings: + default_welcome: "සාදරයෙන් පිළිගනිමු {සඳහන් කරන්න}! ඔබට කොහොමද?" + thanks_for_add: "මාව ඔබේ කණ්ඩායමට එක් කිරීම ගැන ස්තූතියි! මගේ ප්‍රවෘත්ති නාලිකාව a ඩේසිඑක්ස් යාවත්කාලීන කිරීම් දෙස බලන්න / උදව් කරන්න." + default_security_note: "සාදරයෙන් පිළිගනිමු {සඳහන් කරන්න}! කරුණාකර ඔබ මිනිසෙකු බව තහවුරු කර ගැනීමට පහත බොත්තම ඔබන්න!" + + bool_invalid_arg: වලංගු නොවන තර්කය, මත / අක්‍රීය . + + # / setwelcome + saved: පිළිගැනීමේ පණිවිඩය %s තුළ සුරකින ලදි + updatedදි: පිළිගැනීමේ පණිවිඩය %s තුළ යාවත්කාලීන කරන ලදි + + # / turnwelcome + turnwelcome_status: පිළිගැනීම් යනු {chat_name} හි {status} වේ. + turnwelcome_enabled: %s තුළ පිළිගැනීම් සක්‍රීය කර ඇත + turnwelcome_disabled: සාදරයෙන් පිළිගනිමු %s + + # / සාදරයෙන් පිළිගනිමු + enabled: සක්‍රීය + disabled: අක්‍රීයයි + wlcm_security_enabled: "සක්‍රීය, {level} set ලෙස සකසා ඇත" + wlcm_mutes_enabled: "සක්‍රීය කර ඇත, {time} set ලෙස සකසන්න" + welcome_info: | + settings chat_name in හි පිළිගැනීමේ සැකසුම්: + පිළිගැනීම් {Welcome_status} වේ + පිළිගැනීමේ ආරක්ෂාව {wlcm_security} වේ + පිළිගැනීමේ නිශ්ශබ්දතාව {wlcm_mutes} වේ + පැරණි පිළිගැනීම් මකා දැමීම {clean_welcome} වේ + සේවා පණිවිඩ මකා දැමීම {clean_service} වේ + wlcm_note: "\n \n ආයුබෝවන් සටහන:" + raw_wlcm_note: "අමු පිළිගැනීමේ සටහන:" + security_note: "පිළිගැනීමේ ආරක්ෂක පෙළ:" + + # / setsecuritynote + security_note_saved: "අභිරුචි ආරක්ෂක පෙළ සුරකින ලදි %s" + security_note_updated: "අභිරුචි ආරක්ෂක පෙළ යාවත්කාලීන කරන ලදි %s" + + # / delsecuritynote + security_note_not_found: % s හි ආරක්ෂක පෙළ මීට පෙර සකසා නොමැත + del_security_note_ok: % s හි ආරක්ෂක පෙළ පෙරනිමියට යළි පිහිටුවන ලදි + + # / cleanwelcome + cleanwelcome_enabled: % s හි පිරිසිදු කිරීමේ පිළිගැනීම් සක්‍රීය කර ඇත + cleanwelcome_disabled: පිරිසිදු කිරීම % s හි අක්‍රීය කර ඇත + cleanwelcome_status: පිළිගැනීම් පිරිසිදු කිරීම යනු {chat_name} හි {status} වේ. + + # / පිරිසිදු කිරීමේ සේවය + cleanservice_enabled: % s හි සේවා පණිවිඩ පිරිසිදු කිරීම සක්‍රීය කර ඇත + cleanservice_disabled: % s හි සේවා පණිවිඩ පිරිසිදු කිරීම අක්‍රීය කර ඇත + cleanservice_status: සේවා පණිවිඩ පිරිසිදු කිරීම {chat_name} හි {status} වේ. + + # / පිළිගැනීම + welcomemute_invalid_arg: අවලංගු තර්කයක්, කාලයක් බලාපොරොත්තු වේ. + welcomemute_enabled: පිළිගැනීමේ නිශ්ශබ්දතාව % s තුළ සක්‍රීය කර ඇත + welcomemute_disabled: පිළිගැනීමේ නිශ්ශබ්දතාව % s හි අක්‍රීය කර ඇත + welcomemute_status: පිළිගැනීමේ නිශ්ශබ්දතාවය {chat_name} හි {status} වේ. + + # / පිළිගැනීමේ සුරක්ෂිතභාවය + Welcomeecurity_enabled: පිළිගැනීමේ ආරක්ෂාව {chat_name}{level} වෙත සක්‍රීය කර ඇත. + Welcomeecurity_enabled:customized_time: | + {chat_name} හි {level} වෙත සාදරයෙන් පිළිගනිමු, සහ {කාලය} තුළ සත්‍යාපනය නොකළ පරිශීලකයින්ට පයින් ගසන්න. + ask_for_time_customization: | + සත්‍යාපනය නොකළ පරිශීලකයාට පයින් ගැසීමට කාල සීමාව රිසිකරණය කිරීමට ඔබට අවශ්‍යද? (පෙරනිමියෙන් එය {කාලය}) + send_time: කරුණාකර වේලාවක් සඳහන් කරන්න. + invalid_time: "අවලංගු කාලය!" + Welcomeecurity_enabled_word: සක්‍රීය, level මට්ටමට සකසා ඇත + welcomesecurity_disabled: % s හි පිළිගැනීමේ ආරක්ෂාව අක්‍රීය කර ඇත. + welcomesecurity_invalid_arg: වලංගු නොවන තර්කය, අපේක්ෂිත බොත්තම / කැප්චා . + welcomesecurity_status: පිළිගැනීමේ ආරක්ෂාව යනු {chat_name} හි {status} වේ + + yes_btn: "ඔව්" + no_btn: "නැත" + + # පිළිගැනීමේ සුරක්‍ෂිතතා හසුරුවන්නා + click_here: මෙහි ක්ලික් කරන්න! + not_allowed: ඔබට මෙය භාවිතා කිරීමට අවසර නැත! + ws_captcha_text: හායි {user}, කරුණාකර චැට් එකෙහි නිශ්ශබ්ද වීමට කැප්චාගේ අංකය ඇතුළත් කරන්න. + regen_captcha_btn: කැප්චා වෙනස් කරන්න + num_is_not_digit: කරුණාකර අංක පමණක් ඇතුළත් කරන්න! + bad_num: වැරදි අංකය! කරුණාකර නැවත උත්සාහ කරන්න. + passed: සත්‍යාපනය සම්මත විය, ඔබ %s හි unmute කර ඇත! + pass_no_frm: සත්‍යාපනය සම්මත විය, ඔබ %s වලින් unmute කර ඇත! + last_chance: ඔබට කැප්චා වලට ඇතුල් වීමට අවසාන අවස්ථාව තිබේ! + + btn_button_text: "කරුණාකර ඔබම ඔප්පු කිරීමට පහත බොත්තම ක්ලික් කරන්න." + + btn_wc_text: | + කරුණාකර මෙම ප්‍රකාශනයට ගැලපෙන දකුණු බොත්තම ඔබන්න: + %s + math_wc_rtr_text: "ඔබට අවසාන අවස්ථාව තිබේ! ප්‍රවේශම් වන්න. \n" + math_wc_wrong: වැරදියි, කරුණාකර නැවත උත්සාහ කරන්න! + math_wc_sry: කණගාටුයි ඔබ අවසන් උත්සාහය භාවිතා කළා. චැට් පරිපාලකයින්ගෙන් විමසන්න. + + not_admin_wm: මට මෙහි පුද්ගලයින් සීමා කළ නොහැක, එබැවින් මම පිළිගැනීමේ නිශ්ශබ්දතාවය භාවිතා නොකරමි! + not_admin_ws: මට මෙහි පුද්ගලයින් සීමා කළ නොහැක, එබැවින් මම පිළිගැනීමේ ආරක්ෂාව භාවිතා නොකරමි! + not_admin_wsr: මට මෙහි පණිවිඩ මකා දැමිය නොහැක, එබැවින් මම සේවා පණිවිඩ මකා නොදමමි! + + # / යළි පිහිටුවීම + deleted: {chat} හි සුබ පැතුම් සාර්ථකව යළි පිහිටුවන්න! + not_found: නැවත සැකසීමට කිසිවක් නැත! + + # ... + verification_done: සත්‍යාපනය සිදු කර ඇත! ඔබ නිශ්ශබ්ද කර ඇත . + locks: + locks_header: "Here is the current lock settings in {chat_title} \n\n" + no_lock_args: Give me some args to lock! + already_locked: This is already locked bruh + no_such_lock: See /locks to find out what can be locked and unlocked! + locked_successfully: Locked {lock} in {chat} sucessfully! + no_unlock_args: Give me some args to unlock! + not_locked: Bruh, its not locked to unlock. + unlocked_successfully: Unlocked {lock} in {chat} successfully! + + antiflood: + + # enforcer + flood_exceeded: "කිරීම නිසා {user} ව {action} කරා!" + + # /setflood + invalid_args:setflood: "අංකයක් බලාපොරොත්තු විය!" + overflowed_count: උපරිම ගණන 200 ට වඩා අඩු විය යුතුය! + config_proc_1: කරුණාකර කල් ඉකුත් වීමේ කාලය යවන්න, '0' (බිංදුව) කල් ඉකුත් නොවීම සඳහා. + cancel: ❌ අවලංගු කරන්න + setup_success: ප්‍රති-ෆ්ලූඩ් සාර්ථකව වින්‍යාස කර ඇති අතර, දැන් {count} පණිවිඩ {time} තුළ send යවන අයට එරෙහිව ක්‍රියාත්මක වේ! + setup_success:no_exp: සාර්ථකව වින්‍යාසගත කරන ලද ප්‍රති-ෆ්ලූඩ්, පණිවිඩ {count} සඳහා අඛණ්ඩව ඉඩ දෙයි! + invalid_time: අවලංගු කාලය! + + # /antiflood | /flood + not_configured: මෙම කතාබහ තුළ Antiflood තවමත් වින්‍යාස කර නොමැත! + turned_off: {chat_title} හි antiflood අක්‍රීය කර ඇත! + configuration_info:with_time: ප්‍රති-ෆ්ලූඩ් සාර්ථකව වින්‍යාස කර ඇති අතර, දැන් {count} පණිවිඩ {time} තුළ send යවන අයට එරෙහිව {action} ක්‍රියාත්මක වේ! + configuration_info: මෙම කතාබහ තුළ ඇන්ටිෆ්ලූඩ් වින්‍යාස කර ඇත, අඛණ්ඩව {count} message යවන අය {action} වනු ඇත! + ban: තහනම් + mute: නිශ්ශබ්ද + kick: පයින් ගැසුවා + + # /setfloodaction + invalid_args: "සහාය නොදක්වන ක්‍රියාවකි, අපේක්ෂිත {supported_actions}!" + setfloodaction_success: "Antiflood ක්‍රියාව {action} වෙත සාර්ථකව යාවත්කාලීන කරන ලදි!" + + afk: + is_afk: "{user} දැන් ඕෆ්ලයින්!\nහේතුව: {reason}" + unafk: "{user} ආපහු ඔන්ලයින් ආවා!" + afk_anon: "AFK නිර්නාමික පරිපාලකයෙකු හට භාවිතා කළ නොහැක." diff --git a/DaisyX/localization/tr_TR.yaml b/DaisyX/localization/tr_TR.yaml new file mode 100644 index 00000000..956e81ef --- /dev/null +++ b/DaisyX/localization/tr_TR.yaml @@ -0,0 +1,630 @@ +--- +language_info: + flag: "🇹🇷" + code: tr +STRINGS: + notes: + #/save + note_saved: "🗒 {chat_title} içinde {note_name} notu kaydedildi!" + note_updated: "🗒 {chat_title} içinde {note_name} notu güncellendi!" + you_can_get_note: "Bu notu /get{note_name} veya #{note_name} kullanarak alabilirsiniz." + #Translator note: please keep sure that you don't have space in the end of string + note_aliases: "Not takma adları:" + blank_note: "Boş notu kaydetmeye izin verilmiyor!" + notename_cant_contain: "Not adı \"{symbol}\" içeremez!" + #/get + cant_find_note: Bu notu {chat_name} içinde bulamıyorum! + no_note: "Not bulunamadı." + no_notes_pattern: "%s desenine göre not bulunamadı" + u_mean: "Bunu mu demek istediniz #{note_name} ?" + #/notes + notelist_no_notes: "{chat_title} içinde herhangi bir not yok!" + notelist_header: "{chat_name}'deki notlar:" + notes_in_private: "Not listesini almak için aşağıdaki düğmeye tıklayın." + notelist_search: | + Desen: {request} + Eşleşen notlar: + #/clear + note_removed: "{chat_name} 'da #{note_name} notu kaldırıldı!" + note_removed_multiple: | + {chat_name} içinde birden fazla not kaldırılıyor + Kaldırılan:{removed} + not_removed_multiple: "Kaldırılmadı:{not_removed}" + #/clearall + clear_all_text: "Bu, tüm notları {chat_name} ' dan kaldıracaktır. Emin misin?" + clearall_btn_yes: "Evet. Tüm notlarımı kaldır!" + clearall_btn_no: "Hayır!" + clearall_done: "{chat_name} içindeki ({num}) not kaldırıldı!" + #/search + search_header: | + {chat_name} içinde arama isteği: + Desen: {request} + Eşleşen notlar: + #/noteinfo + note_info_title: "Not bilgisi\n" + note_info_note: "Not adları: %s\n" + note_info_content: "Not içeriği: %s\n" + note_info_parsing: "Ayrıştırma modu: %s" + note_info_created: "{user} tarafından {date} tarihinde oluşturuldu" + note_info_updated: "Son güncelleme tarihi: {user} tarafından {date}" + user_blocked: "PM'den /start yaz" + #/privatenotes + private_notes_false_args: "2 seçeneğiniz var Devre Dışı Bırak ve Etkinleştir!" + already_enabled: "Özel notlar %s içinde zaten etkinleştirildi" + current_state_info: "Şu anda özel not {chat} içinde {state}!" + enabled_successfully: "Özel notlar %s içinde Etkinleştirildi!" + disabled_successfully: "Özel notlar %s içinde Devre dışı bırakıldı!" + not_enabled: "Özel notlar etkinleştirilmediği için devre dışı bırakılamıyor." + privatenotes_notif: "{chat} notlarına başarıyla bağlandınız! Bağlantıyı kesmek için lütfen /disconnect komutunu kullanın!" + enabled: 'Etkinleştirildi' + disabled: 'Devre dışı bırakıldı' + #/cleannotes + clean_notes_enable: "{chat_name} grubunda temizleme servisi başarıyla etkinleştirildi" + clean_notes_disable: "{chat_name} grubunda temizleme servisi başarıyla devre dışı bırakıldı" + clean_notes_enabled: "{chat_name} grubunda temizleme servisi başarıyla etkinleştirildi" + clean_notes_disabled: "{chat_name} grubunda temizleme servisi başarıyla devre dışı bırakıldı" + #Filter + filters_title: 'Bir not gönder' + filters_setup_start: 'Lütfen bir not adı gönderin.' + #delmsg_no_arg: "deletemsg button can contain only 'admin' or 'user' argument!" + #bot_msg: "I'm sorry, I am not able to get this message, probably this a other bot's message, so I can't save it." + filters: + no_filters_found: "{chat_name} içerisinde filtre bulunamadı!" + #/addfilter + adding_filter: | + {chat_name} içinde {handler} filtresi ekleme + Aşağıdaki işlemi seçin: + #/filters + list_filters: "{chat_name} grubundaki filtreler:\n" + #/delfilter + no_such_filter: Bu filtreyi {chat_name} içinde bulamıyorum, etkin filtreleri /filters komutuyla kontrol edin. + del_filter: "'{handler}' işleyicisiyle filtre başarıyla kaldırıldı!" + select_filter_to_remove: | + '{handler}' işleyicisine sahip birçok filtre buldum. + Lütfen kaldırmak için birini seçin: + warns: + #/warn + warn_sofi: "Haha, kendimi uyarmanın bir yolu yok!" + warn_self: "Kendini uyarmak ister misin? Sadece sohbetten ayrıl." + warn_admin: "Şey... yanılıyorsun. Bir yöneticiyi uyaramazsın." + warn: "{admin}, {chat_name} 'da {user} adlı kullanıcıyı uyardı" + warn_bun: "Uyarı sınırı aşıldı! {user} yasaklandı!" + warn_num: "Mevcut uyarılar: {curr_warns}/{max_warns}\n" + warn_rsn: "Sebep: {reason}\n" + max_warn_exceeded: Uyarılar aşıldı! %s %s oldu! + "max_warn_exceeded:tmute": Uyarılar aşıldı! %s, %s için açıldı! + #warn rmv callback + warn_no_admin_alert: "⚠ Uyarıyı kaldırmak için yönetici DEĞİLSİNİZ!" + warn_btn_rmvl_success: "✅ Uyarı {admin} tarafından kaldırıldı !" + #/warns + warns_header: "İşte bu sohbetteki uyarılarınız\n\n" + warns: "{admin} tarafından {count} : {reason}" + no_warns: "{user} adlı kullanıcının herhangi bir uyarısı yok." + #/warnlimit + warn_limit: "{chat_name} grubunda uyarı limiti: {num}" + warnlimit_short: 'Uyarı limiti en az 2 olmalıdır!' + warnlimit_long: 'Uyarı limiti 1.000''den daha kısa olmalıdır!' + warnlimit_updated: '✅ Uyarı limiti {num} olarak güncellendi' + #/delwarns + purged_warns: "{admin}, {user} kullanıcısının {num} adet uyarısını {chat_title} grubunda kaldırdı!" + usr_no_wrn: "{user} sıfırlamak için herhangi bir uyarıya sahip değildir." + rst_wrn_sofi: 'AllMight asla sıfırlama konusunda uyarmadı.' + not_digit: Uyarı limiti rakam olmalı! + #/warnmode + same_mode: Bu geçerli mod! Bunu nasıl değiştirebilirim? + no_time: '''tmute'' modunu seçmek için bir zaman diliminden bahsetmelisiniz!' + invalid_time: Geçersiz zaman ! + warnmode_success: %s uyarı modu başarıyla %s olarak değiştirildi! + wrng_args: | + Mevcut seçenekler şunlardır: + %s + mode_info: 'Bu sohbetteki geçerli mod %s.' + banned: yasaklandı + muted: susturuldu + filters_title: Kullanıcıyı uyar + filter_handle_rsn: Otomatik filtre eylemi! + msg_deleting: + no_rights_purge: "Bu grubu temizlemek için yeterli izniniz yok!" + reply_to_msg: "Silmek için bir mesaja cevap verin!" + purge_error: "Silmeye devam edemiyorum, çünkü çoğunlukla 2 günden daha eski bir mesajdan silmeye başladınız." + fast_purge_done: "Hızlı temizleme tamamlandı!\nBu mesaj 5 saniye içinde silinecektir." + purge_user_done: "{user} adlı kullanıcının tüm mesajları başarıyla silindi!" + misc: + your_id: "Senin ID: {id}\n" + chat_id: "Grup ID: {id}\n" + user_id: "{user}'in ID: {id}\n" + conn_chat_id: "Geçerli bağlanmış olan sohbet kimliği: {id}" + help_btn: "Yardım için tıkla!" + help_txt: "Yardım için aşağıdaki düğmeye tıklayın!" + paste_success_extra: "Başarıyla yapıştırıldı!\nKısaltılmış link: {}\nOrijinal doglin linki: {}" + paste_success: "Başarıyla yapıştırıldı!\nDogbin linki: {}" + paste_fail: "Dogbin'e ulaşılamadı!" + paste_no_text: "Yapıştırmak için bir metin girmeniz gerekiyor!" + delmsg_filter_title: 'Mesajı sil' + send_text: Lütfen cevap mesajını gönderin! + replymsg_filter_title: 'Mesaja cevap verin' + promotes: + promote_success: "{user}, {chat_name} içinde başarıyla yükseltildi!" + promote_title: "Özel rol {role} ile!" + rank_to_loong: "Özel rol metni 16 sembolden uzun olamaz." + promote_failed: "Yetkilendirme başarısız! İzinlerimi kontrol et." + demote_success: "{user}, {chat_name} içinde başarıyla düşürüldü!" + demote_not_admin: "Bu kullanıcı yönetici değil." + demote_failed: "Düşürme başarısız. Yetkim olmayabilir veya bu kişi başka bir kişi tarafından yönetici yapıldı" + cant_get_user: "Kullanıcıyı alamadım! Eğer sakıncası varsa, lütfen mesajına cevap verin ve tekrar deneyin." + emoji_not_allowed: "Üzgünüm, ama yönetici rollerinde emoji olamaz 😞" + pm_menu: + start_hi_group: 'Selam! Benim adım AllMight' + start_hi: "Selam! Adım AllMight, grubunuzu verimli bir şekilde yönetmenize yardım ediyorum!" + btn_source: "📦 Code source" + btn_help: "❔ Yardım" + btn_lang: "🇹🇷 Dil" + btn_group: "👥 Daisy Grubu" + btn_channel: "📡 Daisy Kanalı" + back: Geri + #/help + help_header: Hey! check help here + click_btn: Buraya tıklayın + disable: + #/disableable + disablable: "Devredışı bırakılabilen komutlar:\n" + #/disabled + no_disabled_cmds: {chat_name} içinde devre dışı bırakılmış komut yok! + disabled_list: "{chat_name} grubunda devre dışı bırakılan komutlar:\n" + #/disable + wot_to_disable: "Neyi devre dışı bırakmak istiyorsun?" + already_disabled: "Bu komut zaten devre dışı!" + disabled: "{cmd} komutu {chat_name} grubunda devre dışı bırakıldı!" + #/enable + wot_to_enable: "Neyi etkinleştirmek istiyorsunuz?" + already_enabled: "Bu komut devre dışı bırakılamaz!" + enabled: "{cmd} komutu {chat_name} grubunda etkinleştirildi!" + #/enableall + not_disabled_anything: "{chat_title} 'da hiçbir şey devre dışı bırakılmadı!" + enable_all_text: "Bu, {chat_name} içindeki tüm komutları etkinleştirir. Emin misin?" + enable_all_btn_yes: "Evet. Tüm komutları etkinleştir!" + enable_all_btn_no: "Hayır!" + enable_all_done: "Tüm ({num}) adet komut {chat_name} için etkinleştirildi!" + bot_rights: + change_info: "Grup bilgisini değiştirmek için yetkim yok. Gerekli yetkileri verirsin işini görebilirim." + edit_messages: "Mesajları düzenleme iznim yok!" + delete_messages: "Mesajları silmek için yeterince yetkim yok." + ban_user: "Kullanıcıları yasaklamam için yetkim yok, lütfen bana bu yetkiyle yöneticilik verin." + pin_messages: "Mesajları sabitlemek için yetkim yok, lütfen bana bu yetkiyle yöneticilik verin." + add_admins: "Yönetici ekleme iznim yok, lütfen beni bu izin ile yönetici yap." + feds: + #decorators + need_fed_admin: "{name} Federasyonunda yönetici değilsin" + need_fed_owner: "{name} Federasyonunun sahibi değilsin" + cant_get_user: "Maalesef, bu kullanıcıyı bulamadım, kullanıcı ID kullanmayı deneyin" + #/fnew + fed_name_long: "Federasyon adı 60 sembolden daha uzun olamaz!" + can_only_1_fed: "1 kullanıcı sadece 1 Federasyon oluşturabilir, lütfen birini kaldırın." + name_not_avaible: "{name} isimli federasyon zaten mevcut! Lütfen başka bir ad kullanın." + created_fed: | + Tebrikler, Federasyon'u başarıyla oluşturdunuz. + İsim: {name} + ID: {id} + Oluşturan: {creator} + /fjoin {id} federasyonu sohbete bağlama için bu komutu kullanın + #/fjoin + only_creators: "Sohbeti federasyona bağlayabilmek için bir sohbet yaratıcısı olmalısınız." + fed_id_invalid: "Belirtilen Federasyon Kimliği geçersiz! Lütfen bana geçerli bir kimlik verin." + joined_fed_already: "Bu sohbet zaten bir Federasyon katıldı! Bu federasyondan ayrılmak için lütfen /fleave komutunu kullanın" + join_fed_success: "Harika! Sohbet {chat} artık {fed} Federasyonunun bir parçası!" + join_chat_fed_log: | + Federasyona yeni sohbet katıldı #SohbetKatıldı + Federasyon: {fed_name} ({fed_id}) + Sohbet: {chat_name} ({chat_id}) + #/leavefed + chat_not_in_fed: "Bu sohbet henüz herhangi bir federasyonda değil." + leave_fed_success: "Sohbet {chat} {fed} federasyonundan ayrıldı." + leave_chat_fed_log: | + Sohbet federasyondan ayrıldı #SohbetAyrıldı + Federasyon: {fed_name} ({fed_id}) + Sohbet: {chat_name} ({chat_id}) + #/fsub + already_subsed: "{name} federasyonu zaten {name2} 'a abone oldu" + subsed_success: "{name} federasyonu {name2} 'a abone oldu!" + #/funsub + not_subsed: "{name} federasyonu zaten {name2} 'a abone olmadığı için abonelikten çıkılamıyor" + unsubsed_success: "{name} federasyonu {name2} 'a abonelikten ayrıldı!" + #/fpromote + #admin_already_in_fed: "User {user} is already a fed admin in {name} Federation." + admin_added_to_fed: "Kullanıcı {user} {name} örgütündeki yöneticilere eklendi." + promote_user_fed_log: | + Kullanıcı federasyon yöneticisi oldu #KullanıcıYükseltildi + Federasyon: {fed_name} ({fed_id}) + Kullanıcı: {user} ({user_id}) + #/fdemote + #admin_not_in_fed: "User {user} is not {name} Federation admin." + admin_demoted_from_fed: "{user}, {name} Federasyonu yöneticiliğinden alındı." + demote_user_fed_log: | + Kullanıcı federasyon yöneticilerinden düşürüldü #KullanıcıDüşürüldü + Federasyon: {fed_name} ({fed_id}) + Kullanıcı: {user} ({user_id}) + #/fsetlog + already_have_chatlog: "Görünüşe göre {name} federasyonu için başka bir sohbete / kanala günlükleme özelliği etkinleştirilmiş!" + set_chat_log: | + Bu sohbet artık {name} Federasyonundaki tüm işlemleri günlüğe kaydediyor + + Federasyon günlüğü, federasyonun ve performansının iç değerlendirmesi için kullanılır. + kullanıcı verilerine ve gizlilik yönergelerimize saygı göstererek halka gösterme duvarı olarak KULLANILMAMALIDIR. + set_log_fed_log: | + Etkinleştirilen günlük kaydı #GünlükKaydıEtkin + Federasyon: {fed_name} ({fed_id}) + no_right_to_post: O kanala mesaj gönderme hakkım yok! + #/funsetlog + not_logging: "{name} Federasyonu hiçbir sohbete / kanala giriş yapamıyor!" + logging_removed: "Günlük kaydı alma {name} Federasyonundan başarıyla kaldırıldı." + unset_log_fed_log: | + Devre dışı bırakılan günlük kaydı #GünlükKaydıDevreDışı + Federasyon: {fed_name} ({fed_id}) + #/fchatlist + chats_in_fed: "Sohbet {name} Örgüt'üne bağlandı:\n" + too_large: "Çıktı çok büyük, dosya olarak gönderiliyor" + #/finfo + finfo_text: | + Federasyon Bilgisi + İsim: {name} + ID: {fed_id} + Sahibi: {creator} + Federasyondaki sohbetler: {chats} + Federasyondaki yasaklı kullanıcılar: {fbanned} + finfo_subs_title: "Federasyon şu federasyonlara abone oldu:" + #/fadminlist + fadmins_header: "{fed_name} Federasyonunun yöneticileri:" + #no_fbanned_in_fed: "There isn't any banned users in {fed_name} Federation!" + #/fban + user_wl: "Bu kullanıcı beyaz listede olduğundan yasaklanamıyor." + fban_self: "Bu güzel bir şov!" + fban_creator: "Federasyon sahibini nasıl yasaklayabilirim?! Komik olacağını sanmıyorum." + fban_fed_admin: "Bir federasyon yöneticisini kendi federasyonundan yasaklayamam!" + already_fbanned: "{user} bu federasyonda zaten yasaklanmıştı." + fbanned_header: "Yeni FedBan\n" + fban_info: '| #ChatLeaved Federasyon:{fed}Federasyon yöneticisi:{fadmin}Kullanıcı:{user}({user_id})' + fbanned_reason: "Nedeni: {reason}" + fbanned_process: "Durum: {num} adet federasyon sohbetlerinden yasaklanıyor..." + fbanned_done: "Durum: Bitti! {num} adet federasyon sohbetlerinden yasaklandı!" + fbanned_silence: "Bu mesaj 5 saniye içinde silinecek" + fbanned_subs_process: "\nDurum: {feds} adet federasyona bağlı sohbetlerden yasaklanıyor..." + fbanned_subs_done: "Durum: Bitti! {chats} federasyonun sohbetlerinden, {feds} aboneliğinin {subs_chats} sohbetlerinden yasaklandı" + fban_usr_rmvd: | + {user} kullanıcısı grupta geçerli olan {fed} federasyonundan yasaklandı. Bu sebeple kullanıcı gruptan yasaklandı! + Sebep: {rsn} + fban_log_fed_log: | + Federasyondaki kullanıcıyı yasakla #FedBan + Federasyon: {fed_name} ({fed_id}) + Kullanıcı: {user} ({user_id}) + Kim Tarafından ?: {by} + Sohbetler içinde yasaklama: Kullanıcı {chat_count}/{all_chats} sohbet(ler) de yasaklandı + fban_reason_fed_log: "Nedeni: {reason}" + fban_subs_fed_log: "Abone olunan federasyonlar: {feds} federasyonlarının {subs_chats} sohbetlerinden yasaklandı" + #/unfban + unfban_self: "Bunun yerine eğlenmek için başka bir şey düşünün." + user_not_fbanned: "{user} bu federasyondan yasaklanmadı." + un_fbanned_header: "Yeni Un-FedBan\n" + un_fbanned_process: "Durum: {num} adet federasyon sohbetlerinden yasağı kaldırılıyor..." + un_fbanned_done: "Durum: Bitti! {num} adet federasyon sohbetlerinden yasağı kaldırıldı!" + un_fbanned_subs_process: "\nDurum: {feds} adet federasyona bağlı sohbetlerden yasak kaldırılıyor..." + un_fbanned_subs_done: "Durum: Bitti! {chats} federasyonun sohbetlerinden, {feds} aboneliğinin {subs_chats} sohbetlerinden yasağı kaldırıldı" + un_fban_log_fed_log: | + Federasyondaki kullanıcının yasağını kaldır #FedUnBan + Federasyon: {fed_name} ({fed_id}) + Kullanıcı: {user} ({user_id}) + Kim Tarafından ?: {by} + Sohbetler: Kullanıcı {chat_count}/{all_chats} sohbet(ler) den yasağı kaldırıldı + un_fban_subs_fed_log: "Abone olunan federasyonlar: {feds} federasyonlarının {subs_chats} sohbetlerinden yasağı kaldırıldı" + #/delfed + delfed_btn_yes: Evet. Federasyonu sil + delfed_btn_no: Hayır. + delfed: | + Bu federasyonu %s silmek istediğinizden emin misiniz? + + Uyarı: Federasyonun silinmesi, kendisiyle ilişkili tüm verileri Veritabanımızdan kaldıracaktır! + Bu işlem ne pahasına olursa olsun geri alınamaz! + delfed_success: Federasyon başarıyla silindi! + #/frename + frename_same_name: Lütfen mevcut olandan farklı bir ad girin! + frename_success: Federasyon ismi {old_name} isminden {new_name} ismine başarıyla değiştirildi! + #/fbanlist + fbanlist_locked: Fbanlist'i tekrar kullanmak için lütfen %s kadar bekleyin. + creating_fbanlist: Fban listesi oluşturuluyor! Lütfen bekle .. + fbanlist_done: Federasyonun fban listesi %s! + #/importfbans + send_import_file: | + İçe aktarma dosyasını gönderin! + + Bu işlemi iptal etmek için; /cancel komutunu kullanın + rpl_to_file: Lütfen bir dosyaya cevap verin! + importfbans_locked: Bu federasyonda fbanları içe aktarma %s için kilitlendi. + big_file_csv: Yalnızca {num} Megabayttan daha az içe aktarılan csv dosyalarını destekler! + big_file_json: | + Şu anda json dosyaları {num} Megabyte ile sınırlıdır! + Daha büyük dosyaları içe aktarmak istiyorsanız bir csv biçimi kullanın. + wrong_file_ext: "Hatalı dosya biçimi! Şu anda destek verilen dosya türleri: json, csv" + importing_process: Federasyon yasakları içe aktarılıyor... + import_done: Federasyon yasaklarının içe aktarılması tamamlandı! {num} yasak içe aktarıldı. + #def + automatic_ban: '{user} kullanıcısı grupta geçerli olan {fed_name} federasyonundan yasaklandı. Bu sebeple kullanıcı gruptan yasaklandı!' + automatic_ban_sfed: '{user} kullanıcısı grupta geçerli olan {fed_name} federasyonundan yasaklandı. Bu sebeple kullanıcı gruptan yasaklandı!' + automatic_ban_reason: "Sebep: {text}" + #/fcheck + fcheck_header: "Federasyon yasağı bilgileri:" + fbanned_count_pm: "{count} federasyon(lar) da yasaklandınız!" + "fban_info:fcheck": "{date} tarihinde {fed} federasyonundan yasaklandınız, hiçbir neden belirtilmedi!" + "fban_info:fcheck:reason": "{reason} nedeniyle {date} tarihinde {fed} federasyonundan yasaklandınız" + fbanned_data: "{user}, {count} federasyon(lar) dan yasaklandı" + fbanned_nowhere: "{user} hiçbir yerde yasaklanmadı!" + fbanned_in_fed: "Bu {fed} federasyonunu da içeriyor!" + "fbanned_in_fed:reason": "Bu {fed} federasyonunu -\n {reason}" + contact_in_pm: "Fbanlarınız hakkında daha fazla bilgi için lütfen bana doğrudan mesaj gönderin, böylece gizliliğiniz güvence altına alınır!" + too_long_fbaninfo: "İyi! fbaninfo bir dosya gerektirir!" + didnt_fbanned: "Bu federasyonda yasaklanmadın." + reports: + user_user_admin: "Sen burada bir yöneticisin, neden birini rapor etmek istiyorsun?.." + user_is_whitelisted: "Sen beyaz listeye alınmış bir kullanıcısın, neden birini rapor etmek istiyorsun?.." + reports_disabled: "Rapor etme bu grupta kapalı." + no_user_to_report: "Kimi bildirmek istiyorsun?" + report_admin: "Yönetici bildiremezsiniz." + report_whitedlisted: "Beyaz liste'de bulunan bir kullanıcıyı bildiremezsin." + reported_user: "{user} yöneticilere bildirildi." + reported_reason: "\nSebep:\n{reason}" + reports_on: "Bu grupta rapor etme açıldı." + reports_off: "Bu grupta rapor etme kapatıldı." + reports_already_on: "Rapor etme zaten açık." + reports_turned_on: "Rapor etme açık." + reports_already_off: "Rapor etme zaten kapalı." + wrong_argument: "Yanlış argüman." + language: + your_lang: "Mevcut diliniz: {lang}" + chat_lang: "Mevcut sohbet dili: {lang}" + select_pm_lang: "Yeni dilinizi seçin:" + select_chat_lang: "Yeni sohbet dili seçin:" + crowdin_btn: "🌎 Çeviri konusunda bize yardımcı olun" + help_us_translate: "🌎 Dilleri geliştirmemize yardımcı olun" + not_supported_lang: Bu dil desteklenmiyor! + lang_changed: Dil {lang_name} olarak değiştirildi. + see_translators: '👥 Çevirenleri gör' + back: Geri + connections: + pm_connected: "PM'niz başarıyla {chat_name} sohbetine bağlandı!" + connected_pm_to_me: "PM'niz {chat_name} ile başarıyla bağlandı! Bağlantıyı kullanmaya başlamak için lütfen bana pm atın." + chat_connection_tlt: "Sohbet bağlantısı" + connected_chat: | + Geçerli bağlı sohbet: + {chat_name} + Sohbet bağlantısını kesmek için /disconnect yazın + u_wasnt_connected: "Daha önce hiçbir sohbete bağlanmadın!" + select_chat_to_connect: "Bağlanmak için bir sohbet seçin:" + disconnected: " {chat_name} ile bağlantınız kesildi." + cant_find_chat: "Bu sohbeti bulamıyorum." + cant_find_chat_use_id: "Bu sohbeti bulamıyorum. Sohbet kimliğini kullan." + bad_arg_bool: "Kötü seçim: yalnızca açık/kapalıetkinleştir/devre dışı" + enabled: etkin + disabled: devre dışı + chat_users_connections_info: "Normal kullanıcılar için şu anda {chat_name} için bağlantılar {status}" + chat_users_connections_cng: "Normal kullanıcılar için bağlantılar {chat_name} için {status} olarak değiştirildi" + not_in_group: "Bu grupta değilsin ki bağlanasın, katıl ve mesaj at." + history_empty: "Geçmiş tarihte hiçbir sohbete bağlanmadın, bağlanmak için `/connect (grup id)` yaz" + not_in_chat: "Sen artık bu sohbette değilsin, bağlantını keseceğim." + u_should_be_admin: "{} grubunda yönetici olmalısın!" + usage_only_in_groups: "Gruplarda kullanım sınırlandı!" + global: + u_not_admin: Bunu yapmak için yönetici olmalısınız! + #Rights + "bot_no_right:not_admin": Yönetici değilim! + bot_no_right: "Bunu yapmak için gerekli iznim yok!\nEksik izin(ler): %s" + user_no_right: "Bunu yapmak için gerekli izne sahip olmalısınız!\nEksik izin(ler): %s" + "user_no_right:not_admin": Bunu yapmak için yönetici olmalısınız! + owner_stuff: + father: "\nİşte benim yaratıcım." + sudo_crown: "Hey! Bak, o kullanıcının bir tacı var, bir bakayım... Vay, bir gravür var - 'sudo kullanıcısı'!" + stickers: + dev: "\nHey! Look, that user has a crown, let me see it... Wow, it has an engraving - 'dev user'!" + rpl_to_sticker: "Bir çıkartmaya yanıt verin!" + ur_sticker: | + Emoji: {emoji} + Çıkartma ID: {id} + pins: + no_reply_msg: "Sabitlemek için bir mesajı yanıtlayın!" + chat_not_modified_pin: "Bu mesaj zaten sabitlendi!" + chat_not_modified_unpin: "Sabitlemeyi kaldıracak sabit bir mesaj yok!" + pinned_success: "Başarıyla sabitlendi!" + unpin_success: "Sabitlenme başarıyla kaldırıldı!" + users: + user_info: "Kullanıcı bilgisi:\n" + info_id: "ID:{id}" + info_first: "\nİsim: {first_name}" + info_last: "\nSoyisim: {last_name}" + info_username: "Kullanıcı adı: {username}" + info_sw_ban: "\nThis user is banned in @SpamWatch!" + info_sw_ban_reason: "\nReason: {sw_reason}" + info_link: "\nKullanıcı bağlantısı: {user_link}" + info_saw: "Onu yaklaşık olarak {num} adet grupta gördüm" + info_fbanned: "\nBulunan örgütte yasaklı: " + info_admeme: "\nKullanıcı bu sohbette yönetici." + upd_cache: "Önbellek şimdi güncelleniyor..." + upd_cache_done: "Yönetici önbelleği güncellendi." + admins: "Bu gruptaki yöneticiler:\n" + imports_exports: + #Exporting + started_exporting: Dışa aktarma başladı! Lütfen bekle... + export_done: {chat_name} 'den dışa aktarma bitti! + exports_locked: Lütfen dışa aktarmayı tekrar kullanabilmek için %s süre zarfında bekleyin! + #Importing + send_import_file: Lütfen bir içe aktarma dosyası gönderin! + rpl_to_file: Lütfen bir dosyaya cevap verin! + big_file: Yalnızca 50 MB'ın altındaki dosyaları içe aktarma desteklenir! + started_importing: İçe aktarma başladı! Lütfen bekle... + bad_file: Bozuk dosya! Beklenen 'genel' sütunu. + file_version_so_new: Dışa aktarılan dosya sürümü AllMight'nin şu anda desteklediğinden daha yeni! + imports_locked: Lütfen içe aktarmayı tekrar kullanabilmek için %s süre zarfında bekleyin! + import_done: İçe aktarma tamamlandı! + restrictions: + user_not_in_chat: Kullanıcı bu sohbette değil! + #Kicks + kick_DaisyX: Beni tekmelemeye çalışmak yerine zamanını daha iyi geçirebilirsin. Bu sadece sıkıcı. + kick_admin: Yönetici tekmelemek iyi bir düşünce değildir. + kick_self: Kendini tekmelemek istiyorsan - sadece bu sohbetten ayrıl. Kendi kendini cezalandırma konusundaki bu acınası girişimlere bakmak yerine bir daha seni görmek istemiyorum. + user_kicked: "{user}, {chat_name} grubunda {admin} tarafından tekmelendi." + #Mutes + mute_DaisyX: Aslında, onun yerine seni susturmaktan mutluluk duyarım. + mute_admin: Yönetici susturabileceğinizi düşünüyorsanız, yanılıyorsunuz! + mute_self: Kendini tekmelemek istiyorsan - sadece bu sohbetten ayrıl. Kendi kendini cezalandırma konusundaki bu acınası girişimlere bakmak yerine bir daha seni görmek istemiyorum. + user_muted: "{user}, {chat_name} grubunda {admin} tarafından susturuldu." + unmute_DaisyX: Aslında, sessiz değilim. + unmute_admin: Haha hayır, önce onu susturmayı dene! + unmute_self: Önce seni susturmalı mıyım? + user_unmuted: "{user}, {chat_name} grubunda {admin} tarafından tekrar konuşabilir." + #Bans + ban_DaisyX: Hayır, yapmayacağım! Sohbet sahibinden bunu yapmasını isteyin! + ban_admin: Haha, önce onu /demote edelim. + ban_self: Neden kendini yasaklamaya çalışıyorsun? + user_banned: "{user}, {chat_name} grubunda {admin} tarafından yasaklandı." + unban_DaisyX: Aslında, yasaklı değilim. + unban_admin: Haha hayır, önce onu yasaklamayı dene! + unban_self: Önce seni yasaklamalı mıyım? + user_unband: "{user}, {chat_name} grubunda {admin} tarafından yasağı kaldırıldı." + invalid_time: Geçersiz zaman! + enter_time: Lütfen zaman ekleyin! + on_time: "%s için" + reason: "Sebep: %s" + purge: "\n5 saniye içinde mesajlar silinecek!" + filter_title_ban: Kullanıcıyı yasakla + filter_action_rsn: Otomatik filtre eylemi! + filtr_ban_success: '%s, %s grubunda %s süre zarfında yasaklandı' + filtr_mute_success: '%s, %s grubunda %s süre zarfında susturuldu' + filtr_tmute_success: | + %s, %s grubunda %s süre zarfında susturuldu + nedeni %s + time_setup_start: Lütfen zaman gönderin! + filtr_tban_success: | + %s, %s grubunda %s süre zarfında yasaklandı + nedeni %s + filter_title_tmute: Kullanıcıyı zamanlı susturma + filter_title_mute: Kullanıcıyı susturma + filter_title_tban: Kullanıcıyı zamanlı yasakla + filter_title_kick: Kullanıcı tekmele + rules: + #Set + saved: Kurallar %s içinde kaydedildi + updated: Kurallar %s içinde güncellendi + #Reset + deleted: Kurallar silindi! + not_found: Kurallar bulunamadı! + #Callback + user_blocked: "PM'den /start yaz!" + rules_was_pmed: Kurallar PM'den gönderildi! + #cannot_find_note: "I can't find the note {}" + #set_note: "Successfully set the rules note to {}" + #didnt_set_note: "This group doesn't have rules note set." + #cannot_find_set_note: "I can't find the rules note." + #havent_set_rules_note: "You haven't set the rules note yet." + #success_remove_rules_note: "Successfully removed rules note in {}." + greetings: + default_welcome: "Merhaba {mention}! Nasılsın?" + default_security_note: "Hoşgeldin {mention}! Lütfen kendini insan olarak doğrulamak için aşağıdaki düğmeye basın!" + thank_for_add: "Thanks for adding me to your group! Take a look at /help and follow my news channel @DaisyXUpdates." + bool_invalid_arg: Geçersiz argüman, beklenen on/off. + #/setwelcome + saved: Karşılama mesajı %s içinde kaydedildi + updated: Karşılama mesajı %s içinde güncellendi + #/turnwelcome + turnwelcome_status: Karşılamalar {chat_name} sohbeti için {status} + turnwelcome_enabled: %s içinde karşılama mesajı etkinleştirildi + turnwelcome_disabled: %s içinde karşılama mesajı devre dışı bırakıldı + #/welcome + enabled: etkin + disabled: devre dışı + wlcm_security_enabled: "etkinleştirildi, {level} olarak ayarlandı" + wlcm_mutes_enabled: "etkinleştirildi, {time} olarak ayarlandı" + welcome_info: | + {chat_name} içindeki karşılama ayarları: + Karşılama durumu {welcomes_status} + Karşılama güvenliği {wlcm_security} + Karşılama susturma durumu {wlcm_mutes} + Eski karşılama mesajlarının silinmesi {clean_welcomes} + Servis mesajlarını silme {clean_service} + wlcm_note: "\n\nKarşılama notu:" + raw_wlcm_note: "Raw karşılama notu:" + security_note: "Karşılama güvenlik metni:" + #/setsecuritynote + security_note_saved: "Özel güvenlik metni %s kaydedildi" + security_note_updated: "Özel güvenlik metni %s güncellendi" + #/delsecuritynote + security_note_not_found: %s içindeki güvenlik metni daha önce yüklenmedi + del_security_note_ok: Güvenlik metni %s 'de varsayılana sıfırlandı + #/cleanwelcome + cleanwelcome_enabled: Temiz karşılama %s içinde etkinleştirildi + cleanwelcome_disabled: Temiz karşılama %s içinde devre dışı bırakıldı + cleanwelcome_status: Karşılama mesajını temizleme {chat_name} sohbeti için {status} + #/cleanservice + cleanservice_enabled: Temizlenen hizmeti iletileri %s içinde etkinleştirildi + cleanservice_disabled: Temizlenen hizmeti iletileri %s içinde devre dışı bırakıldı + cleanservice_status: Hizmet mesajları temizliği {chat_name} sohbeti için {status} + #/welcomemute + welcomemute_invalid_arg: Geçersiz argüman, beklenen süre. + welcomemute_enabled: %s içinde karşılama susturması etkinleştirildi + welcomemute_disabled: %s içinde karşılama susturması devre dışı bırakıldı + welcomemute_status: Karşılama susturması {chat_name} sohbeti için {status} + #/welcomesecurity + welcomesecurity_enabled: Karşılama güvenliği {chat_name} 'da {level}. seviyeye etkinleştirildi. + welcomesecurity_enabled_word: etkinleştirildi, {level}. seviye düzeyine ayarlandı + welcomesecurity_disabled: Karşılama güvenliği %s içinde devre dışı bırakıldı. + welcomesecurity_invalid_arg: Geçersiz argüman, beklenen button/captcha. + welcomesecurity_status: Karşılama güvenliği {chat_name} sohbeti için {status} + #Welcomesecurity handler + click_here: Buraya tıklayın! + not_allowed: Bunu kullanma izniniz yok! + ws_captcha_text: Merhaba {user}, lütfen sohbette sesini açmak için captcha'nın numarasını girin. + regen_captcha_btn: '🔄 Captcha''yı değiştir' + num_is_not_digit: Lütfen yalnızca rakam girin! + bad_num: Yanlış numara! Lütfen tekrar deneyin. + passed: Doğrulama başarıyla geçildi, %s içinde tekrar konuşabilirsin! + passed_no_frm: Doğrulama başarılı, %s içinde sesiniz açıldı! + last_chance: Captcha'ya girmek için son bir şansın var! + btn_button_text: "Kendinizi kanıtlamak için aşağıdaki düğmeye tıklayınız." + btn_wc_text: | + Lütfen bu ifadeyle eşleşen sağ düğmeye basın: + %s + math_wc_rtr_text: "Son şansın! Dikkatli ol.\n" + math_wc_wrong: Yanlış, lütfen tekrar deneyin! + math_wc_sry: Üzgünüm son hakkını kullandın. Sohbet yöneticilerine sorun. + not_admin_wm: Burada insanları kısıtlayamıyorum, bu yüzden karşılama sessizliğini kullanmayacağım! + not_admin_ws: Burada insanları kısıtlayamıyorum, bu yüzden hoşgeldin güvenliğini kullanmayacağım! + not_admin_wsr: Burada mesajları silemiyorum, bu yüzden servis mesajlarını silmeyeceğim! + #/resetwelcome + deleted: {chat} 'de karşılama mesajı başarıyla sıfırlandı! + not_found: Sıfırlanacak bir şey yok! + #/welcomerestrict + del_url: '{user} ''dan URL içeren mesaj silindi!' + del_fwd: '{user} adlı kullanıcıdan iletilen mesaj silindi!' + del_media: '{user} kullanıcısından medya içeren mesaj silindi!' + banned: '{by} tarafından yasaklandı {user}!' + whitelisted: '{by} beyaz listeye eklenen {user}!' + enabled_sucessfully: Sohbet güvenliği {chat} 'de başarıyla etkinleştirildi + disabled_sucessfully: Sohbet güvenliği {chat} 'de başarıyla devre dışı bırakıldı + already_enabled: Sohbet güvenliği zaten etkinleştirildi! + already_disabled: Sohbet güvenliği zaten devre dışı bırakıldı! + restrict_status: Karşılama kısıtlaması {chat} sohbeti için {state}! + unkown_option: Lütfen bana geçerli bir seçenek verin! (etkinleştir veya devre dışı) + whitelist: '🏳️ Beyaz Liste' + ban: '❌ Yasakla' + locks: + locks_header: "{chat_title} içindeki mevcut kilit ayarları" + no_lock_args: Kilitlemem için bana birkaç argüman ver! + already_locked: Bu zaten kilitli kardeşim + no_such_lock: Neyin kilitlenebileceğini ve kilidinin açılabileceğini öğrenmek için /locks komutunu kullan! + locked_successfully: '{chat} içinde {lock} başarıyla kilitlendi!' + no_unlock_args: Kilidi açmam için bana birkaç argüman ver! + not_locked: Kilidini açacağım bir şey yok. + unlocked_successfully: '{chat} içinde {lock} kilidi başarıyla açıldı!' + afk: + is_afk: "{user} is AFK!\nReason: {reason}" + unafk: "{user} is not AFK anymore!" + afk_anon: "AFK mode cannot be used as an anonymous administrator." + + + + diff --git a/DaisyX/modules/AI_Chat.py b/DaisyX/modules/AI_Chat.py new file mode 100644 index 00000000..40824604 --- /dev/null +++ b/DaisyX/modules/AI_Chat.py @@ -0,0 +1,400 @@ +# Copyright (C) 2021 Red-Aura & TeamDaisyX & HamkerCat + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +import re + +import emoji + +IBM_WATSON_CRED_URL = "https://api.us-south.speech-to-text.watson.cloud.ibm.com/instances/bd6b59ba-3134-4dd4-aff2-49a79641ea15" +IBM_WATSON_CRED_PASSWORD = "UQ1MtTzZhEsMGK094klnfa-7y_4MCpJY1yhd52MXOo3Y" +url = "https://acobot-brainshop-ai-v1.p.rapidapi.com/get" +import re + +import aiohttp +from google_trans_new import google_translator +from pyrogram import filters + +from DaisyX import BOT_ID +from DaisyX.db.mongo_helpers.aichat import add_chat, get_session, remove_chat +from DaisyX.function.inlinehelper import arq +from DaisyX.function.pluginhelpers import admins_only, edit_or_reply +from DaisyX.services.pyrogram import pbot as daisyx + +translator = google_translator() + + +async def lunaQuery(query: str, user_id: int): + luna = await arq.luna(query, user_id) + return luna.result + + +def extract_emojis(s): + return "".join(c for c in s if c in emoji.UNICODE_EMOJI) + + +async def fetch(url): + try: + async with aiohttp.Timeout(10.0): + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + try: + data = await resp.json() + except: + data = await resp.text() + return data + except: + print("AI response Timeout") + return + + +daisy_chats = [] +en_chats = [] +# AI Chat (C) 2020-2021 by @InukaAsith +""" +@daisyx.on_message( + filters.voice & filters.reply & ~filters.bot & ~filters.via_bot & ~filters.forwarded, + group=2, +) +async def hmm(client, message): + if not get_session(int(message.chat.id)): + message.continue_propagation() + if message.reply_to_message.from_user.id != BOT_ID: + message.continue_propagation() + previous_message = message + required_file_name = message.download() + if IBM_WATSON_CRED_URL is None or IBM_WATSON_CRED_PASSWORD is None: + await message.reply( + "You need to set the required ENV variables for this module. \nModule stopping" + ) + else: + headers = { + "Content-Type": previous_message.voice.mime_type, + } + data = open(required_file_name, "rb").read() + response = requests.post( + IBM_WATSON_CRED_URL + "/v1/recognize", + headers=headers, + data=data, + auth=("apikey", IBM_WATSON_CRED_PASSWORD), + ) + r = response.json() + print(r) + await client.send_message(message, r) +""" + + +@daisyx.on_message( + filters.command("chatbot") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def hmm(_, message): + global daisy_chats + if len(message.command) != 2: + await message.reply_text( + "I only recognize `/chatbot on` and /chatbot `off only`" + ) + message.continue_propagation() + status = message.text.split(None, 1)[1] + chat_id = message.chat.id + if status == "ON" or status == "on" or status == "On": + lel = await edit_or_reply(message, "`Processing...`") + lol = add_chat(int(message.chat.id)) + if not lol: + await lel.edit("Daisy AI Already Activated In This Chat") + return + await lel.edit( + f"Daisy AI Successfully Added For Users In The Chat {message.chat.id}" + ) + + elif status == "OFF" or status == "off" or status == "Off": + lel = await edit_or_reply(message, "`Processing...`") + Escobar = remove_chat(int(message.chat.id)) + if not Escobar: + await lel.edit("Daisy AI Was Not Activated In This Chat") + return + await lel.edit( + f"Daisy AI Successfully Deactivated For Users In The Chat {message.chat.id}" + ) + + elif status == "EN" or status == "en" or status == "english": + if not chat_id in en_chats: + en_chats.append(chat_id) + await message.reply_text("English AI chat Enabled!") + return + await message.reply_text("AI Chat Is Already Disabled.") + message.continue_propagation() + else: + await message.reply_text( + "I only recognize `/chatbot on` and /chatbot `off only`" + ) + + +@daisyx.on_message( + filters.text + & filters.reply + & ~filters.bot + & ~filters.edited + & ~filters.via_bot + & ~filters.forwarded, + group=2, +) +async def hmm(client, message): + if not get_session(int(message.chat.id)): + return + if not message.reply_to_message: + return + try: + senderr = message.reply_to_message.from_user.id + except: + return + if senderr != BOT_ID: + return + msg = message.text + chat_id = message.chat.id + if msg.startswith("/") or msg.startswith("@"): + message.continue_propagation() + if chat_id in en_chats: + test = msg + test = test.replace("daisy", "Aco") + test = test.replace("Daisy", "Aco") + response = await lunaQuery( + test, message.from_user.id if message.from_user else 0 + ) + response = response.replace("Aco", "Daisy") + response = response.replace("aco", "Daisy") + + pro = response + try: + await daisyx.send_chat_action(message.chat.id, "typing") + await message.reply_text(pro) + except CFError: + return + + else: + u = msg.split() + emj = extract_emojis(msg) + msg = msg.replace(emj, "") + if ( + [(k) for k in u if k.startswith("@")] + and [(k) for k in u if k.startswith("#")] + and [(k) for k in u if k.startswith("/")] + and re.findall(r"\[([^]]+)]\(\s*([^)]+)\s*\)", msg) != [] + ): + + h = " ".join(filter(lambda x: x[0] != "@", u)) + km = re.sub(r"\[([^]]+)]\(\s*([^)]+)\s*\)", r"", h) + tm = km.split() + jm = " ".join(filter(lambda x: x[0] != "#", tm)) + hm = jm.split() + rm = " ".join(filter(lambda x: x[0] != "/", hm)) + elif [(k) for k in u if k.startswith("@")]: + + rm = " ".join(filter(lambda x: x[0] != "@", u)) + elif [(k) for k in u if k.startswith("#")]: + rm = " ".join(filter(lambda x: x[0] != "#", u)) + elif [(k) for k in u if k.startswith("/")]: + rm = " ".join(filter(lambda x: x[0] != "/", u)) + elif re.findall(r"\[([^]]+)]\(\s*([^)]+)\s*\)", msg) != []: + rm = re.sub(r"\[([^]]+)]\(\s*([^)]+)\s*\)", r"", msg) + else: + rm = msg + # print (rm) + try: + lan = translator.detect(rm) + except: + return + test = rm + if not "en" in lan and not lan == "": + try: + test = translator.translate(test, lang_tgt="en") + except: + return + # test = emoji.demojize(test.strip()) + + test = test.replace("daisy", "Aco") + test = test.replace("Daisy", "Aco") + response = await lunaQuery( + test, message.from_user.id if message.from_user else 0 + ) + response = response.replace("Aco", "Daisy") + response = response.replace("aco", "Daisy") + pro = response + if not "en" in lan and not lan == "": + try: + pro = translator.translate(pro, lang_tgt=lan[0]) + except: + return + try: + await daisyx.send_chat_action(message.chat.id, "typing") + await message.reply_text(pro) + except CFError: + return + + +@daisyx.on_message( + filters.text & filters.private & ~filters.edited & filters.reply & ~filters.bot +) +async def inuka(client, message): + msg = message.text + if msg.startswith("/") or msg.startswith("@"): + message.continue_propagation() + u = msg.split() + emj = extract_emojis(msg) + msg = msg.replace(emj, "") + if ( + [(k) for k in u if k.startswith("@")] + and [(k) for k in u if k.startswith("#")] + and [(k) for k in u if k.startswith("/")] + and re.findall(r"\[([^]]+)]\(\s*([^)]+)\s*\)", msg) != [] + ): + + h = " ".join(filter(lambda x: x[0] != "@", u)) + km = re.sub(r"\[([^]]+)]\(\s*([^)]+)\s*\)", r"", h) + tm = km.split() + jm = " ".join(filter(lambda x: x[0] != "#", tm)) + hm = jm.split() + rm = " ".join(filter(lambda x: x[0] != "/", hm)) + elif [(k) for k in u if k.startswith("@")]: + + rm = " ".join(filter(lambda x: x[0] != "@", u)) + elif [(k) for k in u if k.startswith("#")]: + rm = " ".join(filter(lambda x: x[0] != "#", u)) + elif [(k) for k in u if k.startswith("/")]: + rm = " ".join(filter(lambda x: x[0] != "/", u)) + elif re.findall(r"\[([^]]+)]\(\s*([^)]+)\s*\)", msg) != []: + rm = re.sub(r"\[([^]]+)]\(\s*([^)]+)\s*\)", r"", msg) + else: + rm = msg + # print (rm) + try: + lan = translator.detect(rm) + except: + return + test = rm + if not "en" in lan and not lan == "": + try: + test = translator.translate(test, lang_tgt="en") + except: + return + + # test = emoji.demojize(test.strip()) + + # Kang with the credits bitches @InukaASiTH + test = test.replace("daisy", "Aco") + test = test.replace("Daisy", "Aco") + + response = await lunaQuery(test, message.from_user.id if message.from_user else 0) + response = response.replace("Aco", "Daisy") + response = response.replace("aco", "Daisy") + + pro = response + if not "en" in lan and not lan == "": + pro = translator.translate(pro, lang_tgt=lan[0]) + try: + await daisyx.send_chat_action(message.chat.id, "typing") + await message.reply_text(pro) + except CFError: + return + + +@daisyx.on_message( + filters.regex("Daisy|daisy|DaisyX|daisyx|Daisyx") + & ~filters.bot + & ~filters.via_bot + & ~filters.forwarded + & ~filters.reply + & ~filters.channel + & ~filters.edited +) +async def inuka(client, message): + msg = message.text + if msg.startswith("/") or msg.startswith("@"): + message.continue_propagation() + u = msg.split() + emj = extract_emojis(msg) + msg = msg.replace(emj, "") + if ( + [(k) for k in u if k.startswith("@")] + and [(k) for k in u if k.startswith("#")] + and [(k) for k in u if k.startswith("/")] + and re.findall(r"\[([^]]+)]\(\s*([^)]+)\s*\)", msg) != [] + ): + + h = " ".join(filter(lambda x: x[0] != "@", u)) + km = re.sub(r"\[([^]]+)]\(\s*([^)]+)\s*\)", r"", h) + tm = km.split() + jm = " ".join(filter(lambda x: x[0] != "#", tm)) + hm = jm.split() + rm = " ".join(filter(lambda x: x[0] != "/", hm)) + elif [(k) for k in u if k.startswith("@")]: + + rm = " ".join(filter(lambda x: x[0] != "@", u)) + elif [(k) for k in u if k.startswith("#")]: + rm = " ".join(filter(lambda x: x[0] != "#", u)) + elif [(k) for k in u if k.startswith("/")]: + rm = " ".join(filter(lambda x: x[0] != "/", u)) + elif re.findall(r"\[([^]]+)]\(\s*([^)]+)\s*\)", msg) != []: + rm = re.sub(r"\[([^]]+)]\(\s*([^)]+)\s*\)", r"", msg) + else: + rm = msg + # print (rm) + try: + lan = translator.detect(rm) + except: + return + test = rm + if not "en" in lan and not lan == "": + try: + test = translator.translate(test, lang_tgt="en") + except: + return + + # test = emoji.demojize(test.strip()) + + test = test.replace("daisy", "Aco") + test = test.replace("Daisy", "Aco") + response = await lunaQuery(test, message.from_user.id if message.from_user else 0) + response = response.replace("Aco", "Daisy") + response = response.replace("aco", "Daisy") + + pro = response + if not "en" in lan and not lan == "": + try: + pro = translator.translate(pro, lang_tgt=lan[0]) + except Exception: + return + try: + await daisyx.send_chat_action(message.chat.id, "typing") + await message.reply_text(pro) + except CFError: + return + + +__help__ = """ + Chatbot +DAISY AI 3.0 IS THE ONLY AI SYSTEM WHICH CAN DETECT & REPLY UPTO 200 LANGUAGES + + - /chatbot [ON/OFF]: Enables and disables AI Chat mode (EXCLUSIVE) + - /chatbot EN : Enables English only chatbot + + + Assistant + - /ask [question]: Ask question from daisy + - /ask [reply to voice note]: Get voice reply + +""" + +__mod_name__ = "AI Assistant" diff --git a/DaisyX/modules/Autofilters b/DaisyX/modules/Autofilters new file mode 100644 index 00000000..11bc589a --- /dev/null +++ b/DaisyX/modules/Autofilters @@ -0,0 +1,43 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +__mod_name__ = "Auto Filters" +__help__ = """ + AUTO FILTERS +Daisy Can filter content of a given channel automatically +Currently support: + - Videos + - Media + - Documents + - Music + + Setting up +1) Add @DaisyXBot to your channel +2) Make bot admin with full permissions +2) Go back to your group + + Commands +- /autofilter [Channel Username] : Add given channel to autofiltering +- /autofilterdel [Channel Username] : Remove given channel from auto filtering +- /autofilterdelall : Remove all channels from automatic filtering +- /autofiltersettings : Show settings panel about auto filtering channels + + Inspired by https://github.com/AlbertEinsteinTG/Adv-Auto-Filter-Bot-V2 +Original work is done by @CrazyBotsz + +""" diff --git a/DaisyX/modules/Cricket_Score.py b/DaisyX/modules/Cricket_Score.py new file mode 100644 index 00000000..54b5f21a --- /dev/null +++ b/DaisyX/modules/Cricket_Score.py @@ -0,0 +1,67 @@ +# Copyright (C) @chsaiujwal 2020-2021 +# Edited by TeamDaisyX +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import urllib.request + +from bs4 import BeautifulSoup +from telethon import events +from telethon.tl import functions, types + +from DaisyX.services.telethon import tbot + + +async def is_register_admin(chat, user): + + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@tbot.on(events.NewMessage(pattern="/cs$")) +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + score_page = "http://static.cricinfo.com/rss/livescores.xml" + page = urllib.request.urlopen(score_page) + soup = BeautifulSoup(page, "html.parser") + result = soup.find_all("description") + Sed = "" + for match in result: + Sed += match.get_text() + "\n\n" + await event.reply( + f"Match information gathered successful\n\n\n{Sed}", + parse_mode="HTML", + ) diff --git a/DaisyX/modules/ForceSubscribe.py b/DaisyX/modules/ForceSubscribe.py new file mode 100644 index 00000000..50209eed --- /dev/null +++ b/DaisyX/modules/ForceSubscribe.py @@ -0,0 +1,233 @@ +# Copyright (C) 2020-2021 by @InukaAsith +# This programme is a part of Daisy TG bot project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import logging +import time + +from pyrogram import filters +from pyrogram.errors import RPCError +from pyrogram.errors.exceptions.bad_request_400 import ( + ChannelPrivate, + ChatAdminRequired, + PeerIdInvalid, + UsernameNotOccupied, + UserNotParticipant, +) +from pyrogram.types import ChatPermissions, InlineKeyboardButton, InlineKeyboardMarkup + +from DaisyX import BOT_ID + +# from DaisyX import OWNER_ID as SUDO_USERS +from DaisyX.services.pyrogram import pbot +from DaisyX.services.sql import forceSubscribe_sql as sql + +logging.basicConfig(level=logging.INFO) + +static_data_filter = filters.create( + lambda _, __, query: query.data == "onUnMuteRequest" +) + + +@pbot.on_callback_query(static_data_filter) +def _onUnMuteRequest(client, cb): + try: + user_id = cb.from_user.id + chat_id = cb.message.chat.id + except: + return + chat_db = sql.fs_settings(chat_id) + if chat_db: + channel = chat_db.channel + try: + chat_member = client.get_chat_member(chat_id, user_id) + except: + return + if chat_member.restricted_by: + if chat_member.restricted_by.id == BOT_ID: + try: + client.get_chat_member(channel, user_id) + client.unban_chat_member(chat_id, user_id) + cb.message.delete() + # if cb.message.reply_to_message.from_user.id == user_id: + # cb.message.delete() + except UserNotParticipant: + client.answer_callback_query( + cb.id, + text=f"❗ Join our @{channel} channel and press 'UnMute Me' button.", + show_alert=True, + ) + except ChannelPrivate: + client.unban_chat_member(chat_id, user_id) + cb.message.delete() + + else: + client.answer_callback_query( + cb.id, + text="❗ You have been muted by admins due to some other reason.", + show_alert=True, + ) + else: + if not client.get_chat_member(chat_id, BOT_ID).status == "administrator": + client.send_message( + chat_id, + f"❗ **{cb.from_user.mention} is trying to UnMute himself but i can't unmute him because i am not an admin in this chat add me as admin again.**\n__#Leaving this chat...__", + ) + + else: + client.answer_callback_query( + cb.id, + text="❗ Warning! Don't press the button when you cn talk.", + show_alert=True, + ) + + +@pbot.on_message(filters.text & ~filters.private & ~filters.edited, group=1) +def _check_member(client, message): + chat_id = message.chat.id + chat_db = sql.fs_settings(chat_id) + if chat_db: + try: + user_id = message.from_user.id + except: + return + try: + if ( + not client.get_chat_member(chat_id, user_id).status + in ("administrator", "creator") + and not user_id == 1141839926 + ): + channel = chat_db.channel + try: + client.get_chat_member(channel, user_id) + except UserNotParticipant: + try: + sent_message = message.reply_text( + "Welcome {} 🙏 \n **You havent joined our @{} Channel yet** 😭 \n \nPlease Join [Our Channel](https://t.me/{}) and hit the **UNMUTE ME** Button. \n \n ".format( + message.from_user.mention, channel, channel + ), + disable_web_page_preview=True, + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + "Join Channel", + url="https://t.me/{}".format(channel), + ) + ], + [ + InlineKeyboardButton( + "UnMute Me", callback_data="onUnMuteRequest" + ) + ], + ] + ), + ) + client.restrict_chat_member( + chat_id, user_id, ChatPermissions(can_send_messages=False) + ) + except ChatAdminRequired: + sent_message.edit( + "❗ **Daisy is not admin here..**\n__Give me ban permissions and retry.. \n#Ending FSub...__" + ) + except RPCError: + return + + except ChatAdminRequired: + client.send_message( + chat_id, + text=f"❗ **I not an admin of @{channel} channel.**\n__Give me admin of that channel and retry.\n#Ending FSub...__", + ) + except ChannelPrivate: + return + except: + return + + +@pbot.on_message(filters.command(["forcesubscribe", "forcesub"]) & ~filters.private) +def config(client, message): + user = client.get_chat_member(message.chat.id, message.from_user.id) + if user.status is "creator" or user.user.id == 1141839926: + chat_id = message.chat.id + if len(message.command) > 1: + input_str = message.command[1] + input_str = input_str.replace("@", "") + if input_str.lower() in ("off", "no", "disable"): + sql.disapprove(chat_id) + message.reply_text("❌ **Force Subscribe is Disabled Successfully.**") + elif input_str.lower() in ("clear"): + sent_message = message.reply_text( + "**Unmuting all members who are muted by me...**" + ) + try: + for chat_member in client.get_chat_members( + message.chat.id, filter="restricted" + ): + if chat_member.restricted_by.id == BOT_ID: + client.unban_chat_member(chat_id, chat_member.user.id) + time.sleep(1) + sent_message.edit("✅ **UnMuted all members who are muted by me.**") + except ChatAdminRequired: + sent_message.edit( + "❗ **I am not an admin in this chat.**\n__I can't unmute members because i am not an admin in this chat make me admin with ban user permission.__" + ) + else: + try: + client.get_chat_member(input_str, "me") + sql.add_channel(chat_id, input_str) + message.reply_text( + f"✅ **Force Subscribe is Enabled**\n__Force Subscribe is enabled, all the group members have to subscribe this [channel](https://t.me/{input_str}) in order to send messages in this group.__", + disable_web_page_preview=True, + ) + except UserNotParticipant: + message.reply_text( + f"❗ **Not an Admin in the Channel**\n__I am not an admin in the [channel](https://t.me/{input_str}). Add me as a admin in order to enable ForceSubscribe.__", + disable_web_page_preview=True, + ) + except (UsernameNotOccupied, PeerIdInvalid): + message.reply_text(f"❗ **Invalid Channel Username.**") + except Exception as err: + message.reply_text(f"❗ **ERROR:** ```{err}```") + else: + if sql.fs_settings(chat_id): + message.reply_text( + f"✅ **Force Subscribe is enabled in this chat.**\n__For this [Channel](https://t.me/{sql.fs_settings(chat_id).channel})__", + disable_web_page_preview=True, + ) + else: + message.reply_text("❌ **Force Subscribe is disabled in this chat.**") + else: + message.reply_text( + "❗ **Group Creator Required**\n__You have to be the group creator to do that.__" + ) + + +__help__ = """ +ForceSubscribe: +- Daisy can mute members who are not subscribed your channel until they subscribe +- When enabled I will mute unsubscribed members and show them a unmute button. When they pressed the button I will unmute them +Setup +1) First of all add me in the group as admin with ban users permission and in the channel as admin. +Note: Only creator of the group can setup me and i will not allow force subscribe again if not done so. + +Commmands + - /forcesubscribe - To get the current settings. + - /forcesubscribe no/off/disable - To turn of ForceSubscribe. + - /forcesubscribe {channel username} - To turn on and setup the channel. + - /forcesubscribe clear - To unmute all members who muted by me. +Note: /forcesub is an alias of /forcesubscribe + +""" +__mod_name__ = "Force Subscribe " diff --git a/DaisyX/modules/Updator.py b/DaisyX/modules/Updator.py new file mode 100644 index 00000000..53f583fa --- /dev/null +++ b/DaisyX/modules/Updator.py @@ -0,0 +1,189 @@ +import asyncio +import sys +from os import environ, execle, path, remove + +import heroku3 +from git import Repo +from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError + +from DaisyX import OWNER_ID +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as update + +HEROKU_APP_NAME = get_str_key("HEROKU_APP_NAME", None) +HEROKU_API_KEY = get_str_key("HEROKU_API_KEY", None) +UPSTREAM_REPO_URL = get_str_key("UPSTREAM_REPO_URL", None) +if not UPSTREAM_REPO_URL: + UPSTREAM_REPO_URL = "https://github.com/TeamDaisyX/DaisyX-v2.0" + +requirements_path = path.join( + path.dirname(path.dirname(path.dirname(__file__))), "requirements.txt" +) + + +async def gen_chlog(repo, diff): + ch_log = "" + d_form = "%d/%m/%y" + for c in repo.iter_commits(diff): + ch_log += ( + f"•[{c.committed_datetime.strftime(d_form)}]: {c.summary} by <{c.author}>\n" + ) + return ch_log + + +async def updateme_requirements(): + reqs = str(requirements_path) + try: + process = await asyncio.create_subprocess_shell( + " ".join([sys.executable, "-m", "pip", "install", "-r", reqs]), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + await process.communicate() + return process.returncode + except Exception as e: + return repr(e) + + +@register(pattern="^/update(?: |$)(.*)") +async def upstream(ups): + global UPSTREAM_REPO_URL + check = ups.message.sender_id + OK = int(OWNER_ID) + if int(check) != OK: + return + lol = await ups.reply("`Checking for updates, please wait....`") + conf = ups.pattern_match.group(1) + off_repo = UPSTREAM_REPO_URL + force_update = False + + try: + txt = "`Oops.. Updater cannot continue " + txt += "please add heroku apikey, name`\n\n**LOGTRACE:**\n" + repo = Repo() + except NoSuchPathError as error: + await lol.edit(f"{txt}\n`directory {error} is not found`") + repo.__del__() + return + except GitCommandError as error: + await lol.edit(f"{txt}\n`Early failure! {error}`") + repo.__del__() + return + except InvalidGitRepositoryError as error: + if conf != "now": + await lol.edit( + f"**Unfortunately, the directory {error} does not seem to be a git repository.\ + \nBut we can fix that by force updating the bot using** `/update now`" + ) + return + repo = Repo.init() + origin = repo.create_remote("upstream", off_repo) + origin.fetch() + force_update = True + repo.create_head("main", origin.refs.main) + repo.heads.main.set_tracking_branch(origin.refs.main) + repo.heads.main.checkout(True) + + ac_br = repo.active_branch.name + if ac_br != "main": + await lol.edit( + f"**[UPDATER]:**` Looks like you are using your own custom branch ({ac_br}). " + "in that case, Updater is unable to identify " + "which branch is to be merged. " + "please checkout to any official branch`" + ) + repo.__del__() + return + + try: + repo.create_remote("upstream", off_repo) + except BaseException: + pass + + ups_rem = repo.remote("upstream") + ups_rem.fetch(ac_br) + + changelog = await gen_chlog(repo, f"HEAD..upstream/{ac_br}") + + if not changelog and not force_update: + await lol.edit("\nYour DaisyX >> **up-to-date** \n") + repo.__del__() + return + + if conf != "now" and not force_update: + changelog_str = ( + f"**New UPDATE available for {ac_br}\n\nCHANGELOG:**\n`{changelog}`" + ) + if len(changelog_str) > 4096: + await lol.edit("`Changelog is too big, view the file to see it.`") + file = open("output.txt", "w+") + file.write(changelog_str) + file.close() + await update.send_file( + ups.chat_id, + "output.txt", + reply_to=ups.id, + ) + remove("output.txt") + else: + await lol.edit(changelog_str) + await ups.respond("**do** `/update now` **to update**") + return + + if force_update: + await lol.edit("`Force-Syncing to latest main bot code, please wait...`") + else: + await lol.edit("`Still Running ....`") + if conf == "deploy": + if HEROKU_API_KEY is not None: + heroku = heroku3.from_key(HEROKU_API_KEY) + heroku_app = None + heroku_applications = heroku.apps() + if not HEROKU_APP_NAME: + await lol.edit( + "`Please set up the HEROKU_APP_NAME variable to be able to update your bot.`" + ) + repo.__del__() + return + for app in heroku_applications: + if app.name == HEROKU_APP_NAME: + heroku_app = app + break + if heroku_app is None: + await lol.edit( + f"{txt}\n`Invalid Heroku credentials for updating bot dyno.`" + ) + repo.__del__() + return + await lol.edit( + "`[Updater]\ + Your bot is being deployed, please wait for it to complete.\nIt may take upto 5 minutes `" + ) + ups_rem.fetch(ac_br) + repo.git.reset("--hard", "FETCH_HEAD") + heroku_git_url = heroku_app.git_url.replace( + "https://", "https://api:" + HEROKU_API_KEY + "@" + ) + if "heroku" in repo.remotes: + remote = repo.remote("heroku") + remote.set_url(heroku_git_url) + else: + remote = repo.create_remote("heroku", heroku_git_url) + try: + remote.push(refspec="HEAD:refs/heads/main", force=True) + except GitCommandError as error: + await lol.edit(f"{txt}\n`Here is the error log:\n{error}`") + repo.__del__() + return + await lol.edit("Successfully Updated!\n" "Restarting.......") + else: + try: + ups_rem.pull(ac_br) + except GitCommandError: + repo.git.reset("--hard", "FETCH_HEAD") + await updateme_requirements() + await lol.edit("`Successfully Updated!\n" "restarting......`") + args = [sys.executable, "-m", "DaisyX"] + execle(sys.executable, *args, environ) + return diff --git a/DaisyX/modules/UrlLock.py b/DaisyX/modules/UrlLock.py new file mode 100644 index 00000000..2abfe04a --- /dev/null +++ b/DaisyX/modules/UrlLock.py @@ -0,0 +1,120 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio + +from pyrogram import filters +from pyrogram.errors import RPCError + +from DaisyX import BOT_ID +from DaisyX.db.mongo_helpers.lockurl import add_chat, get_session, remove_chat +from DaisyX.function.pluginhelpers import ( + admins_only, + edit_or_reply, + get_url, + member_permissions, +) +from DaisyX.services.pyrogram import pbot + + +@pbot.on_message( + filters.command("urllock") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def hmm(_, message): + global daisy_chats + try: + user_id = message.from_user.id + except: + return + if not "can_change_info" in (await member_permissions(message.chat.id, user_id)): + await message.reply_text("**You don't have enough permissions**") + return + if len(message.command) != 2: + await message.reply_text( + "I only recognize `/urllock on` and /urllock `off only`" + ) + return + status = message.text.split(None, 1)[1] + message.chat.id + if status == "ON" or status == "on" or status == "On": + lel = await edit_or_reply(message, "`Processing...`") + lol = add_chat(int(message.chat.id)) + if not lol: + await lel.edit("URL Block Already Activated In This Chat") + return + await lel.edit( + f"URL Block Successfully Added For Users In The Chat {message.chat.id}" + ) + + elif status == "OFF" or status == "off" or status == "Off": + lel = await edit_or_reply(message, "`Processing...`") + Escobar = remove_chat(int(message.chat.id)) + if not Escobar: + await lel.edit("URL Block Was Not Activated In This Chat") + return + await lel.edit( + f"URL Block Successfully Deactivated For Users In The Chat {message.chat.id}" + ) + else: + await message.reply_text( + "I only recognize `/urllock on` and /urllock `off only`" + ) + + +@pbot.on_message( + filters.incoming & filters.text & ~filters.private & ~filters.channel & ~filters.bot +) +async def hi(client, message): + if not get_session(int(message.chat.id)): + message.continue_propagation() + try: + user_id = message.from_user.id + except: + return + try: + if not len(await member_permissions(message.chat.id, user_id)) < 1: + message.continue_propagation() + if len(await member_permissions(message.chat.id, BOT_ID)) < 1: + message.continue_propagation() + if not "can_delete_messages" in ( + await member_permissions(message.chat.id, BOT_ID) + ): + message.continue_propagation() + except RPCError: + return + try: + + lel = get_url(message) + except: + return + + if lel: + try: + await message.delete() + sender = message.from_user.mention() + lol = await client.send_message( + message.chat.id, + f"{sender}, Your message was deleted as it contain a link(s). \n ❗️ Links are not allowed here", + ) + await asyncio.sleep(10) + await lol.delete() + except: + message.continue_propagation() + else: + message.continue_propagation() diff --git a/DaisyX/modules/VoiceChatbot.py b/DaisyX/modules/VoiceChatbot.py new file mode 100644 index 00000000..b9e3df80 --- /dev/null +++ b/DaisyX/modules/VoiceChatbot.py @@ -0,0 +1,56 @@ +# Voics Chatbot Module Credits Pranav Ajay 🐰Github = Red-Aura 🐹 Telegram= @madepranav +# @lyciachatbot support Now +import os + +import aiofiles +import aiohttp +from pyrogram import filters + +from DaisyX.services.pyrogram import pbot as LYCIA + + +async def fetch(url): + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + try: + data = await resp.json() + except: + data = await resp.text() + return data + + +async def ai_lycia(url): + ai_name = "Daisyx.mp3" + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status == 200: + f = await aiofiles.open(ai_name, mode="wb") + await f.write(await resp.read()) + await f.close() + return ai_name + + +@LYCIA.on_message(filters.command("daisy")) +async def Lycia(_, message): + if len(message.command) < 2: + await message.reply_text("DaisyX AI Voice Chatbot") + return + text = message.text.split(None, 1)[1] + lycia = text.replace(" ", "%20") + m = await message.reply_text("Daisyx Is Best...") + try: + L = await fetch( + f"https://api.affiliateplus.xyz/api/chatbot?message={lycia}&botname=Daisy&ownername=TeamDaisyX&user=1" + ) + chatbot = L["message"] + VoiceAi = f"https://lyciavoice.herokuapp.com/lycia?text={chatbot}&lang=hi" + name = "DaisyX" + except Exception as e: + await m.edit(str(e)) + return + await m.edit("Made By @madepranav...") + LyciaVoice = await ai_lycia(VoiceAi) + await m.edit("Repyping...") + await message.reply_audio(audio=LyciaVoice, title=chatbot, performer=name) + os.remove(LyciaVoice) + await m.delete() diff --git a/DaisyX/modules/Voice_Ai.py b/DaisyX/modules/Voice_Ai.py new file mode 100644 index 00000000..df4f786b --- /dev/null +++ b/DaisyX/modules/Voice_Ai.py @@ -0,0 +1,188 @@ +# Ported from @MissJuliaRobot + +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import os +import subprocess + +import requests +from gtts import gTTS, gTTSError +from requests import get +from telethon.tl import functions, types +from telethon.tl.types import * + +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +IBM_WATSON_CRED_PASSWORD = get_str_key("IBM_WATSON_CRED_PASSWORD", None) +IBM_WATSON_CRED_URL = get_str_key("IBM_WATSON_CRED_URL", None) +WOLFRAM_ID = get_str_key("WOLFRAM_ID", None) +TEMP_DOWNLOAD_DIRECTORY = "./" + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern=r"^/ask(?: |$)([\s\S]*)") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + if not event.reply_to_msg_id: + i = event.pattern_match.group(1) + appid = WOLFRAM_ID + server = f"https://api.wolframalpha.com/v1/spoken?appid={appid}&i={i}" + res = get(server) + if "Wolfram Alpha did not understand" in res.text: + await event.reply( + "Sorry, Daisy's AI systems could't recognized your question.." + ) + return + await event.reply(f"**{i}**\n\n" + res.text, parse_mode="markdown") + + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + required_file_name = await tbot.download_media( + previous_message, TEMP_DOWNLOAD_DIRECTORY + ) + if IBM_WATSON_CRED_URL is None or IBM_WATSON_CRED_PASSWORD is None: + await event.reply( + "You need to set the required ENV variables for this module. \nModule stopping" + ) + else: + headers = { + "Content-Type": previous_message.media.document.mime_type, + } + data = open(required_file_name, "rb").read() + response = requests.post( + IBM_WATSON_CRED_URL + "/v1/recognize", + headers=headers, + data=data, + auth=("apikey", IBM_WATSON_CRED_PASSWORD), + ) + r = response.json() + if "results" in r: + # process the json to appropriate string format + results = r["results"] + transcript_response = "" + for alternative in results: + alternatives = alternative["alternatives"][0] + transcript_response += " " + str(alternatives["transcript"]) + if transcript_response != "": + string_to_show = "{}".format(transcript_response) + appid = WOLFRAM_ID + server = f"https://api.wolframalpha.com/v1/spoken?appid={appid}&i={string_to_show}" + res = get(server) + + if "Wolfram Alpha did not understand" in res: + answer = ( + "I'm sorry Daisy's AI system can't undestand your problem" + ) + else: + answer = res.text + try: + tts = gTTS(answer, tld="com", lang="en") + tts.save("results.mp3") + except AssertionError: + return + except ValueError: + return + except RuntimeError: + return + except gTTSError: + return + with open("results.mp3", "r"): + await tbot.send_file( + event.chat_id, + "results.mp3", + voice_note=True, + reply_to=event.id, + ) + os.remove("results.mp3") + os.remove(required_file_name) + elif ( + transcript_response == "Wolfram Alpha did not understand your input" + ): + try: + answer = "Sorry, Daisy's AI system can't understand you.." + tts = gTTS(answer, tld="com", lang="en") + tts.save("results.mp3") + except AssertionError: + return + except ValueError: + return + except RuntimeError: + return + except gTTSError: + return + with open("results.mp3", "r"): + await tbot.send_file( + event.chat_id, + "results.mp3", + voice_note=True, + reply_to=event.id, + ) + os.remove("results.mp3") + os.remove(required_file_name) + else: + await event.reply("API Failure !") + os.remove(required_file_name) + + +@register(pattern="^/howdoi (.*)") +async def howdoi(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + + str = event.pattern_match.group(1) + jit = subprocess.check_output(["howdoi", f"{str}"]) + pit = jit.decode() + await event.reply(pit) diff --git a/DaisyX/modules/__init__.py b/DaisyX/modules/__init__.py new file mode 100644 index 00000000..d76fa26c --- /dev/null +++ b/DaisyX/modules/__init__.py @@ -0,0 +1,56 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +import sys + +from DaisyX.utils.logger import log + +LOADED_MODULES = [] +MOD_HELP = {} + + +def list_all_modules() -> list: + modules_directory = "DaisyX/modules" + + all_modules = [] + for module_name in os.listdir(modules_directory): + path = modules_directory + "/" + module_name + + if "__init__" in path or "__pycache__" in path: + continue + + if path in all_modules: + log.path("Modules with same name can't exists!") + sys.exit(5) + + # One file module type + if path.endswith(".py"): + # TODO: removesuffix + all_modules.append(module_name.split(".py")[0]) + + # Module directory + if os.path.isdir(path) and os.path.exists(path + "/__init__.py"): + all_modules.append(module_name) + + return all_modules + + +ALL_MODULES = sorted(list_all_modules()) +__all__ = ALL_MODULES + ["ALL_MODULES"] diff --git a/DaisyX/modules/_webss.py b/DaisyX/modules/_webss.py new file mode 100644 index 00000000..22190dd0 --- /dev/null +++ b/DaisyX/modules/_webss.py @@ -0,0 +1,28 @@ +# Ported From William Butcher Bot :- https://github.com/thehamkercat/WilliamButcherBot/edit/dev/wbb/modules/webss.py . +# All Credit to WilliamButcherBot. + + +from pyrogram import filters + +from DaisyX.function.pluginhelpers import admins_only +from DaisyX.services.pyrogram import pbot as app + + +@app.on_message(filters.command("webss") & ~filters.private & ~filters.edited) +@admins_only +async def take_ss(_, message): + if len(message.command) != 2: + await message.reply_text("Give A Url To Fetch Screenshot.") + return + url = message.text.split(None, 1)[1] + m = await message.reply_text("**Taking Screenshot**") + await m.edit("**Uploading**") + try: + await app.send_photo( + message.chat.id, + photo=f"https://webshot.amanoteam.com/print?q={url}", + ) + except TypeError: + await m.edit("No Such Website.") + return + await m.delete() diff --git a/DaisyX/modules/admin.py b/DaisyX/modules/admin.py new file mode 100644 index 00000000..496c7466 --- /dev/null +++ b/DaisyX/modules/admin.py @@ -0,0 +1,159 @@ +""" +MIT License +Copyright (c) 2021 TheHamkerCat +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" +import os + +from pyrogram import filters + +from DaisyX.function.pluginhelpers import member_permissions +from DaisyX.services.pyrogram import pbot as app + + +@app.on_message(filters.command("setgrouptitle") & ~filters.private) +async def set_chat_title(_, message): + try: + chat_id = message.chat.id + user_id = message.from_user.id + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await message.reply_text("You Don't Have Enough Permissions.") + return + if len(message.command) < 2: + await message.reply_text("**Usage:**\n/set_chat_title NEW NAME") + return + old_title = message.chat.title + new_title = message.text.split(None, 1)[1] + await message.chat.set_title(new_title) + await message.reply_text( + f"Successfully Changed Group Title From {old_title} To {new_title}" + ) + except Exception as e: + print(e) + await message.reply_text(e) + + +@app.on_message(filters.command("settitle") & ~filters.private) +async def set_user_title(_, message): + try: + chat_id = message.chat.id + user_id = message.from_user.id + from_user = message.reply_to_message.from_user + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await message.reply_text("You Don't Have Enough Permissions.") + return + if len(message.command) < 2: + await message.reply_text( + "**Usage:**\n/set_user_title NEW ADMINISTRATOR TITLE" + ) + return + title = message.text.split(None, 1)[1] + await app.set_administrator_title(chat_id, from_user.id, title) + await message.reply_text( + f"Successfully Changed {from_user.mention}'s Admin Title To {title}" + ) + except Exception as e: + print(e) + await message.reply_text(e) + + +@app.on_message(filters.command("setgrouppic") & ~filters.private) +async def set_chat_photo(_, message): + try: + chat_id = message.chat.id + user_id = message.from_user.id + + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await message.reply_text("You Don't Have Enough Permissions.") + return + if not message.reply_to_message: + await message.reply_text("Reply to a photo to set it as chat_photo") + return + if not message.reply_to_message.photo and not message.reply_to_message.document: + await message.reply_text("Reply to a photo to set it as chat_photo") + return + photo = await message.reply_to_message.download() + await message.chat.set_photo(photo) + await message.reply_text("Successfully Changed Group Photo") + os.remove(photo) + except Exception as e: + print(e) + await message.reply_text(e) + + +__mod_name__ = "Admin" + +__help__ = """ +Make it easy to admins for manage users and groups with the admin module! + +Available commands: + + Admin List +- /adminlist: Shows all admins of the chat.* +- /admincache: Update the admin cache, to take into account new admins/admin permissions.* + + Mutes +- /mute: mute a user +- /unmute: unmutes a user +- /tmute [entity] : temporarily mutes a user for the time interval. +- /unmuteall: Unmute all muted members + + Bans & Kicks +- /ban: bans a user +- /tban [entity] : temporarily bans a user for the time interval. +- /unban: unbans a user +- /unbanall: Unban all banned members +- /banme: Bans you +- /kick: kicks a user +- /kickme: Kicks you + + Promotes & Demotes +- /promote (user) (?admin's title): Promotes the user to admin.* +- /demote (user): Demotes the user from admin.* +- /lowpromote: Promote a member with low rights* +- /midpromote: Promote a member with mid rights* +- /highpromote: Promote a member with max rights* +- /lowdemote: Demote an admin to low permissions* +- /middemote: Demote an admin to mid permissions* + + Cleaner/Purges +- /purge: deletes all messages from the message you replied to +- /del: deletes the message replied to +- /zombies: counts the number of deleted account in your group +- /kickthefools: Kick inactive members from group (one week) + + User Info +- /info: Get user's info +- /users: Get users list of group +- /spwinfo : Check user's spam info from intellivoid's Spamprotection service +- /whois : Gives user's info like pyrogram + + Other +- /invitelink: Get chat's invitr link +- /settitle [entity] [title]: sets a custom title for an admin. If no [title] provided defaults to "Admin" +- /setgrouptitle [text] set group title +- /setgrouppic: reply to an image to set as group photo +- /setdescription: Set group description +- /setsticker: Set group sticker + +*Note: +Sometimes, you promote or demote an admin manually, and Daisy doesn't realise it immediately. This is because to avoid spamming telegram servers, admin status is cached locally. +This means that you sometimes have to wait a few minutes for admin rights to update. If you want to update them immediately, you can use the /admincache command; that'll force Daisy to check who the admins are again. +""" diff --git a/DaisyX/modules/adminmisc.py b/DaisyX/modules/adminmisc.py new file mode 100644 index 00000000..9f6328db --- /dev/null +++ b/DaisyX/modules/adminmisc.py @@ -0,0 +1,699 @@ +# Copyright (C) 2021 AlainX &TeamDaisyX + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +from time import sleep + +from telethon import * +from telethon import events +from telethon.errors import * +from telethon.errors import FloodWaitError +from telethon.tl import * +from telethon.tl import functions, types +from telethon.tl.functions.channels import EditAdminRequest, EditBannedRequest +from telethon.tl.types import * +from telethon.tl.types import ( + ChatAdminRights, + ChatBannedRights, + MessageEntityMentionName, +) + +from DaisyX import OWNER_ID +from DaisyX.services.telethon import tbot as bot + +# =================== CONSTANT =================== +PP_TOO_SMOL = "**The image is too small**" +PP_ERROR = "**Failure while processing image**" +NO_ADMIN = "**I am not an admin**" +NO_PERM = "**I don't have sufficient permissions!**" + +CHAT_PP_CHANGED = "**Chat Picture Changed**" +CHAT_PP_ERROR = ( + "**Some issue with updating the pic,**" + "**maybe you aren't an admin,**" + "**or don't have the desired rights.**" +) +INVALID_MEDIA = "Invalid Extension" +BANNED_RIGHTS = ChatBannedRights( + until_date=None, + view_messages=True, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True, +) +UNBAN_RIGHTS = ChatBannedRights( + until_date=None, + send_messages=None, + send_media=None, + send_stickers=None, + send_gifs=None, + send_games=None, + send_inline=None, + embed_links=None, +) +KICK_RIGHTS = ChatBannedRights(until_date=None, view_messages=True) +MUTE_RIGHTS = ChatBannedRights(until_date=None, send_messages=True) +UNMUTE_RIGHTS = ChatBannedRights(until_date=None, send_messages=False) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await bot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +async def can_promote_users(message): + result = await bot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.ban_users + ) + + +async def can_ban_users(message): + result = await bot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.ban_users + ) + + +async def can_change_info(message): + result = await bot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.change_info + ) + + +async def can_del(message): + result = await bot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.delete_messages + ) + + +async def can_pin_msg(message): + result = await bot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.pin_messages + ) + + +async def get_user_sender_id(user, event): + if isinstance(user, str): + user = int(user) + + try: + user_obj = await bot.get_entity(user) + except (TypeError, ValueError) as err: + await event.edit(str(err)) + return None + + return user_obj + + +async def get_user_from_event(event): + """Get the user from argument or replied message.""" + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + user_obj = await bot.get_entity(previous_message.sender_id) + else: + user = event.pattern_match.group(1) + + if user.isnumeric(): + user = int(user) + + if not user: + await event.reply( + "**I don't know who you're talking about, you're going to need to specify a user...!**" + ) + return + + if event.message.entities is not None: + probable_user_mention_entity = event.message.entities[0] + + if isinstance(probable_user_mention_entity, MessageEntityMentionName): + user_id = probable_user_mention_entity.user_id + user_obj = await bot.get_entity(user_id) + return user_obj + try: + user_obj = await bot.get_entity(user) + except (TypeError, ValueError) as err: + await event.reply(str(err)) + return None + + return user_obj + + +def find_instance(items, class_or_tuple): + for item in items: + if isinstance(item, class_or_tuple): + return item + return None + + +@bot.on(events.NewMessage(pattern="/lowpromote ?(.*)")) +async def lowpromote(promt): + if promt.is_group: + if promt.sender_id == OWNER_ID: + pass + else: + if not await can_promote_users(message=promt): + return + else: + return + + user = await get_user_from_event(promt) + if promt.is_group: + if await is_register_admin(promt.input_chat, user.id): + await promt.reply("**Well! i cant promote user who is already an admin**") + return + else: + return + + new_rights = ChatAdminRights( + add_admins=False, + invite_users=True, + change_info=False, + ban_users=False, + delete_messages=True, + pin_messages=False, + ) + + if user: + pass + else: + return + quew = promt.pattern_match.group(1) + if quew: + title = quew + else: + title = "Moderator" + # Try to promote if current user is admin or creator + try: + await bot(EditAdminRequest(promt.chat_id, user.id, new_rights, title)) + await promt.reply("**Successfully promoted!**") + + # If Telethon spit BadRequestError, assume + # we don't have Promote permission + except Exception: + await promt.reply("Failed to promote.") + return + + +@bot.on(events.NewMessage(pattern="/midpromote ?(.*)")) +async def midpromote(promt): + if promt.is_group: + if promt.sender_id == OWNER_ID: + pass + else: + if not await can_promote_users(message=promt): + return + else: + return + + user = await get_user_from_event(promt) + if promt.is_group: + if await is_register_admin(promt.input_chat, user.id): + await promt.reply("**Well! i cant promote user who is already an admin**") + return + else: + return + + new_rights = ChatAdminRights( + add_admins=False, + invite_users=True, + change_info=True, + ban_users=False, + delete_messages=True, + pin_messages=True, + ) + + if user: + pass + else: + return + quew = promt.pattern_match.group(1) + if quew: + title = quew + else: + title = "Admin" + # Try to promote if current user is admin or creator + try: + await bot(EditAdminRequest(promt.chat_id, user.id, new_rights, title)) + await promt.reply("**Successfully promoted!**") + + # If Telethon spit BadRequestError, assume + # we don't have Promote permission + except Exception: + await promt.reply("Failed to promote.") + return + + +@bot.on(events.NewMessage(pattern="/highpromote ?(.*)")) +async def highpromote(promt): + if promt.is_group: + if promt.sender_id == OWNER_ID: + pass + else: + if not await can_promote_users(message=promt): + return + else: + return + + user = await get_user_from_event(promt) + if promt.is_group: + if await is_register_admin(promt.input_chat, user.id): + await promt.reply("**Well! i cant promote user who is already an admin**") + return + else: + return + + new_rights = ChatAdminRights( + add_admins=True, + invite_users=True, + change_info=True, + ban_users=True, + delete_messages=True, + pin_messages=True, + ) + + if user: + pass + else: + return + quew = promt.pattern_match.group(1) + if quew: + title = quew + else: + title = "Admin" + # Try to promote if current user is admin or creator + try: + await bot(EditAdminRequest(promt.chat_id, user.id, new_rights, title)) + await promt.reply("**Successfully promoted!**") + + # If Telethon spit BadRequestError, assume + # we don't have Promote permission + except Exception: + await promt.reply("Failed to promote.") + return + + +@bot.on(events.NewMessage(pattern="/lowdemote(?: |$)(.*)")) +async def lowdemote(dmod): + if dmod.is_group: + if not await can_promote_users(message=dmod): + return + else: + return + + user = await get_user_from_event(dmod) + if dmod.is_group: + if not await is_register_admin(dmod.input_chat, user.id): + await dmod.reply("**Hehe, i cant demote non-admin**") + return + else: + return + + if user: + pass + else: + return + + # New rights after demotion + newrights = ChatAdminRights( + add_admins=False, + invite_users=True, + change_info=False, + ban_users=False, + delete_messages=True, + pin_messages=False, + ) + # Edit Admin Permission + try: + await bot(EditAdminRequest(dmod.chat_id, user.id, newrights, "Admin")) + await dmod.reply("**Demoted Successfully!**") + + # If we catch BadRequestError from Telethon + # Assume we don't have permission to demote + except Exception: + await dmod.reply("**Failed to demote.**") + return + + +@bot.on(events.NewMessage(pattern="/middemote(?: |$)(.*)")) +async def middemote(dmod): + if dmod.is_group: + if not await can_promote_users(message=dmod): + return + else: + return + + user = await get_user_from_event(dmod) + if dmod.is_group: + if not await is_register_admin(dmod.input_chat, user.id): + await dmod.reply("**Hehe, i cant demote non-admin**") + return + else: + return + + if user: + pass + else: + return + + # New rights after demotion + newrights = ChatAdminRights( + add_admins=False, + invite_users=True, + change_info=True, + ban_users=False, + delete_messages=True, + pin_messages=True, + ) + # Edit Admin Permission + try: + await bot(EditAdminRequest(dmod.chat_id, user.id, newrights, "Admin")) + await dmod.reply("**Demoted Successfully!**") + + # If we catch BadRequestError from Telethon + # Assume we don't have permission to demote + except Exception: + await dmod.reply("**Failed to demote.**") + return + + +@bot.on(events.NewMessage(pattern="/users$")) +async def get_users(show): + if not show.is_group: + return + if show.is_group: + if not await is_register_admin(show.input_chat, show.sender_id): + return + info = await bot.get_entity(show.chat_id) + title = info.title if info.title else "this chat" + mentions = "Users in {}: \n".format(title) + async for user in bot.iter_participants(show.chat_id): + if not user.deleted: + mentions += f"\n[{user.first_name}](tg://user?id={user.id}) {user.id}" + else: + mentions += f"\nDeleted Account {user.id}" + file = open("userslist.txt", "w+") + file.write(mentions) + file.close() + await bot.send_file( + show.chat_id, + "userslist.txt", + caption="Users in {}".format(title), + reply_to=show.id, + ) + os.remove("userslist.txt") + + +@bot.on(events.NewMessage(pattern="/kickthefools$")) +async def _(event): + if event.fwd_from: + return + + if event.is_group: + if not await can_ban_users(message=event): + return + else: + return + + # Here laying the sanity check + chat = await event.get_chat() + admin = chat.admin_rights.ban_users + creator = chat.creator + + # Well + if not admin and not creator: + await event.reply("`I don't have enough permissions!`") + return + + c = 0 + KICK_RIGHTS = ChatBannedRights(until_date=None, view_messages=True) + done = await event.reply("Working ...") + async for i in bot.iter_participants(event.chat_id): + + if isinstance(i.status, UserStatusLastMonth): + status = await tbot(EditBannedRequest(event.chat_id, i, KICK_RIGHTS)) + if not status: + return + c = c + 1 + + if isinstance(i.status, UserStatusLastWeek): + status = await tbot(EditBannedRequest(event.chat_id, i, KICK_RIGHTS)) + if not status: + return + c = c + 1 + + if c == 0: + await done.edit("Got no one to kick 😔") + return + + required_string = "Successfully Kicked **{}** users" + await event.reply(required_string.format(c)) + + +@bot.on(events.NewMessage(pattern="/unbanall$")) +async def _(event): + if not event.is_group: + return + + if event.is_group: + if not await can_ban_users(message=event): + return + + # Here laying the sanity check + chat = await event.get_chat() + admin = chat.admin_rights.ban_users + creator = chat.creator + + # Well + if not admin and not creator: + await event.reply("`I don't have enough permissions!`") + return + + done = await event.reply("Searching Participant Lists.") + p = 0 + async for i in bot.iter_participants( + event.chat_id, filter=ChannelParticipantsKicked, aggressive=True + ): + rights = ChatBannedRights(until_date=0, view_messages=False) + try: + await bot(functions.channels.EditBannedRequest(event.chat_id, i, rights)) + except FloodWaitError as ex: + logger.warn("sleeping for {} seconds".format(ex.seconds)) + sleep(ex.seconds) + except Exception as ex: + await event.reply(str(ex)) + else: + p += 1 + + if p == 0: + await done.edit("No one is banned in this chat") + return + required_string = "Successfully unbanned **{}** users" + await event.reply(required_string.format(p)) + + +@bot.on(events.NewMessage(pattern="/unmuteall$")) +async def _(event): + if not event.is_group: + return + if event.is_group: + if not await can_ban_users(message=event): + return + + # Here laying the sanity check + chat = await event.get_chat() + admin = chat.admin_rights.ban_users + creator = chat.creator + + # Well + if not admin and not creator: + await event.reply("`I don't have enough permissions!`") + return + + done = await event.reply("Working ...") + p = 0 + async for i in bot.iter_participants( + event.chat_id, filter=ChannelParticipantsBanned, aggressive=True + ): + rights = ChatBannedRights( + until_date=0, + send_messages=False, + ) + try: + await bot(functions.channels.EditBannedRequest(event.chat_id, i, rights)) + except FloodWaitError as ex: + logger.warn("sleeping for {} seconds".format(ex.seconds)) + sleep(ex.seconds) + except Exception as ex: + await event.reply(str(ex)) + else: + p += 1 + + if p == 0: + await done.edit("No one is muted in this chat") + return + required_string = "Successfully unmuted **{}** users" + await event.reply(required_string.format(p)) + + +@bot.on(events.NewMessage(pattern="/banme$")) +async def banme(bon): + if not bon.is_group: + return + + try: + await bot(EditBannedRequest(bon.chat_id, sender, BANNED_RIGHTS)) + await bon.reply("Ok Banned !") + + except Exception: + await bon.reply("I don't think so!") + return + + +@bot.on(events.NewMessage(pattern="/kickme$")) +async def kickme(bon): + if not bon.is_group: + return + try: + await bot.kick_participant(bon.chat_id, bon.sender_id) + await bon.reply("Sure!") + except Exception: + await bon.reply("Failed to kick !") + return + + +@bot.on(events.NewMessage(pattern=r"/setdescription ([\s\S]*)")) +async def set_group_des(gpic): + input_str = gpic.pattern_match.group(1) + # print(input_str) + if gpic.is_group: + if not await can_change_info(message=gpic): + return + else: + return + + try: + await bot( + functions.messages.EditChatAboutRequest(peer=gpic.chat_id, about=input_str) + ) + await gpic.reply("Successfully set new group description.") + except BaseException: + await gpic.reply("Failed to set group description.") + + +@bot.on(events.NewMessage(pattern="/setsticker$")) +async def set_group_sticker(gpic): + if gpic.is_group: + if not await can_change_info(message=gpic): + return + else: + return + + rep_msg = await gpic.get_reply_message() + if not rep_msg.document: + await gpic.reply("Reply to any sticker plox.") + return + stickerset_attr_s = rep_msg.document.attributes + stickerset_attr = find_instance(stickerset_attr_s, DocumentAttributeSticker) + if not stickerset_attr.stickerset: + await gpic.reply("Sticker does not belong to a pack.") + return + try: + id = stickerset_attr.stickerset.id + access_hash = stickerset_attr.stickerset.access_hash + print(id) + print(access_hash) + await bot( + functions.channels.SetStickersRequest( + channel=gpic.chat_id, + stickerset=types.InputStickerSetID(id=id, access_hash=access_hash), + ) + ) + await gpic.reply("Group sticker pack successfully set !") + except Exception as e: + print(e) + await gpic.reply("Failed to set group sticker pack.") + + +async def extract_time(message, time_val): + if any(time_val.endswith(unit) for unit in ("m", "h", "d")): + unit = time_val[-1] + time_num = time_val[:-1] # type: str + if not time_num.isdigit(): + await message.reply("Invalid time amount specified.") + return "" + + if unit == "m": + bantime = int(time.time() + int(time_num) * 60) + elif unit == "h": + bantime = int(time.time() + int(time_num) * 60 * 60) + elif unit == "d": + bantime = int(time.time() + int(time_num) * 24 * 60 * 60) + else: + return + return bantime + else: + await message.reply( + "Invalid time type specified. Expected m,h, or d, got: {}".format( + time_val[-1] + ) + ) + return diff --git a/DaisyX/modules/afk.py b/DaisyX/modules/afk.py new file mode 100644 index 00000000..63b333bf --- /dev/null +++ b/DaisyX/modules/afk.py @@ -0,0 +1,92 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 HitaloSama. +# Copyright (C) 2019 Aiogram. +# +# This file is part of Hitsuki (Telegram Bot) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import html +import re + +from DaisyX.decorator import register +from DaisyX.services.mongo import db + +from .utils.disable import disableable_dec +from .utils.language import get_strings_dec +from .utils.message import get_args_str +from .utils.user_details import get_user, get_user_by_id, get_user_link + + +@register(cmds="afk") +@disableable_dec("afk") +@get_strings_dec("afk") +async def afk(message, strings): + try: + arg = get_args_str(message) + except: + return + # dont support AFK as anon admin + if message.from_user.id == 1087968824: + await message.reply(strings["afk_anon"]) + return + + if not arg: + reason = "No reason" + else: + reason = arg + + user = await get_user_by_id(message.from_user.id) + user_afk = await db.afk.find_one({"user": user["user_id"]}) + if user_afk: + return + + await db.afk.insert_one({"user": user["user_id"], "reason": reason}) + text = strings["is_afk"].format( + user=(await get_user_link(user["user_id"])), reason=html.escape(reason) + ) + await message.reply(text) + + +@register(f="text", allow_edited=False) +@get_strings_dec("afk") +async def check_afk(message, strings): + if bool(message.reply_to_message): + if message.reply_to_message.from_user.id in (1087968824, 777000): + return + if message.from_user.id in (1087968824, 777000): + return + user_afk = await db.afk.find_one({"user": message.from_user.id}) + if user_afk: + afk_cmd = re.findall("^[!/]afk(.*)", message.text) + if not afk_cmd: + await message.reply( + strings["unafk"].format( + user=(await get_user_link(message.from_user.id)) + ) + ) + await db.afk.delete_one({"_id": user_afk["_id"]}) + + user = await get_user(message) + if not user: + return + + user_afk = await db.afk.find_one({"user": user["user_id"]}) + if user_afk: + await message.reply( + strings["is_afk"].format( + user=(await get_user_link(user["user_id"])), + reason=html.escape(user_afk["reason"]), + ) + ) diff --git a/DaisyX/modules/android.py b/DaisyX/modules/android.py new file mode 100644 index 00000000..09784108 --- /dev/null +++ b/DaisyX/modules/android.py @@ -0,0 +1,452 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import time + +import httpx +import rapidjson as json +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from bs4 import BeautifulSoup + +from DaisyX import decorator +from DaisyX.decorator import register + +from .utils.android import GetDevice +from .utils.disable import disableable_dec +from .utils.message import get_arg, get_cmd + +MIUI_FIRM = "https://raw.githubusercontent.com/XiaomiFirmwareUpdater/miui-updates-tracker/master/data/latest.yml" +REALME_FIRM = "https://raw.githubusercontent.com/RealmeUpdater/realme-updates-tracker/master/data/latest.yml" + + +@register(cmds="whatis") +@disableable_dec("whatis") +async def whatis(message): + device = get_arg(message) + if not device: + m = "Please write your codename into it, i.e /whatis raphael" + await message.reply(m) + return + + data = await GetDevice(device).get() + if data: + name = data["name"] + device = data["device"] + brand = data["brand"] + data["model"] + else: + m = "coudn't find your device, check device & try!" + await message.reply(m) + return + + m = f"{device} is {brand} {name}\n" + await message.reply(m) + + +@decorator.register(cmds=["models", "variants"]) +@disableable_dec("models") +async def variants(message): + device = get_arg(message) + if not device: + m = "Please write your codename into it, i.e /specs herolte" + await message.reply(m) + return + + data = await GetDevice(device).get() + if data: + name = data["name"] + device = data["device"] + else: + m = "coudn't find your device, chack device & try!" + await message.reply(m) + return + + async with httpx.AsyncClient(http2=True) as http: + data = await http.get( + "https://raw.githubusercontent.com/androidtrackers/certified-android-devices/master/by_device.json" + ) + db = json.loads(data.content) + device = db[device] + m = f"{name} variants:\n\n" + + for i in device: + name = i["name"] + model = i["model"] + m += "Model: {} \nName: {}\n\n".format( + model, name + ) + + await http.aclose() + await message.reply(m) + + +@register(cmds="magisk") +@disableable_dec("magisk") +async def magisk(message): + url = "https://raw.githubusercontent.com/topjohnwu/magisk_files/" + releases = "Latest Magisk Releases:\n" + variant = ["master/stable", "master/beta", "canary/canary"] + for variants in variant: + async with httpx.AsyncClient(http2=True) as http: + fetch = await http.get(url + variants + ".json") + data = json.loads(fetch.content) + if variants == "master/stable": + name = "Stable" + cc = 0 + branch = "master" + elif variants == "master/beta": + name = "Beta" + cc = 0 + branch = "master" + elif variants == "canary/canary": + name = "Canary" + cc = 1 + branch = "canary" + + if variants == "canary/canary": + releases += f'{name}: v{data["magisk"]["version"]} ({data["magisk"]["versionCode"]}) | ' + else: + releases += f'{name}: v{data["magisk"]["version"]} ({data["magisk"]["versionCode"]}) | ' + + if cc == 1: + releases += ( + f'Uninstaller | ' + f'Changelog\n' + ) + else: + releases += ( + f'Uninstaller\n' + f'Changelog\n' + ) + + await http.aclose() + await message.reply(releases, disable_web_page_preview=True) + + +@register(cmds="phh") +@disableable_dec("phh") +async def phh(message): + async with httpx.AsyncClient(http2=True) as http: + fetch = await http.get( + "https://api.github.com/repos/phhusson/treble_experimentations/releases/latest" + ) + usr = json.loads(fetch.content) + text = "Phh's latest GSI release(s):\n" + for i in range(len(usr)): + try: + name = usr["assets"][i]["name"] + url = usr["assets"][i]["browser_download_url"] + text += f"{name}\n" + except IndexError: + continue + + await http.aclose() + await message.reply(text) + + +@register(cmds="phhmagisk") +@disableable_dec("phhmagisk") +async def phh_magisk(message): + async with httpx.AsyncClient(http2=True) as http: + fetch = await http.get( + "https://api.github.com/repos/expressluke/phh-magisk-builder/releases/latest" + ) + usr = json.loads(fetch.content) + text = "Phh's latest Magisk release(s):\n" + for i in range(len(usr)): + try: + usr["assets"][i]["name"] + url = usr["assets"][i]["browser_download_url"] + tag = usr["tag_name"] + size_bytes = usr["assets"][i]["size"] + size = float("{:.2f}".format((size_bytes / 1024) / 1024)) + text += f"Tag: {tag}\n" + text += f"Size: {size} MB\n\n" + btn = "Click here to download!" + button = InlineKeyboardMarkup().add(InlineKeyboardButton(text=btn, url=url)) + except IndexError: + continue + + await http.aclose() + await message.reply(text, reply_markup=button) + return + + +@register(cmds="twrp") +@disableable_dec("twrp") +async def twrp(message): + device = get_arg(message).lower() + + if not device: + m = "Type the device codename, example: /twrp j7xelte" + await message.reply(m) + return + + async with httpx.AsyncClient(http2=True) as http: + url = await http.get(f"https://eu.dl.twrp.me/{device}/") + if url.status_code == 404: + m = f"TWRP is not available for {device}" + await message.reply(m) + return + + else: + m = "TeamWin Recovery official release\n" + m += f" Device: {device}\n" + page = BeautifulSoup(url.content, "lxml") + date = page.find("em").text.strip() + m += f" Updated: {date}\n" + trs = page.find("table").find_all("tr") + row = 2 if trs[0].find("a").text.endswith("tar") else 1 + + for i in range(row): + download = trs[i].find("a") + dl_link = f"https://dl.twrp.me{download['href']}" + dl_file = download.text + size = trs[i].find("span", {"class": "filesize"}).text + m += f" Size: {size}\n" + m += f" File: {dl_file.lower()}" + btn = "⬇️ Download" + button = InlineKeyboardMarkup().add(InlineKeyboardButton(text=btn, url=dl_link)) + + await http.aclose() + await message.reply(m, reply_markup=button) + + +@decorator.register(cmds=["samcheck", "samget"]) +@disableable_dec("samcheck") +async def check(message): + try: + msg_args = message.text.split() + temp = msg_args[1] + csc = msg_args[2] + except IndexError: + m = f"Please type your device MODEL and CSC into it!\ni.e /{get_cmd(message)} SM-J710MN ZTO!" + await message.reply(m) + return + + model = "sm-" + temp if not temp.upper().startswith("SM-") else temp + async with httpx.AsyncClient(http2=True) as http: + fota = await http.get( + f"http://fota-cloud-dn.ospserver.net/firmware/{csc.upper()}/{model.upper()}/version.xml" + ) + test = await http.get( + f"http://fota-cloud-dn.ospserver.net/firmware/{csc.upper()}/{model.upper()}/version.test.xml" + ) + await http.aclose() + if test.status_code != 200: + m = f"Couldn't find any firmwares for {temp.upper()} - {csc.upper()}, please refine your search or try again later!" + await message.reply(m) + return + + page1 = BeautifulSoup(fota.content, "lxml") + page2 = BeautifulSoup(test.content, "lxml") + os1 = page1.find("latest").get("o") + os2 = page2.find("latest").get("o") + if page1.find("latest").text.strip(): + pda1, csc1, phone1 = page1.find("latest").text.strip().split("/") + m = f"MODEL: {model.upper()}\nCSC: {csc.upper()}\n\n" + m += "Latest available firmware:\n" + m += f"• PDA: {pda1}\n• CSC: {csc1}\n" + if phone1: + m += f"• Phone: {phone1}\n" + if os1: + m += f"• Android: {os1}\n" + m += "\n" + else: + m = f"No public release found for {model.upper()} and {csc.upper()}.\n\n" + m += "Latest test firmware:\n" + if len(page2.find("latest").text.strip().split("/")) == 3: + pda2, csc2, phone2 = page2.find("latest").text.strip().split("/") + m += f"• PDA: {pda2}\n• CSC: {csc2}\n" + if phone2: + m += f"• Phone: {phone2}\n" + if os2: + m += f"• Android: {os2}\n" + else: + md5 = page2.find("latest").text.strip() + m += f"• Hash: {md5}\n• Android: {os2}\n" + + if get_cmd(message) == "samcheck": + await message.reply(m) + + elif get_cmd(message) == "samget": + m += "\nDownload from below:\n" + buttons = InlineKeyboardMarkup() + buttons.add( + InlineKeyboardButton( + "SamMobile", + url="https://www.sammobile.com/samsung/firmware/{}/{}/".format( + model.upper(), csc.upper() + ), + ), + InlineKeyboardButton( + "SamFw", + url="https://samfw.com/firmware/{}/{}/".format( + model.upper(), csc.upper() + ), + ), + InlineKeyboardButton( + "SamFrew", + url="https://samfrew.com/model/{}/region/{}/".format( + model.upper(), csc.upper() + ), + ), + ) + + await message.reply(m, reply_markup=buttons) + + +@decorator.register(cmds=["ofox", "of"]) +@disableable_dec("ofox") +async def orangefox(message): + API_HOST = "https://api.orangefox.download/v3/" + try: + args = message.text.split() + codename = args[1].lower() + except BaseException: + codename = "" + try: + build_type = args[2].lower() + except BaseException: + build_type = "" + + if build_type == "": + build_type = "stable" + + if codename == "devices" or codename == "": + reply_text = ( + f"OrangeFox Recovery {build_type} is currently avaible for:" + ) + + async with httpx.AsyncClient(http2=True) as http: + data = await http.get( + API_HOST + f"devices/?release_type={build_type}&sort=device_name_asc" + ) + devices = json.loads(data.text) + await http.aclose() + try: + for device in devices["data"]: + reply_text += ( + f"\n - {device['full_name']} ({device['codename']})" + ) + except BaseException: + await message.reply( + f"'{build_type}' is not a type of build available, the types are just 'beta' or 'stable'." + ) + return + + if build_type == "stable": + reply_text += ( + "\n\n" + + f"To get the latest Stable release use /ofox (codename), for example: /ofox raphael" + ) + elif build_type == "beta": + reply_text += ( + "\n\n" + + f"To get the latest Beta release use /ofox (codename) beta, for example: /ofox raphael beta" + ) + await message.reply(reply_text) + return + + async with httpx.AsyncClient(http2=True) as http: + data = await http.get(API_HOST + f"devices/get?codename={codename}") + device = json.loads(data.text) + await http.aclose() + if data.status_code == 404: + await message.reply("Device is not found!") + return + + async with httpx.AsyncClient(http2=True) as http: + data = await http.get( + API_HOST + + f"releases/?codename={codename}&type={build_type}&sort=date_desc&limit=1" + ) + if data.status_code == 404: + btn = "Device's page" + url = f"https://orangefox.download/device/{device['codename']}" + button = InlineKeyboardMarkup().add(InlineKeyboardButton(text=btn, url=url)) + await message.reply( + f"⚠️ There is no '{build_type}' releases for {device['full_name']}.", + reply_markup=button, + disable_web_page_preview=True, + ) + return + find_id = json.loads(data.text) + await http.aclose() + for build in find_id["data"]: + file_id = build["_id"] + + async with httpx.AsyncClient(http2=True) as http: + data = await http.get(API_HOST + f"releases/get?_id={file_id}") + release = json.loads(data.text) + await http.aclose() + if data.status_code == 404: + await message.reply("Release is not found!") + return + + reply_text = f"OrangeFox Recovery {build_type} release\n" + reply_text += (" Device: {fullname} ({codename})\n").format( + fullname=device["full_name"], codename=device["codename"] + ) + reply_text += (" Version: {}\n").format(release["version"]) + reply_text += (" Release date: {}\n").format( + time.strftime("%d/%m/%Y", time.localtime(release["date"])) + ) + + reply_text += (" Maintainer: {name}\n").format( + name=device["maintainer"]["name"] + ) + changelog = release["changelog"] + try: + reply_text += " Changelog:\n" + for entry_num in range(len(changelog)): + if entry_num == 10: + break + reply_text += f" - {changelog[entry_num]}\n" + except BaseException: + pass + + btn = "⬇️ Download" + url = release["mirrors"]["DL"] + button = InlineKeyboardMarkup().add(InlineKeyboardButton(text=btn, url=url)) + await message.reply(reply_text, reply_markup=button, disable_web_page_preview=True) + return + + +__mod_name__ = "Android" + +__help__ = """ +Module specially made for Android users. + +GSI +- /phh: Get the latest PHH AOSP GSIs. +- /phhmagisk: Get the latest PHH Magisk. + +Device firmware: +- /samcheck (model) (csc): Samsung only - shows the latest firmware info for the given device, taken from samsung servers. +- /samget (model) (csc): Similar to the /samcheck command but having download buttons. + +Misc +- /magisk: Get latest Magisk releases. +- /twrp (codename): Gets latest TWRP for the android device using the codename. +- /ofox (codename): Gets latest OFRP for the android device using the codename. +- /ofox devices: Sends the list of devices with stable releases supported by OFRP. +- /models (codename): Search for Android device models using codename. +- /whatis (codename): Find out which smartphone is using the codename. +""" diff --git a/DaisyX/modules/anime.py b/DaisyX/modules/anime.py new file mode 100644 index 00000000..a0235a40 --- /dev/null +++ b/DaisyX/modules/anime.py @@ -0,0 +1,339 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 HitaloSama. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import html + +import bs4 +import jikanpy +import requests +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from pyrogram import filters + +from DaisyX.decorator import register +from DaisyX.services.pyrogram import pbot + +from .utils.anime import ( + airing_query, + anime_query, + character_query, + manga_query, + shorten, + t, +) +from .utils.disable import disableable_dec + +url = "https://graphql.anilist.co" + + +@register(cmds="airing") +@disableable_dec("airing") +async def anime_airing(message): + search_str = message.text.split(" ", 1) + if len(search_str) == 1: + await message.reply("Provide anime name!") + return + + variables = {"search": search_str[1]} + response = requests.post( + url, json={"query": airing_query, "variables": variables} + ).json()["data"]["Media"] + ms_g = f"Name: {response['title']['romaji']}({response['title']['native']})\nID: {response['id']}" + if response["nextAiringEpisode"]: + airing_time = response["nextAiringEpisode"]["timeUntilAiring"] * 1000 + airing_time_final = t(airing_time) + ms_g += f"\nEpisode: {response['nextAiringEpisode']['episode']}\nAiring In: {airing_time_final}" + else: + ms_g += f"\nEpisode: {response['episodes']}\nStatus: N/A" + await message.reply(ms_g) + + +@register(cmds="anime") +@disableable_dec("anime") +async def anime_search(message): + search = message.text.split(" ", 1) + if len(search) == 1: + await message.reply("Provide anime name!") + return + else: + search = search[1] + variables = {"search": search} + json = ( + requests.post(url, json={"query": anime_query, "variables": variables}) + .json()["data"] + .get("Media", None) + ) + if json: + msg = f"{json['title']['romaji']}({json['title']['native']})\nType: {json['format']}\nStatus: {json['status']}\nEpisodes: {json.get('episodes', 'N/A')}\nDuration: {json.get('duration', 'N/A')} Per Ep.\nScore: {json['averageScore']}\nGenres: " + for x in json["genres"]: + msg += f"{x}, " + msg = msg[:-2] + "\n" + msg += "Studios: " + for x in json["studios"]["nodes"]: + msg += f"{x['name']}, " + msg = msg[:-2] + "\n" + info = json.get("siteUrl") + trailer = json.get("trailer", None) + if trailer: + trailer_id = trailer.get("id", None) + site = trailer.get("site", None) + if site == "youtube": + trailer = "https://youtu.be/" + trailer_id + description = ( + json.get("description", "N/A") + .replace("", "") + .replace("", "") + .replace("
", "") + ) + msg += shorten(description, info) + image = info.replace("anilist.co/anime/", "img.anili.st/media/") + if trailer: + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton(text="More Info", url=info), + InlineKeyboardButton(text="Trailer 🎬", url=trailer), + ) + else: + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton(text="More Info", url=info) + ) + + if image: + try: + await message.reply_photo(image, caption=msg, reply_markup=buttons) + except: + msg += f" [〽️]({image})" + await message.reply(msg) + else: + await message.reply(msg) + + +@register(cmds="character") +@disableable_dec("character") +async def character_search(message): + search = message.text.split(" ", 1) + if len(search) == 1: + await message.reply("Provide character name!") + return + search = search[1] + variables = {"query": search} + json = ( + requests.post(url, json={"query": character_query, "variables": variables}) + .json()["data"] + .get("Character", None) + ) + if json: + ms_g = f"{json.get('name').get('full')}({json.get('name').get('native')})\n" + description = (f"{json['description']}").replace("__", "") + site_url = json.get("siteUrl") + ms_g += shorten(description, site_url) + image = json.get("image", None) + if image: + image = image.get("large") + await message.reply_photo(image, caption=ms_g) + else: + await message.reply(ms_g) + + +@register(cmds="manga") +@disableable_dec("manga") +async def manga_search(message): + search = message.text.split(" ", 1) + if len(search) == 1: + await message.reply("Provide manga name!") + return + search = search[1] + variables = {"search": search} + json = ( + requests.post(url, json={"query": manga_query, "variables": variables}) + .json()["data"] + .get("Media", None) + ) + ms_g = "" + if json: + title, title_native = json["title"].get("romaji", False), json["title"].get( + "native", False + ) + start_date, status, score = ( + json["startDate"].get("year", False), + json.get("status", False), + json.get("averageScore", False), + ) + if title: + ms_g += f"{title}" + if title_native: + ms_g += f"({title_native})" + if start_date: + ms_g += f"\nStart Date - {start_date}" + if status: + ms_g += f"\nStatus - {status}" + if score: + ms_g += f"\nScore - {score}" + ms_g += "\nGenres - " + for x in json.get("genres", []): + ms_g += f"{x}, " + ms_g = ms_g[:-2] + + image = json.get("bannerImage", False) + ms_g += ( + (f"\n{json.get('description', None)}") + .replace("
", "") + .replace("
", "") + ) + if image: + try: + await message.reply_photo(image, caption=ms_g) + except: + ms_g += f" [〽️]({image})" + await message.reply(ms_g) + else: + await message.reply(ms_g) + + +@register(cmds="upcoming") +@disableable_dec("upcoming") +async def upcoming(message): + jikan = jikanpy.jikan.Jikan() + upcoming = jikan.top("anime", page=1, subtype="upcoming") + + upcoming_list = [entry["title"] for entry in upcoming["top"]] + upcoming_message = "" + + for entry_num in range(len(upcoming_list)): + if entry_num == 10: + break + upcoming_message += f"{entry_num + 1}. {upcoming_list[entry_num]}\n" + + await message.reply(upcoming_message) + + +async def site_search(message, site: str): + args = message.text.split(" ", 1) + more_results = True + + try: + search_query = args[1] + except IndexError: + await message.reply("Give something to search") + return + + if site == "kaizoku": + search_url = f"https://animekaizoku.com/?s={search_query}" + html_text = requests.get(search_url).text + soup = bs4.BeautifulSoup(html_text, "html.parser") + search_result = soup.find_all("h2", {"class": "post-title"}) + + if search_result: + result = f"Search results for {html.escape(search_query)} on AnimeKaizoku: \n" + for entry in search_result: + post_link = entry.a["href"] + post_name = html.escape(entry.text) + result += f"• {post_name}\n" + else: + more_results = False + result = f"No result found for {html.escape(search_query)} on AnimeKaizoku" + + elif site == "kayo": + search_url = f"https://animekayo.com/?s={search_query}" + html_text = requests.get(search_url).text + soup = bs4.BeautifulSoup(html_text, "html.parser") + search_result = soup.find_all("h2", {"class": "title"}) + + result = f"Search results for {html.escape(search_query)} on AnimeKayo: \n" + for entry in search_result: + + if entry.text.strip() == "Nothing Found": + result = f"No result found for {html.escape(search_query)} on AnimeKayo" + more_results = False + break + + post_link = entry.a["href"] + post_name = html.escape(entry.text.strip()) + result += f"• {post_name}\n" + + elif site == "ganime": + search_url = f"https://gogoanime.so//search.html?keyword={search_query}" + html_text = requests.get(search_url).text + soup = bs4.BeautifulSoup(html_text, "html.parser") + search_result = soup.find_all("h2", {"class": "title"}) + + result = f"Search results for {html.escape(search_query)} on gogoanime: \n" + for entry in search_result: + + if entry.text.strip() == "Nothing Found": + result = f"No result found for {html.escape(search_query)} on gogoanime" + more_results = False + break + + post_link = entry.a["href"] + post_name = html.escape(entry.text.strip()) + result += f"• {post_name}\n" + + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton(text="See all results", url=search_url) + ) + + if more_results: + await message.reply(result, reply_markup=buttons, disable_web_page_preview=True) + else: + await message.reply(result) + + +@register(cmds="kaizoku") +@disableable_dec("kaizoku") +async def kaizoku(message): + await site_search(message, "kaizoku") + + +@register(cmds="kayo") +@disableable_dec("kayo") +async def kayo(message): + await site_search(message, "kayo") + + +@register(cmds="ganime") +@disableable_dec("ganime") +async def kayo(message): + await site_search(message, "ganime") + + +@pbot.on_message(filters.command("aq")) +def quote(_, message): + quote = requests.get("https://animechan.vercel.app/api/random").json() + quote = truth.get("quote") + message.reply_text(quote) + + +# added ganime search based on gogoanime.so + +__mod_name__ = "Anime" + +__help__ = """ +Get information about anime, manga or anime characters. + +Available commands: +- /anime (anime): returns information about the anime. +- /character (character): returns information about the character. +- /manga (manga): returns information about the manga. +- /airing (anime): returns anime airing info. +- /kaizoku (anime): search an anime on animekaizoku.com +- /kayo (anime): search an anime on animekayo.com +- /ganime (anime): search an anime on gogoanime.so +- /upcoming: returns a list of new anime in the upcoming seasons. +- /aq : get anime random quote +""" diff --git a/DaisyX/modules/antiflood.py b/DaisyX/modules/antiflood.py new file mode 100644 index 00000000..4c638781 --- /dev/null +++ b/DaisyX/modules/antiflood.py @@ -0,0 +1,407 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import pickle +from dataclasses import dataclass +from typing import Optional + +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.dispatcher.handler import CancelHandler +from aiogram.dispatcher.middlewares import BaseMiddleware +from aiogram.types import ChatType, InlineKeyboardMarkup +from aiogram.types.callback_query import CallbackQuery +from aiogram.types.inline_keyboard import InlineKeyboardButton +from aiogram.types.message import ContentType, Message +from aiogram.utils.callback_data import CallbackData +from babel.dates import format_timedelta + +from DaisyX import dp +from DaisyX.decorator import register +from DaisyX.modules.utils.connections import chat_connection +from DaisyX.modules.utils.language import get_strings, get_strings_dec +from DaisyX.modules.utils.message import ( + InvalidTimeUnit, + convert_time, + get_args, + need_args_dec, +) +from DaisyX.modules.utils.restrictions import ban_user, kick_user, mute_user +from DaisyX.modules.utils.user_details import get_user_link, is_user_admin +from DaisyX.services.mongo import db +from DaisyX.services.redis import bredis, redis +from DaisyX.utils.cached import cached +from DaisyX.utils.logger import log + +cancel_state = CallbackData("cancel_state", "user_id") + + +class AntiFloodConfigState(StatesGroup): + expiration_proc = State() + + +class AntiFloodActionState(StatesGroup): + set_time_proc = State() + + +@dataclass +class CacheModel: + count: int + + +class AntifloodEnforcer(BaseMiddleware): + state_cache_key = "floodstate:{chat_id}" + + async def enforcer(self, message: Message, database: dict): + if (not (data := self.get_flood(message))) or int( + self.get_state(message) + ) != message.from_user.id: + to_set = CacheModel(count=1) + self.insert_flood(to_set, message, database) + self.set_state(message) + return False # we aint banning anybody + + # update count + data.count += 1 + + # check exceeding + if data.count >= database["count"]: + if await self.do_action(message, database): + self.reset_flood(message) + return True + + self.insert_flood(data, message, database) + return False + + @classmethod + def is_message_valid(cls, message) -> bool: + _pre = [ContentType.NEW_CHAT_MEMBERS, ContentType.LEFT_CHAT_MEMBER] + if message.content_type in _pre: + return False + elif message.chat.type in (ChatType.PRIVATE,): + return False + return True + + def get_flood(self, message) -> Optional[CacheModel]: + if data := bredis.get(self.cache_key(message)): + data = pickle.loads(data) + return data + return None + + def insert_flood(self, data: CacheModel, message: Message, database: dict): + ex = ( + convert_time(database["time"]) + if database.get("time", None) is not None + else None + ) + return bredis.set(self.cache_key(message), pickle.dumps(data), ex=ex) + + def reset_flood(self, message): + return bredis.delete(self.cache_key(message)) + + def check_flood(self, message): + return bredis.exists(self.cache_key(message)) + + def set_state(self, message: Message): + return bredis.set( + self.state_cache_key.format(chat_id=message.chat.id), message.from_user.id + ) + + def get_state(self, message: Message): + return bredis.get(self.state_cache_key.format(chat_id=message.chat.id)) + + @classmethod + def cache_key(cls, message: Message): + return f"antiflood:{message.chat.id}:{message.from_user.id}" + + @classmethod + async def do_action(cls, message: Message, database: dict): + action = database["action"] if "action" in database else "ban" + + if action == "ban": + return await ban_user(message.chat.id, message.from_user.id) + elif action == "kick": + return await kick_user(message.chat.id, message.from_user.id) + elif action == "mute": + return await mute_user(message.chat.id, message.from_user.id) + elif action.startswith("t"): + time = database.get("time", None) + if not time: + return False + if action == "tmute": + return await mute_user( + message.chat.id, message.from_user.id, until_date=convert_time(time) + ) + elif action == "tban": + return await ban_user( + message.chat.id, message.from_user.id, until_date=convert_time(time) + ) + else: + return False + + async def on_pre_process_message(self, message: Message, _): + log.debug( + f"Enforcing flood control on {message.from_user.id} in {message.chat.id}" + ) + if self.is_message_valid(message): + if await is_user_admin(message.chat.id, message.from_user.id): + return self.set_state(message) + if (database := await get_data(message.chat.id)) is None: + return + + if await self.enforcer(message, database): + await message.delete() + strings = await get_strings(message.chat.id, "antiflood") + await message.answer( + strings["flood_exceeded"].format( + action=( + strings[database["action"]] + if "action" in database + else "banned" + ).capitalize(), + user=await get_user_link(message.from_user.id), + ) + ) + raise CancelHandler + + +@register( + cmds=["setflood"], user_can_restrict_members=True, bot_can_restrict_members=True +) +@need_args_dec() +@chat_connection() +@get_strings_dec("antiflood") +async def setflood_command(message: Message, chat: dict, strings: dict): + try: + args = int(get_args(message)[0]) + except ValueError: + return await message.reply(strings["invalid_args:setflood"]) + if args > 200: + return await message.reply(strings["overflowed_count"]) + + await AntiFloodConfigState.expiration_proc.set() + redis.set(f"antiflood_setup:{chat['chat_id']}", args) + await message.reply( + strings["config_proc_1"], + reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton( + text=strings["cancel"], + callback_data=cancel_state.new(user_id=message.from_user.id), + ) + ), + ) + + +@register( + state=AntiFloodConfigState.expiration_proc, + content_types=ContentType.TEXT, + allow_kwargs=True, +) +@chat_connection() +@get_strings_dec("antiflood") +async def antiflood_expire_proc( + message: Message, chat: dict, strings: dict, state, **_ +): + try: + if (time := message.text) not in (0, "0"): + parsed_time = convert_time(time) # just call for making sure its valid + else: + time, parsed_time = None, None + except (TypeError, ValueError): + await message.reply(strings["invalid_time"]) + else: + if not (data := redis.get(f'antiflood_setup:{chat["chat_id"]}')): + await message.reply(strings["setup_corrupted"]) + else: + await db.antiflood.update_one( + {"chat_id": chat["chat_id"]}, + {"$set": {"time": time, "count": int(data)}}, + upsert=True, + ) + await get_data.reset_cache(chat["chat_id"]) + kw = {"count": data} + if time is not None: + kw.update( + { + "time": format_timedelta( + parsed_time, locale=strings["language_info"]["babel"] + ) + } + ) + await message.reply( + strings[ + "setup_success" if time is not None else "setup_success:no_exp" + ].format(**kw) + ) + finally: + await state.finish() + + +@register(cmds=["antiflood", "flood"], is_admin=True) +@chat_connection(admin=True) +@get_strings_dec("antiflood") +async def antiflood(message: Message, chat: dict, strings: dict): + if not (data := await get_data(chat["chat_id"])): + return await message.reply(strings["not_configured"]) + + if message.get_args().lower() in ("off", "0", "no"): + await db.antiflood.delete_one({"chat_id": chat["chat_id"]}) + await get_data.reset_cache(chat["chat_id"]) + return await message.reply( + strings["turned_off"].format(chat_title=chat["chat_title"]) + ) + + if data["time"] is None: + return await message.reply( + strings["configuration_info"].format( + action=strings[data["action"]] if "action" in data else strings["ban"], + count=data["count"], + ) + ) + return await message.reply( + strings["configuration_info:with_time"].format( + action=strings[data["action"]] if "action" in data else strings["ban"], + count=data["count"], + time=format_timedelta( + convert_time(data["time"]), locale=strings["language_info"]["babel"] + ), + ) + ) + + +@register(cmds=["setfloodaction"], user_can_restrict_members=True) +@need_args_dec() +@chat_connection(admin=True) +@get_strings_dec("antiflood") +async def setfloodaction(message: Message, chat: dict, strings: dict): + SUPPORTED_ACTIONS = ["kick", "ban", "mute", "tmute", "tban"] # noqa + if (action := message.get_args().lower()) not in SUPPORTED_ACTIONS: + return await message.reply( + strings["invalid_args"].format( + supported_actions=", ".join(SUPPORTED_ACTIONS) + ) + ) + + if action.startswith("t"): + await message.reply( + "Send a time for t action", allow_sending_without_reply=True + ) + redis.set(f"floodactionstate:{chat['chat_id']}", action) + return await AntiFloodActionState.set_time_proc.set() + + await db.antiflood.update_one( + {"chat_id": chat["chat_id"]}, {"$set": {"action": action}}, upsert=True + ) + await get_data.reset_cache(message.chat.id) + return await message.reply(strings["setfloodaction_success"].format(action=action)) + + +@register( + state=AntiFloodActionState.set_time_proc, + user_can_restrict_members=True, + allow_kwargs=True, +) +@chat_connection(admin=True) +@get_strings_dec("antiflood") +async def set_time_config( + message: Message, chat: dict, strings: dict, state: FSMContext, **_ +): + if not (action := redis.get(f"floodactionstate:{chat['chat_id']}")): + await message.reply("setup_corrupted", allow_sending_without_reply=True) + return await state.finish() + try: + parsed_time = convert_time( + time := message.text.lower() + ) # just call for making sure its valid + except (TypeError, ValueError, InvalidTimeUnit): + await message.reply("Invalid time") + else: + await db.antiflood.update_one( + {"chat_id": chat["chat_id"]}, + {"$set": {"action": action, "time": time}}, + upsert=True, + ) + await get_data.reset_cache(chat["chat_id"]) + text = strings["setfloodaction_success"].format(action=action) + text += f" ({format_timedelta(parsed_time, locale=strings['language_info']['babel'])})" + await message.reply(text, allow_sending_without_reply=True) + finally: + await state.finish() + + +async def __before_serving__(_): + dp.middleware.setup(AntifloodEnforcer()) + + +@register(cancel_state.filter(), f="cb") +async def cancel_state_cb(event: CallbackQuery): + await event.message.delete() + + +@cached() +async def get_data(chat_id: int): + return await db.antiflood.find_one({"chat_id": chat_id}) + + +async def __export__(chat_id: int): + data = await get_data(chat_id) + if not data: + return + + del data["_id"], data["chat_id"] + return data + + +async def __import__(chat_id: int, data: dict): # noqa + await db.antiflood.update_one({"chat_id": chat_id}, {"$set": data}) + + +__mod_name__ = "AntiFlood" + +__help__ = """ +You know how sometimes, people join, send 100 messages, and ruin your chat? With antiflood, that happens no more! + +Antiflood allows you to take action on users that send more than x messages in a row. + +Admins only: +- /antiflood: Gives you current configuration of antiflood in the chat +- /antiflood off: Disables Antiflood +- /setflood (limit): Sets flood limit + +Replace (limit) with any integer, should be less than 200. When setting up, Daisy would ask you to send expiration time, if you dont understand what this expiration time for? User who sends specified limit of messages consecutively within this TIME, would be kicked, banned whatever the action is. if you dont want this TIME, wants to take action against those who exceeds specified limit without mattering TIME INTERVAL between the messages. you can reply to question with 0 + +Configuring the time: +2m = 2 minutes +2h = 2 hours +2d = 2 days + +Example: +Me: /setflood 10 +Daisy: Please send expiration time [...] +Me: 5m (5 minutes) +DONE! + +- /setfloodaction (action): Sets the action to taken when user exceeds flood limit + +Currently supported actions: +ban +mute +kick +More soon™ +""" diff --git a/DaisyX/modules/antivirus.py b/DaisyX/modules/antivirus.py new file mode 100644 index 00000000..bef3e7ed --- /dev/null +++ b/DaisyX/modules/antivirus.py @@ -0,0 +1,116 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import os + +import cloudmersive_virus_api_client +from telethon.tl import functions, types +from telethon.tl.types import DocumentAttributeFilename, MessageMediaDocument + +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +VIRUS_API_KEY = get_str_key("VIRUS_API_KEY", required=False) +configuration = cloudmersive_virus_api_client.Configuration() +configuration.api_key["Apikey"] = VIRUS_API_KEY +api_instance = cloudmersive_virus_api_client.ScanApi( + cloudmersive_virus_api_client.ApiClient(configuration) +) +allow_executables = True +allow_invalid_files = True +allow_scripts = True +allow_password_protected_files = True + + +@register(pattern="^/scanit$") +async def virusscan(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + if not event.reply_to_msg_id: + await event.reply("Reply to a file to scan it.") + return + + c = await event.get_reply_message() + try: + c.media.document + except Exception: + await event.reply("Thats not a file.") + return + h = c.media + try: + k = h.document.attributes + except Exception: + await event.reply("Thats not a file.") + return + if not isinstance(h, MessageMediaDocument): + await event.reply("Thats not a file.") + return + if not isinstance(k[0], DocumentAttributeFilename): + await event.reply("Thats not a file.") + return + try: + virus = c.file.name + await event.client.download_file(c, virus) + gg = await event.reply("Scanning the file ...") + fsize = c.file.size + if not fsize <= 3145700: # MAX = 3MB + await gg.edit("File size exceeds 3MB") + return + api_response = api_instance.scan_file_advanced( + c.file.name, + allow_executables=allow_executables, + allow_invalid_files=allow_invalid_files, + allow_scripts=allow_scripts, + allow_password_protected_files=allow_password_protected_files, + ) + if api_response.clean_result is True: + await gg.edit("This file is safe ✔️\nNo virus detected 🐞") + else: + await gg.edit("This file is Dangerous ☠️️\nVirus detected 🐞") + os.remove(virus) + except Exception as e: + print(e) + os.remove(virus) + await gg.edit("Some error occurred.") + return + + +_mod_name_ = "Virus Scan" +_help_ = """ + - /scanit: Scan a file for virus (MAX SIZE = 3MB) + """ diff --git a/DaisyX/modules/blacklist.py b/DaisyX/modules/blacklist.py new file mode 100644 index 00000000..551d16ce --- /dev/null +++ b/DaisyX/modules/blacklist.py @@ -0,0 +1,219 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import html + +import tldextract +from telethon import events, types +from telethon.tl import functions + +import DaisyX.services.sql.urlblacklist_sql as urlsql +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + + +async def can_change_info(message): + result = await tbot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.change_info + ) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/addurl") +async def _(event): + if event.fwd_from: + return + if event.is_private: + return + if event.is_group: + if await can_change_info(message=event): + pass + else: + return + chat = event.chat + urls = event.text.split(None, 1) + if len(urls) > 1: + urls = urls[1] + to_blacklist = list({uri.strip() for uri in urls.split("\n") if uri.strip()}) + blacklisted = [] + + for uri in to_blacklist: + extract_url = tldextract.extract(uri) + if extract_url.domain and extract_url.suffix: + blacklisted.append(extract_url.domain + "." + extract_url.suffix) + urlsql.blacklist_url( + chat.id, extract_url.domain + "." + extract_url.suffix + ) + + if len(to_blacklist) == 1: + extract_url = tldextract.extract(to_blacklist[0]) + if extract_url.domain and extract_url.suffix: + await event.reply( + "Added {} domain to the blacklist!".format( + html.escape(extract_url.domain + "." + extract_url.suffix) + ), + parse_mode="html", + ) + else: + await event.reply("You are trying to blacklist an invalid url") + else: + await event.reply( + "Added {} domains to the blacklist.".format( + len(blacklisted) + ), + parse_mode="html", + ) + else: + await event.reply("Tell me which urls you would like to add to the blacklist.") + + +@register(pattern="^/delurl") +async def _(event): + if event.fwd_from: + return + if event.is_private: + return + if event.is_group: + if await can_change_info(message=event): + pass + else: + return + chat = event.chat + urls = event.text.split(None, 1) + + if len(urls) > 1: + urls = urls[1] + to_unblacklist = list({uri.strip() for uri in urls.split("\n") if uri.strip()}) + unblacklisted = 0 + for uri in to_unblacklist: + extract_url = tldextract.extract(uri) + success = urlsql.rm_url_from_blacklist( + chat.id, extract_url.domain + "." + extract_url.suffix + ) + if success: + unblacklisted += 1 + + if len(to_unblacklist) == 1: + if unblacklisted: + await event.reply( + "Removed {} from the blacklist!".format( + html.escape(to_unblacklist[0]) + ), + parse_mode="html", + ) + else: + await event.reply("This isn't a blacklisted domain...!") + elif unblacklisted == len(to_unblacklist): + await event.reply( + "Removed {} domains from the blacklist.".format( + unblacklisted + ), + parse_mode="html", + ) + elif not unblacklisted: + await event.reply("None of these domains exist, so they weren't removed.") + else: + await event.reply( + "Removed {} domains from the blacklist. {} did not exist, so were not removed.".format( + unblacklisted, len(to_unblacklist) - unblacklisted + ), + parse_mode="html", + ) + else: + await event.reply( + "Tell me which domains you would like to remove from the blacklist." + ) + + +@tbot.on(events.NewMessage(incoming=True)) +async def on_url_message(event): + if event.is_private: + return + chat = event.chat + extracted_domains = [] + for (ent, txt) in event.get_entities_text(): + if ent.offset != 0: + break + if isinstance(ent, types.MessageEntityUrl): + url = txt + extract_url = tldextract.extract(url) + extracted_domains.append(extract_url.domain + "." + extract_url.suffix) + for url in urlsql.get_blacklisted_urls(chat.id): + if url in extracted_domains: + try: + await event.delete() + except: + return + + +@register(pattern="^/geturl$") +async def _(event): + if event.fwd_from: + return + if event.is_private: + return + if event.is_group: + if await can_change_info(message=event): + pass + else: + return + chat = event.chat + base_string = "Current blacklisted domains:\n" + blacklisted = urlsql.get_blacklisted_urls(chat.id) + if not blacklisted: + await event.reply("There are no blacklisted domains here!") + return + for domain in blacklisted: + base_string += "- {}\n".format(domain) + await event.reply(base_string, parse_mode="html") + + +__help__ = """ + Daisy's filters are the blacklist too + - /addfilter [trigger] Select action: blacklists the trigger + - /delfilter [trigger] : stop blacklisting a certain blacklist trigger + - /filters: list all active blacklist filters + + Url Blacklist + - /geturl: View the current blacklisted urls + - /addurl [urls]: Add a domain to the blacklist. The bot will automatically parse the url. + - /delurl [urls]: Remove urls from the blacklist. + Example: + - /addblacklist the admins suck: This will remove "the admins suck" everytime some non-admin types it + - /addurl bit.ly: This would delete any message containing url "bit.ly" +""" +__mod_name__ = "Blacklist" diff --git a/DaisyX/modules/books.py b/DaisyX/modules/books.py new file mode 100644 index 00000000..e62bc03a --- /dev/null +++ b/DaisyX/modules/books.py @@ -0,0 +1,78 @@ +# Copyright (C) 2020 DevsExpo +# Copyright (C) 2021 Inuka Asith +# Copyright (C) 2021 TeamDaisyX + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import os +import re + +import requests +from bs4 import BeautifulSoup +from telethon import events + +from DaisyX.services.telethon import tbot + + +@tbot.on(events.NewMessage(pattern="^/book (.*)")) +async def _(event): + if event.fwd_from: + return + input_str = event.pattern_match.group(1) + lool = 0 + KkK = await event.reply("searching for the book...") + lin = "https://b-ok.cc/s/" + text = input_str + link = lin + text + + headers = [ + "User-Agent", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:74.0) Gecko/20100101 Firefox/74.0", + ] + page = requests.get(link) + soup = BeautifulSoup(page.content, "html.parser") + f = open("book.txt", "w") + total = soup.find(class_="totalCounter") + for nb in total.descendants: + nbx = nb.replace("(", "").replace(")", "") + if nbx == "0": + await event.reply("No Books Found with that name.") + else: + + for tr in soup.find_all("td"): + for td in tr.find_all("h3"): + for ts in td.find_all("a"): + title = ts.get_text() + lool = lool + 1 + for ts in td.find_all("a", attrs={"href": re.compile("^/book/")}): + ref = ts.get("href") + link = "https://b-ok.cc" + ref + + f.write("\n" + title) + f.write("\nBook link:- " + link + "\n\n") + + f.write("By @DaisyXBot.") + f.close() + caption = "A collabration with Friday.\n Join Support @DaisySupport_Official" + + await tbot.send_file( + event.chat_id, + "book.txt", + caption=f"**BOOKS GATHERED SUCCESSFULLY!\n\nBY DAISYX. JOIN THE SUPPORT @DaisySupport_Official.**", + ) + os.remove("book.txt") + await KkK.delete() diff --git a/DaisyX/modules/cash.py b/DaisyX/modules/cash.py new file mode 100644 index 00000000..6897d8ef --- /dev/null +++ b/DaisyX/modules/cash.py @@ -0,0 +1,93 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import requests +from telethon import types +from telethon.tl import functions + +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +CASH_API_KEY = get_str_key("CASH_API_KEY", required=False) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/cash") +async def _(event): + if event.fwd_from: + return + """this method of approve system is made by @AyushChatterjee, god will curse your family if you kang it motherfucker""" + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + + cmd = event.text + + args = cmd.split(" ") + + if len(args) == 4: + try: + orig_cur_amount = float(args[1]) + + except ValueError: + await event.reply("Invalid Amount Of Currency") + return + + orig_cur = args[2].upper() + + new_cur = args[3].upper() + + request_url = ( + f"https://www.alphavantage.co/query" + f"?function=CURRENCY_EXCHANGE_RATE" + f"&from_currency={orig_cur}" + f"&to_currency={new_cur}" + f"&apikey={CASH_API_KEY}" + ) + response = requests.get(request_url).json() + try: + current_rate = float( + response["Realtime Currency Exchange Rate"]["5. Exchange Rate"] + ) + except KeyError: + await event.reply("Currency Not Supported.") + return + new_cur_amount = round(orig_cur_amount * current_rate, 5) + await event.reply(f"{orig_cur_amount} {orig_cur} = {new_cur_amount} {new_cur}") + + elif len(args) == 1: + await event.reply(__help__) + + else: + await event.reply( + f"**Invalid Args!!:** Required 3 But Passed {len(args) -1}", + ) diff --git a/DaisyX/modules/cc_checker.py b/DaisyX/modules/cc_checker.py new file mode 100644 index 00000000..abcf8fb7 --- /dev/null +++ b/DaisyX/modules/cc_checker.py @@ -0,0 +1,279 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import datetime + +from telethon.tl import functions, types + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot +from DaisyX.services.telethonuserbot import ubot + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/gen (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + m = await event.reply("Generating CC...Pls Weit.") + ok = event.pattern_match.group(1) + async with ubot.conversation("@ccgen_robot") as bot_conv: + await bot_conv.send_message("/generate") + await bot_conv.send_message("💳Credit Card Generator💳") + await asyncio.sleep(2) + await bot_conv.send_message(ok) + await asyncio.sleep(1) + response = await bot_conv.get_response() + await asyncio.sleep(1) + await response.click(text="✅Generate✅") + await asyncio.sleep(2) + text = "****Generated Cards:****\n" + gen = await bot_conv.get_response() + card = gen.text + text = f"{card.splitlines()[0]}\n" + text += f"{card.splitlines()[1]}\n" + text += f"{card.splitlines()[2]}\n" + text += f"{card.splitlines()[3]}\n" + text += f"{card.splitlines()[4]}\n" + text += f"{card.splitlines()[5]}\n" + text += f"\nGenerated By: **{fname}**" + await m.edit(text) + + +@register(pattern="^/key (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + ok = event.pattern_match.group(1) + k = await event.reply("**Wait for Result.**") + start_time = datetime.datetime.now() + async with ubot.conversation("@Carol5_bot") as bot_conv: + await bot_conv.send_message(f"/key {ok}") + await asyncio.sleep(6) + response = await bot_conv.get_response() + await event.delete() + end_time = datetime.datetime.now() + pingtime = end_time - start_time + time = str(round(pingtime.total_seconds(), 2)) + "s" + if "Invalid" in response.text: + reply = f"SK Key : {ok}\n" + reply += "Result: Invalid API Key\n" + reply += "RESPONSE: ❌Invalid Key❌\n" + reply += f"Time: {time}\n" + reply += f"Checked By **{fname}**" + elif "Test" in response.text: + reply = f"SK Key : sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + reply += "Result: Test mode Key\n" + reply += "RESPONSE: ❌Test Mode Key❌\n" + reply += f"Time: {time}\n" + reply += f"Checked By **{fname}**" + elif "Valid" in response.text: + reply = f"SK Key : sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n" + reply += "Result: LIVE\n" + reply += "RESPONSE: ✅Valid Key\n" + reply += f"Time: {time}\n" + reply += f"Checked By **{fname}**" + else: + reply = "Error, Report @LunaBotSupport" + await k.edit(reply) + + +@register(pattern="^/ss (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + ok = event.pattern_match.group(1) + k = await event.reply("**Wait for Result.**") + async with ubot.conversation("@Carol5_bot") as bot_conv: + await bot_conv.send_message(f"/ss {ok}") + await asyncio.sleep(9) + response = await bot_conv.get_response() + if "Try again after" in response.text: + await k.edit(response) + return + if "Your date is invalid" in response.text: + await k.edit("Format Wrong or invalid cc.") + return + res = response.text + text = f"{res.splitlines()[0]}\n" + text += f"{res.splitlines()[1]}\n" + text += f"{res.splitlines()[2]}\n" + text += f"{res.splitlines()[3]}\n" + text += f"{res.splitlines()[4]}\n" + text += f"{res.splitlines()[5]}\n" + text += f"{res.splitlines()[6]}\n" + text += f"Checked By **{fname}**" + await k.edit(text) + + +@register(pattern="^/pp (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + ok = event.pattern_match.group(1) + k = await event.reply("**Wait for Result.**") + async with ubot.conversation("@Carol5_bot") as bot_conv: + await bot_conv.send_message(f"/pp {ok}") + await asyncio.sleep(14) + response = await bot_conv.get_response() + if "Try again after" in response.text: + await k.edit(response) + return + if "Your date is invalid" in response.text: + await k.edit("Format Wrong or invalid cc.") + return + res = response.text + text = f"{res.splitlines()[0]}\n" + text += f"{res.splitlines()[1]}\n" + text += f"{res.splitlines()[2]}\n" + text += f"{res.splitlines()[3]}\n" + text += f"{res.splitlines()[4]}\n" + text += f"{res.splitlines()[5]}\n" + text += f"{res.splitlines()[6]}\n" + text += f"Checked By **{fname}**" + await k.edit(text) + + +@register(pattern="^/ch (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + ok = event.pattern_match.group(1) + async with ubot.conversation("@Carol5_bot") as bot_conv: + await bot_conv.send_message(f"/ch {ok}") + k = await event.reply("**Wait for Result.**") + await asyncio.sleep(18) + response = await bot_conv.get_response() + if "Try again after" in response.text: + await k.edit(response) + return + if "Your date is invalid" in response.text: + await k.edit("Format Wrong or invalid cc.") + return + res = response.text + text = f"{res.splitlines()[0]}\n" + text += f"{res.splitlines()[1]}\n" + text += f"{res.splitlines()[2]}\n" + text += f"{res.splitlines()[3]}\n" + text += f"{res.splitlines()[4]}\n" + text += f"{res.splitlines()[5]}\n" + text += f"{res.splitlines()[6]}\n" + text += f"Checked By **{fname}**" + await k.edit(text) + + +@register(pattern="^/au (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + ok = event.pattern_match.group(1) + async with ubot.conversation("@Carol5_bot") as bot_conv: + await bot_conv.send_message(f"/au {ok}") + k = await event.reply("**Wait for Result.**") + await asyncio.sleep(18) + response = await bot_conv.get_response() + if "Try again after" in response.text: + await event.reply(response) + return + if "Your date is invalid" in response.text: + await event.reply("Format Wrong or invalid cc.") + return + res = response.text + text = f"{res.splitlines()[0]}\n" + text += f"{res.splitlines()[1]}\n" + text += f"{res.splitlines()[2]}\n" + text += f"{res.splitlines()[3]}\n" + text += f"{res.splitlines()[4]}\n" + text += f"{res.splitlines()[5]}\n" + text += f"{res.splitlines()[6]}\n" + text += f"Checked By **{fname}**" + await k.edit(text) + + +@register(pattern="^/bin (.*)") +async def alive(event): + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + sender = await event.get_sender() + fname = sender.first_name + k = await event.reply("**Wait for Result.**") + ok = event.pattern_match.group(1) + async with ubot.conversation("@Carol5_bot") as bot_conv: + await bot_conv.send_message(f"/bin {ok}") + await asyncio.sleep(5) + response = await bot_conv.get_response() + res = response.text + if "❌" in res: + text = "🤬❌ INVALID BIN ❌🤬\n" + text += f"Checked By **{fname}**" + await k.edit(text) + else: + text = f"{res.splitlines()[0]}\n" + text += f"{res.splitlines()[1]}\n" + text += f"{res.splitlines()[2]}\n" + text += f"{res.splitlines()[3]}\n" + text += f"{res.splitlines()[4]}\n" + text += f"{res.splitlines()[5]}\n" + text += f"{res.splitlines()[6]}\n" + text += f"Checked By **{fname}**" + await k.edit(text) diff --git a/DaisyX/modules/classicfilters.py b/DaisyX/modules/classicfilters.py new file mode 100644 index 00000000..e9ed2975 --- /dev/null +++ b/DaisyX/modules/classicfilters.py @@ -0,0 +1,279 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import os +import re + +from telethon import Button, events, utils +from telethon.tl import functions, types + +from DaisyX.services.events import register +from DaisyX.services.sql.filters_sql import ( + add_filter, + get_all_filters, + remove_all_filters, + remove_filter, +) +from DaisyX.services.telethon import tbot + +DELETE_TIMEOUT = 0 +TYPE_TEXT = 0 +TYPE_PHOTO = 1 +TYPE_DOCUMENT = 2 +last_triggered_filters = {} + + +async def can_change_info(message): + result = await tbot( + functions.channels.GetParticipantRequest( + channel=message.chat_id, + user_id=message.sender_id, + ) + ) + p = result.participant + return isinstance(p, types.ChannelParticipantCreator) or ( + isinstance(p, types.ChannelParticipantAdmin) and p.admin_rights.change_info + ) + + +@tbot.on(events.NewMessage(pattern=None)) +async def on_snip(event): + + global last_triggered_filters + + name = event.raw_text + + if event.chat_id in last_triggered_filters: + + if name in last_triggered_filters[event.chat_id]: + + return False + + snips = get_all_filters(event.chat_id) + + if snips: + + for snip in snips: + + pattern = r"( |^|[^\w])" + re.escape(snip.keyword) + r"( |$|[^\w])" + + if re.search(pattern, name, flags=re.IGNORECASE): + + if snip.snip_type == TYPE_PHOTO: + + media = types.InputPhoto( + int(snip.media_id), + int(snip.media_access_hash), + snip.media_file_reference, + ) + + elif snip.snip_type == TYPE_DOCUMENT: + + media = types.InputDocument( + int(snip.media_id), + int(snip.media_access_hash), + snip.media_file_reference, + ) + + else: + + media = None + + event.message.id + + if event.reply_to_msg_id: + + event.reply_to_msg_id + + filter = "" + options = "" + butto = None + + if "|" in snip.reply: + filter, options = snip.reply.split("|") + else: + filter = str(snip.reply) + try: + filter = filter.strip() + button = options.strip() + if "•" in button: + mbutton = button.split("•") + lbutton = [] + for i in mbutton: + params = re.findall(r"\'(.*?)\'", i) or re.findall( + r"\"(.*?)\"", i + ) + lbutton.append(params) + longbutton = [] + for c in lbutton: + butto = [Button.url(*c)] + longbutton.append(butto) + else: + params = re.findall(r"\'(.*?)\'", button) or re.findall( + r"\"(.*?)\"", button + ) + butto = [Button.url(*params)] + except BaseException: + filter = filter.strip() + butto = None + + try: + await event.reply(filter, buttons=longbutton, file=media) + except: + await event.reply(filter, buttons=butto, file=media) + + if event.chat_id not in last_triggered_filters: + + last_triggered_filters[event.chat_id] = [] + + last_triggered_filters[event.chat_id].append(name) + + await asyncio.sleep(DELETE_TIMEOUT) + + last_triggered_filters[event.chat_id].remove(name) + + +@register(pattern="^/cfilter (.*)") +async def on_snip_save(event): + if event.is_group: + if not await can_change_info(message=event): + return + else: + return + + name = event.pattern_match.group(1) + msg = await event.get_reply_message() + + if msg: + + snip = {"type": TYPE_TEXT, "text": msg.message or ""} + + if msg.media: + + media = None + + if isinstance(msg.media, types.MessageMediaPhoto): + + media = utils.get_input_photo(msg.media.photo) + + snip["type"] = TYPE_PHOTO + + elif isinstance(msg.media, types.MessageMediaDocument): + + media = utils.get_input_document(msg.media.document) + + snip["type"] = TYPE_DOCUMENT + + if media: + + snip["id"] = media.id + + snip["hash"] = media.access_hash + + snip["fr"] = media.file_reference + + add_filter( + event.chat_id, + name, + snip["text"], + snip["type"], + snip.get("id"), + snip.get("hash"), + snip.get("fr"), + ) + + await event.reply( + f"Classic Filter {name} saved successfully. you can get it with {name}\nNote: Try our new filter system /addfilter " + ) + + else: + + await event.reply( + "Usage: Reply to user message with /cfilter .. \nNot Recomended use new filter system /savefilter" + ) + + +@register(pattern="^/stopcfilter (.*)") +async def on_snip_delete(event): + if event.is_group: + if not await can_change_info(message=event): + return + else: + return + name = event.pattern_match.group(1) + + remove_filter(event.chat_id, name) + + await event.reply(f"Filter **{name}** deleted successfully") + + +@register(pattern="^/cfilters$") +async def on_snip_list(event): + if event.is_group: + pass + else: + return + all_snips = get_all_filters(event.chat_id) + + OUT_STR = "Available Classic Filters in the Current Chat:\n" + + if len(all_snips) > 0: + + for a_snip in all_snips: + + OUT_STR += f"👉{a_snip.keyword} \n" + + else: + + OUT_STR = "No Classic Filters in this chat. " + + if len(OUT_STR) > 4096: + + with io.BytesIO(str.encode(OUT_STR)) as out_file: + + out_file.name = "filters.text" + + await tbot.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + caption="Available Classic Filters in the Current Chat", + reply_to=event, + ) + + else: + + await event.reply(OUT_STR) + + +@register(pattern="^/stopallcfilters$") +async def on_all_snip_delete(event): + if event.is_group: + if not await can_change_info(message=event): + return + else: + return + remove_all_filters(event.chat_id) + await event.reply(f"Classic Filter in current chat deleted !") + + +file_help = os.path.basename(__file__) +file_help = file_help.replace(".py", "") +file_helpo = file_help.replace("_", " ") diff --git a/DaisyX/modules/connection.py b/DaisyX/modules/connection.py new file mode 100644 index 00000000..62687601 --- /dev/null +++ b/DaisyX/modules/connection.py @@ -0,0 +1,282 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import re + +from aiogram.dispatcher.filters.builtin import CommandStart +from aiogram.types import CallbackQuery +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.callback_data import CallbackData +from aiogram.utils.deep_linking import get_start_link +from aiogram.utils.exceptions import BotBlocked, CantInitiateConversation + +from DaisyX import bot +from DaisyX.decorator import register +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis + +from .utils.connections import chat_connection, get_connection_data, set_connected_chat +from .utils.language import get_strings_dec +from .utils.message import get_arg +from .utils.notes import BUTTONS +from .utils.user_details import get_chat_dec, is_user_admin + +connect_to_chat_cb = CallbackData("connect_to_chat_cb", "chat_id") + + +@get_strings_dec("connections") +async def def_connect_chat(message, user_id, chat_id, chat_title, strings, edit=False): + await set_connected_chat(user_id, chat_id) + + text = strings["pm_connected"].format(chat_name=chat_title) + if edit: + await message.edit_text(text) + else: + await message.reply(text) + + +# In chat - connect directly to chat +@register(cmds="connect", only_groups=True, no_args=True) +@get_strings_dec("connections") +async def connect_to_chat_direct(message, strings): + user_id = message.from_user.id + chat_id = message.chat.id + + if user_id == 1087968824: + # just warn the user that connections with admin rights doesn't work + return await message.reply( + strings["anon_admin_conn"], + reply_markup=InlineKeyboardMarkup().add( + InlineKeyboardButton( + strings["click_here"], callback_data="anon_conn_cb" + ) + ), + ) + + chat = await db.chat_list.find_one({"chat_id": chat_id}) + chat_title = chat["chat_title"] if chat is not None else message.chat.title + text = strings["pm_connected"].format(chat_name=chat_title) + + try: + await bot.send_message(user_id, text) + await def_connect_chat(message, user_id, chat_id, chat_title) + except (BotBlocked, CantInitiateConversation): + await message.reply(strings["connected_pm_to_me"].format(chat_name=chat_title)) + redis.set("DaisyX_connected_start_state:" + str(user_id), 1) + + +# In pm without args - show last connected chats +@register(cmds="connect", no_args=True, only_pm=True) +@get_strings_dec("connections") +@chat_connection() +async def connect_chat_keyboard(message, strings, chat): + connected_data = await get_connection_data(message.from_user.id) + if not connected_data: + return await message.reply(strings["u_wasnt_connected"]) + + if chat["status"] != "private": + text = strings["connected_chat"].format(chat_name=chat["chat_title"]) + elif "command" in connected_data: + if chat := await db.chat_list.find_one({"chat_id": connected_data["chat_id"]}): + chat_title = chat["chat_title"] + else: + chat_title = connected_data["chat_id"] + text = strings["connected_chat:cmds"].format( + chat_name=chat_title, + # disconnect is builtin command, should not be shown + commands=", ".join( + f"/{cmd}" + for cmd in connected_data["command"] + if cmd != "disconnect" + ), + ) + else: + text = "" + + text += strings["select_chat_to_connect"] + markup = InlineKeyboardMarkup(row_width=1) + for chat_id in reversed(connected_data["history"][-3:]): + chat = await db.chat_list.find_one({"chat_id": chat_id}) + markup.insert( + InlineKeyboardButton( + chat["chat_title"], + callback_data=connect_to_chat_cb.new(chat_id=chat_id), + ) + ) + + await message.reply(text, reply_markup=markup) + + +# Callback for prev. function +@register(connect_to_chat_cb.filter(), f="cb", allow_kwargs=True) +async def connect_chat_keyboard_cb(message, callback_data=False, **kwargs): + chat_id = int(callback_data["chat_id"]) + chat = await db.chat_list.find_one({"chat_id": chat_id}) + await def_connect_chat( + message.message, message.from_user.id, chat_id, chat["chat_title"], edit=True + ) + + +# In pm with args - connect to chat by arg +@register(cmds="connect", has_args=True, only_pm=True) +@get_chat_dec() +@get_strings_dec("connections") +async def connect_to_chat_from_arg(message, chat, strings): + user_id = message.from_user.id + chat_id = chat["chat_id"] + + arg = get_arg(message) + if arg.startswith("-"): + chat_id = int(arg) + + if not chat_id: + await message.reply(strings["cant_find_chat_use_id"]) + return + + await def_connect_chat(message, user_id, chat_id, chat["chat_title"]) + + +@register(cmds="disconnect", only_pm=True) +@get_strings_dec("connections") +async def disconnect_from_chat_direct(message, strings): + if (data := await get_connection_data(message.from_user.id)) and "chat_id" in data: + chat = await db.chat_list.find_one({"chat_id": data["chat_id"]}) + user_id = message.from_user.id + await set_connected_chat(user_id, None) + await message.reply( + strings["disconnected"].format(chat_name=chat["chat_title"]) + ) + + +@register(cmds="allowusersconnect") +@get_strings_dec("connections") +@chat_connection(admin=True, only_groups=True) +async def allow_users_to_connect(message, strings, chat): + chat_id = chat["chat_id"] + arg = get_arg(message).lower() + if not arg: + status = strings["enabled"] + data = await db.chat_connection_settings.find_one({"chat_id": chat_id}) + if ( + data + and "allow_users_connect" in data + and data["allow_users_connect"] is False + ): + status = strings["disabled"] + await message.reply( + strings["chat_users_connections_info"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + return + enable = ("enable", "on", "ok", "yes") + disable = ("disable", "off", "no") + if arg in enable: + r_bool = True + status = strings["enabled"] + elif arg in disable: + r_bool = False + status = strings["disabled"] + else: + await message.reply(strings["bad_arg_bool"]) + return + + await db.chat_connection_settings.update_one( + {"chat_id": chat_id}, {"$set": {"allow_users_connect": r_bool}}, upsert=True + ) + await message.reply( + strings["chat_users_connections_cng"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + + +@register(cmds="start", only_pm=True) +@get_strings_dec("connections") +@chat_connection() +async def connected_start_state(message, strings, chat): + key = "DaisyX_connected_start_state:" + str(message.from_user.id) + if redis.get(key): + await message.reply( + strings["pm_connected"].format(chat_name=chat["chat_title"]) + ) + redis.delete(key) + + +BUTTONS.update({"connect": "btn_connect_start"}) + + +@register(CommandStart(re.compile(r"btn_connect_start")), allow_kwargs=True) +@get_strings_dec("connections") +async def connect_start(message, strings, regexp=None, **kwargs): + args = message.get_args().split("_") + + # In case if button have arg it will be used. # TODO: Check chat_id, parse chat nickname. + arg = args[3] + + if arg.startswith("-") or arg.isdigit(): + chat = await db.chat_list.find_one({"chat_id": int(arg)}) + elif arg.startswith("@"): + chat = await db.chat_list.find_one({"chat_nick": arg.lower()}) + else: + await message.reply(strings["cant_find_chat_use_id"]) + return + + await def_connect_chat( + message, message.from_user.id, chat["chat_id"], chat["chat_title"] + ) + + +@register(regexp="anon_conn_cb", f="cb") +async def connect_anon_admins(event: CallbackQuery): + if not await is_user_admin(event.message.chat.id, event.from_user.id): + return + + if ( + event.message.chat.id + not in (data := await db.user_list.find_one({"user_id": event.from_user.id}))[ + "chats" + ] + ): + await db.user_list.update_one( + {"_id": data["_id"]}, {"$addToSet": {"chats": event.message.chat.id}} + ) + return await event.answer( + url=await get_start_link(f"btn_connect_start_{event.message.chat.id}") + ) + + +__mod_name__ = "Connections" + +__help__ = """ +Sometimes you need change something in your chat, like notes, but you don't want to spam in it, try connections, this allow you change chat settings and manage chat's content in personal message with Daisy. + +Available commands are: +Avaible only in PM: +- /connect: Show last connected chats button for fast connection +- /connect (chat ID or chat nickname): Connect to chat by argument which you provided +- /reconnect: Connect to last connected chat before +- /disconnect: Disconnect from + +Avaible only in groups: +- /connect: Direct connect to this group + +Other commands: +- /allowusersconnect (on/off enable/disable): Enable or disable connection feature for regular users, for admins connections will be works always +""" diff --git a/DaisyX/modules/contributors.py b/DaisyX/modules/contributors.py new file mode 100644 index 00000000..d922f36d --- /dev/null +++ b/DaisyX/modules/contributors.py @@ -0,0 +1,34 @@ +# Copyright (C) 2021 ProgrammingError + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import github # pyGithub +from pyrogram import filters + +from DaisyX.services.pyrogram import pbot as client + + +@client.on_message(filters.command("contributors") & ~filters.edited) +async def give_cobtribs(c, m): + g = github.Github() + co = "" + n = 0 + repo = g.get_repo("TeamDaisyX/DaisyX") + for i in repo.get_contributors(): + n += 1 + co += f"{n}. [{i.login}](https://github.com/{i.login})\n" + t = f"**DaisyX Contributors**\n\n{co}" + await m.reply(t, disable_web_page_preview=True) diff --git a/DaisyX/modules/country.py b/DaisyX/modules/country.py new file mode 100644 index 00000000..7a19469e --- /dev/null +++ b/DaisyX/modules/country.py @@ -0,0 +1,121 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from countryinfo import CountryInfo + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as borg + + +@register(pattern="^/country (.*)") +async def msg(event): + if event.fwd_from: + return + input_str = event.pattern_match.group(1) + lol = input_str + country = CountryInfo(lol) + try: + a = country.info() + except: + await event.reply("Country Not Avaiable Currently") + name = a.get("name") + bb = a.get("altSpellings") + hu = "" + for p in bb: + hu += p + ", " + + area = a.get("area") + borders = "" + hell = a.get("borders") + for fk in hell: + borders += fk + ", " + + call = "" + WhAt = a.get("callingCodes") + for what in WhAt: + call += what + " " + + capital = a.get("capital") + currencies = "" + fker = a.get("currencies") + for FKer in fker: + currencies += FKer + ", " + + HmM = a.get("demonym") + geo = a.get("geoJSON") + pablo = geo.get("features") + Pablo = pablo[0] + PAblo = Pablo.get("geometry") + EsCoBaR = PAblo.get("type") + iso = "" + iSo = a.get("ISO") + for hitler in iSo: + po = iSo.get(hitler) + iso += po + ", " + fla = iSo.get("alpha2") + fla.upper() + + languages = a.get("languages") + lMAO = "" + for lmao in languages: + lMAO += lmao + ", " + + nonive = a.get("nativeName") + waste = a.get("population") + reg = a.get("region") + sub = a.get("subregion") + tik = a.get("timezones") + tom = "" + for jerry in tik: + tom += jerry + ", " + + GOT = a.get("tld") + lanester = "" + for targaryen in GOT: + lanester += targaryen + ", " + + wiki = a.get("wiki") + + caption = f"""Information Gathered Successfully + +Country Name:- {name} +Alternative Spellings:- {hu} +Country Area:- {area} square kilometers +Borders:- {borders} +Calling Codes:- {call} +Country's Capital:- {capital} +Country's currency:- {currencies} +Demonym:- {HmM} +Country Type:- {EsCoBaR} +ISO Names:- {iso} +Languages:- {lMAO} +Native Name:- {nonive} +population:- {waste} +Region:- {reg} +Sub Region:- {sub} +Time Zones:- {tom} +Top Level Domain:- {lanester} +wikipedia:- {wiki} +Gathered By Daisy X.
+""" + + await borg.send_message( + event.chat_id, + caption, + parse_mode="HTML", + ) diff --git a/DaisyX/modules/covid.py b/DaisyX/modules/covid.py new file mode 100644 index 00000000..803b8846 --- /dev/null +++ b/DaisyX/modules/covid.py @@ -0,0 +1,38 @@ +# Copyright (C) 2021 TheHamkerCat +# Edited by TeamDaisyX + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from pyrogram import filters + +from DaisyX.function.pluginhelpers import fetch, json_prettify +from DaisyX.services.pyrogram import pbot as app + + +@app.on_message(filters.command("covid") & ~filters.edited) +async def covid(_, message): + if len(message.command) == 1: + data = await fetch("https://corona.lmao.ninja/v2/all") + data = await json_prettify(data) + await app.send_message(message.chat.id, text=data) + return + if len(message.command) != 1: + country = message.text.split(None, 1)[1].strip() + country = country.replace(" ", "") + data = await fetch(f"https://corona.lmao.ninja/v2/countries/{country}") + data = await json_prettify(data) + await app.send_message(message.chat.id, text=data) + return diff --git a/DaisyX/modules/datetime.py b/DaisyX/modules/datetime.py new file mode 100644 index 00000000..c3e1f2b5 --- /dev/null +++ b/DaisyX/modules/datetime.py @@ -0,0 +1,135 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import datetime +from typing import List + +import requests +from telethon import types +from telethon.tl import functions + +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +TIME_API_KEY = get_str_key("TIME_API_KEY", required=False) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +def generate_time(to_find: str, findtype: List[str]) -> str: + data = requests.get( + f"http://api.timezonedb.com/v2.1/list-time-zone" + f"?key={TIME_API_KEY}" + f"&format=json" + f"&fields=countryCode,countryName,zoneName,gmtOffset,timestamp,dst" + ).json() + + for zone in data["zones"]: + for eachtype in findtype: + if to_find in zone[eachtype].lower(): + country_name = zone["countryName"] + country_zone = zone["zoneName"] + country_code = zone["countryCode"] + + if zone["dst"] == 1: + daylight_saving = "Yes" + else: + daylight_saving = "No" + + date_fmt = r"%d-%m-%Y" + time_fmt = r"%H:%M:%S" + day_fmt = r"%A" + gmt_offset = zone["gmtOffset"] + timestamp = datetime.datetime.now( + datetime.timezone.utc + ) + datetime.timedelta(seconds=gmt_offset) + current_date = timestamp.strftime(date_fmt) + current_time = timestamp.strftime(time_fmt) + current_day = timestamp.strftime(day_fmt) + + break + + try: + result = ( + f"🌍Country : {country_name}\n" + f"⏳Zone Name : {country_zone}\n" + f"🗺Country Code : {country_code}\n" + f"🌞Daylight saving : {daylight_saving}\n" + f"🌅Day : {current_day}\n" + f"⌚Current Time : {current_time}\n" + f"📆Current Date : {current_date}" + ) + except BaseException: + result = None + + return result + + +@register(pattern="^/datetime ?(.*)") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + + gay = event.pattern_match.group(1) + + try: + query = gay + except BaseException: + await event.reply("Provide a country name/abbreviation/timezone to find.") + return + + send_message = await event.reply( + f"Finding timezone info for {query}", parse_mode="html" + ) + + query_timezone = query.lower() + if len(query_timezone) == 2: + result = generate_time(query_timezone, ["countryCode"]) + else: + result = generate_time(query_timezone, ["zoneName", "countryName"]) + + if not result: + await send_message.edit( + f"Timezone info not available for {query}", parse_mode="html" + ) + return + + await send_message.edit(result, parse_mode="html") + + +_mod_name_ = "Date Time" +_help_ = """ + - /datetime [timezone]: Get the present date and time information +**You can check out this [link](https://timezonedb.com/time-zones) for the available timezones** +""" diff --git a/DaisyX/modules/devs.py b/DaisyX/modules/devs.py new file mode 100644 index 00000000..9880186d --- /dev/null +++ b/DaisyX/modules/devs.py @@ -0,0 +1,37 @@ +# Credits Telegram @ChankitSaini + +__mod_name__ = "Devs" +__help__ = """ +⚠️ Notice: +Commands listed here only work for users with special access are mainly used for troubleshooting, debugging purposes. +Group admins/group owners do not need these commands. + +Commands: + +Bot Owner Only: +- /leavechat chat id : Leaves the chat +- /ip : Fetch server's ip (Works in pm only) +- /term : Runs the shell commands +- /sbroadcast : Smart broadcast in chats where bot is currently added. +- /stopsbroadcast : Stops the current ongoing broadcast +- /continuebroadcast : Continue the stopped broadcast +- /purgecache : Clear cache in redis server + +System Commands +- /botstop : Shutdowns the bot. +- /restart : Restarts the bot +- /upgrade : Updates the bot. +- /upload : Uploads the file from bot's server +- /crash : Crashes the bot +- /update : updates the bot + +Operators: +- /stats : To get bot's current stats +- /allcommands : Shows the list of all available commands +- /allcmdsaliases : Shows the aliases list +- /loadedmodules : Shows the currently loaded modules +- /avaiblebtns : Shows the list of all message inline buttons +- /logs : Uploads the bot logs as file +- /event : To get a event via aiogram + +""" diff --git a/DaisyX/modules/direct_link.py b/DaisyX/modules/direct_link.py new file mode 100644 index 00000000..ddd0a1a6 --- /dev/null +++ b/DaisyX/modules/direct_link.py @@ -0,0 +1,100 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import re +from random import choice + +import requests +from bs4 import BeautifulSoup + +from DaisyX.decorator import register + +from .utils.disable import disableable_dec +from .utils.message import get_arg + + +@register(cmds="direct") +@disableable_dec("direct") +async def direct_link_generator(message): + text = get_arg(message) + + if not text: + m = "Usage: /direct (url)" + await message.reply(m) + return + + if text: + links = re.findall(r"\bhttps?://.*\.\S+", text) + else: + return + + reply = [] + if not links: + await message.reply("No links found!") + return + + for link in links: + if "sourceforge.net" in link: + reply.append(sourceforge(link)) + else: + reply.append( + re.findall(r"\bhttps?://(.*?[^/]+)", link)[0] + " is not supported" + ) + + await message.reply("\n".join(reply)) + + +def sourceforge(url: str) -> str: + try: + link = re.findall(r"\bhttps?://.*sourceforge\.net\S+", url)[0] + except IndexError: + reply = "No SourceForge links found\n" + return reply + + file_path = re.findall(r"/files(.*)/download", link) + if not file_path: + file_path = re.findall(r"/files(.*)", link) + file_path = file_path[0] + reply = f"Mirrors for {file_path.split('/')[-1]}\n" + project = re.findall(r"projects?/(.*?)/files", link)[0] + mirrors = ( + f"https://sourceforge.net/settings/mirror_choices?" + f"projectname={project}&filename={file_path}" + ) + page = BeautifulSoup(requests.get(mirrors).content, "lxml") + info = page.find("ul", {"id": "mirrorList"}).findAll("li") + + for mirror in info[1:]: + name = re.findall(r"\((.*)\)", mirror.text.strip())[0] + dl_url = ( + f'https://{mirror["id"]}.dl.sourceforge.net/project/{project}/{file_path}' + ) + reply += f'{name} ' + return reply + + +def useragent(): + useragents = BeautifulSoup( + requests.get( + "https://developers.whatismybrowser.com/" + "useragents/explore/operating_system_name/android/" + ).content, + "lxml", + ).findAll("td", {"class": "useragent"}) + user_agent = choice(useragents) + return user_agent.text diff --git a/DaisyX/modules/disabling.py b/DaisyX/modules/disabling.py new file mode 100644 index 00000000..c13813a5 --- /dev/null +++ b/DaisyX/modules/disabling.py @@ -0,0 +1,200 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup + +from DaisyX.decorator import COMMANDS_ALIASES, register +from DaisyX.services.mongo import db + +from .utils.connections import chat_connection +from .utils.disable import DISABLABLE_COMMANDS, disableable_dec +from .utils.language import get_strings_dec +from .utils.message import get_arg, need_args_dec + + +@register(cmds="disableable") +@disableable_dec("disableable") +@get_strings_dec("disable") +async def list_disablable(message, strings): + text = strings["disablable"] + for command in DISABLABLE_COMMANDS: + text += f"* /{command}\n" + await message.reply(text) + + +@register(cmds="disabled") +@chat_connection(only_groups=True) +@get_strings_dec("disable") +async def list_disabled(message, chat, strings): + text = strings["disabled_list"].format(chat_name=chat["chat_title"]) + + if not (disabled := await db.disabled.find_one({"chat_id": chat["chat_id"]})): + await message.reply( + strings["no_disabled_cmds"].format(chat_name=chat["chat_title"]) + ) + return + + commands = disabled["cmds"] + for command in commands: + text += f"* /{command}\n" + await message.reply(text) + + +@register(cmds="disable", user_admin=True) +@need_args_dec() +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("disable") +async def disable_command(message, chat, strings): + cmd = get_arg(message).lower() + if cmd[0] == "/" or cmd[0] == "!": + cmd = cmd[1:] + + # Check on commands aliases + for name, keys in COMMANDS_ALIASES.items(): + if cmd in keys: + cmd = name + break + + if cmd not in DISABLABLE_COMMANDS: + await message.reply(strings["wot_to_disable"]) + return + + if await db.disabled.find_one({"chat_id": chat["chat_id"], "cmds": {"$in": [cmd]}}): + await message.reply(strings["already_disabled"]) + return + + await db.disabled.update_one( + {"chat_id": chat["chat_id"]}, + {"$addToSet": {"cmds": {"$each": [cmd]}}}, + upsert=True, + ) + + await message.reply( + strings["disabled"].format(cmd=cmd, chat_name=chat["chat_title"]) + ) + + +@register(cmds="enable") +@need_args_dec() +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("disable") +async def enable_command(message, chat, strings): + chat_id = chat["chat_id"] + cmd = get_arg(message).lower() + if cmd[0] == "/" or cmd[0] == "!": + cmd = cmd[1:] + + # Check on commands aliases + for name, keys in COMMANDS_ALIASES.items(): + if cmd in keys: + cmd = name + break + + if cmd not in DISABLABLE_COMMANDS: + await message.reply(strings["wot_to_enable"]) + return + + if not await db.disabled.find_one( + {"chat_id": chat["chat_id"], "cmds": {"$in": [cmd]}} + ): + await message.reply(strings["already_enabled"]) + return + + await db.disabled.update_one({"chat_id": chat_id}, {"$pull": {"cmds": cmd}}) + + await message.reply( + strings["enabled"].format(cmd=cmd, chat_name=chat["chat_title"]) + ) + + +@register(cmds="enableall", is_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("disable") +async def enable_all(message, chat, strings): + # Ensure that something is disabled + if not await db.disabled.find_one({"chat_id": chat["chat_id"]}): + await message.reply( + strings["not_disabled_anything"].format(chat_title=chat["chat_title"]) + ) + return + + text = strings["enable_all_text"].format(chat_name=chat["chat_title"]) + buttons = InlineKeyboardMarkup() + buttons.add( + InlineKeyboardButton( + strings["enable_all_btn_yes"], callback_data="enable_all_notes_cb" + ) + ) + buttons.add( + InlineKeyboardButton(strings["enable_all_btn_no"], callback_data="cancel") + ) + await message.reply(text, reply_markup=buttons) + + +@register(regexp="enable_all_notes_cb", f="cb", is_admin=True) +@chat_connection(admin=True) +@get_strings_dec("disable") +async def enable_all_notes_cb(event, chat, strings): + data = await db.disabled.find_one({"chat_id": chat["chat_id"]}) + await db.disabled.delete_one({"_id": data["_id"]}) + + text = strings["enable_all_done"].format( + num=len(data["cmds"]), chat_name=chat["chat_title"] + ) + await event.message.edit_text(text) + + +async def __export__(chat_id): + disabled = await db.disabled.find_one({"chat_id": chat_id}) + + return {"disabling": disabled["cmds"] if disabled else []} + + +async def __import__(chat_id, data): + new = [] + for cmd in data: + if cmd not in DISABLABLE_COMMANDS: + continue + + new.append(cmd) + + await db.disabled.update_one( + {"chat_id": chat_id}, {"$set": {"cmds": new}}, upsert=True + ) + + +__mod_name__ = "Disabling" + +__help__ = """ +Disabling module is allow you to disable certain commands from be executed by users. + +Available commands: +- /disableable: Shows commands which can be disabled +- /disabled: Shows the all disabled commands of the chat +- /disable (command name): Disables the command. Command should be disable-able +- /enable (command name): Enables the disabled command back. +- /enableall: Enables all disabled commands + +Examples: +/disable help +It would disable usauge of /help command in the chat! + +/enable help +This enables previously disable command /help. +""" diff --git a/DaisyX/modules/error.py b/DaisyX/modules/error.py new file mode 100644 index 00000000..84ed230e --- /dev/null +++ b/DaisyX/modules/error.py @@ -0,0 +1,155 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import html +import sys + +from aiogram.types import Update +from redis.exceptions import RedisError + +from DaisyX import OWNER_ID, bot, dp +from DaisyX.services.redis import redis +from DaisyX.utils.logger import log + +SENT = [] + + +def catch_redis_error(**dec_kwargs): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + global SENT + # We can't use redis here + # So we save data - 'message sent to' in a list variable + update: Update = args[0] + + if update.message is not None: + message = update.message + elif update.callback_query is not None: + message = update.callback_query.message + elif update.edited_message is not None: + message = update.edited_message + else: + return True + + chat_id = message.chat.id if "chat" in message else None + try: + return await func(*args, **kwargs) + except RedisError: + if chat_id not in SENT: + text = ( + "Sorry for inconvenience! I encountered error in my redis DB, which is necessary for " + "running bot \n\nPlease report this to my support group immediately when you see this error!" + ) + if await bot.send_message(chat_id, text): + SENT.append(chat_id) + # Alert bot owner + if OWNER_ID not in SENT: + text = "Texas panic: Got redis error" + if await bot.send_message(OWNER_ID, text): + SENT.append(OWNER_ID) + log.error(RedisError, exc_info=True) + return True + + return wrapped_1 + + return wrapped + + +@dp.errors_handler() +@catch_redis_error() +async def all_errors_handler(update: Update, error): + if update.message is not None: + message = update.message + elif update.callback_query is not None: + message = update.callback_query.message + elif update.edited_message is not None: + message = update.edited_message + else: + return True # we don't want other guys in playground + + chat_id = message.chat.id + err_tlt = sys.exc_info()[0].__name__ + err_msg = str(sys.exc_info()[1]) + + log.warn( + "Error caused update is: \n" + + html.escape(str(parse_update(message)), quote=False) + ) + + if redis.get(chat_id) == str(error): + # by err_tlt we assume that it is same error + return True + + if err_tlt == "BadRequest" and err_msg == "Have no rights to send a message": + return True + + ignored_errors = ( + "FloodWaitError", + "RetryAfter", + "SlowModeWaitError", + "InvalidQueryID", + ) + if err_tlt in ignored_errors: + return True + + if err_tlt in ("NetworkError", "TelegramAPIError", "RestartingTelegram"): + log.error("Conn/API error detected", exc_info=error) + return True + + text = "Sorry, I encountered a error!\n" + text += f"{html.escape(err_tlt, quote=False)}: {html.escape(err_msg, quote=False)}" + redis.set(chat_id, str(error), ex=600) + await bot.send_message(chat_id, text) + + +def parse_update(update): + # The parser to hide sensitive informations in the update (for logging) + + if isinstance(update, Update): # Hacc + if update.message is not None: + update = update.message + elif update.callback_query is not None: + update = update.callback_query.message + elif update.edited_message is not None: + update = update.edited_message + else: + return + + if "chat" in update: + chat = update["chat"] + chat["id"] = chat["title"] = chat["username"] = chat["first_name"] = chat[ + "last_name" + ] = [] + if user := update["from"]: + user["id"] = user["first_name"] = user["last_name"] = user["username"] = [] + if "reply_to_message" in update: + reply_msg = update["reply_to_message"] + reply_msg["chat"]["id"] = reply_msg["chat"]["title"] = reply_msg["chat"][ + "first_name" + ] = reply_msg["chat"]["last_name"] = reply_msg["chat"]["username"] = [] + reply_msg["from"]["id"] = reply_msg["from"]["first_name"] = reply_msg["from"][ + "last_name" + ] = reply_msg["from"]["username"] = [] + reply_msg["message_id"] = [] + reply_msg["new_chat_members"] = reply_msg["left_chat_member"] = [] + if ("new_chat_members", "left_chat_member") in update: + update["new_chat_members"] = update["left_chat_member"] = [] + if "message_id" in update: + update["message_id"] = [] + return update diff --git a/DaisyX/modules/fakeit.py b/DaisyX/modules/fakeit.py new file mode 100644 index 00000000..9a39d8ae --- /dev/null +++ b/DaisyX/modules/fakeit.py @@ -0,0 +1,66 @@ +# Copyright (C) @chsaiujwal 2020-2021 +# Edited by TeamDaisyX +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os + +import requests +from faker import Faker +from faker.providers import internet +from telethon import events + +from DaisyX.function.telethonbasics import is_admin +from DaisyX.services.telethon import tbot + + +@tbot.on(events.NewMessage(pattern="/fakegen$")) +async def hi(event): + if event.fwd_from: + return + if event.is_group: + if not await is_admin(event, event.message.sender_id): + await event.reply("`You Should Be Admin To Do This!`") + return + fake = Faker() + print("FAKE DETAILS GENERATED\n") + name = str(fake.name()) + fake.add_provider(internet) + address = str(fake.address()) + ip = fake.ipv4_private() + cc = fake.credit_card_full() + email = fake.ascii_free_email() + job = fake.job() + android = fake.android_platform_token() + pc = fake.chrome() + await event.reply( + f" Fake Information Generated\nName :-{name}\n\nAddress:-{address}\n\nIP ADDRESS:-{ip}\n\ncredit card:-{cc}\n\nEmail Id:-{email}\n\nJob:-{job}\n\nandroid user agent:-{android}\n\nPc user agent:-{pc}", + parse_mode="HTML", + ) + + +@tbot.on(events.NewMessage(pattern="/picgen$")) +async def _(event): + if event.fwd_from: + return + if await is_admin(event, event.message.sender_id): + url = "https://thispersondoesnotexist.com/image" + response = requests.get(url) + if response.status_code == 200: + with open("FRIDAYOT.jpg", "wb") as f: + f.write(response.content) + + captin = f"Fake Image powered by @DaisySupport_Official." + fole = "FRIDAYOT.jpg" + await tbot.send_file(event.chat_id, fole, caption=captin) + await event.delete() + os.system("rm ./FRIDAYOT.jpg ") diff --git a/DaisyX/modules/feds.py b/DaisyX/modules/feds.py new file mode 100644 index 00000000..040e2883 --- /dev/null +++ b/DaisyX/modules/feds.py @@ -0,0 +1,1404 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import asyncio +import csv +import html +import io +import os +import re +import time +import uuid +from contextlib import suppress +from datetime import datetime, timedelta +from typing import Optional + +import babel +import rapidjson +from aiogram import types +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import InputFile, Message +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.callback_data import CallbackData +from aiogram.utils.exceptions import ( + ChatNotFound, + NeedAdministratorRightsInTheChannel, + Unauthorized, +) +from babel.dates import format_timedelta +from pymongo import DeleteMany, InsertOne + +from DaisyX import BOT_ID, OPERATORS, OWNER_ID, bot, decorator +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis +from DaisyX.services.telethon import tbot + +from ..utils.cached import cached +from .utils.connections import chat_connection, get_connected_chat +from .utils.language import get_string, get_strings, get_strings_dec +from .utils.message import get_cmd, need_args_dec +from .utils.restrictions import ban_user, unban_user +from .utils.user_details import ( + check_admin_rights, + get_chat_dec, + get_user_and_text, + get_user_link, + is_chat_creator, + is_user_admin, +) + + +class ImportFbansFileWait(StatesGroup): + waiting = State() + + +delfed_cb = CallbackData("delfed_cb", "fed_id", "creator_id") + + +# functions + + +async def get_fed_f(message): + chat = await get_connected_chat(message, admin=True) + if "err_msg" not in chat: + if chat["status"] == "private": + # return fed which user is created + fed = await get_fed_by_creator(chat["chat_id"]) + else: + fed = await db.feds.find_one({"chats": {"$in": [chat["chat_id"]]}}) + if not fed: + return False + return fed + + +async def fed_post_log(fed, text): + if "log_chat_id" not in fed: + return + chat_id = fed["log_chat_id"] + with suppress(Unauthorized, NeedAdministratorRightsInTheChannel, ChatNotFound): + await bot.send_message(chat_id, text) + + +# decorators + + +def get_current_chat_fed(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + real_chat_id = message.chat.id + if not (fed := await get_fed_f(message)): + await message.reply( + await get_string(real_chat_id, "feds", "chat_not_in_fed") + ) + return + + return await func(*args, fed, **kwargs) + + return wrapped_1 + + +def get_fed_user_text(skip_no_fed=False, self=False): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + fed = None + message = args[0] + real_chat_id = message.chat.id + user, text = await get_user_and_text(message) + strings = await get_strings(real_chat_id, "feds") + + # Check non exits user + data = message.get_args().split(" ") + if ( + not user + and len(data) > 0 + and data[0].isdigit() + and int(data[0]) <= 2147483647 + ): + user = {"user_id": int(data[0])} + text = " ".join(data[1:]) if len(data) > 1 else None + elif not user: + if self is True: + user = await db.user_list.find_one( + {"user_id": message.from_user.id} + ) + else: + await message.reply(strings["cant_get_user"]) + # Passing 'None' user will throw err + return + + # Check fed_id in args + if text: + text_args = text.split(" ", 1) + if len(text_args) >= 1: + if text_args[0].count("-") == 4: + text = text_args[1] if len(text_args) > 1 else "" + if not (fed := await get_fed_by_id(text_args[0])): + await message.reply(strings["fed_id_invalid"]) + return + else: + text = " ".join(text_args) + + if not fed: + if not (fed := await get_fed_f(message)): + if not skip_no_fed: + await message.reply(strings["chat_not_in_fed"]) + return + else: + fed = None + + return await func(*args, fed, user, text, **kwargs) + + return wrapped_1 + + return wrapped + + +def get_fed_dec(func): + async def wrapped_1(*args, **kwargs): + fed = None + message = args[0] + real_chat_id = message.chat.id + + if message.text: + text_args = message.text.split(" ", 2) + if not len(text_args) < 2 and text_args[1].count("-") == 4: + if not (fed := await get_fed_by_id(text_args[1])): + await message.reply( + await get_string(real_chat_id, "feds", "fed_id_invalid") + ) + return + + # Check whether fed is still None; This will allow above fed variable to be passed + # TODO(Better handling?) + if fed is None: + if not (fed := await get_fed_f(message)): + await message.reply( + await get_string(real_chat_id, "feds", "chat_not_in_fed") + ) + return + + return await func(*args, fed, **kwargs) + + return wrapped_1 + + +def is_fed_owner(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + fed = args[1] + user_id = message.from_user.id + + # check on anon + if user_id in [1087968824, 777000]: + return + + if not user_id == fed["creator"] and user_id != OWNER_ID: + text = (await get_string(message.chat.id, "feds", "need_fed_admin")).format( + name=html.escape(fed["fed_name"], False) + ) + await message.reply(text) + return + + return await func(*args, **kwargs) + + return wrapped_1 + + +def is_fed_admin(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + fed = args[1] + user_id = message.from_user.id + + # check on anon + if user_id in [1087968824, 777000]: + return + + if not user_id == fed["creator"] and user_id != OWNER_ID: + if "admins" not in fed or user_id not in fed["admins"]: + text = ( + await get_string(message.chat.id, "feds", "need_fed_admin") + ).format(name=html.escape(fed["fed_name"], False)) + return await message.reply(text) + + return await func(*args, **kwargs) + + return wrapped_1 + + +# cmds + + +@decorator.register(cmds=["newfed", "fnew"]) +@need_args_dec() +@get_strings_dec("feds") +async def new_fed(message, strings): + fed_name = html.escape(message.get_args()) + user_id = message.from_user.id + # dont support creation of newfed as anon admin + if user_id == 1087968824: + return await message.reply(strings["disallow_anon"]) + + if not fed_name: + await message.reply(strings["no_args"]) + + if len(fed_name) > 60: + await message.reply(strings["fed_name_long"]) + return + + if await get_fed_by_creator(user_id) and not user_id == OWNER_ID: + await message.reply(strings["can_only_1_fed"]) + return + + if await db.feds.find_one({"fed_name": fed_name}): + await message.reply(strings["name_not_avaible"].format(name=fed_name)) + return + + data = {"fed_name": fed_name, "fed_id": str(uuid.uuid4()), "creator": user_id} + await db.feds.insert_one(data) + await get_fed_by_id.reset_cache(data["fed_id"]) + await get_fed_by_creator.reset_cache(data["creator"]) + await message.reply( + strings["created_fed"].format( + name=fed_name, id=data["fed_id"], creator=await get_user_link(user_id) + ) + ) + + +@decorator.register(cmds=["joinfed", "fjoin"]) +@need_args_dec() +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("feds") +async def join_fed(message, chat, strings): + fed_id = message.get_args().split(" ")[0] + user_id = message.from_user.id + chat_id = chat["chat_id"] + + if not await is_chat_creator(message, chat_id, user_id): + await message.reply(strings["only_creators"]) + return + + # Assume Fed ID invalid + if not (fed := await get_fed_by_id(fed_id)): + await message.reply(strings["fed_id_invalid"]) + return + + # Assume chat already joined this/other fed + if "chats" in fed and chat_id in fed["chats"]: + await message.reply(strings["joined_fed_already"]) + return + + await db.feds.update_one( + {"_id": fed["_id"]}, {"$addToSet": {"chats": {"$each": [chat_id]}}} + ) + await get_fed_by_id.reset_cache(fed["fed_id"]) + await message.reply( + strings["join_fed_success"].format( + chat=chat["chat_title"], fed=html.escape(fed["fed_name"], False) + ) + ) + await fed_post_log( + fed, + strings["join_chat_fed_log"].format( + fed_name=fed["fed_name"], + fed_id=fed["fed_id"], + chat_name=chat["chat_title"], + chat_id=chat_id, + ), + ) + + +@decorator.register(cmds=["leavefed", "fleave"]) +@chat_connection(admin=True, only_groups=True) +@get_current_chat_fed +@get_strings_dec("feds") +async def leave_fed_comm(message, chat, fed, strings): + user_id = message.from_user.id + if not await is_chat_creator(message, chat["chat_id"], user_id): + await message.reply(strings["only_creators"]) + return + + await db.feds.update_one({"_id": fed["_id"]}, {"$pull": {"chats": chat["chat_id"]}}) + await get_fed_by_id.reset_cache(fed["fed_id"]) + await message.reply( + strings["leave_fed_success"].format( + chat=chat["chat_title"], fed=html.escape(fed["fed_name"], False) + ) + ) + + await fed_post_log( + fed, + strings["leave_chat_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), + fed_id=fed["fed_id"], + chat_name=chat["chat_title"], + chat_id=chat["chat_id"], + ), + ) + + +@decorator.register(cmds="fsub") +@need_args_dec() +@get_current_chat_fed +@is_fed_owner +@get_strings_dec("feds") +async def fed_sub(message, fed, strings): + fed_id = message.get_args().split(" ")[0] + + # Assume Fed ID is valid + if not (fed2 := await get_fed_by_id(fed_id)): + await message.reply(strings["fed_id_invalid"]) + return + + # Assume chat already joined this/other fed + if "subscribed" in fed and fed_id in fed["subscribed"]: + await message.reply( + strings["already_subsed"].format( + name=html.escape(fed["fed_name"], False), + name2=html.escape(fed2["fed_name"], False), + ) + ) + return + + await db.feds.update_one( + {"_id": fed["_id"]}, {"$addToSet": {"subscribed": {"$each": [fed_id]}}} + ) + await get_fed_by_id.reset_cache(fed["fed_id"]) + await message.reply( + strings["subsed_success"].format( + name=html.escape(fed["fed_name"], False), + name2=html.escape(fed2["fed_name"], False), + ) + ) + + +@decorator.register(cmds="funsub") +@need_args_dec() +@get_current_chat_fed +@is_fed_owner +@get_strings_dec("feds") +async def fed_unsub(message, fed, strings): + fed_id = message.get_args().split(" ")[0] + + if not (fed2 := await get_fed_by_id(fed_id)): + await message.reply(strings["fed_id_invalid"]) + return + + if "subscribed" in fed and fed_id not in fed["subscribed"]: + message.reply( + strings["not_subsed"].format( + name=html.escape(fed["fed_name"], False), name2=fed2["fed_name"] + ) + ) + return + + await db.feds.update_one( + {"_id": fed["_id"]}, {"$pull": {"subscribed": str(fed_id)}} + ) + await get_fed_by_id.reset_cache(fed["fed_id"]) + await message.reply( + strings["unsubsed_success"].format( + name=html.escape(fed["fed_name"], False), + name2=html.escape(fed2["fed_name"], False), + ) + ) + + +@decorator.register(cmds="fpromote") +@get_fed_user_text() +@is_fed_owner +@get_strings_dec("feds") +async def promote_to_fed(message, fed, user, text, strings): + restricted_ids = [1087968824, 777000] + if user["user_id"] in restricted_ids: + return await message.reply(strings["restricted_user:promote"]) + await db.feds.update_one( + {"_id": fed["_id"]}, {"$addToSet": {"admins": {"$each": [user["user_id"]]}}} + ) + await get_fed_by_id.reset_cache(fed["fed_id"]) + await message.reply( + strings["admin_added_to_fed"].format( + user=await get_user_link(user["user_id"]), + name=html.escape(fed["fed_name"], False), + ) + ) + + await fed_post_log( + fed, + strings["promote_user_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), + fed_id=fed["fed_id"], + user=await get_user_link(user["user_id"]), + user_id=user["user_id"], + ), + ) + + +@decorator.register(cmds="fdemote") +@get_fed_user_text() +@is_fed_owner +@get_strings_dec("feds") +async def demote_from_fed(message, fed, user, text, strings): + await db.feds.update_one( + {"_id": fed["_id"]}, {"$pull": {"admins": user["user_id"]}} + ) + await get_fed_by_id.reset_cache(fed["fed_id"]) + + await message.reply( + strings["admin_demoted_from_fed"].format( + user=await get_user_link(user["user_id"]), + name=html.escape(fed["fed_name"], False), + ) + ) + + await fed_post_log( + fed, + strings["demote_user_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), + fed_id=fed["fed_id"], + user=await get_user_link(user["user_id"]), + user_id=user["user_id"], + ), + ) + + +@decorator.register(cmds=["fsetlog", "setfedlog"], only_groups=True) +@get_fed_dec +@get_chat_dec(allow_self=True, fed=True) +@is_fed_owner +@get_strings_dec("feds") +async def set_fed_log_chat(message, fed, chat, strings): + chat_id = chat["chat_id"] if "chat_id" in chat else chat["id"] + if chat["type"] == "channel": + if ( + await check_admin_rights(message, chat_id, BOT_ID, ["can_post_messages"]) + is not True + ): + return await message.reply(strings["no_right_to_post"]) + + if "log_chat_id" in fed and fed["log_chat_id"]: + await message.reply( + strings["already_have_chatlog"].format( + name=html.escape(fed["fed_name"], False) + ) + ) + return + + await db.feds.update_one({"_id": fed["_id"]}, {"$set": {"log_chat_id": chat_id}}) + await get_fed_by_id.reset_cache(fed["fed_id"]) + + text = strings["set_chat_log"].format(name=html.escape(fed["fed_name"], False)) + await message.reply(text) + + # Current fed variable is not updated + await fed_post_log( + await get_fed_by_id(fed["fed_id"]), + strings["set_log_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), fed_id=fed["fed_id"] + ), + ) + + +@decorator.register(cmds=["funsetlog", "unsetfedlog"], only_groups=True) +@get_fed_dec +@is_fed_owner +@get_strings_dec("feds") +async def unset_fed_log_chat(message, fed, strings): + if "log_chat_id" not in fed or not fed["log_chat_id"]: + await message.reply( + strings["already_have_chatlog"].format( + name=html.escape(fed["fed_name"], False) + ) + ) + return + + await db.feds.update_one({"_id": fed["_id"]}, {"$unset": {"log_chat_id": 1}}) + await get_fed_by_id.reset_cache(fed["fed_id"]) + + text = strings["logging_removed"].format(name=html.escape(fed["fed_name"], False)) + await message.reply(text) + + await fed_post_log( + fed, + strings["unset_log_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), fed_id=fed["fed_id"] + ), + ) + + +@decorator.register(cmds=["fchatlist", "fchats"]) +@get_fed_dec +@is_fed_admin +@get_strings_dec("feds") +async def fed_chat_list(message, fed, strings): + text = strings["chats_in_fed"].format(name=html.escape(fed["fed_name"], False)) + if "chats" not in fed: + return await message.reply( + strings["no_chats"].format(name=html.escape(fed["fed_name"], False)) + ) + + for chat_id in fed["chats"]: + chat = await db.chat_list.find_one({"chat_id": chat_id}) + text += "* {} ({})\n".format(chat["chat_title"], chat_id) + if len(text) > 4096: + await message.answer_document( + InputFile(io.StringIO(text), filename="chatlist.txt"), + strings["too_large"], + reply=message.message_id, + ) + return + await message.reply(text) + + +@decorator.register(cmds=["fadminlist", "fadmins"]) +@get_fed_dec +@is_fed_admin +@get_strings_dec("feds") +async def fed_admins_list(message, fed, strings): + text = strings["fadmins_header"].format( + fed_name=html.escape(fed["fed_name"], False) + ) + text += "* {} ({})\n".format( + await get_user_link(fed["creator"]), fed["creator"] + ) + if "admins" in fed: + for user_id in fed["admins"]: + text += "* {} ({})\n".format( + await get_user_link(user_id), user_id + ) + await message.reply(text, disable_notification=True) + + +@decorator.register(cmds=["finfo", "fedinfo"]) +@get_fed_dec +@get_strings_dec("feds") +async def fed_info(message, fed, strings): + text = strings["finfo_text"] + banned_num = await db.fed_bans.count_documents({"fed_id": fed["fed_id"]}) + text = text.format( + name=html.escape(fed["fed_name"], False), + fed_id=fed["fed_id"], + creator=await get_user_link(fed["creator"]), + chats=len(fed["chats"] if "chats" in fed else []), + fbanned=banned_num, + ) + + if "subscribed" in fed and len(fed["subscribed"]) > 0: + text += strings["finfo_subs_title"] + for sfed in fed["subscribed"]: + sfed = await get_fed_by_id(sfed) + text += f"* {sfed['fed_name']} ({sfed['fed_id']})\n" + + await message.reply(text, disable_notification=True) + + +async def get_all_subs_feds_r(fed_id, new): + new.append(fed_id) + + fed = await get_fed_by_id(fed_id) + async for item in db.feds.find({"subscribed": {"$in": [fed["fed_id"]]}}): + if item["fed_id"] in new: + continue + new = await get_all_subs_feds_r(item["fed_id"], new) + + return new + + +@decorator.register(cmds=["fban", "sfban"]) +@get_fed_user_text() +@is_fed_admin +@get_strings_dec("feds") +async def fed_ban_user(message, fed, user, reason, strings): + user_id = user["user_id"] + + # Checks + if user_id in OPERATORS: + await message.reply(strings["user_wl"]) + return + + elif user_id == message.from_user.id: + await message.reply(strings["fban_self"]) + return + + elif user_id == BOT_ID: + await message.reply(strings["fban_self"]) + return + + elif user_id == fed["creator"]: + await message.reply(strings["fban_creator"]) + return + + elif "admins" in fed and user_id in fed["admins"]: + await message.reply(strings["fban_fed_admin"]) + return + + elif data := await db.fed_bans.find_one( + {"fed_id": fed["fed_id"], "user_id": user_id} + ): + if "reason" not in data or data["reason"] != reason: + operation = "$set" if reason else "$unset" + await db.fed_bans.update_one( + {"_id": data["_id"]}, {operation: {"reason": reason}} + ) + return await message.reply(strings["update_fban"].format(reason=reason)) + await message.reply( + strings["already_fbanned"].format(user=await get_user_link(user_id)) + ) + return + + text = strings["fbanned_header"] + text += strings["fban_info"].format( + fed=html.escape(fed["fed_name"], False), + fadmin=await get_user_link(message.from_user.id), + user=await get_user_link(user_id), + user_id=user["user_id"], + ) + if reason: + text += strings["fbanned_reason"].format(reason=reason) + + # fban processing msg + num = len(fed["chats"]) if "chats" in fed else 0 + msg = await message.reply(text + strings["fbanned_process"].format(num=num)) + + user = await db.user_list.find_one({"user_id": user_id}) + + banned_chats = [] + + if "chats" in fed: + for chat_id in fed["chats"]: + # We not found the user or user wasn't detected + if not user or "chats" not in user: + continue + + if chat_id in user["chats"]: + await asyncio.sleep(0) # Do not slow down other updates + if await ban_user(chat_id, user_id): + banned_chats.append(chat_id) + + new = { + "fed_id": fed["fed_id"], + "user_id": user_id, + "banned_chats": banned_chats, + "time": datetime.now(), + "by": message.from_user.id, + } + + if reason: + new["reason"] = reason + + await db.fed_bans.insert_one(new) + + channel_text = strings["fban_log_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), + fed_id=fed["fed_id"], + user=await get_user_link(user_id), + user_id=user_id, + by=await get_user_link(message.from_user.id), + chat_count=len(banned_chats), + all_chats=num, + ) + + if reason: + channel_text += strings["fban_reason_fed_log"].format(reason=reason) + + # Check if silent + silent = False + if get_cmd(message) == "sfban": + silent = True + key = "leave_silent:" + str(message.chat.id) + redis.set(key, user_id) + redis.expire(key, 30) + text += strings["fbanned_silence"] + + # SubsFeds process + if len(sfeds_list := await get_all_subs_feds_r(fed["fed_id"], [])) > 1: + sfeds_list.remove(fed["fed_id"]) + this_fed_banned_count = len(banned_chats) + + await msg.edit_text( + text + strings["fbanned_subs_process"].format(feds=len(sfeds_list)) + ) + + all_banned_chats_count = 0 + for s_fed_id in sfeds_list: + if ( + await db.fed_bans.find_one({"fed_id": s_fed_id, "user_id": user_id}) + is not None + ): + # user is already banned in subscribed federation, skip + continue + s_fed = await get_fed_by_id(s_fed_id) + banned_chats = [] + new = { + "fed_id": s_fed_id, + "user_id": user_id, + "banned_chats": banned_chats, + "time": datetime.now(), + "origin_fed": fed["fed_id"], + "by": message.from_user.id, + } + for chat_id in s_fed["chats"]: + if not user: + continue + + elif chat_id == user["user_id"]: + continue + + elif "chats" not in user: + continue + + elif chat_id not in user["chats"]: + continue + + # Do not slow down other updates + await asyncio.sleep(0.2) + + if await ban_user(chat_id, user_id): + banned_chats.append(chat_id) + all_banned_chats_count += 1 + + if reason: + new["reason"] = reason + + await db.fed_bans.insert_one(new) + + await msg.edit_text( + text + + strings["fbanned_subs_done"].format( + chats=this_fed_banned_count, + subs_chats=all_banned_chats_count, + feds=len(sfeds_list), + ) + ) + + channel_text += strings["fban_subs_fed_log"].format( + subs_chats=all_banned_chats_count, feds=len(sfeds_list) + ) + + else: + await msg.edit_text( + text + strings["fbanned_done"].format(num=len(banned_chats)) + ) + + await fed_post_log(fed, channel_text) + + if silent: + to_del = [msg.message_id, message.message_id] + if ( + "reply_to_message" in message + and message.reply_to_message.from_user.id == user_id + ): + to_del.append(message.reply_to_message.message_id) + await asyncio.sleep(5) + await tbot.delete_messages(message.chat.id, to_del) + + +@decorator.register(cmds=["unfban", "funban"]) +@get_fed_user_text() +@is_fed_admin +@get_strings_dec("feds") +async def unfed_ban_user(message, fed, user, text, strings): + user_id = user["user_id"] + + if user == BOT_ID: + await message.reply(strings["unfban_self"]) + return + + elif not ( + banned := await db.fed_bans.find_one( + {"fed_id": fed["fed_id"], "user_id": user_id} + ) + ): + await message.reply( + strings["user_not_fbanned"].format(user=await get_user_link(user_id)) + ) + return + + text = strings["un_fbanned_header"] + text += strings["fban_info"].format( + fed=html.escape(fed["fed_name"], False), + fadmin=await get_user_link(message.from_user.id), + user=await get_user_link(user["user_id"]), + user_id=user["user_id"], + ) + + banned_chats = [] + if "banned_chats" in banned: + banned_chats = banned["banned_chats"] + + # unfban processing msg + msg = await message.reply( + text + strings["un_fbanned_process"].format(num=len(banned_chats)) + ) + + counter = 0 + for chat_id in banned_chats: + await asyncio.sleep(0) # Do not slow down other updates + if await unban_user(chat_id, user_id): + counter += 1 + + await db.fed_bans.delete_one({"fed_id": fed["fed_id"], "user_id": user_id}) + + channel_text = strings["un_fban_log_fed_log"].format( + fed_name=html.escape(fed["fed_name"], False), + fed_id=fed["fed_id"], + user=await get_user_link(user["user_id"]), + user_id=user["user_id"], + by=await get_user_link(message.from_user.id), + chat_count=len(banned_chats), + all_chats=len(fed["chats"]) if "chats" in fed else 0, + ) + + # Subs feds + if len(sfeds_list := await get_all_subs_feds_r(fed["fed_id"], [])) > 1: + sfeds_list.remove(fed["fed_id"]) + this_fed_unbanned_count = counter + + await msg.edit_text( + text + strings["un_fbanned_subs_process"].format(feds=len(sfeds_list)) + ) + + all_unbanned_chats_count = 0 + for sfed_id in sfeds_list: + # revision 19/10/2020: unfbans only those who got banned by `this` fed + ban = await db.fed_bans.find_one( + {"fed_id": sfed_id, "origin_fed": fed["fed_id"], "user_id": user_id} + ) + if ban is None: + # probably old fban + ban = await db.fed_bans.find_one( + {"fed_id": sfed_id, "user_id": user_id} + ) + # if ban['time'] > `replace here with datetime of release of v2.2`: + # continue + banned_chats = [] + if ban is not None and "banned_chats" in ban: + banned_chats = ban["banned_chats"] + + for chat_id in banned_chats: + await asyncio.sleep(0.2) # Do not slow down other updates + if await unban_user(chat_id, user_id): + all_unbanned_chats_count += 1 + + await db.fed_bans.delete_one( + {"fed_id": sfed_id, "user_id": user_id} + ) + + await msg.edit_text( + text + + strings["un_fbanned_subs_done"].format( + chats=this_fed_unbanned_count, + subs_chats=all_unbanned_chats_count, + feds=len(sfeds_list), + ) + ) + + channel_text += strings["fban_subs_fed_log"].format( + subs_chats=all_unbanned_chats_count, feds=len(sfeds_list) + ) + else: + await msg.edit_text(text + strings["un_fbanned_done"].format(num=counter)) + + await fed_post_log(fed, channel_text) + + +@decorator.register(cmds=["delfed", "fdel"]) +@get_fed_dec +@is_fed_owner +@get_strings_dec("feds") +async def del_fed_cmd(message, fed, strings): + fed_name = html.escape(fed["fed_name"], False) + fed_id = fed["fed_id"] + fed_owner = fed["creator"] + + buttons = InlineKeyboardMarkup() + buttons.add( + InlineKeyboardButton( + text=strings["delfed_btn_yes"], + callback_data=delfed_cb.new(fed_id=fed_id, creator_id=fed_owner), + ) + ) + buttons.add( + InlineKeyboardButton( + text=strings["delfed_btn_no"], callback_data=f"cancel_{fed_owner}" + ) + ) + + await message.reply(strings["delfed"] % fed_name, reply_markup=buttons) + + +@decorator.register(delfed_cb.filter(), f="cb", allow_kwargs=True) +@get_strings_dec("feds") +async def del_fed_func(event, strings, callback_data=None, **kwargs): + fed_id = callback_data["fed_id"] + fed_owner = callback_data["creator_id"] + + if event.from_user.id != int(fed_owner): + return + + await db.feds.delete_one({"fed_id": fed_id}) + await get_fed_by_id.reset_cache(fed_id) + await get_fed_by_creator.reset_cache(int(fed_owner)) + async for subscribed_fed in db.feds.find({"subscribed": fed_id}): + await db.feds.update_one( + {"_id": subscribed_fed["_id"]}, {"$pull": {"subscribed": fed_id}} + ) + await get_fed_by_id.reset_cache(subscribed_fed["fed_id"]) + + # delete all fbans of it + await db.fed_bans.delete_many({"fed_id": fed_id}) + + await event.message.edit_text(strings["delfed_success"]) + + +@decorator.register(regexp="cancel_(.*)", f="cb") +async def cancel(event): + if event.from_user.id != int((re.search(r"cancel_(.*)", event.data)).group(1)): + return + await event.message.delete() + + +@decorator.register(cmds="frename") +@need_args_dec() +@get_fed_dec +@is_fed_owner +@get_strings_dec("feds") +async def fed_rename(message, fed, strings): + # Check whether first arg is fed ID | TODO: Remove this + args = message.get_args().split(" ", 2) + if len(args) > 1 and args[0].count("-") == 4: + new_name = " ".join(args[1:]) + else: + new_name = " ".join(args[0:]) + + if new_name == fed["fed_name"]: + await message.reply(strings["frename_same_name"]) + return + + await db.feds.update_one({"_id": fed["_id"]}, {"$set": {"fed_name": new_name}}) + await get_fed_by_id.reset_cache(fed["fed_id"]) + await message.reply( + strings["frename_success"].format( + old_name=html.escape(fed["fed_name"], False), + new_name=html.escape(new_name, False), + ) + ) + + +@decorator.register(cmds=["fbanlist", "exportfbans", "fexport"]) +@get_fed_dec +@is_fed_admin +@get_strings_dec("feds") +async def fban_export(message, fed, strings): + fed_id = fed["fed_id"] + key = "fbanlist_lock:" + str(fed_id) + if redis.get(key) and message.from_user.id not in OPERATORS: + ttl = format_timedelta( + timedelta(seconds=redis.ttl(key)), strings["language_info"]["babel"] + ) + await message.reply(strings["fbanlist_locked"] % ttl) + return + + redis.set(key, 1) + redis.expire(key, 600) + + msg = await message.reply(strings["creating_fbanlist"]) + fields = ["user_id", "reason", "by", "time", "banned_chats"] + with io.StringIO() as f: + writer = csv.DictWriter(f, fields) + writer.writeheader() + async for banned_data in db.fed_bans.find({"fed_id": fed_id}): + await asyncio.sleep(0) + + data = {"user_id": banned_data["user_id"]} + + if "reason" in banned_data: + data["reason"] = banned_data["reason"] + + if "time" in banned_data: + data["time"] = int(time.mktime(banned_data["time"].timetuple())) + + if "by" in banned_data: + data["by"] = banned_data["by"] + + if "banned_chats" in banned_data: + data["banned_chats"] = banned_data["banned_chats"] + + writer.writerow(data) + + text = strings["fbanlist_done"] % html.escape(fed["fed_name"], False) + f.seek(0) + await message.answer_document(InputFile(f, filename="fban_export.csv"), text) + await msg.delete() + + +@decorator.register(cmds=["importfbans", "fimport"]) +@get_fed_dec +@is_fed_admin +@get_strings_dec("feds") +async def importfbans_cmd(message, fed, strings): + fed_id = fed["fed_id"] + key = "importfbans_lock:" + str(fed_id) + if redis.get(key) and message.from_user.id not in OPERATORS: + ttl = format_timedelta( + timedelta(seconds=redis.ttl(key)), strings["language_info"]["babel"] + ) + await message.reply(strings["importfbans_locked"] % ttl) + return + + redis.set(key, 1) + redis.expire(key, 600) + + if "document" in message: + document = message.document + else: + if "reply_to_message" not in message: + await ImportFbansFileWait.waiting.set() + await message.reply(strings["send_import_file"]) + return + + elif "document" not in message.reply_to_message: + await message.reply(strings["rpl_to_file"]) + return + document = message.reply_to_message.document + + await importfbans_func(message, fed, document=document) + + +@get_strings_dec("feds") +async def importfbans_func(message, fed, strings, document=None): + global user_id + file_type = os.path.splitext(document["file_name"])[1][1:] + + if file_type == "json": + if document["file_size"] > 1000000: + await message.reply(strings["big_file_json"].format(num="1")) + return + elif file_type == "csv": + if document["file_size"] > 52428800: + await message.reply(strings["big_file_csv"].format(num="50")) + return + else: + await message.reply(strings["wrong_file_ext"]) + return + + f = await bot.download_file_by_id(document.file_id, io.BytesIO()) + msg = await message.reply(strings["importing_process"]) + + data = None + if file_type == "json": + try: + data = rapidjson.load(f).items() + except ValueError: + return await message.reply(strings["invalid_file"]) + elif file_type == "csv": + data = csv.DictReader(io.TextIOWrapper(f)) + + real_counter = 0 + + queue_del = [] + queue_insert = [] + current_time = datetime.now() + for row in data: + if file_type == "json": + user_id = row[0] + data = row[1] + elif file_type == "csv": + if "user_id" in row: + user_id = int(row["user_id"]) + elif "id" in row: + user_id = int(row["id"]) + else: + continue + else: + raise NotImplementedError + + new = {"fed_id": fed["fed_id"], "user_id": user_id} + + if "reason" in row: + new["reason"] = row["reason"] + + if "by" in row: + new["by"] = int(row["by"]) + else: + new["by"] = message.from_user.id + + if "time" in row: + new["time"] = datetime.fromtimestamp(int(row["time"])) + else: + new["time"] = current_time + + if "banned_chats" in row and type(row["banned_chats"]) is list: + new["banned_chats"] = row["banned_chats"] + + queue_del.append(DeleteMany({"fed_id": fed["fed_id"], "user_id": user_id})) + queue_insert.append(InsertOne(new)) + + if len(queue_insert) == 1000: + real_counter += len(queue_insert) + + # Make delete operation ordered before inserting. + if queue_del: + await db.fed_bans.bulk_write(queue_del, ordered=False) + await db.fed_bans.bulk_write(queue_insert, ordered=False) + + queue_del = [] + queue_insert = [] + + # Process last bans + real_counter += len(queue_insert) + if queue_del: + await db.fed_bans.bulk_write(queue_del, ordered=False) + if queue_insert: + await db.fed_bans.bulk_write(queue_insert, ordered=False) + + await msg.edit_text(strings["import_done"].format(num=real_counter)) + + +@decorator.register( + state=ImportFbansFileWait.waiting, + content_types=types.ContentTypes.DOCUMENT, + allow_kwargs=True, +) +@get_fed_dec +@is_fed_admin +async def import_state(message, fed, state=None, **kwargs): + await importfbans_func(message, fed, document=message.document) + await state.finish() + + +@decorator.register(only_groups=True) +@chat_connection(only_groups=True) +@get_strings_dec("feds") +async def check_fbanned(message: Message, chat, strings): + if message.sender_chat: + # should be channel/anon + return + + user_id = message.from_user.id + chat_id = chat["chat_id"] + + if not (fed := await get_fed_f(message)): + return + + elif await is_user_admin(chat_id, user_id): + return + + feds_list = [fed["fed_id"]] + + if "subscribed" in fed: + feds_list.extend(fed["subscribed"]) + + if ban := await db.fed_bans.find_one( + {"fed_id": {"$in": feds_list}, "user_id": user_id} + ): + + # check whether banned fed_id is chat's fed id else + # user is banned in sub fed + if fed["fed_id"] == ban["fed_id"] and "origin_fed" not in ban: + text = strings["automatic_ban"].format( + user=await get_user_link(user_id), + fed_name=html.escape(fed["fed_name"], False), + ) + else: + s_fed = await get_fed_by_id( + ban["fed_id"] if "origin_fed" not in ban else ban["origin_fed"] + ) + if s_fed is None: + return + + text = strings["automatic_ban_sfed"].format( + user=await get_user_link(user_id), fed_name=s_fed["fed_name"] + ) + + if "reason" in ban: + text += strings["automatic_ban_reason"].format(text=ban["reason"]) + + if not await ban_user(chat_id, user_id): + return + + await message.reply(text) + + await db.fed_bans.update_one( + {"_id": ban["_id"]}, {"$addToSet": {"banned_chats": chat_id}} + ) + + +@decorator.register(cmds=["fcheck", "fbanstat"]) +@get_fed_user_text(skip_no_fed=True, self=True) +@get_strings_dec("feds") +async def fedban_check(message, fed, user, _, strings): + fbanned_fed = False # A variable to find if user is banned in current fed of chat + fban_data = None + + total_count = await db.fed_bans.count_documents({"user_id": user["user_id"]}) + if fed: + fed_list = [fed["fed_id"]] + # check fbanned in subscribed + if "subscribed" in fed: + fed_list.extend(fed["subscribed"]) + + if fban_data := await db.fed_bans.find_one( + {"user_id": user["user_id"], "fed_id": {"$in": fed_list}} + ): + fbanned_fed = True + + # re-assign fed if user is banned in sub-fed + if fban_data["fed_id"] != fed["fed_id"] or "origin_fed" in fban_data: + fed = await get_fed_by_id( + fban_data[ + "fed_id" if "origin_fed" not in fban_data else "origin_fed" + ] + ) + + # create text + text = strings["fcheck_header"] + if message.chat.type == "private" and message.from_user.id == user["user_id"]: + if bool(fed): + if bool(fban_data): + if "reason" not in fban_data: + text += strings["fban_info:fcheck"].format( + fed=html.escape(fed["fed_name"], False), + date=babel.dates.format_date( + fban_data["time"], + "long", + locale=strings["language_info"]["babel"], + ), + ) + else: + text += strings["fban_info:fcheck:reason"].format( + fed=html.escape(fed["fed_name"], False), + date=babel.dates.format_date( + fban_data["time"], + "long", + locale=strings["language_info"]["babel"], + ), + reason=fban_data["reason"], + ) + else: + return await message.reply(strings["didnt_fbanned"]) + else: + text += strings["fbanned_count_pm"].format(count=total_count) + if total_count > 0: + count = 0 + async for fban in db.fed_bans.find({"user_id": user["user_id"]}): + count += 1 + _fed = await get_fed_by_id(fban["fed_id"]) + if _fed: + fed_name = _fed["fed_name"] + text += f'{count}: {fban["fed_id"]}: {fed_name}\n' + else: + if total_count > 0: + text += strings["fbanned_data"].format( + user=await get_user_link(user["user_id"]), count=total_count + ) + else: + text += strings["fbanned_nowhere"].format( + user=await get_user_link(user["user_id"]) + ) + + if fbanned_fed is True: + if "reason" in fban_data: + text += strings["fbanned_in_fed:reason"].format( + fed=html.escape(fed["fed_name"], False), reason=fban_data["reason"] + ) + else: + text += strings["fbanned_in_fed"].format( + fed=html.escape(fed["fed_name"], False) + ) + elif fed is not None: + text += strings["not_fbanned_in_fed"].format( + fed_name=html.escape(fed["fed_name"], quote=False) + ) + + if total_count > 0: + if message.from_user.id == user["user_id"]: + text += strings["contact_in_pm"] + if len(text) > 4096: + return await message.answer_document( + InputFile(io.StringIO(text), filename="fban_info.txt"), + strings["too_long_fbaninfo"], + reply=message.message_id, + ) + await message.reply(text) + + +@cached() +async def get_fed_by_id(fed_id: str) -> Optional[dict]: + return await db.feds.find_one({"fed_id": fed_id}) + + +@cached() +async def get_fed_by_creator(creator: int) -> Optional[dict]: + return await db.feds.find_one({"creator": creator}) + + +async def __export__(chat_id): + if chat_fed := await db.feds.find_one({"chats": [chat_id]}): + return {"feds": {"fed_id": chat_fed["fed_id"]}} + + +async def __import__(chat_id, data): + if fed_id := data["fed_id"]: + if current_fed := await db.feds.find_one({"chats": [int(chat_id)]}): + await db.feds.update_one( + {"_id": current_fed["_id"]}, {"$pull": {"chats": chat_id}} + ) + await get_fed_by_id.reset_cache(current_fed["fed_id"]) + await db.feds.update_one({"fed_id": fed_id}, {"$addToSet": {"chats": chat_id}}) + await get_fed_by_id.reset_cache(fed_id) + + +__mod_name__ = "Federations" + +__help__ = """ +Well basically there is 2 reasons to use Federations: +1. You have many chats and want to ban users in all of them with 1 command +2. You want to subscribe to any of the antispam Federations to have your chat(s) protected. +In both cases Daisy will help you. +Arguments types help: +(): required argument +(user): required but you can reply on any user's message instead +(file): required file, if file isn't provided you will be entered in file state, this means Daisy will wait file message from you. Type /cancel to leave from it. +(? ): additional argument +Only Federation owner: +- /fnew (name) or /newfed (name): Creates a new Federation +- /frename (?Fed ID) (new name): Renames your federation +- /fdel (?Fed ID) or /delfed (?Fed ID): Removes your Federation +- /fpromote (user) (?Fed ID): Promotes a user to the your Federation +- /fdemote (user) (?Fed ID): Demotes a user from the your Federation +- /fsub (Fed ID): Subscibes your Federation over provided +- /funsub (Fed ID): unsubscibes your Federation from provided +- /fsetlog (? Fed ID) (? chat/channel id) or /setfedlog (? Fed ID) (? chat/channel id): Set's a log chat/channel for your Federation +- /funsetlog (?Fed ID) or /unsetfedlog (?Fed ID): Unsets a Federation log chat\channel +- /fexport (?Fed ID): Exports Federation bans +- /fimport (?Fed ID) (file): Imports Federation bans +Only Chat owner: +- /fjoin (Fed ID) or /joinfed (Fed ID): Joins current chat to provided Federation +- /fleave or /leavefed: Leaves current chat from the fed +Avaible for Federation admins and owners: +- /fchatlist (?Fed ID) or /fchats (?Fed ID): Shows a list of chats in the your Federation list +- /fban (user) (?Fed ID) (?reason): Bans user in the Fed and Feds which subscribed on this Fed +- /sfban (user) (?Fed ID) (?reason): As above, but silently - means the messages about fbanning and replied message (if was provided) will be removed +- /unfban (user) (?Fed ID) (?reason): Unbans a user from a Federation +Avaible for all users: +- /fcheck (?user): Check user's federation ban info +- /finfo (?Fed ID): Info about Federation +""" diff --git a/DaisyX/modules/filters.py b/DaisyX/modules/filters.py new file mode 100644 index 00000000..2d524c1d --- /dev/null +++ b/DaisyX/modules/filters.py @@ -0,0 +1,479 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import functools +import random +import re +from contextlib import suppress +from string import printable + +import regex +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import CallbackQuery, Message +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.callback_data import CallbackData +from aiogram.utils.exceptions import MessageCantBeDeleted, MessageToDeleteNotFound +from async_timeout import timeout +from bson.objectid import ObjectId +from pymongo import UpdateOne + +from DaisyX import bot, loop +from DaisyX.decorator import register +from DaisyX.modules import LOADED_MODULES +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis +from DaisyX.utils.logger import log + +from .utils.connections import chat_connection, get_connected_chat +from .utils.language import get_string, get_strings_dec +from .utils.message import get_args_str, need_args_dec +from .utils.user_details import is_chat_creator, is_user_admin + +filter_action_cp = CallbackData("filter_action_cp", "filter_id") +filter_remove_cp = CallbackData("filter_remove_cp", "id") +filter_delall_yes_cb = CallbackData("filter_delall_yes_cb", "chat_id") + +FILTERS_ACTIONS = {} + + +class NewFilter(StatesGroup): + handler = State() + setup = State() + + +async def update_handlers_cache(chat_id): + redis.delete(f"filters_cache_{chat_id}") + filters = db.filters.find({"chat_id": chat_id}) + handlers = [] + async for filter in filters: + handler = filter["handler"] + if handler in handlers: + continue + + handlers.append(handler) + redis.lpush(f"filters_cache_{chat_id}", handler) + + return handlers + + +@register() +async def check_msg(message): + log.debug("Running check msg for filters function.") + chat = await get_connected_chat(message, only_groups=True) + if "err_msg" in chat or message.chat.type == "private": + return + + chat_id = chat["chat_id"] + if not (filters := redis.lrange(f"filters_cache_{chat_id}", 0, -1)): + filters = await update_handlers_cache(chat_id) + + if len(filters) == 0: + return + + text = message.text + + # Workaround to disable all filters if admin want to remove filter + if await is_user_admin(chat_id, message.from_user.id): + if text[1:].startswith("addfilter") or text[1:].startswith("delfilter"): + return + + for handler in filters: # type: str + if handler.startswith("re:"): + func = functools.partial( + regex.search, handler.replace("re:", "", 1), text, timeout=0.1 + ) + else: + # TODO: Remove this (handler.replace(...)). kept for backward compatibility + func = functools.partial( + re.search, + re.escape(handler).replace("(+)", "(.*)"), + text, + flags=re.IGNORECASE, + ) + + try: + async with timeout(0.1): + matched = await loop.run_in_executor(None, func) + except (asyncio.TimeoutError, TimeoutError): + continue + + if matched: + # We can have few filters with same handler, that's why we create a new loop. + filters = db.filters.find({"chat_id": chat_id, "handler": handler}) + async for filter in filters: + action = filter["action"] + await FILTERS_ACTIONS[action]["handle"](message, chat, filter) + + +@register(cmds=["addfilter", "newfilter"], is_admin=True, user_can_change_info=True) +@need_args_dec() +@chat_connection(only_groups=True, admin=True) +@get_strings_dec("filters") +async def add_handler(message, chat, strings): + # filters doesn't support anon admins + if message.from_user.id == 1087968824: + return await message.reply(strings["anon_detected"]) + # if not await check_admin_rights(message, chat_id, message.from_user.id, ["can_change_info"]): + # return await message.reply("You can't change info of this group") + + handler = get_args_str(message) + + if handler.startswith("re:"): + pattern = handler + random_text_str = "".join(random.choice(printable) for i in range(50)) + try: + regex.match(pattern, random_text_str, timeout=0.2) + except TimeoutError: + await message.reply(strings["regex_too_slow"]) + return + else: + handler = handler.lower() + + text = strings["adding_filter"].format( + handler=handler, chat_name=chat["chat_title"] + ) + + buttons = InlineKeyboardMarkup(row_width=2) + for action in FILTERS_ACTIONS.items(): + filter_id = action[0] + data = action[1] + + buttons.insert( + InlineKeyboardButton( + await get_string( + chat["chat_id"], data["title"]["module"], data["title"]["string"] + ), + callback_data=filter_action_cp.new(filter_id=filter_id), + ) + ) + buttons.add(InlineKeyboardButton(strings["cancel_btn"], callback_data="cancel")) + + user_id = message.from_user.id + chat_id = chat["chat_id"] + redis.set(f"add_filter:{user_id}:{chat_id}", handler) + if handler is not None: + await message.reply(text, reply_markup=buttons) + + +async def save_filter(message, data, strings): + if await db.filters.find_one(data): + # prevent saving duplicate filter + await message.reply("Duplicate filter!") + return + + await db.filters.insert_one(data) + await update_handlers_cache(data["chat_id"]) + await message.reply(strings["saved"]) + + +@register(filter_action_cp.filter(), f="cb", allow_kwargs=True) +@chat_connection(only_groups=True, admin=True) +@get_strings_dec("filters") +async def register_action( + event, chat, strings, callback_data=None, state=None, **kwargs +): + if not await is_user_admin(event.message.chat.id, event.from_user.id): + return await event.answer("You are not admin to do this") + filter_id = callback_data["filter_id"] + action = FILTERS_ACTIONS[filter_id] + + user_id = event.from_user.id + chat_id = chat["chat_id"] + + handler = redis.get(f"add_filter:{user_id}:{chat_id}") + + if not handler: + return await event.answer( + "Something went wrong! Please try again!", show_alert=True + ) + + data = {"chat_id": chat_id, "handler": handler, "action": filter_id} + + if "setup" in action: + await NewFilter.setup.set() + setup_co = len(action["setup"]) - 1 if type(action["setup"]) is list else 0 + async with state.proxy() as proxy: + proxy["data"] = data + proxy["filter_id"] = filter_id + proxy["setup_co"] = setup_co + proxy["setup_done"] = 0 + proxy["msg_id"] = event.message.message_id + + if setup_co > 0: + await action["setup"][0]["start"](event.message) + else: + await action["setup"]["start"](event.message) + return + + await save_filter(event.message, data, strings) + + +@register(state=NewFilter.setup, f="any", is_admin=True, allow_kwargs=True) +@chat_connection(only_groups=True, admin=True) +@get_strings_dec("filters") +async def setup_end(message, chat, strings, state=None, **kwargs): + async with state.proxy() as proxy: + data = proxy["data"] + filter_id = proxy["filter_id"] + setup_co = proxy["setup_co"] + curr_step = proxy["setup_done"] + with suppress(MessageCantBeDeleted, MessageToDeleteNotFound): + await bot.delete_message(message.chat.id, proxy["msg_id"]) + + action = FILTERS_ACTIONS[filter_id] + + func = ( + action["setup"][curr_step]["finish"] + if type(action["setup"]) is list + else action["setup"]["finish"] + ) + if not bool(a := await func(message, data)): + await state.finish() + return + + data.update(a) + + if setup_co > 0: + await action["setup"][curr_step + 1]["start"](message) + async with state.proxy() as proxy: + proxy["data"] = data + proxy["setup_co"] -= 1 + proxy["setup_done"] += 1 + return + + await state.finish() + await save_filter(message, data, strings) + + +@register(cmds=["filters", "listfilters"]) +@chat_connection(only_groups=True) +@get_strings_dec("filters") +async def list_filters(message, chat, strings): + text = strings["list_filters"].format(chat_name=chat["chat_title"]) + + filters = db.filters.find({"chat_id": chat["chat_id"]}) + filters_text = "" + async for filter in filters: + filters_text += f"- {filter['handler']}: {filter['action']}\n" + + if not filters_text: + await message.reply( + strings["no_filters_found"].format(chat_name=chat["chat_title"]) + ) + return + + await message.reply(text + filters_text) + + +@register(cmds="delfilter", is_admin=True, user_can_change_info=True) +@need_args_dec() +@chat_connection(only_groups=True, admin=True) +@get_strings_dec("filters") +async def del_filter(message, chat, strings): + handler = get_args_str(message) + chat_id = chat["chat_id"] + filters = await db.filters.find({"chat_id": chat_id, "handler": handler}).to_list( + 9999 + ) + if not filters: + await message.reply( + strings["no_such_filter"].format(chat_name=chat["chat_title"]) + ) + return + + # Remove filter in case if we found only 1 filter with same header + filter = filters[0] + if len(filters) == 1: + await db.filters.delete_one({"_id": filter["_id"]}) + await update_handlers_cache(chat_id) + await message.reply(strings["del_filter"].format(handler=filter["handler"])) + return + + # Build keyboard row for select which exactly filter user want to remove + buttons = InlineKeyboardMarkup(row_width=1) + text = strings["select_filter_to_remove"].format(handler=handler) + for filter in filters: + action = FILTERS_ACTIONS[filter["action"]] + buttons.add( + InlineKeyboardButton( + # If module's filter support custom del btn names else just show action name + "" + action["del_btn_name"](message, filter) + if "del_btn_name" in action + else filter["action"], + callback_data=filter_remove_cp.new(id=str(filter["_id"])), + ) + ) + + await message.reply(text, reply_markup=buttons) + + +@register(filter_remove_cp.filter(), f="cb", allow_kwargs=True) +@chat_connection(only_groups=True, admin=True) +@get_strings_dec("filters") +async def del_filter_cb(event, chat, strings, callback_data=None, **kwargs): + if not await is_user_admin(event.message.chat.id, event.from_user.id): + return await event.answer("You are not admin to do this") + filter_id = ObjectId(callback_data["id"]) + filter = await db.filters.find_one({"_id": filter_id}) + await db.filters.delete_one({"_id": filter_id}) + await update_handlers_cache(chat["chat_id"]) + await event.message.edit_text( + strings["del_filter"].format(handler=filter["handler"]) + ) + return + + +@register(cmds=["delfilters", "delallfilters"]) +@get_strings_dec("filters") +async def delall_filters(message: Message, strings: dict): + if not await is_chat_creator(message, message.chat.id, message.from_user.id): + return await message.reply(strings["not_chat_creator"]) + buttons = InlineKeyboardMarkup() + buttons.add( + *[ + InlineKeyboardButton( + strings["confirm_yes"], + callback_data=filter_delall_yes_cb.new(chat_id=message.chat.id), + ), + InlineKeyboardButton( + strings["confirm_no"], callback_data="filter_delall_no_cb" + ), + ] + ) + return await message.reply(strings["delall_header"], reply_markup=buttons) + + +@register(filter_delall_yes_cb.filter(), f="cb", allow_kwargs=True) +@get_strings_dec("filters") +async def delall_filters_yes( + event: CallbackQuery, strings: dict, callback_data: dict, **_ +): + if not await is_chat_creator( + event, chat_id := int(callback_data["chat_id"]), event.from_user.id + ): + return False + result = await db.filters.delete_many({"chat_id": chat_id}) + await update_handlers_cache(chat_id) + return await event.message.edit_text( + strings["delall_success"].format(count=result.deleted_count) + ) + + +@register(regexp="filter_delall_no_cb", f="cb") +@get_strings_dec("filters") +async def delall_filters_no(event: CallbackQuery, strings: dict): + if not await is_chat_creator(event, event.message.chat.id, event.from_user.id): + return False + await event.message.delete() + + +async def __before_serving__(loop): + log.debug("Adding filters actions") + for module in LOADED_MODULES: + if not getattr(module, "__filters__", None): + continue + + module_name = module.__name__.split(".")[-1] + log.debug(f"Adding filter action from {module_name} module") + for data in module.__filters__.items(): + FILTERS_ACTIONS[data[0]] = data[1] + + +async def __export__(chat_id): + data = [] + filters = db.filters.find({"chat_id": chat_id}) + async for filter in filters: + del filter["_id"], filter["chat_id"] + if "time" in filter: + filter["time"] = str(filter["time"]) + data.append(filter) + + return {"filters": data} + + +async def __import__(chat_id, data): + new = [] + for filter in data: + new.append( + UpdateOne( + { + "chat_id": chat_id, + "handler": filter["handler"], + "action": filter["action"], + }, + {"$set": filter}, + upsert=True, + ) + ) + await db.filters.bulk_write(new) + await update_handlers_cache(chat_id) + + +__mod_name__ = "Filters" + +__help__ = """ + GENERAL FILTERS +Filter module is great for everything! filter in here is used to filter words or sentences in your chat - send notes, warn, ban those! + General (Admins): +- /addfilter (word/sentence): This is used to add filters. +- /delfilter (word/sentence): Use this command to remove a specific filter. +- /delallfilters: As in command this is used to remove all filters of group. + + As of now, there is 6 actions that you can do: +- Send a note +- Warn the user +- Ban the user +- Mute the user +- tBan the user +- tMute the user + + A filter can support multiple actions ! + +Ah if you don't understand what this actions are for? Actions says bot what to do when the given word/sentence is triggered. +You can also use regex and buttons for filters. Check /buttonshelp to know more. + + Available for all users: +- /filters or /listfilters + +You want to know all filter of your chat/ chat you joined? Use this command. It will list all filters along with specified actions ! + + TEXT FILTERS +Text filters are for short and text replies + Commands available +- /filter [KEYWORD] [REPLY TO MESSAGE] : Filters the replied message with given keyword. +- /stop [KEYWORD] : Stops the given filter. + + + Difference between text filter and filter +* If you filtered word "hi" with /addfilter it filters all words including hi. + Future explained: + - When a filter added to hi as "hello" when user sent a message like "It was a hit" bot replies as "Hello" as word contain hi + ** You can use regex to remove this if you like + Text filters won't reply like that. It only replies if word = "hi" (According to example taken) +Text filters can filter +- A single word +- A sentence +- A sticker + + CLASSIC FILTERS +Classic filters are just like marie's filter system. If you still like that kind of filter system. Use /cfilterhelp to know more + +⚠️ READ FROM TOP +""" diff --git a/DaisyX/modules/gban.py b/DaisyX/modules/gban.py new file mode 100644 index 00000000..6cb3a60b --- /dev/null +++ b/DaisyX/modules/gban.py @@ -0,0 +1,215 @@ +from DaisyX import SUDO_USERS, tbot, OWNER_ID +from telethon.tl.types import ChatBannedRights +from telethon import events +from telethon.tl.functions.channels import EditBannedRequest +from pymongo import MongoClient +import asyncio +import os +BANNED_RIGHTS = ChatBannedRights( + until_date=None, + view_messages=True, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True, +) + + + +MONGO_DB_URI = os.environ.get("MONGO_DB_URI") +sed = os.environ.get("GBAN_LOGS") + +def get_reason(id): + return gbanned.find_one({"user": id}) + +client = MongoClient() +client = MongoClient(MONGO_DB_URI) +db = client["daisyx"] +gbanned = db.gban + +edit_time = 3 +@tbot.on(events.NewMessage(pattern="^/gban (.*)")) +async def _(event): + if event.fwd_from: + return + if event.sender_id in SUDO_USERS: + pass + elif event.sender_id == OWNER_ID: + pass + else: + return + + quew = event.pattern_match.group(1) + sun = "None" + if "|" in quew: + iid, reasonn = quew.split("|") + cid = iid.strip() + reason = reasonn.strip() + elif "|" not in quew: + cid = quew + reason = sun + if cid.isnumeric(): + cid = int(cid) + entity = await tbot.get_input_entity(cid) + try: + r_sender_id = entity.user_id + except Exception: + await event.reply("Couldn't fetch that user.") + return + if not reason: + await event.reply("Need a reason for gban.") + return + chats = gbanned.find({}) + + if r_sender_id == OWNER_ID: + await event.reply("Fool, how can I gban my master ?") + return + if r_sender_id in SUDO_USERS: + await event.reply("Hey that's a sudo user idiot.") + return + + for c in chats: + if r_sender_id == c["user"]: + to_check = get_reason(id=r_sender_id) + gbanned.update_one( + { + "_id": to_check["_id"], + "bannerid": to_check["bannerid"], + "user": to_check["user"], + "reason": to_check["reason"], + }, + {"$set": {"reason": reason, "bannerid": event.sender_id}}, + ) + await event.reply( + "This user is already gbanned, I am updating the reason of the gban with your reason." + ) + await event.client.send_message( + sed, + "**GLOBAL BAN UPDATE**\n\n**PERMALINK:** [user](tg://user?id={})\n**UPDATER:** `{}`**\nREASON:** `{}`".format( + r_sender_id, event.sender_id, reason + ), + ) + return + + gbanned.insert_one( + {"bannerid": event.sender_id, "user": r_sender_id, "reason": reason} + ) + k = await event.reply("Initiating Gban.") + await asyncio.sleep(edit_time) + await k.edit("Gbanned Successfully !") + await event.client.send_message( + GBAN_LOGS, + "**NEW GLOBAL BAN**\n\n**PERMALINK:** [user](tg://user?id={})\n**BANNER:** `{}`\n**REASON:** `{}`".format( + r_sender_id, event.sender_id, reason + ), + ) + + +@tbot.on(events.NewMessage(pattern="^/ungban (.*)")) +async def _(event): + if event.fwd_from: + return + if event.sender_id in SUDO_USERS: + pass + elif event.sender_id == OWNER_ID: + pass + else: + return + + quew = event.pattern_match.group(1) + + if "|" in quew: + iid, reasonn = quew.split("|") + cid = iid.strip() + reason = reasonn.strip() + if cid.isnumeric(): + cid = int(cid) + entity = await tbot.get_input_entity(cid) + try: + r_sender_id = entity.user_id + except Exception: + await event.reply("Couldn't fetch that user.") + return + if not reason: + await event.reply("Need a reason for ungban.") + return + chats = gbanned.find({}) + + if r_sender_id == OWNER_ID: + await event.reply("Fool, how can I ungban my master ?") + return + if r_sender_id in SUDO_USERS: + await event.reply("Hey that's a sudo user idiot.") + return + + for c in chats: + if r_sender_id == c["user"]: + to_check = get_reason(id=r_sender_id) + gbanned.delete_one({"user": r_sender_id}) + h = await event.reply("Initiating Ungban") + await asyncio.sleep(edit_time) + await h.edit("Ungbanned Successfully !") + await event.client.send_message( + GBAN_LOGS, + "**REMOVAL OF GLOBAL BAN**\n\n**PERMALINK:** [user](tg://user?id={})\n**REMOVER:** `{}`\n**REASON:** `{}`".format( + r_sender_id, event.sender_id, reason + ), + ) + return + await event.reply("Is that user even gbanned ?") + + +@tbot.on(events.ChatAction()) +async def join_ban(event): + if event.chat_id == int(sed): + return + if event.chat_id == int(sed): + return + pass + user = event.user_id + chats = gbanned.find({}) + for c in chats: + if user == c["user"]: + if event.user_joined: + try: + to_check = get_reason(id=user) + reason = to_check["reason"] + bannerid = to_check["bannerid"] + await tbot(EditBannedRequest(event.chat_id, user, BANNED_RIGHTS)) + await event.reply( + "This user is gbanned and has been removed !\n\n**Gbanned By**: `{}`\n**Reason**: `{}`".format( + bannerid, reason + ) + ) + except Exception as e: + print(e) + return + + +@tbot.on(events.NewMessage(pattern=None)) +async def type_ban(event): + if event.chat_id == int(sed): + return + if event.chat_id == int(sed): + return + pass + chats = gbanned.find({}) + for c in chats: + if event.sender_id == c["user"]: + try: + to_check = get_reason(id=event.sender_id) + reason = to_check["reason"] + bannerid = to_check["bannerid"] + await tbot( + EditBannedRequest(event.chat_id, event.sender_id, BANNED_RIGHTS) + ) + await event.reply( + "This user is gbanned and has been removed !\n\n**Gbanned By**: `{}`\n**Reason**: `{}`".format( + bannerid, reason + ) + ) + except Exception: + return diff --git a/DaisyX/modules/gogoanime.py b/DaisyX/modules/gogoanime.py new file mode 100644 index 00000000..cf7fb477 --- /dev/null +++ b/DaisyX/modules/gogoanime.py @@ -0,0 +1,111 @@ +""" Anime Download Command + Repo - https://github.com/Red-Aura/WatchAnimeBot + Credits - @cosmicauracommunity +""" + +from gogoanimeapi import gogoanime as anime +from telethon import Button, events + +from DaisyX.services.telethon import tbot as GogoAnime + + +@GogoAnime.on(events.NewMessage(pattern="^/gogo ?(.*)")) +async def gogo(event): + args = event.pattern_match.group(1) + if not args: + return await event.respond( + "Your Query should be in This format: /search Name of the Anime you want to Search." + ) + result = anime.get_search_results(args) + buttons = [] + for i in result: + k = [ + Button.inline("{}".format(i["name"]), data="search_{}".format(i["animeid"])) + ] + buttons.append(k) + if len(buttons) == 99: + break + await event.reply("search", buttons=buttons) + + +@GogoAnime.on(events.CallbackQuery(pattern="search(\_(.*))")) +async def search(event): + tata = event.pattern_match.group(1) + data = tata.decode() + input = data.split("_", 1)[1] + animeid = input + await event.answer("Fetching Anime Details") + result = anime.get_anime_details(animeid) + episodes = result["episodes"] + nfo = f"{animeid}?{episodes}" + buttons = Button.inline("Download", data="episode_{}".format(nfo)) + text = """ +{} (Released: {}) + +Type: {} + +Status: {} + +Generies: {} + +Episodes: {} + +Summary: {} +""" + await event.edit( + text.format( + result["title"], + result["year"], + result["type"], + result["status"], + result["genre"], + result["episodes"], + result["plot_summary"], + ), + buttons=buttons, + ) + + +@GogoAnime.on(events.CallbackQuery(pattern="episode(\_(.*))")) +async def episode(event): + tata = event.pattern_match.group(1) + data = tata.decode() + input = data.split("_", 1)[1] + animeid, episodes = input.split("?", 1) + animeid = animeid.strip() + epsd = int(episodes.strip()) + buttons = [] + cbutton = [] + for i in range(epsd): + nfo = f"{i}?{animeid}" + button = Button.inline(f"{i}", data="download_{}".format(nfo)) + buttons.append(button) + if len(buttons) == 4: + cbutton.append(buttons) + buttons = [] + text = f"You selected {animeid},\n\nSelect the Episode you want :-" + await event.edit(text, buttons=cbutton) + + +@GogoAnime.on(events.CallbackQuery(pattern="download(\_(.*))")) +async def episode(event): + tata = event.pattern_match.group(1) + data = tata.decode() + input = data.split("_", 1)[1] + imd, episode = input.split("?", 1) + animeid = episode.strip() + epsd = imd.strip() + result = anime.get_episodes_link(animeid, epsd) + text = "You are watching Episode {} of {}:\n\nNote: Select HDP link for faster streaming.".format( + epsd, animeid + ) + butons = [] + cbutton = [] + for i in result: + if not i == "title": + k = Button.url(f"{i}", f"{result[i]}") + butons.append(k) + if len(butons) == 1: + cbutton.append(butons) + butons = [] + await event.edit(text, buttons=cbutton) diff --git a/DaisyX/modules/gps.py b/DaisyX/modules/gps.py new file mode 100644 index 00000000..2e6cfb46 --- /dev/null +++ b/DaisyX/modules/gps.py @@ -0,0 +1,84 @@ +# Copyright (C) 2021 TeamDaisyX + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from geopy.geocoders import Nominatim +from telethon import * +from telethon.tl import * + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as client + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +GMAPS_LOC = "https://maps.googleapis.com/maps/api/geocode/json" + + +@register(pattern="^/gps (.*)") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply( + "You are not Admin. So, You can't use this. Try in my inbox" + ) + return + + args = event.pattern_match.group(1) + + try: + geolocator = Nominatim(user_agent="SkittBot") + location = args + geoloc = geolocator.geocode(location) + longitude = geoloc.longitude + latitude = geoloc.latitude + gm = "https://www.google.com/maps/search/{},{}".format(latitude, longitude) + await client.send_file( + event.chat_id, + file=types.InputMediaGeoPoint( + types.InputGeoPoint(float(latitude), float(longitude)) + ), + ) + await event.reply( + "Open with: [Google Maps]({})".format(gm), + link_preview=False, + ) + except Exception as e: + print(e) + await event.reply("I can't find that") diff --git a/DaisyX/modules/greetings.py b/DaisyX/modules/greetings.py new file mode 100644 index 00000000..0b41a087 --- /dev/null +++ b/DaisyX/modules/greetings.py @@ -0,0 +1,1139 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import io +import random +import re +from contextlib import suppress +from datetime import datetime +from typing import Optional, Union + +from aiogram.dispatcher.filters.builtin import CommandStart +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import CallbackQuery, ContentType, Message +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.types.input_media import InputMediaPhoto +from aiogram.utils.callback_data import CallbackData +from aiogram.utils.exceptions import ( + BadRequest, + ChatAdminRequired, + MessageCantBeDeleted, + MessageToDeleteNotFound, +) +from apscheduler.jobstores.base import JobLookupError +from babel.dates import format_timedelta +from captcha.image import ImageCaptcha +from telethon.tl.custom import Button + +from DaisyX import BOT_ID, BOT_USERNAME, bot, dp +from DaisyX.config import get_str_key +from DaisyX.decorator import register +from DaisyX.services.apscheduller import scheduler +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis +from DaisyX.services.telethon import tbot +from DaisyX.stuff.fonts import ALL_FONTS + +from ..utils.cached import cached +from .utils.connections import chat_connection +from .utils.language import get_strings_dec +from .utils.message import convert_time, need_args_dec +from .utils.notes import get_parsed_note_list, send_note, t_unparse_note_item +from .utils.restrictions import kick_user, mute_user, restrict_user, unmute_user +from .utils.user_details import check_admin_rights, get_user_link, is_user_admin + + +class WelcomeSecurityState(StatesGroup): + button = State() + captcha = State() + math = State() + + +@register(cmds="welcome") +@chat_connection(only_groups=True) +@get_strings_dec("greetings") +async def welcome(message, chat, strings): + chat_id = chat["chat_id"] + send_id = message.chat.id + + if len(args := message.get_args().split()) > 0: + no_format = True if "no_format" == args[0] or "raw" == args[0] else False + else: + no_format = None + + if not (db_item := await get_greetings_data(chat_id)): + db_item = {} + if "note" not in db_item: + db_item["note"] = {"text": strings["default_welcome"]} + + if no_format: + await message.reply(strings["raw_wlcm_note"]) + text, kwargs = await t_unparse_note_item( + message, db_item["note"], chat_id, noformat=True + ) + await send_note(send_id, text, **kwargs) + return + + text = strings["welcome_info"] + + text = text.format( + chat_name=chat["chat_title"], + welcomes_status=strings["disabled"] + if "welcome_disabled" in db_item and db_item["welcome_disabled"] is True + else strings["enabled"], + wlcm_security=strings["disabled"] + if "welcome_security" not in db_item + or db_item["welcome_security"]["enabled"] is False + else strings["wlcm_security_enabled"].format( + level=db_item["welcome_security"]["level"] + ), + wlcm_mutes=strings["disabled"] + if "welcome_mute" not in db_item or db_item["welcome_mute"]["enabled"] is False + else strings["wlcm_mutes_enabled"].format(time=db_item["welcome_mute"]["time"]), + clean_welcomes=strings["enabled"] + if "clean_welcome" in db_item and db_item["clean_welcome"]["enabled"] is True + else strings["disabled"], + clean_service=strings["enabled"] + if "clean_service" in db_item and db_item["clean_service"]["enabled"] is True + else strings["disabled"], + ) + if "welcome_disabled" not in db_item: + text += strings["wlcm_note"] + await message.reply(text) + text, kwargs = await t_unparse_note_item(message, db_item["note"], chat_id) + await send_note(send_id, text, **kwargs) + else: + await message.reply(text) + + if "welcome_security" in db_item: + if "security_note" not in db_item: + db_item["security_note"] = {"text": strings["default_security_note"]} + await message.reply(strings["security_note"]) + text, kwargs = await t_unparse_note_item( + message, db_item["security_note"], chat_id + ) + await send_note(send_id, text, **kwargs) + + +@register(cmds=["setwelcome", "savewelcome"], user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def set_welcome(message, chat, strings): + chat_id = chat["chat_id"] + + if len(args := message.get_args().lower().split()) < 1: + db_item = await get_greetings_data(chat_id) + + if ( + db_item + and "welcome_disabled" in db_item + and db_item["welcome_disabled"] is True + ): + status = strings["disabled"] + else: + status = strings["enabled"] + + await message.reply( + strings["turnwelcome_status"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + return + + no = ["no", "off", "0", "false", "disable"] + + if args[0] in no: + await db.greetings.update_one( + {"chat_id": chat_id}, + {"$set": {"chat_id": chat_id, "welcome_disabled": True}}, + upsert=True, + ) + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["turnwelcome_disabled"] % chat["chat_title"]) + return + else: + note = await get_parsed_note_list(message, split_args=-1) + + if ( + await db.greetings.update_one( + {"chat_id": chat_id}, + { + "$set": {"chat_id": chat_id, "note": note}, + "$unset": {"welcome_disabled": 1}, + }, + upsert=True, + ) + ).modified_count > 0: + text = strings["updated"] + else: + text = strings["saved"] + + await get_greetings_data.reset_cache(chat_id) + await message.reply(text % chat["chat_title"]) + + +@register(cmds="resetwelcome", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def reset_welcome(message, chat, strings): + chat_id = chat["chat_id"] + + if (await db.greetings.delete_one({"chat_id": chat_id})).deleted_count < 1: + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["not_found"]) + return + + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["deleted"].format(chat=chat["chat_title"])) + + +@register(cmds="cleanwelcome", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def clean_welcome(message, chat, strings): + chat_id = chat["chat_id"] + + if len(args := message.get_args().lower().split()) < 1: + db_item = await get_greetings_data(chat_id) + + if ( + db_item + and "clean_welcome" in db_item + and db_item["clean_welcome"]["enabled"] is True + ): + status = strings["enabled"] + else: + status = strings["disabled"] + + await message.reply( + strings["cleanwelcome_status"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + return + + yes = ["yes", "on", "1", "true", "enable"] + no = ["no", "off", "0", "false", "disable"] + + if args[0] in yes: + await db.greetings.update_one( + {"chat_id": chat_id}, + {"$set": {"chat_id": chat_id, "clean_welcome": {"enabled": True}}}, + upsert=True, + ) + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["cleanwelcome_enabled"] % chat["chat_title"]) + elif args[0] in no: + await db.greetings.update_one( + {"chat_id": chat_id}, {"$unset": {"clean_welcome": 1}}, upsert=True + ) + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["cleanwelcome_disabled"] % chat["chat_title"]) + else: + await message.reply(strings["bool_invalid_arg"]) + + +@register(cmds="cleanservice", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def clean_service(message, chat, strings): + chat_id = chat["chat_id"] + + if len(args := message.get_args().lower().split()) < 1: + db_item = await get_greetings_data(chat_id) + + if ( + db_item + and "clean_service" in db_item + and db_item["clean_service"]["enabled"] is True + ): + status = strings["enabled"] + else: + status = strings["disabled"] + + await message.reply( + strings["cleanservice_status"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + return + + yes = ["yes", "on", "1", "true", "enable"] + no = ["no", "off", "0", "false", "disable"] + + if args[0] in yes: + await db.greetings.update_one( + {"chat_id": chat_id}, + {"$set": {"chat_id": chat_id, "clean_service": {"enabled": True}}}, + upsert=True, + ) + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["cleanservice_enabled"] % chat["chat_title"]) + elif args[0] in no: + await db.greetings.update_one( + {"chat_id": chat_id}, {"$unset": {"clean_service": 1}}, upsert=True + ) + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["cleanservice_disabled"] % chat["chat_title"]) + else: + await message.reply(strings["bool_invalid_arg"]) + + +@register(cmds="welcomemute", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def welcome_mute(message, chat, strings): + chat_id = chat["chat_id"] + + if len(args := message.get_args().lower().split()) < 1: + db_item = await get_greetings_data(chat_id) + + if ( + db_item + and "welcome_mute" in db_item + and db_item["welcome_mute"]["enabled"] is True + ): + status = strings["enabled"] + else: + status = strings["disabled"] + + await message.reply( + strings["welcomemute_status"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + return + + no = ["no", "off", "0", "false", "disable"] + + if args[0].endswith(("m", "h", "d")): + await db.greetings.update_one( + {"chat_id": chat_id}, + { + "$set": { + "chat_id": chat_id, + "welcome_mute": {"enabled": True, "time": args[0]}, + } + }, + upsert=True, + ) + await get_greetings_data.reset_cache(chat_id) + text = strings["welcomemute_enabled"] % chat["chat_title"] + try: + await message.reply(text) + except BadRequest: + await message.answer(text) + elif args[0] in no: + text = strings["welcomemute_disabled"] % chat["chat_title"] + await db.greetings.update_one( + {"chat_id": chat_id}, {"$unset": {"welcome_mute": 1}}, upsert=True + ) + await get_greetings_data.reset_cache(chat_id) + try: + await message.reply(text) + except BadRequest: + await message.answer(text) + else: + text = strings["welcomemute_invalid_arg"] + try: + await message.reply(text) + except BadRequest: + await message.answer(text) + + +# Welcome Security + +wlcm_sec_config_proc = CallbackData("wlcm_sec_proc", "chat_id", "user_id", "level") +wlcm_sec_config_cancel = CallbackData("wlcm_sec_cancel", "user_id", "level") + + +class WelcomeSecurityConf(StatesGroup): + send_time = State() + + +@register(cmds="welcomesecurity", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def welcome_security(message, chat, strings): + chat_id = chat["chat_id"] + + if len(args := message.get_args().lower().split()) < 1: + db_item = await get_greetings_data(chat_id) + + if ( + db_item + and "welcome_security" in db_item + and db_item["welcome_security"]["enabled"] is True + ): + status = strings["welcomesecurity_enabled_word"].format( + level=db_item["welcome_security"]["level"] + ) + else: + status = strings["disabled"] + + await message.reply( + strings["welcomesecurity_status"].format( + status=status, chat_name=chat["chat_title"] + ) + ) + return + + no = ["no", "off", "0", "false", "disable"] + + if args[0].lower() in ["button", "math", "captcha"]: + level = args[0].lower() + elif args[0] in no: + await db.greetings.update_one( + {"chat_id": chat_id}, {"$unset": {"welcome_security": 1}}, upsert=True + ) + await get_greetings_data.reset_cache(chat_id) + await message.reply(strings["welcomesecurity_disabled"] % chat["chat_title"]) + return + else: + await message.reply(strings["welcomesecurity_invalid_arg"]) + return + + await db.greetings.update_one( + {"chat_id": chat_id}, + { + "$set": { + "chat_id": chat_id, + "welcome_security": {"enabled": True, "level": level}, + } + }, + upsert=True, + ) + await get_greetings_data.reset_cache(chat_id) + buttons = InlineKeyboardMarkup() + buttons.add( + InlineKeyboardButton( + strings["no_btn"], + callback_data=wlcm_sec_config_cancel.new( + user_id=message.from_user.id, level=level + ), + ), + InlineKeyboardButton( + strings["yes_btn"], + callback_data=wlcm_sec_config_proc.new( + chat_id=chat_id, user_id=message.from_user.id, level=level + ), + ), + ) + await message.reply( + strings["ask_for_time_customization"].format( + time=format_timedelta( + convert_time(get_str_key("JOIN_CONFIRM_DURATION")), + locale=strings["language_info"]["babel"], + ) + ), + reply_markup=buttons, + ) + + +@register(wlcm_sec_config_cancel.filter(), f="cb", allow_kwargs=True) +@chat_connection(admin=True) +@get_strings_dec("greetings") +async def welcome_security_config_cancel( + event: CallbackQuery, chat: dict, strings: dict, callback_data: dict, **_ +): + if int(callback_data["user_id"]) == event.from_user.id and is_user_admin( + chat["chat_id"], event.from_user.id + ): + await event.message.edit_text( + text=strings["welcomesecurity_enabled"].format( + chat_name=chat["chat_title"], level=callback_data["level"] + ) + ) + + +@register(wlcm_sec_config_proc.filter(), f="cb", allow_kwargs=True) +@chat_connection(admin=True) +@get_strings_dec("greetings") +async def welcome_security_config_proc( + event: CallbackQuery, chat: dict, strings: dict, callback_data: dict, **_ +): + if int(callback_data["user_id"]) != event.from_user.id and is_user_admin( + chat["chat_id"], event.from_user.id + ): + return + + await WelcomeSecurityConf.send_time.set() + async with dp.get_current().current_state().proxy() as data: + data["level"] = callback_data["level"] + await event.message.edit_text(strings["send_time"]) + + +@register( + state=WelcomeSecurityConf.send_time, + content_types=ContentType.TEXT, + allow_kwargs=True, +) +@chat_connection(admin=True) +@get_strings_dec("greetings") +async def wlcm_sec_time_state(message: Message, chat: dict, strings: dict, state, **_): + async with state.proxy() as data: + level = data["level"] + try: + con_time = convert_time(message.text) + except (ValueError, TypeError): + await message.reply(strings["invalid_time"]) + else: + await db.greetings.update_one( + {"chat_id": chat["chat_id"]}, + {"$set": {"welcome_security.expire": message.text}}, + ) + await get_greetings_data.reset_cache(chat["chat_id"]) + await message.reply( + strings["welcomesecurity_enabled:customized_time"].format( + chat_name=chat["chat_title"], + level=level, + time=format_timedelta( + con_time, locale=strings["language_info"]["babel"] + ), + ) + ) + finally: + await state.finish() + + +@register(cmds=["setsecuritynote", "sevesecuritynote"], user_admin=True) +@need_args_dec() +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def set_security_note(message, chat, strings): + chat_id = chat["chat_id"] + + if message.get_args().lower().split()[0] in ["raw", "noformat"]: + db_item = await get_greetings_data(chat_id) + if "security_note" not in db_item: + db_item = {"security_note": {}} + db_item["security_note"]["text"] = strings["default_security_note"] + db_item["security_note"]["parse_mode"] = "md" + + text, kwargs = await t_unparse_note_item( + message, db_item["security_note"], chat_id, noformat=True + ) + kwargs["reply_to"] = message.message_id + + await send_note(chat_id, text, **kwargs) + return + + note = await get_parsed_note_list(message, split_args=-1) + + if ( + await db.greetings.update_one( + {"chat_id": chat_id}, + {"$set": {"chat_id": chat_id, "security_note": note}}, + upsert=True, + ) + ).modified_count > 0: + await get_greetings_data.reset_cache(chat_id) + text = strings["security_note_updated"] + else: + text = strings["security_note_saved"] + + await message.reply(text % chat["chat_title"]) + + +@register(cmds="delsecuritynote", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("greetings") +async def reset_security_note(message, chat, strings): + chat_id = chat["chat_id"] + + if ( + await db.greetings.update_one( + {"chat_id": chat_id}, {"$unset": {"security_note": 1}}, upsert=True + ) + ).modified_count > 0: + await get_greetings_data.reset_cache(chat_id) + text = strings["security_note_updated"] + else: + text = strings["del_security_note_ok"] + + await message.reply(text % chat["chat_title"]) + + +@register(only_groups=True, f="welcome") +@get_strings_dec("greetings") +async def welcome_security_handler(message: Message, strings): + if len(message.new_chat_members) > 1: + # FIXME: AllMightRobot doesnt support adding multiple users currently + return + + new_user = message.new_chat_members[0] + chat_id = message.chat.id + user_id = new_user.id + + if user_id == BOT_ID: + return + + db_item = await get_greetings_data(message.chat.id) + if not db_item or "welcome_security" not in db_item: + return + + if not await check_admin_rights(message, chat_id, BOT_ID, ["can_restrict_members"]): + await message.reply(strings["not_admin_ws"]) + return + + user = await message.chat.get_member(user_id) + # Check if user was muted before + if user["status"] == "restricted": + if user["can_send_messages"] is False: + return + + # Check on OPs and chat owner + if await is_user_admin(chat_id, user_id): + return + + # check if user added is a bot + if new_user.is_bot and await is_user_admin(chat_id, message.from_user.id): + return + + if "security_note" not in db_item: + db_item["security_note"] = {} + db_item["security_note"]["text"] = strings["default_security_note"] + db_item["security_note"]["parse_mode"] = "md" + + text, kwargs = await t_unparse_note_item(message, db_item["security_note"], chat_id) + + kwargs["reply_to"] = ( + None + if "clean_service" in db_item and db_item["clean_service"]["enabled"] is True + else message.message_id + ) + + kwargs["buttons"] = [] if not kwargs["buttons"] else kwargs["buttons"] + kwargs["buttons"] += [ + Button.inline(strings["click_here"], f"ws_{chat_id}_{user_id}") + ] + + # FIXME: Better workaround + if not (msg := await send_note(chat_id, text, **kwargs)): + # Wasn't able to sent message + return + + # Mute user + try: + await mute_user(chat_id, user_id) + except BadRequest as error: + # TODO: Delete the "sent" message ^ + return await message.reply(f"welcome security failed due to {error.args[0]}") + + redis.set(f"welcome_security_users:{user_id}:{chat_id}", msg.id) + + if raw_time := db_item["welcome_security"].get("expire", None): + time = convert_time(raw_time) + else: + time = convert_time(get_str_key("JOIN_CONFIRM_DURATION")) + + scheduler.add_job( + join_expired, + "date", + id=f"wc_expire:{chat_id}:{user_id}", + run_date=datetime.utcnow() + time, + kwargs={ + "chat_id": chat_id, + "user_id": user_id, + "message_id": msg.id, + "wlkm_msg_id": message.message_id, + }, + replace_existing=True, + ) + + +async def join_expired(chat_id, user_id, message_id, wlkm_msg_id): + user = await bot.get_chat_member(chat_id, user_id) + if user.status != "restricted": + return + + bot_user = await bot.get_chat_member(chat_id, BOT_ID) + if ( + "can_restrict_members" not in bot_user + or bot_user["can_restrict_members"] is False + ): + return + + key = "leave_silent:" + str(chat_id) + redis.set(key, user_id) + + await unmute_user(chat_id, user_id) + await kick_user(chat_id, user_id) + await tbot.delete_messages(chat_id, [message_id, wlkm_msg_id]) + + +@register(regexp=re.compile(r"ws_"), f="cb") +@get_strings_dec("greetings") +async def ws_redirecter(message, strings): + payload = message.data.split("_")[1:] + chat_id = int(payload[0]) + real_user_id = int(payload[1]) + called_user_id = message.from_user.id + + url = f"https://t.me/{BOT_USERNAME}?start=ws_{chat_id}_{called_user_id}_{message.message.message_id}" + if not called_user_id == real_user_id: + # The persons which are muted before wont have their signatures registered on cache + if not redis.exists(f"welcome_security_users:{called_user_id}:{chat_id}"): + await message.answer(strings["not_allowed"], show_alert=True) + return + else: + # For those who lost their buttons + url = f"https://t.me/{BOT_USERNAME}?start=ws_{chat_id}_{called_user_id}_{message.message.message_id}_0" + await message.answer(url=url) + + +@register(CommandStart(re.compile(r"ws_")), allow_kwargs=True) +@get_strings_dec("greetings") +async def welcome_security_handler_pm( + message: Message, strings, regexp=None, state=None, **kwargs +): + args = message.get_args().split("_") + chat_id = int(args[1]) + + async with state.proxy() as data: + data["chat_id"] = chat_id + data["msg_id"] = int(args[3]) + data["to_delete"] = bool(int(args[4])) if len(args) > 4 else True + + db_item = await get_greetings_data(chat_id) + + level = db_item["welcome_security"]["level"] + + if level == "button": + await WelcomeSecurityState.button.set() + await send_button(message, state) + + elif level == "math": + await WelcomeSecurityState.math.set() + await send_btn_math(message, state) + + elif level == "captcha": + await WelcomeSecurityState.captcha.set() + await send_captcha(message, state) + + +@get_strings_dec("greetings") +async def send_button(message, state, strings): + text = strings["btn_button_text"] + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton(strings["click_here"], callback_data="wc_button_btn") + ) + verify_msg_id = (await message.reply(text, reply_markup=buttons)).message_id + async with state.proxy() as data: + data["verify_msg_id"] = verify_msg_id + + +@register( + regexp="wc_button_btn", f="cb", state=WelcomeSecurityState.button, allow_kwargs=True +) +async def wc_button_btn_cb(event, state=None, **kwargs): + await welcome_security_passed(event, state) + + +def generate_captcha(number=None): + if not number: + number = str(random.randint(10001, 99999)) + captcha = ImageCaptcha(fonts=ALL_FONTS, width=200, height=100).generate_image( + number + ) + img = io.BytesIO() + captcha.save(img, "PNG") + img.seek(0) + return img, number + + +@get_strings_dec("greetings") +async def send_captcha(message, state, strings): + img, num = generate_captcha() + async with state.proxy() as data: + data["captcha_num"] = num + text = strings["ws_captcha_text"].format( + user=await get_user_link(message.from_user.id) + ) + + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton( + strings["regen_captcha_btn"], callback_data="regen_captcha" + ) + ) + + verify_msg_id = ( + await message.answer_photo(img, caption=text, reply_markup=buttons) + ).message_id + async with state.proxy() as data: + data["verify_msg_id"] = verify_msg_id + + +@register( + regexp="regen_captcha", + f="cb", + state=WelcomeSecurityState.captcha, + allow_kwargs=True, +) +@get_strings_dec("greetings") +async def change_captcha(event, strings, state=None, **kwargs): + message = event.message + async with state.proxy() as data: + data["regen_num"] = 1 if "regen_num" not in data else data["regen_num"] + 1 + regen_num = data["regen_num"] + + if regen_num > 3: + img, num = generate_captcha(number=data["captcha_num"]) + text = strings["last_chance"] + await message.edit_media(InputMediaPhoto(img, caption=text)) + return + + img, num = generate_captcha() + data["captcha_num"] = num + + text = strings["ws_captcha_text"].format( + user=await get_user_link(event.from_user.id) + ) + + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton( + strings["regen_captcha_btn"], callback_data="regen_captcha" + ) + ) + + await message.edit_media(InputMediaPhoto(img, caption=text), reply_markup=buttons) + + +@register(f="text", state=WelcomeSecurityState.captcha, allow_kwargs=True) +@get_strings_dec("greetings") +async def check_captcha_text(message, strings, state=None, **kwargs): + num = message.text.split(" ")[0] + + if not num.isdigit(): + await message.reply(strings["num_is_not_digit"]) + return + + async with state.proxy() as data: + captcha_num = data["captcha_num"] + + if not int(num) == int(captcha_num): + await message.reply(strings["bad_num"]) + return + + await welcome_security_passed(message, state) + + +# Btns + + +def gen_expression(): + a = random.randint(1, 10) + b = random.randint(1, 10) + if random.getrandbits(1): + while a < b: + b = random.randint(1, 10) + answr = a - b + expr = f"{a} - {b}" + else: + b = random.randint(1, 10) + + answr = a + b + expr = f"{a} + {b}" + + return expr, answr + + +def gen_int_btns(answer): + buttons = [] + + for a in [random.randint(1, 20) for _ in range(3)]: + while a == answer: + a = random.randint(1, 20) + buttons.append(Button.inline(str(a), data="wc_int_btn:" + str(a))) + + buttons.insert( + random.randint(0, 3), + Button.inline(str(answer), data="wc_int_btn:" + str(answer)), + ) + + return buttons + + +@get_strings_dec("greetings") +async def send_btn_math(message, state, strings, msg_id=False): + chat_id = message.chat.id + expr, answer = gen_expression() + + async with state.proxy() as data: + data["num"] = answer + + btns = gen_int_btns(answer) + + if msg_id: + async with state.proxy() as data: + data["last"] = True + text = strings["math_wc_rtr_text"] + strings["btn_wc_text"] % expr + else: + text = strings["btn_wc_text"] % expr + msg_id = (await message.reply(text)).message_id + + async with state.proxy() as data: + data["verify_msg_id"] = msg_id + + await tbot.edit_message( + chat_id, msg_id, text, buttons=btns + ) # TODO: change to aiogram + + +@register( + regexp="wc_int_btn:", f="cb", state=WelcomeSecurityState.math, allow_kwargs=True +) +@get_strings_dec("greetings") +async def wc_math_check_cb(event, strings, state=None, **kwargs): + num = int(event.data.split(":")[1]) + + async with state.proxy() as data: + answer = data["num"] + if "last" in data: + await state.finish() + await event.answer(strings["math_wc_sry"], show_alert=True) + await event.message.delete() + return + + if not num == answer: + await send_btn_math(event.message, state, msg_id=event.message.message_id) + await event.answer(strings["math_wc_wrong"], show_alert=True) + return + + await welcome_security_passed(event, state) + + +@get_strings_dec("greetings") +async def welcome_security_passed( + message: Union[CallbackQuery, Message], state, strings +): + user_id = message.from_user.id + async with state.proxy() as data: + chat_id = data["chat_id"] + msg_id = data["msg_id"] + verify_msg_id = data["verify_msg_id"] + to_delete = data["to_delete"] + + with suppress(ChatAdminRequired): + await unmute_user(chat_id, user_id) + + with suppress(MessageToDeleteNotFound, MessageCantBeDeleted): + if to_delete: + await bot.delete_message(chat_id, msg_id) + await bot.delete_message(user_id, verify_msg_id) + await state.finish() + + with suppress(MessageToDeleteNotFound, MessageCantBeDeleted): + message_id = redis.get(f"welcome_security_users:{user_id}:{chat_id}") + # Delete the person's real security button if exists! + if message_id: + await bot.delete_message(chat_id, message_id) + + redis.delete(f"welcome_security_users:{user_id}:{chat_id}") + + with suppress(JobLookupError): + scheduler.remove_job(f"wc_expire:{chat_id}:{user_id}") + + title = (await db.chat_list.find_one({"chat_id": chat_id}))["chat_title"] + + if "data" in message: + await message.answer(strings["passed_no_frm"] % title, show_alert=True) + else: + await message.reply(strings["passed"] % title) + + db_item = await get_greetings_data(chat_id) + + if "message" in message: + message = message.message + + # Welcome + if "note" in db_item and not db_item.get("welcome_disabled", False): + text, kwargs = await t_unparse_note_item( + message.reply_to_message + if message.reply_to_message is not None + else message, + db_item["note"], + chat_id, + ) + await send_note(user_id, text, **kwargs) + + # Welcome mute + if "welcome_mute" in db_item and db_item["welcome_mute"]["enabled"] is not False: + user = await bot.get_chat_member(chat_id, user_id) + if "can_send_messages" not in user or user["can_send_messages"] is True: + await restrict_user( + chat_id, + user_id, + until_date=convert_time(db_item["welcome_mute"]["time"]), + ) + + chat = await db.chat_list.find_one({"chat_id": chat_id}) + + buttons = None + if chat_nick := chat["chat_nick"] if chat.get("chat_nick", None) else None: + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton(text=strings["click_here"], url=f"t.me/{chat_nick}") + ) + + await bot.send_message(user_id, strings["verification_done"], reply_markup=buttons) + + +# End Welcome Security + +# Welcomes +@register(only_groups=True, f="welcome") +@get_strings_dec("greetings") +async def welcome_trigger(message: Message, strings): + if len(message.new_chat_members) > 1: + # FIXME: AllMightRobot doesnt support adding multiple users currently + return + + chat_id = message.chat.id + user_id = message.new_chat_members[0].id + + if user_id == BOT_ID: + return + + if not (db_item := await get_greetings_data(message.chat.id)): + db_item = {} + + if "welcome_disabled" in db_item and db_item["welcome_disabled"] is True: + return + + if "welcome_security" in db_item and db_item["welcome_security"]["enabled"]: + return + + # Welcome + if "note" not in db_item: + db_item["note"] = {"text": strings["default_welcome"], "parse_mode": "md"} + reply_to = ( + message.message_id + if "clean_welcome" in db_item + and db_item["clean_welcome"]["enabled"] is not False + else None + ) + text, kwargs = await t_unparse_note_item(message, db_item["note"], chat_id) + msg = await send_note(chat_id, text, reply_to=reply_to, **kwargs) + # Clean welcome + if "clean_welcome" in db_item and db_item["clean_welcome"]["enabled"] is not False: + if "last_msg" in db_item["clean_welcome"]: + with suppress(MessageToDeleteNotFound, MessageCantBeDeleted): + if value := redis.get(_clean_welcome.format(chat=chat_id)): + await bot.delete_message(chat_id, value) + redis.set(_clean_welcome.format(chat=chat_id), msg.id) + + # Welcome mute + if user_id == BOT_ID: + return + if "welcome_mute" in db_item and db_item["welcome_mute"]["enabled"] is not False: + user = await bot.get_chat_member(chat_id, user_id) + if "can_send_messages" not in user or user["can_send_messages"] is True: + if not await check_admin_rights( + message, chat_id, BOT_ID, ["can_restrict_members"] + ): + await message.reply(strings["not_admin_wm"]) + return + + await restrict_user( + chat_id, + user_id, + until_date=convert_time(db_item["welcome_mute"]["time"]), + ) + + +# Clean service trigger +@register(only_groups=True, f="service") +@get_strings_dec("greetings") +async def clean_service_trigger(message, strings): + chat_id = message.chat.id + + if message.new_chat_members[0].id == BOT_ID: + return + + if not (db_item := await get_greetings_data(chat_id)): + return + + if "clean_service" not in db_item or db_item["clean_service"]["enabled"] is False: + return + + if not await check_admin_rights(message, chat_id, BOT_ID, ["can_delete_messages"]): + await bot.send_message(chat_id, strings["not_admin_wsr"]) + return + + with suppress(MessageToDeleteNotFound, MessageCantBeDeleted): + await message.delete() + + +_clean_welcome = "cleanwelcome:{chat}" + + +@cached() +async def get_greetings_data(chat: int) -> Optional[dict]: + return await db.greetings.find_one({"chat_id": chat}) + + +async def __export__(chat_id): + if greetings := await get_greetings_data(chat_id): + del greetings["_id"] + del greetings["chat_id"] + + return {"greetings": greetings} + + +async def __import__(chat_id, data): + await db.greetings.update_one({"chat_id": chat_id}, {"$set": data}, upsert=True) + await get_greetings_data.reset_cache(chat_id) + + +__mod_name__ = "Greetings" + +__help__ = """ +Available commands: +General: +- /setwelcome or /savewelcome: Set welcome +- /setwelcome (on/off): Disable/enabled welcomes in your chat +- /welcome: Shows current welcomes settings and welcome text +- /resetwelcome: Reset welcomes settings +Welcome security: +- /welcomesecurity (level) +Turns on welcome security with specified level, either button or captcha. +Setting up welcome security will give you a choice to customize join expiration time aka minimum time given to user to verify themselves not a bot, users who do not verify within this time would be kicked! +- /welcomesecurity (off/no/0): Disable welcome security +- /setsecuritynote: Customise the "Please press button below to verify themself as human!" text +- /delsecuritynote: Reset security text to defaults +Available levels: +- button: Ask user to press "I'm not a bot" button +- math: Asking to solve simple math query, few buttons with answers will be provided, only one will have right answer +- captcha: Ask user to enter captcha +Welcome mutes: +- /welcomemute (time): Set welcome mute (no media) for X time +- /welcomemute (off/no): Disable welcome mute +Purges: +- /cleanwelcome (on/off): Deletes old welcome messages and last one after 45 mintes +- /cleanservice (on/off): Cleans service messages (user X joined) +If welcome security is enabled, user will be welcomed with security text, if user successfully verify self as user, he/she will be welcomed also with welcome text in his PM (to prevent spamming in chat). +If user didn't verified self for 24 hours he/she will be kicked from chat. +Addings buttons and variables to welcomes or security text: +Buttons and variables syntax is same as notes buttons and variables. +Send /buttonshelp and /variableshelp to get started with using it. +Settings images, gifs, videos or stickers as welcome: +Saving attachments on welcome is same as saving notes with it, read the notes help about it. But keep in mind what you have to replace /save to /setwelcome +Examples: +- Get the welcome message without any formatting +-> /welcome raw +""" diff --git a/DaisyX/modules/imdb.py b/DaisyX/modules/imdb.py new file mode 100644 index 00000000..417c6cc7 --- /dev/null +++ b/DaisyX/modules/imdb.py @@ -0,0 +1,145 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import re + +import bs4 +import requests +from telethon import types +from telethon.tl import functions + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +langi = "en" + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/imdb (.*)") +async def imdb(e): + if e.is_group: + if await is_register_admin(e.input_chat, e.message.sender_id): + pass + else: + return + + try: + movie_name = e.pattern_match.group(1) + remove_space = movie_name.split(" ") + final_name = "+".join(remove_space) + page = requests.get( + "https://www.imdb.com/find?ref_=nv_sr_fn&q=" + final_name + "&s=all" + ) + str(page.status_code) + soup = bs4.BeautifulSoup(page.content, "lxml") + odds = soup.findAll("tr", "odd") + mov_title = odds[0].findNext("td").findNext("td").text + mov_link = ( + "http://www.imdb.com/" + odds[0].findNext("td").findNext("td").a["href"] + ) + page1 = requests.get(mov_link) + soup = bs4.BeautifulSoup(page1.content, "lxml") + if soup.find("div", "poster"): + poster = soup.find("div", "poster").img["src"] + else: + poster = "" + if soup.find("div", "title_wrapper"): + pg = soup.find("div", "title_wrapper").findNext("div").text + mov_details = re.sub(r"\s+", " ", pg) + else: + mov_details = "" + credits = soup.findAll("div", "credit_summary_item") + if len(credits) == 1: + director = credits[0].a.text + writer = "Not available" + stars = "Not available" + elif len(credits) > 2: + director = credits[0].a.text + writer = credits[1].a.text + actors = [] + for x in credits[2].findAll("a"): + actors.append(x.text) + actors.pop() + stars = actors[0] + "," + actors[1] + "," + actors[2] + else: + director = credits[0].a.text + writer = "Not available" + actors = [] + for x in credits[1].findAll("a"): + actors.append(x.text) + actors.pop() + stars = actors[0] + "," + actors[1] + "," + actors[2] + if soup.find("div", "inline canwrap"): + story_line = soup.find("div", "inline canwrap").findAll("p")[0].text + else: + story_line = "Not available" + info = soup.findAll("div", "txt-block") + if info: + mov_country = [] + mov_language = [] + for node in info: + a = node.findAll("a") + for i in a: + if "country_of_origin" in i["href"]: + mov_country.append(i.text) + elif "primary_language" in i["href"]: + mov_language.append(i.text) + if soup.findAll("div", "ratingValue"): + for r in soup.findAll("div", "ratingValue"): + mov_rating = r.strong["title"] + else: + mov_rating = "Not available" + await e.reply( + "" + "Title : " + + mov_title + + "\n" + + mov_details + + "\nRating : " + + mov_rating + + "\nCountry : " + + mov_country[0] + + "\nLanguage : " + + mov_language[0] + + "\nDirector : " + + director + + "\nWriter : " + + writer + + "\nStars : " + + stars + + "\nIMDB Url : " + + mov_link + + "\nStory Line : " + + story_line, + link_preview=True, + parse_mode="HTML", + ) + except IndexError: + await e.reply("Please enter a valid movie name !") diff --git a/DaisyX/modules/imgeditor.py b/DaisyX/modules/imgeditor.py new file mode 100644 index 00000000..42aee138 --- /dev/null +++ b/DaisyX/modules/imgeditor.py @@ -0,0 +1,463 @@ +# By @TroJanzHEX +# Improved by TeamDaisyX + +from pyrogram import filters +from pyrogram.types import ( + CallbackQuery, + InlineKeyboardButton, + InlineKeyboardMarkup, + Message, +) + +# By @TroJanzHEX +from DaisyX.Addons.ImageEditor.edit_1 import ( # pylint:disable=import-error + black_white, + box_blur, + bright, + g_blur, + mix, + normal_blur, +) +from DaisyX.Addons.ImageEditor.edit_2 import ( # pylint:disable=import-error + cartoon, + circle_with_bg, + circle_without_bg, + contrast, + edge_curved, + pencil, + sepia_mode, + sticker, +) +from DaisyX.Addons.ImageEditor.edit_3 import ( # pylint:disable=import-error + black_border, + blue_border, + green_border, + red_border, +) +from DaisyX.Addons.ImageEditor.edit_4 import ( # pylint:disable=import-error + inverted, + removebg_plain, + removebg_sticker, + removebg_white, + rotate_90, + rotate_180, + rotate_270, + round_sticker, +) +from DaisyX.Addons.ImageEditor.edit_5 import ( # pylint:disable=import-error + normalglitch_1, + normalglitch_2, + normalglitch_3, + normalglitch_4, + normalglitch_5, + scanlineglitch_1, + scanlineglitch_2, + scanlineglitch_3, + scanlineglitch_4, + scanlineglitch_5, +) +from DaisyX.services.pyrogram import pbot as Client + +lel = 00000000 +# pylint:disable=import-error +@Client.on_message(filters.command(["edit", "editor"])) +async def photo(client: Client, message: Message): + try: + if not message.reply_to_message.photo: + await client.send_message(message.chat.id, "Reply to an image man!ㅤㅤ") + return + except: + return + global lel + try: + lel = message.from_user.id + except: + return + try: + await client.send_message( + chat_id=message.chat.id, + text="Select your required mode from below!ㅤㅤ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="💡 BRIGHT", callback_data="bright"), + InlineKeyboardButton(text="🖼 MIXED", callback_data="mix"), + InlineKeyboardButton(text="🔳 B&W", callback_data="b|w"), + ], + [ + InlineKeyboardButton(text="🟡 CIRCLE", callback_data="circle"), + InlineKeyboardButton(text="🩸 BLUR", callback_data="blur"), + InlineKeyboardButton(text="🌌 BORDER", callback_data="border"), + ], + [ + InlineKeyboardButton(text="🎉 STICKER", callback_data="stick"), + InlineKeyboardButton(text="↩️ ROTATE", callback_data="rotate"), + InlineKeyboardButton( + text="🔦 CONTRAST", callback_data="contrast" + ), + ], + [ + InlineKeyboardButton(text="🌇 SEPIA", callback_data="sepia"), + InlineKeyboardButton(text="✏️ PENCIL", callback_data="pencil"), + InlineKeyboardButton(text="🐶 CARTOON", callback_data="cartoon"), + ], + [ + InlineKeyboardButton(text="🔄 INVERT", callback_data="inverted"), + InlineKeyboardButton(text="🔮 GLITCH", callback_data="glitch"), + InlineKeyboardButton( + text="✂️ REMOVE BG", callback_data="removebg" + ), + ], + [ + InlineKeyboardButton(text="❌ CLOSE", callback_data="close_e"), + ], + ] + ), + reply_to_message_id=message.reply_to_message.message_id, + ) + except Exception as e: + print("photomarkup error - " + str(e)) + if "USER_IS_BLOCKED" in str(e): + return + else: + try: + await message.reply_text("Something went wrong!", quote=True) + except Exception: + return + + +@Client.on_callback_query() +async def cb_handler(client: Client, query: CallbackQuery): + user_id = query.from_user.id + if lel == user_id: + if query.data == "removebg": + await query.message.edit_text( + "**Select required mode**ㅤㅤㅤㅤ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="WITH WHITE BG", callback_data="rmbgwhite" + ), + InlineKeyboardButton( + text="WITHOUT BG", callback_data="rmbgplain" + ), + ], + [ + InlineKeyboardButton( + text="STICKER", callback_data="rmbgsticker" + ) + ], + ] + ), + ) + elif query.data == "stick": + await query.message.edit( + "**Select a Type**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="Normal", callback_data="stkr"), + InlineKeyboardButton( + text="Edge Curved", callback_data="cur_ved" + ), + ], + [ + InlineKeyboardButton( + text="Circle", callback_data="circle_sticker" + ) + ], + ] + ), + ) + elif query.data == "rotate": + await query.message.edit_text( + "**Select the Degree**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="180", callback_data="180"), + InlineKeyboardButton(text="90", callback_data="90"), + ], + [InlineKeyboardButton(text="270", callback_data="270")], + ] + ), + ) + + elif query.data == "glitch": + await query.message.edit_text( + "**Select required mode**ㅤㅤㅤㅤ", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="NORMAL", callback_data="normalglitch" + ), + InlineKeyboardButton( + text="SCAN LINES", callback_data="scanlineglitch" + ), + ] + ] + ), + ) + elif query.data == "normalglitch": + await query.message.edit_text( + "**Select Glitch power level**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="1", callback_data="normalglitch1" + ), + InlineKeyboardButton( + text="2", callback_data="normalglitch2" + ), + InlineKeyboardButton( + text="3", callback_data="normalglitch3" + ), + ], + [ + InlineKeyboardButton( + text="4", callback_data="normalglitch4" + ), + InlineKeyboardButton( + text="5", callback_data="normalglitch5" + ), + ], + ] + ), + ) + elif query.data == "scanlineglitch": + await query.message.edit_text( + "**Select Glitch power level**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="1", callback_data="scanlineglitch1" + ), + InlineKeyboardButton( + text="2", callback_data="scanlineglitch2" + ), + InlineKeyboardButton( + text="3", callback_data="scanlineglitch3" + ), + ], + [ + InlineKeyboardButton( + text="4", callback_data="scanlineglitch4" + ), + InlineKeyboardButton( + text="5", callback_data="scanlineglitch5" + ), + ], + ] + ), + ) + elif query.data == "blur": + await query.message.edit( + "**Select a Type**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="box", callback_data="box"), + InlineKeyboardButton(text="normal", callback_data="normal"), + ], + [InlineKeyboardButton(text="Gaussian", callback_data="gas")], + ] + ), + ) + elif query.data == "circle": + await query.message.edit_text( + "**Select required mode**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton( + text="WITH BG", callback_data="circlewithbg" + ), + InlineKeyboardButton( + text="WITHOUT BG", callback_data="circlewithoutbg" + ), + ] + ] + ), + ) + elif query.data == "border": + await query.message.edit( + "**Select Border**", + reply_markup=InlineKeyboardMarkup( + [ + [ + InlineKeyboardButton(text="🔴 RED 🔴", callback_data="red"), + InlineKeyboardButton( + text="🟢 Green 🟢", callback_data="green" + ), + ], + [ + InlineKeyboardButton( + text="⚫ Black ⚫", callback_data="black" + ), + InlineKeyboardButton(text="🔵 Blue 🔵", callback_data="blue"), + ], + ] + ), + ) + + elif query.data == "bright": + await query.message.delete() + await bright(client, query.message) + + elif query.data == "close_e": + await query.message.delete() + + elif query.data == "mix": + await query.message.delete() + await mix(client, query.message) + + elif query.data == "b|w": + await query.message.delete() + await black_white(client, query.message) + + elif query.data == "circlewithbg": + await query.message.delete() + await circle_with_bg(client, query.message) + + elif query.data == "circlewithoutbg": + await query.message.delete() + await circle_without_bg(client, query.message) + + elif query.data == "green": + await query.message.delete() + await green_border(client, query.message) + + elif query.data == "blue": + await query.message.delete() + await blue_border(client, query.message) + + elif query.data == "red": + await query.message.delete() + await red_border(client, query.message) + + elif query.data == "black": + await query.message.delete() + await black_border(client, query.message) + + elif query.data == "circle_sticker": + await query.message.delete() + await round_sticker(client, query.message) + + elif query.data == "inverted": + await query.message.delete() + await inverted(client, query.message) + + elif query.data == "stkr": + await query.message.delete() + await sticker(client, query.message) + + elif query.data == "cur_ved": + await query.message.delete() + await edge_curved(client, query.message) + + elif query.data == "90": + await query.message.delete() + await rotate_90(client, query.message) + + elif query.data == "180": + await query.message.delete() + await rotate_180(client, query.message) + + elif query.data == "270": + await query.message.delete() + await rotate_270(client, query.message) + + elif query.data == "contrast": + await query.message.delete() + await contrast(client, query.message) + + elif query.data == "box": + await query.message.delete() + await box_blur(client, query.message) + + elif query.data == "gas": + await query.message.delete() + await g_blur(client, query.message) + + elif query.data == "normal": + await query.message.delete() + await normal_blur(client, query.message) + + elif query.data == "sepia": + await query.message.delete() + await sepia_mode(client, query.message) + + elif query.data == "pencil": + await query.message.delete() + await pencil(client, query.message) + + elif query.data == "cartoon": + await query.message.delete() + await cartoon(client, query.message) + + elif query.data == "normalglitch1": + await query.message.delete() + await normalglitch_1(client, query.message) + + elif query.data == "normalglitch2": + await query.message.delete() + await normalglitch_2(client, query.message) + + elif query.data == "normalglitch3": + await normalglitch_3(client, query.message) + + elif query.data == "normalglitch4": + await query.message.delete() + await normalglitch_4(client, query.message) + + elif query.data == "normalglitch5": + await query.message.delete() + await normalglitch_5(client, query.message) + + elif query.data == "scanlineglitch1": + await query.message.delete() + await scanlineglitch_1(client, query.message) + + elif query.data == "scanlineglitch2": + await query.message.delete() + await scanlineglitch_2(client, query.message) + + elif query.data == "scanlineglitch3": + await query.message.delete() + await scanlineglitch_3(client, query.message) + + elif query.data == "scanlineglitch4": + await query.message.delete() + await scanlineglitch_4(client, query.message) + + elif query.data == "scanlineglitch5": + await query.message.delete() + await scanlineglitch_5(client, query.message) + + elif query.data == "rmbgwhite": + await query.message.delete() + await removebg_white(client, query.message) + + elif query.data == "rmbgplain": + await query.message.delete() + await removebg_plain(client, query.message) + + elif query.data == "rmbgsticker": + await removebg_sticker(client, query.message) + + +__mod_name__ = "Image Editor" +__help__ = """ + IMAGE EDITOR +Daisy have some advanced image editing tools inbuilt +Bright, Circle, RemBG, Blur, Border, Flip, Glitch, Sticker maker and more + +- /edit [reply to image]: Open the image editor +- /rmbg [REPLY]: Revove BG of replied image/sticker. + + Special credits to TroJanzHEX +""" diff --git a/DaisyX/modules/imports_exports.py b/DaisyX/modules/imports_exports.py new file mode 100644 index 00000000..0bf774fe --- /dev/null +++ b/DaisyX/modules/imports_exports.py @@ -0,0 +1,174 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import io +from datetime import datetime, timedelta + +import rapidjson +from aiogram import types +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types.input_file import InputFile +from babel.dates import format_timedelta + +from DaisyX import OPERATORS, bot +from DaisyX.decorator import register +from DaisyX.services.redis import redis + +from . import LOADED_MODULES +from .utils.connections import chat_connection +from .utils.language import get_strings_dec + +VERSION = 5 + + +# Waiting for import file state +class ImportFileWait(StatesGroup): + waiting = State() + + +@register(cmds="export", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("imports_exports") +async def export_chat_data(message, chat, strings): + chat_id = chat["chat_id"] + key = "export_lock:" + str(chat_id) + if redis.get(key) and message.from_user.id not in OPERATORS: + ttl = format_timedelta( + timedelta(seconds=redis.ttl(key)), strings["language_info"]["babel"] + ) + await message.reply(strings["exports_locked"] % ttl) + return + + redis.set(key, 1) + redis.expire(key, 7200) + + msg = await message.reply(strings["started_exporting"]) + data = { + "general": { + "chat_name": chat["chat_title"], + "chat_id": chat_id, + "date": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + "version": VERSION, + } + } + + for module in [m for m in LOADED_MODULES if hasattr(m, "__export__")]: + await asyncio.sleep(0) # Switch to other events before continue + if k := await module.__export__(chat_id): + data.update(k) + + jfile = InputFile( + io.StringIO(rapidjson.dumps(data, indent=2)), filename=f"{chat_id}_export.json" + ) + text = strings["export_done"].format(chat_name=chat["chat_title"]) + await message.answer_document(jfile, text, reply=message.message_id) + await msg.delete() + + +@register(cmds="import", user_admin=True) +@get_strings_dec("imports_exports") +async def import_reply(message, strings): + if "document" in message: + document = message.document + else: + if "reply_to_message" not in message: + await ImportFileWait.waiting.set() + await message.reply(strings["send_import_file"]) + return + + elif "document" not in message.reply_to_message: + await message.reply(strings["rpl_to_file"]) + return + document = message.reply_to_message.document + + await import_fun(message, document) + + +@register( + state=ImportFileWait.waiting, + content_types=types.ContentTypes.DOCUMENT, + allow_kwargs=True, +) +async def import_state(message, state=None, **kwargs): + await import_fun(message, message.document) + await state.finish() + + +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("imports_exports") +async def import_fun(message, document, chat, strings): + chat_id = chat["chat_id"] + key = "import_lock:" + str(chat_id) + if redis.get(key) and message.from_user.id not in OPERATORS: + ttl = format_timedelta( + timedelta(seconds=redis.ttl(key)), strings["language_info"]["babel"] + ) + await message.reply(strings["imports_locked"] % ttl) + return + + redis.set(key, 1) + redis.expire(key, 7200) + + msg = await message.reply(strings["started_importing"]) + if document["file_size"] > 52428800: + await message.reply(strings["big_file"]) + return + data = await bot.download_file_by_id(document.file_id, io.BytesIO()) + try: + data = rapidjson.load(data) + except ValueError: + return await message.reply(strings["invalid_file"]) + + if "general" not in data: + await message.reply(strings["bad_file"]) + return + + file_version = data["general"]["version"] + + if file_version > VERSION: + await message.reply(strings["file_version_so_new"]) + return + + imported = [] + for module in [m for m in LOADED_MODULES if hasattr(m, "__import__")]: + module_name = module.__name__.replace("DaisyX.modules.", "") + if module_name not in data: + continue + if not data[module_name]: + continue + + imported.append(module_name) + await asyncio.sleep(0) # Switch to other events before continue + await module.__import__(chat_id, data[module_name]) + + await msg.edit_text(strings["import_done"]) + + +__mod_name__ = "Backups" + +__help__ = """ +Sometimes you want to see all of your data in your chats or you want to copy your data to another chats or you even want to swift bots, in all these cases imports/exports for you! + +Available commands: +- /export: Export chat's data to JSON file +- /import: Import JSON file to chat + +Notes: Exporting / importing avaible every 2 hours to prevent flooding. +""" diff --git a/DaisyX/modules/inlinebot.py b/DaisyX/modules/inlinebot.py new file mode 100644 index 00000000..adb1c1b5 --- /dev/null +++ b/DaisyX/modules/inlinebot.py @@ -0,0 +1,1025 @@ +# Copyright (C) 2021 TheHamkerCat & TeamDaisyX + +# Ported some parts From WilliamButcherBot. +# Pokedex Inline Credit Red-Aura[Madepranav] +# Credits Goes to WilliamButcherBot + +# This file is part of Daisy (Telegram Bot) + + +import datetime +import re +import time +import urllib.request +from datetime import datetime +from typing import List + +import aiohttp +import requests +from bs4 import BeautifulSoup +from countryinfo import CountryInfo +from faker import Faker +from faker.providers import internet +from PyDictionary import PyDictionary +from pyrogram import errors, filters +from pyrogram.types import ( + InlineKeyboardButton, + InlineKeyboardMarkup, + InlineQueryResultArticle, + InlineQueryResultPhoto, + InputTextMessageContent, +) +from search_engine_parser import GoogleSearch +from tswift import Song +from youtubesearchpython import VideosSearch + +from DaisyX.config import get_str_key +from DaisyX.function.inlinehelper import * +from DaisyX.function.pluginhelpers import fetch, json_prettify +from DaisyX.services.pyrogram import pbot as app + +OPENWEATHERMAP_ID = get_str_key("OPENWEATHERMAP_ID", "") +TIME_API_KEY = get_str_key("TIME_API_KEY", required=False) + +dictionary = PyDictionary() + + +class AioHttp: + @staticmethod + async def get_json(link): + async with aiohttp.ClientSession() as session: + async with session.get(link) as resp: + return await resp.json() + + @staticmethod + async def get_text(link): + async with aiohttp.ClientSession() as session: + async with session.get(link) as resp: + return await resp.text() + + @staticmethod + async def get_raw(link): + async with aiohttp.ClientSession() as session: + async with session.get(link) as resp: + return await resp.read() + + +__mod_name__ = "Inline" +__help__ = """ + INLINE BOT SERVICE OF @DAISYXBOT + + I'm more efficient when added as group admin. By the way these commands can be used by anyone in a group via inline. + +Syntax + @DaisyXBot [command] [query] + + Commands Available +- alive - Check Bot's Stats. +- yt [query] - Youtube Search. +- tr [LANGUAGE_CODE] [QUERY]** - Translate Text. +- modapk [name] - Give you direct link of mod apk. +- ud [QUERY] - Urban Dictionary Query +- google [QUERY] - Google Search. +- webss [URL] - Take Screenshot Of A Website. +- bitly [URL] - Shorten A Link. +- wall [Query] - Find Wallpapers. +- pic [Query] - Find pictures. +- saavn [SONG_NAME] - Get Songs From Saavn. +- deezer [SONG_NAME] - Get Songs From Deezer. +- torrent [QUERY] - Torrent Search. +- reddit [QUERY] - Get memes from reddit. +- imdb [QUERY] - Search movies on imdb. +- spaminfo [ID] - Get spam info of the user. +- lyrics [QUERY] - Get lyrics of the song. +- paste [TEXT] - Paste text on pastebin. +- define [WORD] - Get definition from Dictionary. +- synonyms [WORD] - Get synonyms from Dictionary. +- antonyms [WORD] - Get antonyms from Dictionary. +- country [QUERY] - Get Information about given country. +- cs - Gathers Cricket info (Globally). +- covid [COUNTRY] - Get covid updates of given country. +- fakegen - Gathers fake information. +- weather [QUERY] - Get weather information. +- datetime [QUERY] - Get Date & time information of given country/region. +- app [QUERY] - Search for apps in playstore. +- gh [QUERY] - Search github. +- so [QUERY] - Search stack overflow. +- wiki [QUERY] - Search wikipedia. +- ping - Check ping rate. +- pokedex [TEXT]: Pokemon Search +""" + +__MODULE__ = "Inline" +__HELP__ = """ + ==>> **INLINE BOT SERVICE OF @DAISYXBOT** <<== +`I'm more efficient when added as group admin. By the way these commands can be used by anyone in a group via inline.` + + >> Syntax << +@DaisyXBot [command] [query] + + >> Commands Available << +- **alive** - __Check Bot's Stats.__ +- **yt [query]** - __Youtube Search.__ +- **tr [LANGUAGE_CODE] [QUERY]** - __Translate Text.__ +- **ud [QUERY]** - __Urban Dictionary Query.__ +- **google [QUERY]** - __Google Search.__ +- **modapk [name]** - __Give you direct link of mod apk__ +- **webss [URL]** - __Take Screenshot Of A Website.__ +- **bitly [URL]** - __Shorten A Link.__ +- **wall [Query]** - __Find Wallpapers.__ +- **pic [Query]** - __Find pictures.__ +- **saavn [SONG_NAME]** - __Get Songs From Saavn.__ +- **deezer [SONG_NAME]** - __Get Songs From Deezer.__ +- **torrent [QUERY]** - __Torrent Search.__ +- **reddit [QUERY]** - __Get memes from redit.__ +- **imdb [QUERY]** - __Search movies on imdb.__ +- **spaminfo [id]** - __Get spam info of the user.__ +- **lyrics [QUERY]** - __Get lyrics of given song.__ +- **paste [TEXT]** - __Paste text on pastebin.__ +- **define [WORD]** - __Get definition from Dictionary.__ +- **synonyms [WORD]** - __Get synonyms from Dictionary.__ +- **antonyms [WORD]** - __Get antonyms from Dictionary.__ +- **country [QUERY]** - __Get Information about given country.__ +- **cs** - __Gathers Cricket info (Globally).__ +- **covid [COUNTRY]** - __Get covid updates of given country.__ +- **fakegen** - __Gathers fake information.__ +- **weather [QUERY]** - __Get weather information.__ +- **datetime [QUERY]** - __Get Date & time information of given country/region.__ +- **app [QUERY]** - __Search for apps on playstore. +- **gh [QUERY]** - __Search github.__ +- **so [QUERY]** - __Search stack overfolw.__ +- **wiki [QUERY]** - __Search wikipedia.__ +- **ping** - __Check ping rate.__ +- **pokedex [TEXT]** - __Pokemon Search.__ +""" + + +@app.on_message(filters.command("inline")) +async def inline_help(_, message): + await app.send_message(message.chat.id, text=__HELP__) + + +@app.on_inline_query() +async def inline_query_handler(client, query): + try: + text = query.query.lower() + answers = [] + if text.strip() == "": + answerss = await inline_help_func(__HELP__) + await client.answer_inline_query(query.id, results=answerss, cache_time=10) + return + elif text.split()[0] == "alive": + answerss = await alive_function(answers) + await client.answer_inline_query(query.id, results=answerss, cache_time=10) + elif text.split()[0] == "tr": + lang = text.split()[1] + tex = text.split(None, 2)[2] + answerss = await translate_func(answers, lang, tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=10) + elif text.split()[0] == "ud": + tex = text.split(None, 1)[1] + answerss = await urban_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=10) + elif text.split()[0] == "google": + tex = text.split(None, 1)[1] + answerss = await google_search_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=10) + elif text.split()[0] == "webss": + tex = text.split(None, 1)[1] + answerss = await webss(tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=2) + elif text.split()[0] == "bitly": + tex = text.split(None, 1)[1] + answerss = await shortify(tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=2) + elif text.split()[0] == "wiki": + if len(text.split()) < 2: + await client.answer_inline_query( + query.id, + results=answers, + switch_pm_text="Wikipedia | wiki [QUERY]", + switch_pm_parameter="inline", + ) + return + tex = text.split(None, 1)[1].strip() + answerss = await wiki_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=2) + + elif text.split()[0] == "ping": + answerss = await ping_func(answers) + await client.answer_inline_query(query.id, results=answerss, cache_time=2) + return + + elif text.split()[0] == "yt": + answers = [] + search_query = text.split(None, 1)[1] + search_query = query.query.lower().strip().rstrip() + + if search_query == "": + await client.answer_inline_query( + query.id, + results=answers, + switch_pm_text="Type a YouTube video name...", + switch_pm_parameter="help", + cache_time=0, + ) + else: + search = VideosSearch(search_query, limit=50) + + for result in search.result()["result"]: + answers.append( + InlineQueryResultArticle( + title=result["title"], + description="{}, {} views.".format( + result["duration"], result["viewCount"]["short"] + ), + input_message_content=InputTextMessageContent( + "https://www.youtube.com/watch?v={}".format( + result["id"] + ) + ), + thumb_url=result["thumbnails"][0]["url"], + ) + ) + + try: + await query.answer(results=answers, cache_time=0) + except errors.QueryIdInvalid: + await query.answer( + results=answers, + cache_time=0, + switch_pm_text="Error: Search timed out", + switch_pm_parameter="", + ) + + elif text.split()[0] == "wall": + tex = text.split(None, 1)[1] + answerss = await wall_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss) + + elif text.split()[0] == "pic": + tex = text.split(None, 1)[1] + answerss = await wall_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss) + + elif text.split()[0] == "saavn": + tex = text.split(None, 1)[1] + answerss = await saavn_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss) + + elif text.split()[0] == "deezer": + tex = text.split(None, 1)[1] + answerss = await deezer_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss) + + elif text.split()[0] == "torrent": + tex = text.split(None, 1)[1] + answerss = await torrent_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=10) + elif text.split()[0] == "modapk": + sgname = text.split(None, 1)[1] + PabloEscobar = ( + f"https://an1.com/tags/MOD/?story={sgname}&do=search&subaction=search" + ) + r = requests.get(PabloEscobar) + results = [] + soup = BeautifulSoup(r.content, "html5lib") + mydivs = soup.find_all("div", {"class": "search-results"}) + Pop = soup.find_all("div", {"class": "title"}) + cnte = len(mydivs) + for cnt in range(cnte): + sucker = mydivs[cnt] + pH9 = sucker.find("a").contents[0] + file_name = pH9 + pH = sucker.findAll("img") + imme = pH[0]["src"] + Pablo = Pop[0].a["href"] + ro = requests.get(Pablo) + soupe = BeautifulSoup(ro.content, "html5lib") + myopo = soupe.find_all("div", {"class": "item"}) + capt = f"**{file_name}** \n** {myopo[0].text}**\n**{myopo[1].text}**\n**{myopo[2].text}**\n**{myopo[3].text}**" + mydis0 = soupe.find_all("a", {"class": "get-product"}) + Lol9 = mydis0[0] + lemk = "https://an1.com" + Lol9["href"] + rr = requests.get(lemk) + soup = BeautifulSoup(rr.content, "html5lib") + script = soup.find("script", type="text/javascript") + leek = re.search(r'href=[\'"]?([^\'" >]+)', script.text).group() + dl_link = leek[5:] + + results.append( + InlineQueryResultPhoto( + photo_url=imme, + title=file_name, + caption=capt, + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton("Download Link", url=lemk)], + [ + InlineKeyboardButton( + "Direct Download Link", url=dl_link + ) + ], + ] + ), + ) + ) + + await client.answer_inline_query(query.id, cache_time=0, results=results) + elif text.split()[0] == "reddit": + subreddit = text.split(None, 1)[1] + results = [] + reddit = await arq.reddit(subreddit) + sreddit = reddit.subreddit + title = reddit.title + image = reddit.url + link = reddit.postLink + caption = f"""**Title:** `{title}` + Subreddit: `{sreddit}`""" + results.append( + InlineQueryResultPhoto( + photo_url=image, + title="Meme Search", + caption=caption, + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton("PostLink", url=link)], + ] + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "imdb": + movie_name = text.split(None, 1)[1] + results = [] + remove_space = movie_name.split(" ") + final_name = "+".join(remove_space) + page = requests.get( + "https://www.imdb.com/find?ref_=nv_sr_fn&q=" + final_name + "&s=all" + ) + str(page.status_code) + soup = BeautifulSoup(page.content, "lxml") + odds = soup.findAll("tr", "odd") + mov_title = odds[0].findNext("td").findNext("td").text + mov_link = ( + "http://www.imdb.com/" + odds[0].findNext("td").findNext("td").a["href"] + ) + page1 = requests.get(mov_link) + soup = BeautifulSoup(page1.content, "lxml") + if soup.find("div", "poster"): + poster = soup.find("div", "poster").img["src"] + else: + poster = "" + if soup.find("div", "title_wrapper"): + pg = soup.find("div", "title_wrapper").findNext("div").text + mov_details = re.sub(r"\s+", " ", pg) + else: + mov_details = "" + credits = soup.findAll("div", "credit_summary_item") + if len(credits) == 1: + director = credits[0].a.text + writer = "Not available" + stars = "Not available" + elif len(credits) > 2: + director = credits[0].a.text + writer = credits[1].a.text + actors = [] + for x in credits[2].findAll("a"): + actors.append(x.text) + actors.pop() + stars = actors[0] + "," + actors[1] + "," + actors[2] + else: + director = credits[0].a.text + writer = "Not available" + actors = [] + for x in credits[1].findAll("a"): + actors.append(x.text) + actors.pop() + stars = actors[0] + "," + actors[1] + "," + actors[2] + if soup.find("div", "inline canwrap"): + story_line = soup.find("div", "inline canwrap").findAll("p")[0].text + else: + story_line = "Not available" + info = soup.findAll("div", "txt-block") + if info: + mov_country = [] + mov_language = [] + for node in info: + a = node.findAll("a") + for i in a: + if "country_of_origin" in i["href"]: + mov_country.append(i.text) + elif "primary_language" in i["href"]: + mov_language.append(i.text) + if soup.findAll("div", "ratingValue"): + for r in soup.findAll("div", "ratingValue"): + mov_rating = r.strong["title"] + else: + mov_rating = "Not available" + lol = f"Movie - {mov_title}\n Click to see more" + msg = ( + "" + "Title : " + + mov_title + + "\n" + + mov_details + + "\nRating : " + + mov_rating + + "\nCountry : " + + mov_country[0] + + "\nLanguage : " + + mov_language[0] + + "\nDirector : " + + director + + "\nWriter : " + + writer + + "\nStars : " + + stars + + "\nIMDB Url : " + + mov_link + + "\nStory Line : " + + story_line + ) + results.append( + InlineQueryResultArticle( + title="Imdb Search", + description=lol, + input_message_content=InputTextMessageContent( + msg, disable_web_page_preview=False, parse_mode="HTML" + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + elif text.split()[0] == "spaminfo": + cmd = text.split(None, 1)[1] + results = [] + url = f"https://api.intellivoid.net/spamprotection/v1/lookup?query={cmd}" + a = await AioHttp().get_json(url) + response = a["success"] + if response is True: + date = a["results"]["last_updated"] + stats = f"**◢ Intellivoid• SpamProtection Info**:\n" + stats += f' • **Updated on**: `{datetime.fromtimestamp(date).strftime("%Y-%m-%d %I:%M:%S %p")}`\n' + stats += f" • **Chat Info**: [Link](t.me/SpamProtectionBot/?start=00_{cmd})\n" + + if a["results"]["attributes"]["is_potential_spammer"] is True: + stats += f" • **User**: `USERxSPAM`\n" + elif a["results"]["attributes"]["is_operator"] is True: + stats += f" • **User**: `USERxOPERATOR`\n" + elif a["results"]["attributes"]["is_agent"] is True: + stats += f" • **User**: `USERxAGENT`\n" + elif a["results"]["attributes"]["is_whitelisted"] is True: + stats += f" • **User**: `USERxWHITELISTED`\n" + + stats += f' • **Type**: `{a["results"]["entity_type"]}`\n' + stats += f' • **Language**: `{a["results"]["language_prediction"]["language"]}`\n' + stats += f' • **Language Probability**: `{a["results"]["language_prediction"]["probability"]}`\n' + stats += f"**Spam Prediction**:\n" + stats += f' • **Ham Prediction**: `{a["results"]["spam_prediction"]["ham_prediction"]}`\n' + stats += f' • **Spam Prediction**: `{a["results"]["spam_prediction"]["spam_prediction"]}`\n' + stats += f'**Blacklisted**: `{a["results"]["attributes"]["is_blacklisted"]}`\n' + if a["results"]["attributes"]["is_blacklisted"] is True: + stats += f' • **Reason**: `{a["results"]["attributes"]["blacklist_reason"]}`\n' + stats += f' • **Flag**: `{a["results"]["attributes"]["blacklist_flag"]}`\n' + stats += f'**PTID**:\n`{a["results"]["private_telegram_id"]}`\n' + results.append( + InlineQueryResultArticle( + title="Spam Info", + description="Search Users spam info", + input_message_content=InputTextMessageContent( + stats, disable_web_page_preview=True + ), + ) + ) + await client.answer_inline_query( + query.id, cache_time=0, results=results + ) + elif text.split()[0] == "lyrics": + cmd = text.split(None, 1)[1] + results = [] + + song = "" + song = Song.find_song(cmd) + if song: + if song.lyrics: + reply = song.format() + else: + reply = "Couldn't find any lyrics for that song! try with artist name along with song if still doesnt work try `.glyrics`" + else: + reply = "lyrics not found! try with artist name along with song if still doesnt work try `.glyrics`" + + if len(reply) > 4095: + reply = "lyrics too big, Try using /lyrics" + + results.append( + InlineQueryResultArticle( + title="Song Lyrics", + description="Click here to see lyrics", + input_message_content=InputTextMessageContent( + reply, disable_web_page_preview=False + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + elif text.split()[0] == "pokedex": + if len(text.split()) < 2: + await client.answer_inline_query( + query.id, + results=answers, + switch_pm_text="Pokemon [text]", + switch_pm_parameter="pokedex", + ) + return + pokedex = text.split(None, 1)[1].strip() + Pokedex = await pokedexinfo(answers, pokedex) + await client.answer_inline_query(query.id, results=Pokedex, cache_time=2) + elif text.split()[0] == "paste": + tex = text.split(None, 1)[1] + answerss = await paste_func(answers, tex) + await client.answer_inline_query(query.id, results=answerss, cache_time=2) + + elif text.split()[0] == "covid": + lel = text.split(None, 1)[1] + results = [] + country = lel.replace(" ", "") + data = await fetch(f"https://corona.lmao.ninja/v2/countries/{country}") + data = await json_prettify(data) + results.append( + InlineQueryResultArticle( + title="Covid Info Gathered succesfully", + description=data, + input_message_content=InputTextMessageContent( + data, disable_web_page_preview=False + ), + ) + ) + await client.answer_inline_query(query.id, results=results, cache_time=2) + elif text.split()[0] == "country": + lel = text.split(None, 1)[1] + results = [] + country = CountryInfo(lel) + try: + a = country.info() + except: + a = "Country Not Avaiable Currently" + name = a.get("name") + bb = a.get("altSpellings") + hu = "" + for p in bb: + hu += p + ", " + + area = a.get("area") + borders = "" + hell = a.get("borders") + for fk in hell: + borders += fk + ", " + + call = "" + WhAt = a.get("callingCodes") + for what in WhAt: + call += what + " " + + capital = a.get("capital") + currencies = "" + fker = a.get("currencies") + for FKer in fker: + currencies += FKer + ", " + + HmM = a.get("demonym") + geo = a.get("geoJSON") + pablo = geo.get("features") + Pablo = pablo[0] + PAblo = Pablo.get("geometry") + EsCoBaR = PAblo.get("type") + iso = "" + iSo = a.get("ISO") + for hitler in iSo: + po = iSo.get(hitler) + iso += po + ", " + fla = iSo.get("alpha2") + fla.upper() + + languages = a.get("languages") + lMAO = "" + for lmao in languages: + lMAO += lmao + ", " + + nonive = a.get("nativeName") + waste = a.get("population") + reg = a.get("region") + sub = a.get("subregion") + tik = a.get("timezones") + tom = "" + for jerry in tik: + tom += jerry + ", " + + GOT = a.get("tld") + lanester = "" + for targaryen in GOT: + lanester += targaryen + ", " + + wiki = a.get("wiki") + + caption = f"""Information Gathered Successfully + + Country Name:- {name} + Alternative Spellings:- {hu} + Country Area:- {area} square kilometers + Borders:- {borders} + Calling Codes:- {call} + Country's Capital:- {capital} + Country's currency:- {currencies} + Demonym:- {HmM} + Country Type:- {EsCoBaR} + ISO Names:- {iso} + Languages:- {lMAO} + Native Name:- {nonive} + population:- {waste} + Region:- {reg} + Sub Region:- {sub} + Time Zones:- {tom} + Top Level Domain:- {lanester} + wikipedia:- {wiki} + Gathered By Daisy X.
+ """ + results.append( + InlineQueryResultArticle( + title=f"Infomation of {name}", + description=f""" + Country Name:- {name} + Alternative Spellings:- {hu} + Country Area:- {area} square kilometers + Borders:- {borders} + Calling Codes:- {call} + Country's Capital:- {capital} + + Touch for more info + """, + input_message_content=InputTextMessageContent( + caption, parse_mode="HTML", disable_web_page_preview=True + ), + ) + ) + await client.answer_inline_query(query.id, results=results, cache_time=2) + + elif text.split()[0] == "fakegen": + results = [] + fake = Faker() + name = str(fake.name()) + fake.add_provider(internet) + address = str(fake.address()) + ip = fake.ipv4_private() + cc = fake.credit_card_full() + email = fake.ascii_free_email() + job = fake.job() + android = fake.android_platform_token() + pc = fake.chrome() + res = f" Fake Information Generated\nName :-{name}\n\nAddress:-{address}\n\nIP ADDRESS:-{ip}\n\ncredit card:-{cc}\n\nEmail Id:-{email}\n\nJob:-{job}\n\nandroid user agent:-{android}\n\nPc user agent:-{pc}" + results.append( + InlineQueryResultArticle( + title="Fake infomation gathered", + description="Click here to see them", + input_message_content=InputTextMessageContent( + res, parse_mode="HTML", disable_web_page_preview=True + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "cs": + results = [] + score_page = "http://static.cricinfo.com/rss/livescores.xml" + page = urllib.request.urlopen(score_page) + soup = BeautifulSoup(page, "html.parser") + result = soup.find_all("description") + Sed = "" + for match in result: + Sed += match.get_text() + "\n\n" + res = f"Match information gathered successful\n\n\n{Sed}" + results.append( + InlineQueryResultArticle( + title="Match information gathered", + description="Click here to see them", + input_message_content=InputTextMessageContent( + res, parse_mode="HTML", disable_web_page_preview=False + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "antonyms": + results = [] + lel = text.split(None, 1)[1] + word = f"{lel}" + let = dictionary.antonym(word) + set = str(let) + jet = set.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + results.append( + InlineQueryResultArticle( + title=f"antonyms for {lel}", + description=got, + input_message_content=InputTextMessageContent( + got, disable_web_page_preview=False + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "synonyms": + results = [] + lel = text.split(None, 1)[1] + word = f"{lel}" + let = dictionary.synonym(word) + set = str(let) + jet = set.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + results.append( + InlineQueryResultArticle( + title=f"antonyms for {lel}", + description=got, + input_message_content=InputTextMessageContent( + got, disable_web_page_preview=False + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "define": + results = [] + lel = text.split(None, 1)[1] + word = f"{lel}" + let = dictionary.meaning(word) + set = str(let) + jet = set.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + results.append( + InlineQueryResultArticle( + title=f"Definition for {lel}", + description=got, + input_message_content=InputTextMessageContent( + got, disable_web_page_preview=False + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "weather": + results = [] + sample_url = "https://api.openweathermap.org/data/2.5/weather?q={}&APPID={}&units=metric" + input_str = text.split(None, 1)[1] + async with aiohttp.ClientSession() as session: + response_api_zero = await session.get( + sample_url.format(input_str, OPENWEATHERMAP_ID) + ) + response_api = await response_api_zero.json() + if response_api["cod"] == 200: + country_code = response_api["sys"]["country"] + country_time_zone = int(response_api["timezone"]) + sun_rise_time = int(response_api["sys"]["sunrise"]) + country_time_zone + sun_set_time = int(response_api["sys"]["sunset"]) + country_time_zone + lol = """ + WEATHER INFO GATHERED + Location: {} + Temperature ☀️: {}°С + minimium: {}°С + maximum : {}°С + Humidity 🌤**: {}% + Wind 💨: {}m/s + Clouds ☁️: {}hpa + Sunrise 🌤: {} {} + Sunset 🌝: {} {}""".format( + input_str, + response_api["main"]["temp"], + response_api["main"]["temp_min"], + response_api["main"]["temp_max"], + response_api["main"]["humidity"], + response_api["wind"]["speed"], + response_api["clouds"]["all"], + # response_api["main"]["pressure"], + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(sun_rise_time)), + country_code, + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(sun_set_time)), + country_code, + ) + results.append( + InlineQueryResultArticle( + title=f"Weather Information", + description=lol, + input_message_content=InputTextMessageContent( + lol, disable_web_page_preview=True + ), + ) + ) + await client.answer_inline_query( + query.id, cache_time=0, results=results + ) + + elif text.split()[0] == "datetime": + results = [] + gay = text.split(None, 1)[1] + lel = gay + query_timezone = lel.lower() + if len(query_timezone) == 2: + result = generate_time(query_timezone, ["countryCode"]) + else: + result = generate_time(query_timezone, ["zoneName", "countryName"]) + + if not result: + result = f"Timezone info not available for {lel}" + + results.append( + InlineQueryResultArticle( + title=f"Date & Time info of {lel}", + description=result, + input_message_content=InputTextMessageContent( + result, disable_web_page_preview=False, parse_mode="html" + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "app": + rip = [] + app_name = text.split(None, 1)[1] + remove_space = app_name.split(" ") + final_name = "+".join(remove_space) + page = requests.get( + "https://play.google.com/store/search?q=" + final_name + "&c=apps" + ) + str(page.status_code) + soup = BeautifulSoup(page.content, "lxml", from_encoding="utf-8") + results = soup.findAll("div", "ZmHEEd") + app_name = ( + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "WsMG1c nnK0zc") + .text + ) + app_dev = ( + results[0].findNext("div", "Vpfmgd").findNext("div", "KoLSrc").text + ) + app_dev_link = ( + "https://play.google.com" + + results[0].findNext("div", "Vpfmgd").findNext("a", "mnKHRc")["href"] + ) + app_rating = ( + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "pf5lIe") + .find("div")["aria-label"] + ) + app_link = ( + "https://play.google.com" + + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "vU6FJ p63iDd") + .a["href"] + ) + app_icon = ( + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "uzcko") + .img["data-src"] + ) + app_details = "📲​" + app_details += " " + app_name + "" + app_details += ( + "\n\nDeveloper : " + + app_dev + + "" + ) + app_details += "\nRating : " + app_rating.replace( + "Rated ", "⭐ " + ).replace(" out of ", "/").replace(" stars", "", 1).replace( + " stars", "⭐ " + ).replace( + "five", "5" + ) + app_details += ( + "\nFeatures : View in Play Store" + ) + app_details += "\n\n===> @DaisySupport_Official <===" + rip.append( + InlineQueryResultArticle( + title=f"Datails of {app_name}", + description=app_details, + input_message_content=InputTextMessageContent( + app_details, disable_web_page_preview=True, parse_mode="html" + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=rip) + + elif text.split()[0] == "gh": + results = [] + gett = text.split(None, 1)[1] + text = gett + ' "site:github.com"' + gresults = await GoogleSearch().async_search(text, 1) + result = "" + for i in range(4): + try: + title = gresults["titles"][i].replace("\n", " ") + source = gresults["links"][i] + description = gresults["descriptions"][i] + result += f"[{title}]({source})\n" + result += f"`{description}`\n\n" + except IndexError: + pass + results.append( + InlineQueryResultArticle( + title=f"Results for {gett}", + description=f" Github info of {title}\n Touch to read", + input_message_content=InputTextMessageContent( + result, disable_web_page_preview=True + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + elif text.split()[0] == "so": + results = [] + gett = text.split(None, 1)[1] + text = gett + ' "site:stackoverflow.com"' + gresults = await GoogleSearch().async_search(text, 1) + result = "" + for i in range(4): + try: + title = gresults["titles"][i].replace("\n", " ") + source = gresults["links"][i] + description = gresults["descriptions"][i] + result += f"[{title}]({source})\n" + result += f"`{description}`\n\n" + except IndexError: + pass + results.append( + InlineQueryResultArticle( + title=f"Stack overflow saerch - {title}", + description=f" Touch to view search results on {title}", + input_message_content=InputTextMessageContent( + result, disable_web_page_preview=True + ), + ) + ) + await client.answer_inline_query(query.id, cache_time=0, results=results) + + except (IndexError, TypeError, KeyError, ValueError): + return + + +def generate_time(to_find: str, findtype: List[str]) -> str: + data = requests.get( + f"http://api.timezonedb.com/v2.1/list-time-zone" + f"?key={TIME_API_KEY}" + f"&format=json" + f"&fields=countryCode,countryName,zoneName,gmtOffset,timestamp,dst" + ).json() + + for zone in data["zones"]: + for eachtype in findtype: + if to_find in zone[eachtype].lower(): + country_name = zone["countryName"] + country_zone = zone["zoneName"] + country_code = zone["countryCode"] + + if zone["dst"] == 1: + daylight_saving = "Yes" + else: + daylight_saving = "No" + + date_fmt = r"%d-%m-%Y" + time_fmt = r"%H:%M:%S" + day_fmt = r"%A" + gmt_offset = zone["gmtOffset"] + timestamp = datetime.datetime.now( + datetime.timezone.utc + ) + datetime.timedelta(seconds=gmt_offset) + current_date = timestamp.strftime(date_fmt) + current_time = timestamp.strftime(time_fmt) + current_day = timestamp.strftime(day_fmt) + + break + + try: + result = ( + f" DATE AND TIME OF COUNTRY" + f"🌍Country :{country_name}\n" + f"⏳Zone Name : {country_zone}\n" + f"🗺Country Code: {country_code}\n" + f"🌞Daylight saving : {daylight_saving}\n" + f"🌅Day : {current_day}\n" + f"⌚Current Time : {current_time}\n" + f"📆Current Date :{current_date}" + ) + except BaseException: + result = None + + return result diff --git a/DaisyX/modules/install.py b/DaisyX/modules/install.py new file mode 100644 index 00000000..0738ee80 --- /dev/null +++ b/DaisyX/modules/install.py @@ -0,0 +1,29 @@ +# All Credit To William Butcher Bot. +# Ported This Plugin here By Devil from wbb. +import os + +from pyrogram import filters + +from DaisyX import OWNER_ID +from DaisyX.services.pyrogram import pbot as app + + +@app.on_message(filters.command("install") & filters.user(OWNER_ID)) +async def install_module(_, message): + if not message.reply_to_message: + await message.reply_text("Reply To A .py File To Install It.") + return + if not message.reply_to_message.document: + await message.reply_text("Reply To A .py File To Install It.") + return + document = message.reply_to_message.document + if document.mime_type != "text/x-python": + await message.reply_text("INVALID_MIME_TYPE, Reply To A Correct .py File.") + return + m = await message.reply_text("**Installing Module**") + await message.reply_to_message.download(f"./DaisyX/modules/{document.file_name}") + await m.edit("**Restarting**") + os.execvp( + f"python{str(pyver.split(' ')[0])[:3]}", + [f"python{str(pyver.split(' ')[0])[:3]}", "-m", "DaisyX"], + ) diff --git a/DaisyX/modules/invitelink.py b/DaisyX/modules/invitelink.py new file mode 100644 index 00000000..ff75d257 --- /dev/null +++ b/DaisyX/modules/invitelink.py @@ -0,0 +1,57 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from pyrogram import filters + +from DaisyX.function.pluginhelpers import admins_only +from DaisyX.services.pyrogram import pbot + +__HELP__ = """ +Classic filters are just like marie's filter system. If you still like that kind of filter system +**Admin Only** + - /cfilter : Every time someone says "word", the bot will reply with "message" +You can also include buttons in filters, example send `/savefilter google` in reply to `Click Here To Open Google | [Button.url('Google', 'google.com')]` + - /stopcfilter : Stop that filter. + - /stopallcfilters: Delete all filters in the current chat. +**Admin+Non-Admin** + - /cfilters: List all active filters in the chat + + **Please note classic filters can be unstable. We recommend you to use /addfilter** +""" + + +@pbot.on_message( + filters.command("invitelink") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def invitelink(client, message): + chid = message.chat.id + try: + invitelink = await client.export_chat_invite_link(chid) + except: + await message.reply_text( + "Add me as admin of yor group first", + ) + return + await message.reply_text(f"Invite link generated successfully \n\n {invitelink}") + + +@pbot.on_message(filters.command("cfilterhelp") & ~filters.private & ~filters.edited) +@admins_only +async def filtersghelp(client, message): + await client.send_message(message.chat.id, text=__HELP__) diff --git a/DaisyX/modules/json.py b/DaisyX/modules/json.py new file mode 100644 index 00000000..91f64c31 --- /dev/null +++ b/DaisyX/modules/json.py @@ -0,0 +1,83 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import io + +from telethon import types +from telethon.tl import functions, types +from telethon.tl.types import * + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as borg + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await borg(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await borg.get_peer_id(user) + ps = ( + await borg(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/json$") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + the_real_message = None + reply_to_id = None + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + the_real_message = previous_message.stringify() + reply_to_id = event.reply_to_msg_id + else: + the_real_message = event.stringify() + reply_to_id = event.message.id + if len(the_real_message) > 4095: + with io.BytesIO(str.encode(the_real_message)) as out_file: + out_file.name = "json.text" + await borg.send_file( + event.chat_id, + out_file, + force_document=True, + allow_cache=False, + reply_to=reply_to_id, + ) + await event.delete() + else: + await event.reply("`{}`".format(the_real_message)) diff --git a/DaisyX/modules/karma.py b/DaisyX/modules/karma.py new file mode 100644 index 00000000..22028b35 --- /dev/null +++ b/DaisyX/modules/karma.py @@ -0,0 +1,222 @@ +# Ported From WilliamButcher Bot. +# Credits Goes to WilliamButcherBot + +from typing import Dict, Union + +from pyrogram import filters + +from DaisyX.db.mongo_helpers.karma import is_karma_on, karma_off, karma_on +from DaisyX.function.pluginhelpers import member_permissions +from DaisyX.services.mongo2 import db +from DaisyX.services.pyrogram import pbot as app + +karmadb = db.karma +karma_positive_group = 3 +karma_negative_group = 4 + + +async def int_to_alpha(user_id: int) -> str: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + text = "" + user_id = str(user_id) + for i in user_id: + text += alphabet[int(i)] + return text + + +async def alpha_to_int(user_id_alphabet: str) -> int: + alphabet = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] + user_id = "" + for i in user_id_alphabet: + index = alphabet.index(i) + user_id += str(index) + user_id = int(user_id) + return user_id + + +async def get_karmas_count() -> dict: + chats = karmadb.find({"chat_id": {"$lt": 0}}) + if not chats: + return {} + chats_count = 0 + karmas_count = 0 + for chat in await chats.to_list(length=1000000): + for i in chat["karma"]: + karmas_count += chat["karma"][i]["karma"] + chats_count += 1 + return {"chats_count": chats_count, "karmas_count": karmas_count} + + +async def get_karmas(chat_id: int) -> Dict[str, int]: + karma = await karmadb.find_one({"chat_id": chat_id}) + if karma: + karma = karma["karma"] + else: + karma = {} + return karma + + +async def get_karma(chat_id: int, name: str) -> Union[bool, dict]: + name = name.lower().strip() + karmas = await get_karmas(chat_id) + if name in karmas: + return karmas[name] + + +async def update_karma(chat_id: int, name: str, karma: dict): + name = name.lower().strip() + karmas = await get_karmas(chat_id) + karmas[name] = karma + await karmadb.update_one( + {"chat_id": chat_id}, {"$set": {"karma": karmas}}, upsert=True + ) + + +_mod_name_ = "Karma" +_help_ = """[UPVOTE] - Use upvote keywords like "+", "+1", "thanks" etc to upvote a message. +[DOWNVOTE] - Use downvote keywords like "-", "-1", etc to downvote a message. +Reply to a message with /karma to check a user's karma +Send /karma without replying to any message to chek karma list of top 10 users + Special Credits to WilliamButcherBot """ + + +regex_upvote = r"^((?i)\+|\+\+|\+1|thx|tnx|ty|thank you|thanx|thanks|pro|cool|good|👍)$" +regex_downvote = r"^(\-|\-\-|\-1|👎)$" + + +@app.on_message( + filters.text + & filters.group + & filters.incoming + & filters.reply + & filters.regex(regex_upvote) + & ~filters.via_bot + & ~filters.bot + & ~filters.edited, + group=karma_positive_group, +) +async def upvote(_, message): + + if not await is_karma_on(message.chat.id): + return + try: + if message.reply_to_message.from_user.id == message.from_user.id: + return + except: + return + chat_id = message.chat.id + try: + user_id = message.reply_to_message.from_user.id + except: + return + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma + 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"Incremented Karma of {user_mention} By 1 \nTotal Points: {karma}" + ) + + +@app.on_message( + filters.text + & filters.group + & filters.incoming + & filters.reply + & filters.regex(regex_downvote) + & ~filters.via_bot + & ~filters.bot + & ~filters.edited, + group=karma_negative_group, +) +async def downvote(_, message): + + if not await is_karma_on(message.chat.id): + return + try: + if message.reply_to_message.from_user.id == message.from_user.id: + return + except: + return + chat_id = message.chat.id + try: + user_id = message.reply_to_message.from_user.id + except: + return + user_mention = message.reply_to_message.from_user.mention + current_karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if current_karma: + current_karma = current_karma["karma"] + karma = current_karma - 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + else: + karma = 1 + new_karma = {"karma": karma} + await update_karma(chat_id, await int_to_alpha(user_id), new_karma) + await message.reply_text( + f"Decremented Karma Of {user_mention} By 1 \nTotal Points: {karma}" + ) + + +@app.on_message(filters.command("karma") & filters.group) +async def karma(_, message): + chat_id = message.chat.id + if len(message.command) != 2: + if not message.reply_to_message: + karma = await get_karmas(chat_id) + msg = f"**Karma list of {message.chat.title}:- **\n" + limit = 0 + karma_dicc = {} + for i in karma: + user_id = await alpha_to_int(i) + user_karma = karma[i]["karma"] + karma_dicc[str(user_id)] = user_karma + karma_arranged = dict( + sorted(karma_dicc.items(), key=lambda item: item[1], reverse=True) + ) + for user_idd, karma_count in karma_arranged.items(): + if limit > 9: + break + try: + user_name = (await app.get_users(int(user_idd))).username + except Exception: + continue + msg += f"{user_name} : `{karma_count}`\n" + limit += 1 + await message.reply_text(msg) + else: + user_id = message.reply_to_message.from_user.id + karma = await get_karma(chat_id, await int_to_alpha(user_id)) + if karma: + karma = karma["karma"] + await message.reply_text(f"**Total Points**: __{karma}__") + else: + karma = 0 + await message.reply_text(f"**Total Points**: __{karma}__") + return + status = message.text.split(None, 1)[1].strip() + status = status.lower() + chat_id = message.chat.id + user_id = message.from_user.id + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await message.reply_text("You don't have enough permissions.") + return + if status == "on" or status == "ON": + await karma_on(chat_id) + await message.reply_text( + f"Added Chat {chat_id} To Database. Karma will be enabled here" + ) + elif status == "off" or status == "OFF": + await karma_off(chat_id) + await message.reply_text( + f"Removed Chat {chat_id} To Database. Karma will be disabled here" + ) diff --git a/DaisyX/modules/langtools.py b/DaisyX/modules/langtools.py new file mode 100644 index 00000000..83c2aed9 --- /dev/null +++ b/DaisyX/modules/langtools.py @@ -0,0 +1,135 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import json + +import requests +from google_trans_new import google_translator +from PyDictionary import PyDictionary +from telethon import * +from telethon.tl.types import * + +from DaisyX.services.events import register + +API_KEY = "6ae0c3a0-afdc-4532-a810-82ded0054236" +URL = "http://services.gingersoftware.com/Ginger/correct/json/GingerTheText" + + +@register(pattern="^/tr ?(.*)") +async def _(event): + input_str = event.pattern_match.group(1) + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + text = previous_message.message + lan = input_str or "en" + elif "|" in input_str: + lan, text = input_str.split("|") + else: + await event.reply( + "`/tr ` as reply to a message or `/tr | `" + ) + return + text = text.strip() + lan = lan.strip() + translator = google_translator() + try: + translated = translator.translate(text, lang_tgt=lan) + after_tr_text = translated + detect_result = translator.detect(text) + output_str = ("**TRANSLATED Succesfully** from {} to {}\n\n" "{}").format( + detect_result[0], lan, after_tr_text + ) + await event.reply(output_str) + except Exception as exc: + await event.reply(str(exc)) + + +@register(pattern="^/spell(?: |$)(.*)") +async def _(event): + ctext = await event.get_reply_message() + msg = ctext.text + # print (msg) + params = dict(lang="US", clientVersion="2.0", apiKey=API_KEY, text=msg) + + res = requests.get(URL, params=params) + changes = json.loads(res.text).get("LightGingerTheTextResult") + curr_string = "" + prev_end = 0 + + for change in changes: + start = change.get("From") + end = change.get("To") + 1 + suggestions = change.get("Suggestions") + if suggestions: + sugg_str = suggestions[0].get("Text") + curr_string += msg[prev_end:start] + sugg_str + prev_end = end + + curr_string += msg[prev_end:] + await event.reply(curr_string) + + +dictionary = PyDictionary() + + +@register(pattern="^/define") +async def _(event): + text = event.text[len("/define ") :] + word = f"{text}" + let = dictionary.meaning(word) + set = str(let) + jet = set.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + await event.reply(got) + + +@register(pattern="^/synonyms") +async def _(event): + text = event.text[len("/synonyms ") :] + word = f"{text}" + let = dictionary.synonym(word) + set = str(let) + jet = set.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + await event.reply(got) + + +@register(pattern="^/antonyms") +async def _(event): + text = message.text[len("/antonyms ") :] + word = f"{text}" + let = dictionary.antonym(word) + set = str(let) + jet = set.replace("{", "") + net = jet.replace("}", "") + got = net.replace("'", "") + await event.reply(got) + + +__help__ = """ + - /tr language code or /tr language code , text: Type in reply to a message or (/tr language code , text) to get it's translation in the destination language + - /define text: Type the word or expression you want to search\nFor example /define lesbian + - /spell: while replying to a message, will reply with a grammar corrected version + - /forbesify: Correct your punctuations better use the advanged spell module + - /synonyms word: Find the synonyms of a word + - /antonyms word: Find the antonyms of a word +""" + +__mod_name__ = "Lang-Tools" diff --git a/DaisyX/modules/language.py b/DaisyX/modules/language.py new file mode 100644 index 00000000..6d75669b --- /dev/null +++ b/DaisyX/modules/language.py @@ -0,0 +1,178 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from contextlib import suppress + +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.callback_data import CallbackData +from aiogram.utils.exceptions import MessageNotModified + +from DaisyX.decorator import register +from DaisyX.services.mongo import db + +from .utils.language import ( + LANGUAGES, + change_chat_lang, + get_chat_lang_info, + get_strings, + get_strings_dec, +) +from .utils.message import get_arg + +select_lang_cb = CallbackData("select_lang_cb", "lang", "back_btn") +translators_lang_cb = CallbackData("translators_lang_cb", "lang") + + +@register(cmds="lang", no_args=True, user_can_change_info=True) +async def select_lang_cmd(message): + await select_lang_keyboard(message) + + +@get_strings_dec("language") +async def select_lang_keyboard(message, strings, edit=False): + markup = InlineKeyboardMarkup(row_width=2) + task = message.reply if edit is False else message.edit_text + + lang_info = await get_chat_lang_info(message.chat.id) + + if message.chat.type == "private": + text = strings["your_lang"].format( + lang=lang_info["flag"] + " " + lang_info["babel"].display_name + ) + text += strings["select_pm_lang"] + + # TODO: Connected chat lang info + + else: + text = strings["chat_lang"].format( + lang=lang_info["flag"] + " " + lang_info["babel"].display_name + ) + text += strings["select_chat_lang"] + + for lang in LANGUAGES.values(): + lang_info = lang["language_info"] + markup.insert( + InlineKeyboardButton( + lang_info["flag"] + " " + lang_info["babel"].display_name, + callback_data=select_lang_cb.new( + lang=lang_info["code"], back_btn=False if edit is False else True + ), + ) + ) + + markup.add( + InlineKeyboardButton( + strings["crowdin_btn"], url="https://t.me/Daisysupport_official" + ) + ) + if edit: + markup.add(InlineKeyboardButton(strings["back"], callback_data="go_to_start")) + with suppress(MessageNotModified): + await task(text, reply_markup=markup) + + +async def change_lang(message, lang, e=False, back_btn=False): + chat_id = message.chat.id + await change_chat_lang(chat_id, lang) + + strings = await get_strings(chat_id, "language") + + lang_info = LANGUAGES[lang]["language_info"] + + text = strings["lang_changed"].format( + lang_name=lang_info["flag"] + " " + lang_info["babel"].display_name + ) + text += strings["help_us_translate"] + + markup = InlineKeyboardMarkup() + + if "translators" in lang_info: + markup.add( + InlineKeyboardButton( + strings["see_translators"], + callback_data=translators_lang_cb.new(lang=lang), + ) + ) + + if back_btn == "True": + # Callback_data converts boolean to str + markup.add(InlineKeyboardButton(strings["back"], callback_data="go_to_start")) + + if e: + with suppress(MessageNotModified): + await message.edit_text( + text, reply_markup=markup, disable_web_page_preview=True + ) + else: + await message.reply(text, reply_markup=markup, disable_web_page_preview=True) + + +@register(cmds="lang", has_args=True, user_can_change_info=True) +@get_strings_dec("language") +async def select_lang_msg(message, strings): + lang = get_arg(message).lower() + + if lang not in LANGUAGES: + await message.reply(strings["not_supported_lang"]) + return + + await change_lang(message, lang) + + +@register( + select_lang_cb.filter(), + f="cb", + allow_kwargs=True, +) +async def select_lang_callback(query, callback_data=None, **kwargs): + lang = callback_data["lang"] + back_btn = callback_data["back_btn"] + await change_lang(query.message, lang, e=True, back_btn=back_btn) + + +async def __stats__(): + return f"* {len(LANGUAGES)} languages loaded.\n" + + +async def __export__(chat_id): + lang = await get_chat_lang_info(chat_id) + + return {"language": lang["code"]} + + +async def __import__(chat_id, data): + if data not in LANGUAGES: + return + await db.lang.update_one( + {"chat_id": chat_id}, {"$set": {"lang": data}}, upsert=True + ) + + +__mod_name__ = "Languages" + +__help__ = """ +This module is dedicated towards utlising Daisy's localization feature! You can also contribute for improving localization in Daisy! + +Available commands: +- /lang: Shows a list of avaible languages +- /lang (language codename): Sets a language + +Example: /lang +Daisy will send you bunch of inline buttons where you can select your prefered language interatively without any hassles! +""" diff --git a/DaisyX/modules/lock.py b/DaisyX/modules/lock.py new file mode 100644 index 00000000..d99b3193 --- /dev/null +++ b/DaisyX/modules/lock.py @@ -0,0 +1,136 @@ +# Credit To @TheHamkerCat and his bot William Butcher Bot. + + +""" +MIT License +Copyright (c) 2021 TheHamkerCat +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +""" + +from pyrogram import filters +from pyrogram.errors.exceptions.bad_request_400 import ChatNotModified +from pyrogram.types import ChatPermissions + +from DaisyX.function.pluginhelpers import current_chat_permissions, member_permissions +from DaisyX.services.pyrogram import pbot + +incorrect_parameters = "Incorrect Parameters, Check Locks Section In Help." +data = { + "messages": "can_send_messages", + "stickers": "can_send_stickers", + "gifs": "can_send_animations", + "media": "can_send_media_messages", + "games": "can_send_games", + "inline": "can_use_inline_bots", + "url_prev": "can_add_web_page_previews", + "polls": "can_send_polls", + "info": "can_change_info", + "invite": "can_invite_users", + "pin": "can_pin_messages", +} + + +async def tg_lock(message, permissions: list, perm: str, lock: bool): + if lock: + if perm not in permissions: + await message.reply_text("Already locked.") + return + else: + if perm in permissions: + await message.reply_text("Already Unlocked.") + return + (permissions.remove(perm) if lock else permissions.append(perm)) + permissions = {perm: True for perm in list(set(permissions))} + try: + await pbot.set_chat_permissions(message.chat.id, ChatPermissions(**permissions)) + except ChatNotModified: + await message.reply_text("To unlock this, you have to unlock 'messages' first.") + return + await message.reply_text(("Locked." if lock else "Unlocked.")) + + +@pbot.on_message(filters.command(["locktypes", "chatlocks"]) & ~filters.private) +async def wew(_, message): + lol = """ + **Locktypes available for this chat: ** + +- all +- messages +- stickers +- gifs +- media +- polls +- games +- url_prev +- inline +- pin +- info +- invite + +*Note: For locking url try + `/urllock [on|off]` +""" + await message.reply(lol) + + +@pbot.on_message(filters.command(["lock", "unlock"]) & ~filters.private) +async def locks_func(_, message): + try: + user_id = message.from_user.id + chat_id = message.chat.id + if len(message.command) != 2: + await message.reply_text(incorrect_parameters) + return + + parameter = message.text.strip().split(None, 1)[1].lower() + state = message.command[0].lower() + if parameter not in data and parameter != "all": + await message.reply_text(incorrect_parameters) + return + + permissions = await member_permissions(chat_id, user_id) + if "can_restrict_members" not in permissions: + await message.reply_text("You Don't Have Enough Permissions.") + return + + permissions = await current_chat_permissions(chat_id) + if parameter in data: + await tg_lock( + message, + permissions, + data[parameter], + True if state == "lock" else False, + ) + return + elif parameter == "all" and state == "lock": + await _.set_chat_permissions(chat_id, ChatPermissions()) + await message.reply_text("Locked Everything.") + except Exception as e: + await message.reply_text(str(e)) + print(e) + + +@pbot.on_message(filters.command("locks") & ~filters.private) +async def locktypes(_, message): + permissions = await current_chat_permissions(message.chat.id) + if not permissions: + await message.reply_text("No Permissions.") + return + perms = "" + for i in permissions: + perms += f"__**{i}**__\n" + await message.reply_text(perms) diff --git a/DaisyX/modules/lockshelp.py b/DaisyX/modules/lockshelp.py new file mode 100644 index 00000000..6789c670 --- /dev/null +++ b/DaisyX/modules/lockshelp.py @@ -0,0 +1,34 @@ +__mod_name__ = "Locks" + +__help__ = """ +Use this feature to block users from sending specific message types to your group! + + Basic Locks + +Comands: +- /locks or /locktypes: Use this command to know current state of your locks in your group! +- /lock (locktype): Locks a type of messages +- /unlock (locktype): Unlocks a type of message + +Locktypes: +- all +- messages +- stickers +- gifs +- media +- polls +- games +- url_prev +- inline +- pin +- info +- invite + + + Url Lock +Block links sent by users in your group + +Commands: +- /urllock [on/off]: Enable/Disable URL Lock + +""" diff --git a/DaisyX/modules/math.py b/DaisyX/modules/math.py new file mode 100644 index 00000000..47b5627b --- /dev/null +++ b/DaisyX/modules/math.py @@ -0,0 +1,182 @@ +# Written by Inukaasith for the Daisy project +# This file is part of DaisyXBot (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + + +import json +import math + +import requests + +from DaisyX.decorator import register + +from .utils.disable import disableable_dec +from .utils.message import get_args_str + + +@register(cmds=["math", "simplify"]) +@disableable_dec("math") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/simplify/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds=["factor", "factorize"]) +@disableable_dec("factor") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/factor/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds="derive") +@disableable_dec("derive") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/derive/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds="integrate") +@disableable_dec("integrate") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/integrate/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds="zeroes") +@disableable_dec("zeroes") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/zeroes/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds="tangent") +@disableable_dec("tangent") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/tangent/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds="area") +@disableable_dec("area") +async def _(message): + args = get_args_str(message) + response = requests.get(f"https://newton.now.sh/api/v2/area/{args}") + c = response.text + obj = json.loads(c) + j = obj["result"] + await message.reply(j) + + +@register(cmds="cos") +@disableable_dec("cos") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.cos(int(args)))) + + +@register(cmds="sin") +@disableable_dec("sin") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.sin(int(args)))) + + +@register(cmds="tan") +@disableable_dec("tan") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.tan(int(args)))) + + +@register(cmds="arccos") +@disableable_dec("arccos") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.acos(int(args)))) + + +@register(cmds="arcsin") +@disableable_dec("arcsin") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.asin(int(args)))) + + +@register(cmds="arctan") +@disableable_dec("arctan") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.atan(int(args)))) + + +@register(cmds="abs") +@disableable_dec("abs") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.fabs(int(args)))) + + +@register(cmds="log") +@disableable_dec("log") +async def _(message): + args = get_args_str(message) + await message.reply(str(math.log(int(args)))) + + +__help__ = """ +Solves complex math problems using https://newton.now.sh and python math module + - /simplify- Math /math 2^2+2(2) + - /factor - Factor /factor x^2 + 2x + - /derive - Derive /derive x^2+2x + - /integrate - Integrate /integrate x^2+2x + - /zeroes - Find 0's /zeroes x^2+2x + - /tangent - Find Tangent /tangent 2lx^ + - /area - Area Under Curve /area 2:4lx^3` + - /cos - Cosine /cos pi + - /sin - Sine /sin 0 + - /tan - Tangent /tan 0 + - /arccos - Inverse Cosine /arccos 1 + - /arcsin - Inverse Sine /arcsin 0 + - /arctan - Inverse Tangent /arctan 0 + - /abs - Absolute Value /abs -1 + - /log* - Logarithm /log 2l8 + +Keep in mind, To find the tangent line of a function at a certain x value, send the request as c|f(x) where c is the given x value and f(x) is the function expression, the separator is a vertical bar '|'. See the table above for an example request. +To find the area under a function, send the request as c:d|f(x) where c is the starting x value, d is the ending x value, and f(x) is the function under which you want the curve between the two x values. +To compute fractions, enter expressions as numerator(over)denominator. For example, to process 2/4 you must send in your expression as 2(over)4. The result expression will be in standard math notation (1/2, 3/4). +""" + +__mod_name__ = "Maths" diff --git a/DaisyX/modules/memes.py b/DaisyX/modules/memes.py new file mode 100644 index 00000000..a74114df --- /dev/null +++ b/DaisyX/modules/memes.py @@ -0,0 +1,1798 @@ +# This file is copied from @Missjuliarobot +# Full credits to original author + +import asyncio +import io +import json +import os +import random +import re +import string +import subprocess +import textwrap +import urllib.request +from random import randint, randrange, uniform + +import emoji +import nltk +from cowpy import cow +from fontTools.ttLib import TTFont +from PIL import Image, ImageDraw, ImageEnhance, ImageFont, ImageOps +from selenium import webdriver +from selenium.webdriver.chrome.options import Options +from telethon import * +from telethon.tl import functions +from telethon.tl.types import * +from zalgo_text import zalgo + +from DaisyX import * +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot +from DaisyX.services.telethonuserbot import ubot + +nltk.download("punkt") +nltk.download("averaged_perceptron_tagger") + +WIDE_MAP = {i: i + 0xFEE0 for i in range(0x21, 0x7F)} +WIDE_MAP[0x20] = 0x3000 + + +@register(pattern="^/owu$") +async def msg(event): + + reply_tex = await event.get_reply_message() + reply_text = reply_tex.text + if reply_text is None: + await event.reply("Reply to a message to make meme.") + return + faces = [ + "(・`ω´・)", + ";;w;;", + "owo", + "UwU", + ">w<", + "^w^", + r"\(^o\) (/o^)/", + "( ^ _ ^)∠☆", + "(ô_ô)", + "~:o", + ";____;", + "(*^*)", + "(>_", + "(♥_♥)", + "*(^O^)*", + "((+_+))", + ] + text = re.sub(r"[rl]", "w", reply_text) + text = re.sub(r"[rl]", "w", reply_text) + text = re.sub(r"[RL]", "W", text) + text = re.sub(r"[RL]", "W", text) + text = re.sub(r"n([aeiouaeiou])", r"ny\1", text) + text = re.sub(r"n([aeiou])", r"ny\1", text) + text = re.sub(r"N([aeiouAEIOU])", r"Ny\1", text) + text = re.sub(r"N([aeiouAEIOU])", r"Ny\1", text) + text = re.sub(r"\!+", " " + random.choice(faces), text) + text = re.sub(r"!+", " " + random.choice(faces), text) + text = text.replace("ove", "uv") + text = text.replace("ove", "uv") + text += " " + random.choice(faces) + await event.reply(text) + + +@register(pattern="^/copypasta$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message tto make meme.") + return + emojis = [ + "😂", + "😂", + "👌", + "✌", + "💞", + "👍", + "👌", + "💯", + "🎶", + "👀", + "😂", + "👓", + "👏", + "👐", + "🍕", + "💥", + "🍴", + "💦", + "💦", + "🍑", + "🍆", + "😩", + "😏", + "👉👌", + "👀", + "👅", + "😩", + "🚰", + ] + reply_text = random.choice(emojis) + b_char = random.choice(rtext).lower() + for c in rtext: + if c == " ": + reply_text += random.choice(emojis) + elif c in emojis: + reply_text += c + reply_text += random.choice(emojis) + elif c.lower() == b_char: + reply_text += "🅱️" + else: + if bool(random.getrandbits(1)): + reply_text += c.upper() + else: + reply_text += c.lower() + reply_text += random.choice(emojis) + await event.reply(reply_text) + + +@register(pattern="^/bmoji$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + b_char = random.choice(rtext).lower() + reply_text = rtext.replace(b_char, "🅱️").replace(b_char.upper(), "🅱️") + await event.reply(reply_text) + + +@register(pattern="^/clapmoji$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + reply_text = "👏 " + reply_text += rtext.replace(" ", " 👏 ") + reply_text += " 👏" + await event.reply(reply_text) + + +@register(pattern="^/stretch$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + count = random.randint(3, 10) + reply_text = re.sub(r"([aeiouAEIOUaeiouAEIOU])", (r"\1" * count), rtext) + await event.reply(reply_text) + + +@register(pattern="^/vapor(?: |$)(.*)") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext: + data = rtext + else: + data = event.pattern_match.group(1) + if data is None: + await event.reply("Either provide some input or reply to a message.") + return + + reply_text = str(data).translate(WIDE_MAP) + await event.reply(reply_text) + + +@register(pattern="^/zalgofy$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + reply_text = zalgo.zalgo().zalgofy(rtext) + await event.reply(reply_text) + + +@register(pattern="^/forbesify$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + data = rtext + + data = data.lower() + accidentals = ["VB", "VBD", "VBG", "VBN"] + reply_text = data.split() + offset = 0 + + tagged = dict(nltk.pos_tag(reply_text)) + + for k in range(len(reply_text)): + i = reply_text[k + offset] + if tagged.get(i) in accidentals: + reply_text.insert(k + offset, "accidentally") + offset += 1 + + reply_text = string.capwords(" ".join(reply_text)) + await event.reply(reply_text) + + +@register(pattern="^/shout (.*)") +async def msg(event): + + rtext = event.pattern_match.group(1) + + args = rtext + + if len(args) == 0: + await event.reply("Where is text?") + return + + msg = "```" + text = " ".join(args) + result = [] + result.append(" ".join(list(text))) + for pos, symbol in enumerate(text[1:]): + result.append(symbol + " " + " " * pos + symbol) + result = list("\n".join(result)) + result[0] = text[0] + result = "".join(result) + msg = "```\n" + result + "```" + await event.reply(msg) + + +@register(pattern="^/angrymoji$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + reply_text = "😡 " + for i in rtext: + if i == " ": + reply_text += " 😡 " + else: + reply_text += i + reply_text += " 😡" + await event.reply(reply_text) + + +@register(pattern="^/crymoji$") +async def msg(event): + + rtex = await event.get_reply_message() + rtext = rtex.text + if rtext is None: + await event.reply("Reply to a message to make meme.") + return + reply_text = "😭 " + for i in rtext: + if i == " ": + reply_text += " 😭 " + else: + reply_text += i + reply_text += " 😭" + await event.reply(reply_text) + + +CARBONLANG = "en" + + +@register(pattern="^/carbon (.*)") +async def carbon_api(e): + + jj = "`Processing..`" + gg = await e.reply(jj) + CARBON = "https://carbon.now.sh/?bg=rgba(239%2C40%2C44%2C1)&t=one-light&wt=none&l=application%2Ftypescript&ds=true&dsyoff=20px&dsblur=68px&wc=true&wa=true&pv=56px&ph=56px&ln=false&fl=1&fm=Hack&fs=14px&lh=143%25&si=false&es=2x&wm=false&code={code}" + global CARBONLANG + code = e.pattern_match.group(1) + await gg.edit("`Processing..\n25%`") + os.chdir("./") + if os.path.isfile("./carbon.png"): + os.remove("./carbon.png") + url = CARBON.format(code=code, lang=CARBONLANG) + chrome_options = Options() + chrome_options.add_argument("--headless") + chrome_options.binary_location = GOOGLE_CHROME_BIN + chrome_options.add_argument("--window-size=1920x1080") + chrome_options.add_argument("--disable-dev-shm-usage") + chrome_options.add_argument("--no-sandbox") + chrome_options.add_argument("--disable-gpu") + prefs = {"download.default_directory": "./"} + chrome_options.add_experimental_option("prefs", prefs) + driver = webdriver.Chrome(executable_path=CHROME_DRIVER, options=chrome_options) + driver.get(url) + await gg.edit("`Processing..\n50%`") + download_path = "./" + driver.command_executor._commands["send_command"] = ( + "POST", + "/session/$sessionId/chromium/send_command", + ) + params = { + "cmd": "Page.setDownloadBehavior", + "params": {"behavior": "allow", "downloadPath": download_path}, + } + driver.execute("send_command", params) + driver.find_element_by_xpath("//button[contains(text(),'Export')]").click() + await gg.edit("`Processing..\n75%`") + while not os.path.isfile("./carbon.png"): + await asyncio.sleep(1) + await gg.edit("`Processing..\n100%`") + file = "./carbon.png" + await e.edit("`Uploading..`") + await tbot.send_file( + e.chat_id, + file, + caption="Made using [Carbon](https://carbon.now.sh/about/),\ + \na project by [Dawn Labs](https://dawnlabs.io/)", + force_document=True, + ) + os.remove("./carbon.png") + driver.quit() + + +@register(pattern="^/deepfry(?: |$)(.*)") +async def deepfryer(event): + + try: + frycount = int(event.pattern_match.group(1)) + if frycount < 1: + raise ValueError + except ValueError: + frycount = 1 + if event.is_reply: + reply_message = await event.get_reply_message() + data = await check_media(reply_message) + if isinstance(data, bool): + await event.reply("`I can't deep fry that!`") + return + else: + await event.reply("`Reply to an image or sticker to deep fry it!`") + return + + image = io.BytesIO() + await tbot.download_media(data, image) + image = Image.open(image) + + for _ in range(frycount): + image = await deepfry(image) + fried_io = io.BytesIO() + fried_io.name = "image.jpeg" + image.save(fried_io, "JPEG") + fried_io.seek(0) + await event.reply(file=fried_io) + + +async def deepfry(img: Image) -> Image: + colours = ( + (randint(50, 200), randint(40, 170), randint(40, 190)), + (randint(190, 255), randint(170, 240), randint(180, 250)), + ) + img = img.copy().convert("RGB") + img = img.convert("RGB") + width, height = img.width, img.height + img = img.resize( + (int(width ** uniform(0.8, 0.9)), int(height ** uniform(0.8, 0.9))), + resample=Image.LANCZOS, + ) + img = img.resize( + (int(width ** uniform(0.85, 0.95)), int(height ** uniform(0.85, 0.95))), + resample=Image.BILINEAR, + ) + img = img.resize( + (int(width ** uniform(0.89, 0.98)), int(height ** uniform(0.89, 0.98))), + resample=Image.BICUBIC, + ) + img = img.resize((width, height), resample=Image.BICUBIC) + img = ImageOps.posterize(img, randint(3, 7)) + overlay = img.split()[0] + overlay = ImageEnhance.Contrast(overlay).enhance(uniform(1.0, 2.0)) + overlay = ImageEnhance.Brightness(overlay).enhance(uniform(1.0, 2.0)) + overlay = ImageOps.colorize(overlay, colours[0], colours[1]) + img = Image.blend(img, overlay, uniform(0.1, 0.4)) + img = ImageEnhance.Sharpness(img).enhance(randint(5, 300)) + return img + + +async def check_media(reply_message): + if reply_message and reply_message.media: + if reply_message.photo: + data = reply_message.photo + elif reply_message.document: + if ( + DocumentAttributeFilename(file_name="AnimatedSticker.tgs") + in reply_message.media.document.attributes + ): + return False + if ( + reply_message.gif + or reply_message.video + or reply_message.audio + or reply_message.voice + ): + return False + data = reply_message.media.document + else: + return False + else: + return False + if not data or data is None: + return False + return data + + +@register(pattern="^/type (.*)") +async def typewriter(typew): + + message = typew.pattern_match.group(1) + if message: + pass + else: + await typew.reply("`Give a text to type!`") + return + typing_symbol = "|" + old_text = "" + now = await typew.reply(typing_symbol) + await asyncio.sleep(2) + for character in message: + old_text = old_text + "" + character + typing_text = old_text + "" + typing_symbol + await now.edit(typing_text) + await asyncio.sleep(2) + await now.edit(old_text) + await asyncio.sleep(2) + + +@register(pattern="^/sticklet (.*)") +async def sticklet(event): + + R = random.randint(0, 256) + G = random.randint(0, 256) + B = random.randint(0, 256) + + # get the input text + # the text on which we would like to do the magic on + sticktext = event.pattern_match.group(1) + + # delete the userbot command, + # i don't know why this is required + # await event.delete() + + # https://docs.python.org/3/library/textwrap.html#textwrap.wrap + sticktext = textwrap.wrap(sticktext, width=10) + # converts back the list to a string + sticktext = "\n".join(sticktext) + + image = Image.new("RGBA", (512, 512), (255, 255, 255, 0)) + draw = ImageDraw.Draw(image) + fontsize = 230 + + FONT_FILE = await get_font_file(ubot, "@IndianBot_Fonts") + + font = ImageFont.truetype(FONT_FILE, size=fontsize) + + while draw.multiline_textsize(sticktext, font=font) > (512, 512): + fontsize -= 3 + font = ImageFont.truetype(FONT_FILE, size=fontsize) + + width, height = draw.multiline_textsize(sticktext, font=font) + draw.multiline_text( + ((512 - width) / 2, (512 - height) / 2), sticktext, font=font, fill=(R, G, B) + ) + + image_stream = io.BytesIO() + image_stream.name = "@Julia.webp" + image.save(image_stream, "WebP") + image_stream.seek(0) + + # finally, reply the sticker + await event.reply(file=image_stream, reply_to=event.message.reply_to_msg_id) + # replacing upper line with this to get reply tags + + # cleanup + try: + os.remove(FONT_FILE) + except BaseException: + pass + + +async def get_font_file(client, channel_id): + # first get the font messages + font_file_message_s = await client.get_messages( + entity=channel_id, + filter=InputMessagesFilterDocument, + # this might cause FLOOD WAIT, + # if used too many times + limit=None, + ) + # get a random font from the list of fonts + # https://docs.python.org/3/library/random.html#random.choice + font_file_message = random.choice(font_file_message_s) + # download and return the file path + return await client.download_media(font_file_message) + + +@register(pattern=r"^/(\w+)say (.*)") +async def univsaye(cowmsg): + + """For .cowsay module, uniborg wrapper for cow which says things.""" + if not cowmsg.text[0].isalpha() and cowmsg.text[0] not in ("#", "@"): + arg = cowmsg.pattern_match.group(1).lower() + text = cowmsg.pattern_match.group(2) + + if arg == "cow": + arg = "default" + if arg not in cow.COWACTERS: + return + cheese = cow.get_cow(arg) + cheese = cheese() + + await cowmsg.reply(f"`{cheese.milk(text).replace('`', '´')}`") + + +@register(pattern="^/basketball$") +async def _(event): + if event.fwd_from: + return + + input_str = print(randrange(6)) + r = await event.reply(file=InputMediaDice("🏀")) + if input_str: + try: + required_number = int(input_str) + while not r.media.value == required_number: + await r.delete() + r = await event.reply(file=InputMediaDice("🏀")) + except BaseException: + pass + + +@register(pattern="^/jackpot$") +async def _(event): + if event.fwd_from: + return + + await event.reply(file=InputMediaDice("🎰")) + + +@register(pattern="^/dart$") +async def _(event): + if event.fwd_from: + return + + input_str = print(randrange(7)) + r = await event.reply(file=InputMediaDice("🎯")) + if input_str: + try: + required_number = int(input_str) + while not r.media.value == required_number: + await r.delete() + r = await event.reply(file=InputMediaDice("🎯")) + except BaseException: + pass + + +# Oringinal Source from Nicegrill: https://github.com/erenmetesar/NiceGrill/ +# Ported to Lynda by: @pokurt + +COLORS = [ + "#F07975", + "#F49F69", + "#F9C84A", + "#8CC56E", + "#6CC7DC", + "#80C1FA", + "#BCB3F9", + "#E181AC", +] + + +async def process(msg, user, client, reply, replied=None): + if not os.path.isdir("resources"): + os.mkdir("resources", 0o755) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Roboto-Regular.ttf", + "resources/Roboto-Regular.ttf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Quivira.otf", + "resources/Quivira.otf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Roboto-Medium.ttf", + "resources/Roboto-Medium.ttf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/DroidSansMono.ttf", + "resources/DroidSansMono.ttf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Roboto-Italic.ttf", + "resources/Roboto-Italic.ttf", + ) + + # Importıng fonts and gettings the size of text + font = ImageFont.truetype("resources/Roboto-Medium.ttf", 43, encoding="utf-16") + font2 = ImageFont.truetype("resources/Roboto-Regular.ttf", 33, encoding="utf-16") + mono = ImageFont.truetype("resources/DroidSansMono.ttf", 30, encoding="utf-16") + italic = ImageFont.truetype("resources/Roboto-Italic.ttf", 33, encoding="utf-16") + fallback = ImageFont.truetype("resources/Quivira.otf", 43, encoding="utf-16") + + # Splitting text + maxlength = 0 + width = 0 + text = [] + for line in msg.split("\n"): + length = len(line) + if length > 43: + text += textwrap.wrap(line, 43) + maxlength = 43 + if width < fallback.getsize(line[:43])[0]: + if "MessageEntityCode" in str(reply.entities): + width = mono.getsize(line[:43])[0] + 30 + else: + width = fallback.getsize(line[:43])[0] + next + else: + text.append(line + "\n") + if width < fallback.getsize(line)[0]: + if "MessageEntityCode" in str(reply.entities): + width = mono.getsize(line)[0] + 30 + else: + width = fallback.getsize(line)[0] + if maxlength < length: + maxlength = length + + title = "" + try: + details = await client( + functions.channels.GetParticipantRequest(reply.chat_id, user.id) + ) + if isinstance(details.participant, types.ChannelParticipantCreator): + title = details.participant.rank if details.participant.rank else "Creator" + elif isinstance(details.participant, types.ChannelParticipantAdmin): + title = details.participant.rank if details.participant.rank else "Admin" + except TypeError: + pass + titlewidth = font2.getsize(title)[0] + + # Get user name + lname = "" if not user.last_name else user.last_name + tot = user.first_name + " " + lname + + namewidth = fallback.getsize(tot)[0] + 10 + + if namewidth > width: + width = namewidth + width += titlewidth + 30 if titlewidth > width - namewidth else -(titlewidth - 30) + height = len(text) * 40 + + # Profile Photo BG + pfpbg = Image.new("RGBA", (125, 600), (0, 0, 0, 0)) + + # Draw Template + top, middle, bottom = await drawer(width, height) + # Profile Photo Check and Fetch + yes = False + color = random.choice(COLORS) + async for photo in client.iter_profile_photos(user, limit=1): + yes = True + if yes: + pfp = await client.download_profile_photo(user) + paste = Image.open(pfp) + os.remove(pfp) + paste.thumbnail((105, 105)) + + # Mask + mask_im = Image.new("L", paste.size, 0) + draw = ImageDraw.Draw(mask_im) + draw.ellipse((0, 0, 105, 105), fill=255) + + # Apply Mask + pfpbg.paste(paste, (0, 0), mask_im) + else: + paste, color = await no_photo(user, tot) + pfpbg.paste(paste, (0, 0)) + + # Creating a big canvas to gather all the elements + canvassize = ( + middle.width + pfpbg.width, + top.height + middle.height + bottom.height, + ) + canvas = Image.new("RGBA", canvassize) + draw = ImageDraw.Draw(canvas) + + y = 80 + if replied: + # Creating a big canvas to gather all the elements + replname = "" if not replied.sender.last_name else replied.sender.last_name + reptot = replied.sender.first_name + " " + replname + font2.getsize(reptot)[0] + if reply.sticker: + sticker = await reply.download_media() + stimg = Image.open(sticker) + canvas = canvas.resize((stimg.width + pfpbg.width, stimg.height + 160)) + top = Image.new("RGBA", (200 + stimg.width, 300), (29, 29, 29, 255)) + draw = ImageDraw.Draw(top) + await replied_user(draw, reptot, replied.message.replace("\n", " "), 20) + top = top.crop((135, 70, top.width, 300)) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width + 10, 0)) + canvas.paste(stimg, (pfpbg.width + 10, 140)) + os.remove(sticker) + return True, canvas + canvas = canvas.resize((canvas.width + 60, canvas.height + 120)) + top, middle, bottom = await drawer(middle.width + 60, height + 105) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width, 0)) + canvas.paste(middle, (pfpbg.width, top.height)) + canvas.paste(bottom, (pfpbg.width, top.height + middle.height)) + draw = ImageDraw.Draw(canvas) + if replied.sticker: + replied.text = "Sticker" + elif replied.photo: + replied.text = "Photo" + elif replied.audio: + replied.text = "Audio" + elif replied.voice: + replied.text = "Voice Message" + elif replied.document: + replied.text = "Document" + await replied_user( + draw, + reptot, + replied.message.replace("\n", " "), + maxlength + len(title), + len(title), + ) + y = 200 + elif reply.sticker: + sticker = await reply.download_media() + stimg = Image.open(sticker) + canvas = canvas.resize((stimg.width + pfpbg.width + 30, stimg.height + 10)) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(stimg, (pfpbg.width + 10, 10)) + os.remove(sticker) + return True, canvas + elif reply.document and not reply.audio and not reply.audio: + docname = ".".join(reply.document.attributes[-1].file_name.split(".")[:-1]) + doctype = reply.document.attributes[-1].file_name.split(".")[-1].upper() + if reply.document.size < 1024: + docsize = str(reply.document.size) + " Bytes" + elif reply.document.size < 1048576: + docsize = str(round(reply.document.size / 1024, 2)) + " KB " + elif reply.document.size < 1073741824: + docsize = str(round(reply.document.size / 1024 ** 2, 2)) + " MB " + else: + docsize = str(round(reply.document.size / 1024 ** 3, 2)) + " GB " + docbglen = ( + font.getsize(docsize)[0] + if font.getsize(docsize)[0] > font.getsize(docname)[0] + else font.getsize(docname)[0] + ) + canvas = canvas.resize((pfpbg.width + width + docbglen, 160 + height)) + top, middle, bottom = await drawer(width + docbglen, height + 30) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width, 0)) + canvas.paste(middle, (pfpbg.width, top.height)) + canvas.paste(bottom, (pfpbg.width, top.height + middle.height)) + canvas = await doctype(docname, docsize, doctype, canvas) + y = 80 if text else 0 + else: + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width, 0)) + canvas.paste(middle, (pfpbg.width, top.height)) + canvas.paste(bottom, (pfpbg.width, top.height + middle.height)) + y = 85 + + # Writing User's Name + space = pfpbg.width + 30 + namefallback = ImageFont.truetype("resources/Quivira.otf", 43, encoding="utf-16") + for letter in tot: + if letter in emoji.UNICODE_EMOJI: + newemoji, mask = await emoji_fetch(letter) + canvas.paste(newemoji, (space, 24), mask) + space += 40 + else: + if not await fontTest(letter): + draw.text((space, 20), letter, font=namefallback, fill=color) + space += namefallback.getsize(letter)[0] + else: + draw.text((space, 20), letter, font=font, fill=color) + space += font.getsize(letter)[0] + + if title: + draw.text( + (canvas.width - titlewidth - 20, 25), title, font=font2, fill="#898989" + ) + + # Writing all separating emojis and regular texts + x = pfpbg.width + 30 + bold, mono, italic, link = await get_entity(reply) + index = 0 + emojicount = 0 + textfallback = ImageFont.truetype("resources/Quivira.otf", 33, encoding="utf-16") + textcolor = "white" + for line in text: + for letter in line: + index = ( + msg.find(letter) if emojicount == 0 else msg.find(letter) + emojicount + ) + for offset, length in bold.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/Roboto-Medium.ttf", 33, encoding="utf-16" + ) + textcolor = "white" + for offset, length in italic.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/Roboto-Italic.ttf", 33, encoding="utf-16" + ) + textcolor = "white" + for offset, length in mono.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/DroidSansMono.ttf", 30, encoding="utf-16" + ) + textcolor = "white" + for offset, length in link.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/Roboto-Regular.ttf", 30, encoding="utf-16" + ) + textcolor = "#898989" + if letter in emoji.UNICODE_EMOJI: + newemoji, mask = await emoji_fetch(letter) + canvas.paste(newemoji, (x, y - 2), mask) + x += 45 + emojicount += 1 + else: + if not await fontTest(letter): + draw.text((x, y), letter, font=textfallback, fill=textcolor) + x += textfallback.getsize(letter)[0] + else: + draw.text((x, y), letter, font=font2, fill=textcolor) + x += font2.getsize(letter)[0] + msg = msg.replace(letter, "¶", 1) + y += 40 + x = pfpbg.width + 30 + return True, canvas + + +async def drawer(width, height): + # Top part + top = Image.new("RGBA", (width, 20), (0, 0, 0, 0)) + draw = ImageDraw.Draw(top) + draw.line((10, 0, top.width - 20, 0), fill=(29, 29, 29, 255), width=50) + draw.pieslice((0, 0, 30, 50), 180, 270, fill=(29, 29, 29, 255)) + draw.pieslice((top.width - 75, 0, top.width, 50), 270, 360, fill=(29, 29, 29, 255)) + + # Middle part + middle = Image.new("RGBA", (top.width, height + 75), (29, 29, 29, 255)) + + # Bottom part + bottom = ImageOps.flip(top) + + return top, middle, bottom + + +async def fontTest(letter): + test = TTFont("resources/Roboto-Medium.ttf") + for table in test["cmap"].tables: + if ord(letter) in table.cmap.keys(): + return True + + +async def get_entity(msg): + bold = {0: 0} + italic = {0: 0} + mono = {0: 0} + link = {0: 0} + if not msg.entities: + return bold, mono, italic, link + for entity in msg.entities: + if isinstance(entity, types.MessageEntityBold): + bold[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityItalic): + italic[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityCode): + mono[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityUrl): + link[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityTextUrl): + link[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityMention): + link[entity.offset] = entity.offset + entity.length + return bold, mono, italic, link + + +async def doctype(name, size, type, canvas): + font = ImageFont.truetype("resources/Roboto-Medium.ttf", 38) + doc = Image.new("RGBA", (130, 130), (29, 29, 29, 255)) + draw = ImageDraw.Draw(doc) + draw.ellipse((0, 0, 130, 130), fill="#434343") + draw.line((66, 28, 66, 53), width=14, fill="white") + draw.polygon([(67, 77), (90, 53), (42, 53)], fill="white") + draw.line((40, 87, 90, 87), width=8, fill="white") + canvas.paste(doc, (160, 23)) + draw2 = ImageDraw.Draw(canvas) + draw2.text((320, 40), name, font=font, fill="white") + draw2.text((320, 97), size + type, font=font, fill="#AAAAAA") + return canvas + + +async def no_photo(reply, tot): + pfp = Image.new("RGBA", (105, 105), (0, 0, 0, 0)) + pen = ImageDraw.Draw(pfp) + color = random.choice(COLORS) + pen.ellipse((0, 0, 105, 105), fill=color) + letter = "" if not tot else tot[0] + font = ImageFont.truetype("resources/Roboto-Regular.ttf", 60) + pen.text((32, 17), letter, font=font, fill="white") + return pfp, color + + +async def emoji_fetch(emoji): + emojis = json.loads( + urllib.request.urlopen( + "https://github.com/erenmetesar/modules-repo/raw/master/emojis.txt" + ) + .read() + .decode() + ) + if emoji in emojis: + img = emojis[emoji] + return await transparent( + urllib.request.urlretrieve(img, "resources/emoji.png")[0] + ) + img = emojis["⛔"] + return await transparent(urllib.request.urlretrieve(img, "resources/emoji.png")[0]) + + +async def transparent(emoji): + emoji = Image.open(emoji).convert("RGBA") + emoji.thumbnail((40, 40)) + + # Mask + mask = Image.new("L", (40, 40), 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0, 40, 40), fill=255) + return emoji, mask + + +async def replied_user(draw, tot, text, maxlength, title): + namefont = ImageFont.truetype("resources/Roboto-Medium.ttf", 38) + namefallback = ImageFont.truetype("resources/Quivira.otf", 38) + textfont = ImageFont.truetype("resources/Roboto-Regular.ttf", 32) + textfallback = ImageFont.truetype("resources/Roboto-Medium.ttf", 38) + maxlength = maxlength + 7 if maxlength < 10 else maxlength + text = text[: maxlength - 2] + ".." if len(text) > maxlength else text + draw.line((165, 90, 165, 170), width=5, fill="white") + space = 0 + for letter in tot: + if not await fontTest(letter): + draw.text((180 + space, 86), letter, font=namefallback, fill="#888888") + space += namefallback.getsize(letter)[0] + else: + draw.text((180 + space, 86), letter, font=namefont, fill="#888888") + space += namefont.getsize(letter)[0] + space = 0 + for letter in text: + if not await fontTest(letter): + draw.text((180 + space, 132), letter, font=textfallback, fill="#888888") + space += textfallback.getsize(letter)[0] + else: + draw.text((180 + space, 132), letter, font=textfont, fill="white") + space += textfont.getsize(letter)[0] + + +@register(pattern="^/quotly$") +async def _(event): + if event.fwd_from: + return + + reply = await event.get_reply_message() + msg = reply.message + repliedreply = await reply.get_reply_message() + user = ( + await event.client.get_entity(reply.forward.sender) + if reply.fwd_from + else reply.sender + ) + res, canvas = await process(msg, user, event.client, reply, repliedreply) + if not res: + return + canvas.save("sticker.webp") + await event.client.send_file( + event.chat_id, "sticker.webp", reply_to=event.reply_to_msg_id + ) + os.remove("sticker.webp") + + +EMOJI_PATTERN = re.compile( + "[" + "\U0001F1E0-\U0001F1FF" # flags (iOS) + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F600-\U0001F64F" # emoticons + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F700-\U0001F77F" # alchemical symbols + "\U0001F780-\U0001F7FF" # Geometric Shapes Extended + "\U0001F800-\U0001F8FF" # Supplemental Arrows-C + "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs + "\U0001FA00-\U0001FA6F" # Chess Symbols + "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A + "\U00002702-\U000027B0" # Dingbats + "]+" +) + + +def deEmojify(inputString: str) -> str: + """Remove emojis and other non-safe characters from string""" + return re.sub(EMOJI_PATTERN, "", inputString) + + +# Made By @MissJulia_Robot + + +@register(pattern="^/animate (.*)") +async def stickerizer(event): + + newtext = event.pattern_match.group(1) + animus = [20, 32, 33, 40, 41, 42, 58] + sticcers = await ubot.inline_query( + "stickerizerbot", f"#{random.choice(animus)}{(deEmojify(newtext))}" + ) + null = await sticcers[0].download_media(TEMP_DOWNLOAD_DIRECTORY) + bara = str(null) + await event.client.send_file(event.chat_id, bara, reply_to=event.id) + os.remove(bara) + + +@register(pattern="^/dice$") +async def _(event): + if event.fwd_from: + return + + input_str = print(randrange(7)) + r = await event.reply(file=InputMediaDice("")) + if input_str: + try: + required_number = int(input_str) + while not r.media.value == required_number: + await r.delete() + r = await event.reply(file=InputMediaDice("")) + except BaseException: + pass + + +@register(pattern="^/fortune$") +async def fortunate(event): + if event.fwd_from: + return + + jit = subprocess.check_output(["python", "fortune.py"]) + pit = jit.decode() + await event.reply(pit) + + +ABUSE_STRINGS = ( + "Fuck off", + "Stfu go fuck yourself", + "Ur mum gey", + "Ur dad lesbo", + "You Assfucker", + "Nigga", + "Ur granny tranny", + "you noob", + "Relax your Rear,ders nothing to fear,The Rape train is finally here", + "Stfu bc", + "Stfu and Gtfo U nub", + "GTFO bsdk", + "CUnt", + "Madharchod", + " Gay is here", + "Ur dad gey bc ", +) + +EYES = [ + ["⌐■", "■"], + [" ͠°", " °"], + ["⇀", "↼"], + ["´• ", " •`"], + ["´", "`"], + ["`", "´"], + ["ó", "ò"], + ["ò", "ó"], + ["⸌", "⸍"], + [">", "<"], + ["Ƹ̵̡", "Ʒ"], + ["ᗒ", "ᗕ"], + ["⟃", "⟄"], + ["⪧", "⪦"], + ["⪦", "⪧"], + ["⪩", "⪨"], + ["⪨", "⪩"], + ["⪰", "⪯"], + ["⫑", "⫒"], + ["⨴", "⨵"], + ["⩿", "⪀"], + ["⩾", "⩽"], + ["⩺", "⩹"], + ["⩹", "⩺"], + ["◥▶", "◀◤"], + ["◍", "◎"], + ["/͠-", "┐͡-\\"], + ["⌣", "⌣”"], + [" ͡⎚", " ͡⎚"], + ["≋"], + ["૦ઁ"], + [" ͯ"], + [" ͌"], + ["ළ"], + ["◉"], + ["☉"], + ["・"], + ["▰"], + ["ᵔ"], + [" ゚"], + ["□"], + ["☼"], + ["*"], + ["`"], + ["⚆"], + ["⊜"], + [">"], + ["❍"], + [" ̄"], + ["─"], + ["✿"], + ["•"], + ["T"], + ["^"], + ["ⱺ"], + ["@"], + ["ȍ"], + ["  "], + ["  "], + ["x"], + ["-"], + ["$"], + ["Ȍ"], + ["ʘ"], + ["Ꝋ"], + [""], + ["⸟"], + ["๏"], + ["ⴲ"], + ["◕"], + ["◔"], + ["✧"], + ["■"], + ["♥"], + [" ͡°"], + ["¬"], + [" º "], + ["⨶"], + ["⨱"], + ["⏓"], + ["⏒"], + ["⍜"], + ["⍤"], + ["ᚖ"], + ["ᴗ"], + ["ಠ"], + ["σ"], + ["☯"], +] + +MOUTHS = [ + ["v"], + ["ᴥ"], + ["ᗝ"], + ["Ѡ"], + ["ᗜ"], + ["Ꮂ"], + ["ᨓ"], + ["ᨎ"], + ["ヮ"], + ["╭͜ʖ╮"], + [" ͟ل͜"], + [" ͜ʖ"], + [" ͟ʖ"], + [" ʖ̯"], + ["ω"], + [" ³"], + [" ε "], + ["﹏"], + ["□"], + ["ل͜"], + ["‿"], + ["╭╮"], + ["‿‿"], + ["▾"], + ["‸"], + ["Д"], + ["∀"], + ["!"], + ["人"], + ["."], + ["ロ"], + ["_"], + ["෴"], + ["ѽ"], + ["ഌ"], + ["⏠"], + ["⏏"], + ["⍊"], + ["⍘"], + ["ツ"], + ["益"], + ["╭∩╮"], + ["Ĺ̯"], + ["◡"], + [" ͜つ"], +] + +EARS = [ + ["q", "p"], + ["ʢ", "ʡ"], + ["⸮", "?"], + ["ʕ", "ʔ"], + ["ᖗ", "ᖘ"], + ["ᕦ", "ᕥ"], + ["ᕦ(", ")ᕥ"], + ["ᕙ(", ")ᕗ"], + ["ᘳ", "ᘰ"], + ["ᕮ", "ᕭ"], + ["ᕳ", "ᕲ"], + ["(", ")"], + ["[", "]"], + ["¯\\_", "_/¯"], + ["୧", "୨"], + ["୨", "୧"], + ["⤜(", ")⤏"], + ["☞", "☞"], + ["ᑫ", "ᑷ"], + ["ᑴ", "ᑷ"], + ["ヽ(", ")ノ"], + ["\\(", ")/"], + ["乁(", ")ㄏ"], + ["└[", "]┘"], + ["(づ", ")づ"], + ["(ง", ")ง"], + ["⎝", "⎠"], + ["ლ(", "ლ)"], + ["ᕕ(", ")ᕗ"], + ["(∩", ")⊃━☆゚.*"], +] + +TOSS = ( + "Heads", + "Tails", +) + + +@register(pattern="^/roll$") +async def msg(event): + + await event.reply(str(random.choice(range(1, 7)))) + + +@register(pattern="^/toss$") +async def msg(event): + await event.reply(random.choice(TOSS)) + + +@register(pattern="^/abuse$") +async def msg(event): + + if event.reply_to_msg_id: + reply = await event.get_reply_message() + replyto = reply.sender_id + else: + replyto = event.sender_id + await tbot.send_message( + event.chat_id, random.choice(ABUSE_STRINGS), reply_to=replyto + ) + + +@register(pattern="^/bluetext$") +async def msg(event): + + if event.reply_to_msg_id: + reply = await event.get_reply_message() + replyto = reply.sender_id + else: + replyto = event.sender_id + await tbot.send_message( + event.chat_id, + "/BLUE /TEXT /MUST /CLICK /I /AM /A /STUPID /ANIMAL /THAT /IS /ATTRACTED /TO /COLORS", + reply_to=replyto, + ) + + +@register(pattern="^/rlg$") +async def _(event): + + eyes = random.choice(EYES) + mouth = random.choice(MOUTHS) + ears = random.choice(EARS) + repl = format(ears + eyes + mouth + eyes + ears) + await event.reply(repl) + + +@register(pattern="^/decide$") +async def _(event): + + r = randint(1, 100) + if r <= 65: + await event.reply("Yes.") + elif r <= 90: + await event.reply("NoU.") + else: + await event.reply("Maybe.") + + +@register(pattern="^/table$") +async def _(event): + + r = randint(1, 100) + if r <= 45: + await event.reply("(╯°□°)╯彡 ┻━┻") + elif r <= 90: + await event.reply("Send money to buy new table to flip") + else: + await event.reply("Go do some work instead of flipping tables ma boy.") + + +SFW_STRINGS = ( + "Owww ... Such a stupid idiot.", + "Don't drink and type.", + "I think you should go home or better a mental asylum.", + "Command not found. Just like your brain.", + "Do you realize you are making a fool of yourself? Apparently not.", + "You can type better than that.", + "Bot rule 544 section 9 prevents me from replying to stupid humans like you.", + "Sorry, we do not sell brains.", + "Believe me you are not normal.", + "I bet your brain feels as good as new, seeing that you never use it.", + "If I wanted to kill myself I'd climb your ego and jump to your IQ.", + "Zombies eat brains... you're safe.", + "You didn't evolve from apes, they evolved from you.", + "Come back and talk to me when your I.Q. exceeds your age.", + "I'm not saying you're stupid, I'm just saying you've got bad luck when it comes to thinking.", + "What language are you speaking? Cause it sounds like bullshit.", + "Stupidity is not a crime so you are free to go.", + "You are proof that evolution CAN go in reverse.", + "I would ask you how old you are but I know you can't count that high.", + "As an outsider, what do you think of the human race?", + "Brains aren't everything. In your case they're nothing.", + "Ordinarily people live and learn. You just live.", + "I don't know what makes you so stupid, but it really works.", + "Keep talking, someday you'll say something intelligent! (I doubt it though)", + "Shock me, say something intelligent.", + "Your IQ's lower than your shoe size.", + "Alas! Your neurotransmitters are no more working.", + "Are you crazy you fool.", + "Everyone has the right to be stupid but you are abusing the privilege.", + "I'm sorry I hurt your feelings when I called you stupid. I thought you already knew that.", + "You should try tasting cyanide.", + "Your enzymes are meant to digest rat poison.", + "You should try sleeping forever.", + "Pick up a gun and shoot yourself.", + "You could make a world record by jumping from a plane without parachute.", + "Stop talking BS and jump in front of a running bullet train.", + "Try bathing with Hydrochloric Acid instead of water.", + "Try this: if you hold your breath underwater for an hour, you can then hold it forever.", + "Go Green! Stop inhaling Oxygen.", + "God was searching for you. You should leave to meet him.", + "give your 100%. Now, go donate blood.", + "Try jumping from a hundred story building but you can do it only once.", + "You should donate your brain seeing that you never used it.", + "Volunteer for target in an firing range.", + "Head shots are fun. Get yourself one.", + "You should try swimming with great white sharks.", + "You should paint yourself red and run in a bull marathon.", + "You can stay underwater for the rest of your life without coming back up.", + "How about you stop breathing for like 1 day? That'll be great.", + "Try provoking a tiger while you both are in a cage.", + "Have you tried shooting yourself as high as 100m using a canon.", + "You should try holding TNT in your mouth and igniting it.", + "Try playing catch and throw with RDX its fun.", + "I heard phogine is poisonous but i guess you wont mind inhaling it for fun.", + "Launch yourself into outer space while forgetting oxygen on Earth.", + "You should try playing snake and ladders, with real snakes and no ladders.", + "Dance naked on a couple of HT wires.", + "Active Volcano is the best swimming pool for you.", + "You should try hot bath in a volcano.", + "Try to spend one day in a coffin and it will be yours forever.", + "Hit Uranium with a slow moving neutron in your presence. It will be a worthwhile experience.", + "You can be the first person to step on sun. Have a try.", + "People like you are the reason we have middle fingers.", + "When your mom dropped you off at the school, she got a ticket for littering.", + "You’re so ugly that when you cry, the tears roll down the back of your head…just to avoid your face.", + "If you’re talking behind my back then you’re in a perfect position to kiss my a**!.", + "Stupidity is not a crime so you are free to go.", +) + + +@register(pattern="^/insult$") +async def _(event): + + if event.reply_to_msg_id: + reply = await event.get_reply_message() + replyto = reply.sender_id + else: + replyto = event.sender_id + await tbot.send_message(event.chat_id, random.choice(SFW_STRINGS), reply_to=replyto) + + +reactionhappy = [ + "''̵͇З= ( ▀ ͜͞ʖ▀) =Ε/̵͇/’’", + "ʕ•ᴥ•ʔ", + "(づ。◕‿‿◕。)づ", + "(ノ◕ヮ◕)ノ*:・゚✧ ✧゚・: *ヽ(◕ヮ◕ヽ)", + "(ノ◕ヮ◕)ノ*:・゚✧", + "(☞゚∀゚)☞", + "| (• ◡•)| (❍ᴥ❍Ʋ)", + "(◕‿◕✿)", + "(ᵔᴥᵔ)", + "(☞゚ヮ゚)☞ ☜(゚ヮ゚☜)", + "(づ ̄ ³ ̄)づ", + "♪~ ᕕ(ᐛ)ᕗ", + "♥️‿♥️", + "༼ つ ͡° ͜ʖ ͡° ༽つ", + "༼ つ ಥ_ಥ ༽つ", + "ヾ(⌐■_■)ノ♪", + "~(˘▾˘~)", + "◉_◉", + "(•◡•) /", + "(~˘▾˘)~", + "(。◕‿‿◕。)", + "☜(˚▽˚)☞", + "(•Ω•)", + "(。◕‿◕。)", + "(っ˘ڡ˘Σ)", + "。◕‿‿◕。", + "☜(⌒▽⌒)☞", + "。◕‿◕。", + "(ღ˘⌣˘ღ)", + "(▰˘◡˘▰)", + "^̮^", + "^̮^", + ">_>", + "(^̮^)", + "^̮^", + "^̮^", +] +reactionangry = [ + "▄︻̷┻═━一", + "(▀Ĺ̯▀ )", + "(ง ͠° ͟ل͜ ͡°)ง", + "༼ つ ◕_◕ ༽つ", + "ಠ_ಠ", + "''̵͇З=( ͠° ͟ʖ ͡°)=Ε/̵͇/'", + "(ง'̀-'́)ง", + "(ノಠ益ಠ)ノ彡┻━┻", + "(╯°□°)╯︵ ꞰOOQƎƆⱯɟ", + "ლ(ಠ益ಠლ)", + "ಠ╭╮ಠ", + "''̵͇З=(•_•)=Ε/̵͇/''", + "(╯°□°)╯︵ ┻━┻", + "┻━┻ ︵ヽ(Д´)ノ︵ ┻━┻", + "⌐╦╦═─", + "(╯°□°)╯︵( .O.)", + ":')", + "┬──┬ ノ( ゜-゜ノ)", + "ლ(´ڡლ)", + "(°ロ°)☝️", + "ლ,ᔑ•ﺪ͟͠•ᔐ.ლ", + "┬─┬ノ( º _ ºノ)", + "┬─┬ ︵ /(.□. )", +] + +reactions = [ + "( ͡° ͜ʖ ͡°)", + "( . •́ _ʖ •̀ .)", + "( ಠ ͜ʖ ಠ)", + "( ͡ ͜ʖ ͡ )", + "(ʘ ͜ʖ ʘ)", + "ヾ(´〇`)ノ♪♪♪", + "ヽ(o´∀`)ノ♪♬", + "♪♬((d⌒ω⌒b))♬♪", + "└(^^)┐", + "( ̄▽ ̄)/♫•*¨*•.¸¸♪", + "ヾ(⌐■_■)ノ♪", + "乁( • ω •乁)", + "♬♫♪◖(● o ●)◗♪♫♬", + "(っ˘ڡ˘ς)", + "( ˘▽˘)っ♨", + "( ・ω・)⊃-[二二]", + "(*´ー`)旦 旦( ̄ω ̄*)", + "(  ̄▽ ̄)[] [](≧▽≦ )", + "(* ̄▽ ̄)旦 且(´∀`*)", + "(ノ ˘_˘)ノ ζ|||ζ ζ|||ζ ζ|||ζ", + "(ノ°∀°)ノ⌒・*:.。. .。.:*・゜゚・*☆", + "(⊃。•́‿•̀。)⊃━✿✿✿✿✿✿", + "(∩` ロ ´)⊃━炎炎炎炎炎", + "( ・∀・)・・・--------☆", + "( -ω-)/占~~~~~", + "○∞∞∞∞ヽ(^ー^ )", + "(*^^)/~~~~~~~~~~◎", + "(((  ̄□)_/", + "(メ ̄▽ ̄)︻┳═一", + "ヽ( ・∀・)ノ_θ彡☆Σ(ノ `Д´)ノ", + "(*`0´)θ☆(メ°皿°)ノ", + "(; -_-)――――――C<―_-)", + "ヽ(>_<ヽ) ―⊂|=0ヘ(^‿^ )", + "(҂` ロ ´)︻デ═一 \(º □ º l|l)/", + "/( .□.)\ ︵╰(°益°)╯︵ /(.□. /)", + "(`⌒*)O-(`⌒´Q)", + "(っ•﹏•)っ ✴==≡눈٩(`皿´҂)ง", + "ヾ(・ω・)メ(・ω・)ノ", + "(*^ω^)八(⌒▽⌒)八(-‿‿- )ヽ", + "ヽ( ⌒ω⌒)人(=^‥^= )ノ", + "。*:☆(・ω・人・ω・)。:゜☆。", + "(°(°ω(°ω°(☆ω☆)°ω°)ω°)°)", + "(っ˘▽˘)(˘▽˘)˘▽˘ς)", + "(*^ω^)人(^ω^*)", + r"\(▽ ̄ \ ( ̄▽ ̄) /  ̄▽)/", + "( ̄Θ ̄)", + "\( ˋ Θ ´ )/", + "( ´(00)ˋ )", + "\( ̄(oo) ̄)/", + "/(≧ x ≦)\", + "/(=・ x ・=)\", + "(=^・ω・^=)", + "(= ; ェ ; =)", + "(=⌒‿‿⌒=)", + "(^• ω •^)", + "ଲ(ⓛ ω ⓛ)ଲ", + "ଲ(ⓛ ω ⓛ)ଲ", + "(^◔ᴥ◔^)", + "[(--)]..zzZ", + "( ̄o ̄) zzZZzzZZ", + "(_ _*) Z z z", + "☆ミ(o*・ω・)ノ", + "ε=ε=ε=ε=┌(; ̄▽ ̄)┘", + "ε===(っ≧ω≦)っ", + "__φ(..)", + "ヾ( `ー´)シφ__", + "( ^▽^)ψ__", + "|・ω・)", + "|д・)", + "┬┴┬┴┤・ω・)ノ", + "|・д・)ノ", + "(* ̄ii ̄)", + "(^〃^)", + "m(_ _)m", + "人(_ _*)", + "(シ. .)シ", + "(^_~)", + "(>ω^)", + "(^_<)〜☆", + "(^_<)", + "(づ ̄ ³ ̄)づ", + "(⊃。•́‿•̀。)⊃", + "⊂(´• ω •`⊂)", + "(*・ω・)ノ", + "(^-^*)/", + "ヾ(*'▽'*)", + "(^0^)ノ", + "(*°ー°)ノ", + "( ̄ω ̄)/", + "(≧▽≦)/", + "w(°o°)w", + "(⊙_⊙)", + "(°ロ°) !", + "∑(O_O;)", + "(¬_¬)", + "(¬_¬ )", + "(↼_↼)", + "( ̄ω ̄;)", + "┐('~`;)┌", + "(・_・;)", + "(@_@)", + "(•ิ_•ิ)?", + "ヽ(ー_ー )ノ", + "┐( ̄ヘ ̄)┌", + "┐( ̄~ ̄)┌", + "┐( ´ д ` )┌", + "╮(︶▽︶)╭", + "ᕕ( ᐛ )ᕗ", + "(ノωヽ)", + "(″ロ゛)", + "(/ω\)", + "(((><)))", + "~(>_<~)", + "(×_×)", + "(×﹏×)", + "(ノ_<。)", + "(μ_μ)", + "o(TヘTo)", + "( ゚,_ゝ`)", + "( ╥ω╥ )", + "(/ˍ・、)", + "(つω`。)", + "(T_T)", + "o(〒﹏〒)o", + "(#`Д´)", + "(・`ω´・)", + "( `ε´ )", + "(メ` ロ ´)", + "Σ(▼□▼メ)", + "(҂ `з´ )", + "٩(╬ʘ益ʘ╬)۶", + "↑_(ΦwΦ)Ψ", + "(ノಥ益ಥ)ノ", + "(#><)", + "(; ̄Д ̄)", + "(¬_¬;)", + "(^^#)", + "( ̄︿ ̄)", + "ヾ(  ̄O ̄)ツ", + "(ᗒᗣᗕ)՞", + "(ノ_<。)ヾ(´ ▽ ` )", + "ヽ( ̄ω ̄(。。 )ゝ", + "(ノ_;)ヾ(´ ∀ ` )", + "(´-ω-`( _ _ )", + "(⌒_⌒;)", + "(*/_\)", + "( ◡‿◡ *)", + "(//ω//)", + "( ̄▽ ̄*)ゞ", + "(„ಡωಡ„)", + "(ノ´ з `)ノ", + "(♡-_-♡)", + "(─‿‿─)♡", + "(´ ω `♡)", + "(ღ˘⌣˘ღ)", + "(´• ω •`) ♡", + "╰(*´︶`*)╯♡", + "(≧◡≦) ♡", + "♡ (˘▽˘>ԅ( ˘⌣˘)", + "σ(≧ε≦σ) ♡", + "(˘∀˘)/(μ‿μ) ❤", + "Σ>―(〃°ω°〃)♡→", + "(* ^ ω ^)", + "(o^▽^o)", + "ヽ(・∀・)ノ", + "(o・ω・o)", + "(^人^)", + "( ´ ω ` )", + "(´• ω •`)", + "╰(▔∀▔)╯", + "(✯◡✯)", + "(⌒‿⌒)", + "(*°▽°*)", + "(´。• ᵕ •。`)", + "ヽ(>∀<☆)ノ", + "\( ̄▽ ̄)/", + "(o˘◡˘o)", + "(╯✧▽✧)╯", + "( ‾́ ◡ ‾́ )", + "(๑˘︶˘๑)", + "(´・ᴗ・ ` )", + "( ͡° ʖ̯ ͡°)", + "( ఠ ͟ʖ ఠ)", + "( ಥ ʖ̯ ಥ)", + "(≖ ͜ʖ≖)", + "ヘ( ̄ω ̄ヘ)", + "(ノ≧∀≦)ノ", + "└( ̄- ̄└))", + "┌(^^)┘", + "(^_^♪)", + "(〜 ̄△ ̄)〜", + "(「• ω •)「", + "( ˘ ɜ˘) ♬♪♫", + "( o˘◡˘o) ┌iii┐", + "♨o(>_<)o♨", + "( ・・)つ―{}@{}@{}-", + "(*´з`)口゚。゚口(・∀・ )", + "( *^^)o∀*∀o(^^* )", + "-●●●-c(・・ )", + "(ノ≧∀≦)ノ ‥…━━━★", + "╰( ͡° ͜ʖ ͡° )つ──☆*:・゚", + "(∩ᄑ_ᄑ)⊃━☆゚*・。*・:≡( ε:)", +] + + +@register(pattern="^/react$") +async def _(event): + + if event.reply_to_msg_id: + reply = await event.get_reply_message() + replyto = reply.sender_id + else: + replyto = event.sender_id + react = random.choice(reactions) + await event.reply(react, reply_to=replyto) + + +@register(pattern="^/rhappy$") +async def _(event): + + if event.reply_to_msg_id: + reply = await event.get_reply_message() + replyto = reply.sender_id + else: + replyto = event.sender_id + rhappy = random.choice(reactionhappy) + await event.reply(rhappy, reply_to=replyto) + + +@register(pattern="^/rangry$") +async def _(event): + + if event.reply_to_msg_id: + reply = await event.get_reply_message() + replyto = reply.sender_id + else: + replyto = event.sender_id + rangry = random.choice(reactionangry) + await event.reply(rangry, reply_to=replyto) + + +file_help = os.path.basename(__file__) +file_help = file_help.replace(".py", "") +file_helpo = file_help.replace("_", " ") + +__help__ = """ +**Some memes command, find it all out yourself !** + + - /owo: OWO de text + - /stretch: STRETCH de text + - /clapmoji: Type in reply to a message and see magic + - /bmoji: Type in reply to a message and see magic + - /copypasta: Type in reply to a message and see magic + - /vapor: owo vapor dis + - /shout text: Write anything that u want it to should + - /zalgofy: reply to a message to glitch it out! + - /table: get flip/unflip :v. + - /decide: Randomly answers yes/no/maybe + - /bluetext: Must type for fun + - /toss: Tosses A coin + - /abuse: Abuses the cunt + - /insult: Insult the cunt + - /slap: Slaps the cunt + - /roll: Roll a dice. + - /rlg: Join ears,nose,mouth and create an emo ;-; + - /react: Check on your own + - /rhappy: Check on your own + - /rangry: Check on your own + - /angrymoji: Check on your own + - /crymoji: Check on your own + - /cowsay, /tuxsay , /milksay , /kisssay , /wwwsay , /defaultsay , /bunnysay , /moosesay , /sheepsay , /rensay , /cheesesay , /ghostbusterssay , /skeletonsay text: Returns a stylish art text from the given text + - /deepfry: Type this in reply to an image/sticker to roast the image/sticker + - /figlet: Another Style art + - /dice: Roll A dice + - /dart: Throw a dart and try your luck + - /basketball: Try your luck if you can enter the ball in the ring + - /type text: Make the bot type something for you in a professional way + - /carbon text: Beautifies your text and enwraps inside a terminal image [ENGLISH ONLY] + - /sticklet text: Turn a text into a sticker + - /fortune: gets a random fortune quote + - /quotly: Type /quotly in reply to a message to make a sticker of that + - /animate: Enwrap your text in a beautiful anime + +""" + +__mod_name__ = "Memes" diff --git a/DaisyX/modules/misc.py b/DaisyX/modules/misc.py new file mode 100644 index 00000000..e6a8445f --- /dev/null +++ b/DaisyX/modules/misc.py @@ -0,0 +1,317 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import re +from contextlib import suppress +from datetime import datetime + +import wikipedia + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message +from aiogram.utils.exceptions import ( + BadRequest, + MessageNotModified, + MessageToDeleteNotFound, +) + +from DaisyX.decorator import register + +from .utils.disable import disableable_dec +from .utils.httpx import http +from .utils.language import get_strings_dec +from .utils.message import get_args_str +from .utils.notes import get_parsed_note_list, send_note, t_unparse_note_item +from .utils.user_details import is_user_admin + + +@register(cmds="buttonshelp", no_args=True, only_pm=True) +async def buttons_help(message): + await message.reply( + """ +Buttons: +Here you will know how to setup buttons in your note, welcome note, etc... + +There are different types of buttons! + +Due to current Implementation adding invalid button syntax to your note will raise error! This will be fixed in next major version. + +Did you know? +You could save buttons in same row using this syntax +[Button](btn{mode}:{args if any}:same) +(adding :same like that does the job.) + +Button Note: +Don't confuse this title with notes with buttons 😜 + +This types of button will allow you to show specific notes to users when they click on buttons! + +You can save note with button note without any hassle by adding below line to your note ( Don't forget to replace notename according to you 😀) + +[Button Name](btnnote:notename) + +URL Button: +Ah as you guessed! This method is used to add URL button to your note. With this you can redirect users to your website or even redirecting them to any channel, chat or messages! + +You can add URL button by adding following syntax to your note + +[Button Name](btnurl:https://your.link.here) + +Button rules: +Well in v2 we introduced some changes, rules are now saved seperately unlike saved as note before v2 so it require seperate button method! + +You can use this button method for including Rules button in your welcome messages, filters etc.. literally anywhere* + +You use this button with adding following syntax to your message which support formatting! +[Button Name](btnrules) + """ + ) + + +@register(cmds="variableshelp", no_args=True, only_pm=True) +async def buttons_help(message): + await message.reply( + """ +Variables: +Variables are special words which will be replaced by actual info + +Avaible variables: +{first}: User's first name +{last}: User's last name +{fullname}: User's full name +{id}: User's ID +{mention}: Mention the user using first name +{username}: Get the username, if user don't have username will be returned mention +{chatid}: Chat's ID +{chatname}: Chat name +{chatnick}: Chat username + """ + ) + + +@register(cmds="wiki") +@disableable_dec("wiki") +async def wiki(message): + args = get_args_str(message) + wikipedia.set_lang("en") + try: + pagewiki = wikipedia.page(args) + except wikipedia.exceptions.PageError as e: + await message.reply(f"No results found!\nError: {e}") + return + except wikipedia.exceptions.DisambiguationError as refer: + refer = str(refer).split("\n") + if len(refer) >= 6: + batas = 6 + else: + batas = len(refer) + text = "" + for x in range(batas): + if x == 0: + text += refer[x] + "\n" + else: + text += "- `" + refer[x] + "`\n" + await message.reply(text) + return + except IndexError: + msg.reply_text("Write a message to search from wikipedia sources.") + return + title = pagewiki.title + summary = pagewiki.summary + button = InlineKeyboardMarkup().add( + InlineKeyboardButton("🔧 More Info...", url=wikipedia.page(args).url) + ) + await message.reply( + ("The result of {} is:\n\n{}\n{}").format(args, title, summary), + reply_markup=button, + ) + + +@register(cmds="github") +@disableable_dec("github") +async def github(message): + text = message.text[len("/github ") :] + response = await http.get(f"https://api.github.com/users/{text}") + usr = response.json() + + if usr.get("login"): + text = f"Username: {usr['login']}" + + whitelist = [ + "name", + "id", + "type", + "location", + "blog", + "bio", + "followers", + "following", + "hireable", + "public_gists", + "public_repos", + "email", + "company", + "updated_at", + "created_at", + ] + + difnames = { + "id": "Account ID", + "type": "Account type", + "created_at": "Account created at", + "updated_at": "Last updated", + "public_repos": "Public Repos", + "public_gists": "Public Gists", + } + + goaway = [None, 0, "null", ""] + + for x, y in usr.items(): + if x in whitelist: + x = difnames.get(x, x.title()) + + if x in ("Account created at", "Last updated"): + y = datetime.strptime(y, "%Y-%m-%dT%H:%M:%SZ") + + if y not in goaway: + if x == "Blog": + x = "Website" + y = f"Here!" + text += "\n{}: {}".format(x, y) + else: + text += "\n{}: {}".format(x, y) + reply_text = text + else: + reply_text = "User not found. Make sure you entered valid username!" + await message.reply(reply_text, disable_web_page_preview=True) + + +@register(cmds="ip") +@disableable_dec("ip") +async def ip(message): + try: + ip = message.text.split(maxsplit=1)[1] + except IndexError: + await message.reply(f"Apparently you forgot something!") + return + + response = await http.get(f"http://ip-api.com/json/{ip}") + if response.status_code == 200: + lookup_json = response.json() + else: + await message.reply( + f"An error occurred when looking for {ip}: {response.status_code}" + ) + return + + fixed_lookup = {} + + for key, value in lookup_json.items(): + special = { + "lat": "Latitude", + "lon": "Longitude", + "isp": "ISP", + "as": "AS", + "asname": "AS name", + } + if key in special: + fixed_lookup[special[key]] = str(value) + continue + + key = re.sub(r"([a-z])([A-Z])", r"\g<1> \g<2>", key) + key = key.capitalize() + + if not value: + value = "None" + + fixed_lookup[key] = str(value) + + text = "" + + for key, value in fixed_lookup.items(): + text = text + f"{key}: {value}\n" + + await message.reply(text) + + +@register(cmds="cancel", state="*", allow_kwargs=True) +async def cancel_handle(message, state, **kwargs): + await state.finish() + await message.reply("Cancelled.") + + +async def delmsg_filter_handle(message, chat, data): + if await is_user_admin(data["chat_id"], message.from_user.id): + return + with suppress(MessageToDeleteNotFound): + await message.delete() + + +async def replymsg_filter_handler(message, chat, data): + text, kwargs = await t_unparse_note_item( + message, data["reply_text"], chat["chat_id"] + ) + kwargs["reply_to"] = message.message_id + with suppress(BadRequest): + await send_note(chat["chat_id"], text, **kwargs) + + +@get_strings_dec("misc") +async def replymsg_setup_start(message, strings): + with suppress(MessageNotModified): + await message.edit_text(strings["send_text"]) + + +async def replymsg_setup_finish(message, data): + reply_text = await get_parsed_note_list( + message, allow_reply_message=False, split_args=-1 + ) + return {"reply_text": reply_text} + + +@get_strings_dec("misc") +async def customise_reason_start(message: Message, strings: dict): + await message.reply(strings["send_customised_reason"]) + + +@get_strings_dec("misc") +async def customise_reason_finish(message: Message, _: dict, strings: dict): + if message.text is None: + await message.reply(strings["expected_text"]) + return False + elif message.text in {"None"}: + return {"reason": None} + return {"reason": message.text} + + +__filters__ = { + "delete_message": { + "title": {"module": "misc", "string": "delmsg_filter_title"}, + "handle": delmsg_filter_handle, + "del_btn_name": lambda msg, data: f"Del message: {data['handler']}", + }, + "reply_message": { + "title": {"module": "misc", "string": "replymsg_filter_title"}, + "handle": replymsg_filter_handler, + "setup": {"start": replymsg_setup_start, "finish": replymsg_setup_finish}, + "del_btn_name": lambda msg, data: f"Reply to {data['handler']}: \"{data['reply_text'].get('text', 'None')}\" ", + }, +} diff --git a/DaisyX/modules/music.py b/DaisyX/modules/music.py new file mode 100644 index 00000000..d478ec49 --- /dev/null +++ b/DaisyX/modules/music.py @@ -0,0 +1,135 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import io +import os + +import lyricsgenius +from pyrogram import filters +from tswift import Song + +from DaisyX.config import get_str_key +from DaisyX.services.pyrogram import pbot + +GENIUS = get_str_key("GENIUS_API_TOKEN", None) + + +# Lel, Didn't Get Time To Make New One So Used Plugin Made br @mrconfused and @sandy1709 dont edit credits + + +@pbot.on_message(filters.command(["lyric", "lyrics"])) +async def _(client, message): + lel = await message.reply("Searching For Lyrics.....") + query = message.text + if not query: + await lel.edit("`What I am Supposed to find `") + return + + song = "" + song = Song.find_song(query) + if song: + if song.lyrics: + reply = song.format() + else: + reply = "Couldn't find any lyrics for that song! try with artist name along with song if still doesnt work try `.glyrics`" + else: + reply = "lyrics not found! try with artist name along with song if still doesnt work try `.glyrics`" + + if len(reply) > 4095: + with io.BytesIO(str.encode(reply)) as out_file: + out_file.name = "lyrics.text" + await client.send_document( + message.chat.id, + out_file, + force_document=True, + allow_cache=False, + caption=query, + reply_to_msg_id=message.message_id, + ) + await lel.delete() + else: + await lel.edit(reply) # edit or reply + + +@pbot.on_message(filters.command(["glyric", "glyrics"])) +async def lyrics(client, message): + + if r"-" in message.text: + pass + else: + await message.reply( + "`Error: please use '-' as divider for and `\n" + "eg: `.glyrics Nicki Minaj - Super Bass`" + ) + return + + if GENIUS is None: + await message.reply( + "`Provide genius access token to config.py or Heroku Config first kthxbye!`" + ) + else: + genius = lyricsgenius.Genius(GENIUS) + try: + args = message.text.split(".lyrics")[1].split("-") + artist = args[0].strip(" ") + song = args[1].strip(" ") + except Exception: + await message.reply("`Lel please provide artist and song names`") + return + + if len(args) < 1: + await message.reply("`Please provide artist and song names`") + return + + lel = await message.reply(f"`Searching lyrics for {artist} - {song}...`") + + try: + songs = genius.search_song(song, artist) + except TypeError: + songs = None + + if songs is None: + await lel.edit(f"Song **{artist} - {song}** not found!") + return + if len(songs.lyrics) > 4096: + await lel.edit("`Lyrics is too big, view the file to see it.`") + with open("lyrics.txt", "w+") as f: + f.write(f"Search query: \n{artist} - {song}\n\n{songs.lyrics}") + await client.send_document( + message.chat.id, + "lyrics.txt", + reply_to_msg_id=message.message_id, + ) + os.remove("lyrics.txt") + else: + await lel.edit( + f"**Search query**: \n`{artist} - {song}`\n\n```{songs.lyrics}```" + ) + return + + +__mod_name__ = "Music" + +__help__ = """ +/video query: download video from youtube. +/deezer query: download from deezer. +/saavn query: download song from saavn. +/music query: download song from yt servers. (API BASED) +/lyrics song name : This plugin searches for song lyrics with song name. +/glyrics song name : This plugin searches for song lyrics with song name and artist. +""" diff --git a/DaisyX/modules/musicplayer b/DaisyX/modules/musicplayer new file mode 100644 index 00000000..901d43c1 --- /dev/null +++ b/DaisyX/modules/musicplayer @@ -0,0 +1,61 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +__mod_name__ = "Music Player" +__help__ = """ + 》** DAISYXMUSIC v2 ** 《 + + DAISYXMUSIC plays plays music in your group's voice chat + + Assistant name >> @DaisyXhelper + + Setting up + +1) Make bot admin +2) Start a voice chat +3) Try `/play [song name]` for the first time by an admin +*) If userbot joined enjoy music, If not add @DaisyXhelper to your group and retry + + Commands + +=>> Song Playing 🎧 + + /play: Play song using youtube music + /play [yt url] : Play the given yt url + /dplay: Play song via deezer + /splay: Play song via jio saavn + +=>> Playback ⏯ + + /player: Open Settings menu of player + /skip: Skips the current track + /pause: Pause track + /resume: Resumes the paused track + /end: Stops media playback + /current: Shows the current Playing track + /playlist: Shows playlist + +=>> More tools 🧑‍🔧 + + /admincache: Updates admin info of your group. Try if bot isn't recognize admin + /userbotjoin: Invite @DaisyXhelper Userbot to your chat + +*Player cmd and all other cmds except /play, /current and /playlist are only for admins with manage group + + PLEASE NOTE THIS SERVICE IS UNSTABLE AND CAN BE STOPPED ANYTIME +""" diff --git a/DaisyX/modules/nightmode.py b/DaisyX/modules/nightmode.py new file mode 100644 index 00000000..2430c916 --- /dev/null +++ b/DaisyX/modules/nightmode.py @@ -0,0 +1,171 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from telethon import events, functions +from telethon.tl.types import ChatBannedRights + +from DaisyX import BOT_ID +from DaisyX.function.telethonbasics import is_admin +from DaisyX.services.sql.night_mode_sql import ( + add_nightmode, + get_all_chat_id, + is_nightmode_indb, + rmnightmode, +) +from DaisyX.services.telethon import tbot + +CLEAN_GROUPS = False +hehes = ChatBannedRights( + until_date=None, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + send_polls=True, + invite_users=True, + pin_messages=True, + change_info=True, +) +openhehe = ChatBannedRights( + until_date=None, + send_messages=False, + send_media=False, + send_stickers=False, + send_gifs=False, + send_games=False, + send_inline=False, + send_polls=False, + invite_users=True, + pin_messages=True, + change_info=True, +) + + +@tbot.on(events.NewMessage(pattern="/nightmode (.*)")) +async def close_ws(event): + + if not event.is_group: + await event.reply("You Can Only Nsfw Watch in Groups.") + return + input_str = event.pattern_match.group(1) + if not await is_admin(event, BOT_ID): + await event.reply("`I Should Be Admin To Do This!`") + return + if await is_admin(event, event.message.sender_id): + if ( + input_str == "on" + or input_str == "On" + or input_str == "ON" + or input_str == "enable" + ): + if is_nightmode_indb(str(event.chat_id)): + await event.reply("This Chat is Has Already Enabled Night Mode.") + return + add_nightmode(str(event.chat_id)) + await event.reply( + f"**Added Chat {event.chat.title} With Id {event.chat_id} To Database. This Group Will Be Closed On 12Am(IST) And Will Opened On 06Am(IST)**" + ) + elif ( + input_str == "off" + or input_str == "Off" + or input_str == "OFF" + or input_str == "disable" + ): + + if not is_nightmode_indb(str(event.chat_id)): + await event.reply("This Chat is Has Not Enabled Night Mode.") + return + rmnightmode(str(event.chat_id)) + await event.reply( + f"**Removed Chat {event.chat.title} With Id {event.chat_id} From Database. This Group Will Be No Longer Closed On 12Am(IST) And Will Opened On 06Am(IST)**" + ) + else: + await event.reply("I undestand `/nightmode on` and `/nightmode off` only") + else: + await event.reply("`You Should Be Admin To Do This!`") + return + + +async def job_close(): + ws_chats = get_all_chat_id() + if len(ws_chats) == 0: + return + for warner in ws_chats: + try: + await tbot.send_message( + int(warner.chat_id), + "`12:00 Am, Group Is Closing Till 6 Am. Night Mode Started !` \n**Powered By @DaisyXbot**", + ) + await tbot( + functions.messages.EditChatDefaultBannedRightsRequest( + peer=int(warner.chat_id), banned_rights=hehes + ) + ) + if CLEAN_GROUPS: + async for user in tbot.iter_participants(int(warner.chat_id)): + if user.deleted: + await tbot.edit_permissions( + int(warner.chat_id), user.id, view_messages=False + ) + except Exception as e: + print(f"Unable To Close Group {warner} - {e}") + + +scheduler = AsyncIOScheduler(timezone="Asia/Kolkata") +scheduler.add_job(job_close, trigger="cron", hour=23, minute=55) +scheduler.start() + + +async def job_open(): + ws_chats = get_all_chat_id() + if len(ws_chats) == 0: + return + for warner in ws_chats: + try: + await tbot.send_message( + int(warner.chat_id), + "`06:00 Am, Group Is Opening.`\n**Powered By @DaisyXBot**", + ) + await tbot( + functions.messages.EditChatDefaultBannedRightsRequest( + peer=int(warner.chat_id), banned_rights=openhehe + ) + ) + except Exception as e: + print(f"Unable To Open Group {warner.chat_id} - {e}") + + +# Run everyday at 06 +scheduler = AsyncIOScheduler(timezone="Asia/Kolkata") +scheduler.add_job(job_open, trigger="cron", hour=6, minute=10) +scheduler.start() + +__mod_name__ = "Night Mode" + +__help__ = """ + The Night mode +Close your group at 12.00 a.m. and open back at 6.00 a.m.(IST) + Only available for asian countries (India Standard time) + +- /nightmode [ON/OFF]: Enable/Disable Night Mode. + +""" diff --git a/DaisyX/modules/notes.py b/DaisyX/modules/notes.py new file mode 100644 index 00000000..08bf5abd --- /dev/null +++ b/DaisyX/modules/notes.py @@ -0,0 +1,818 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import difflib +import re +from contextlib import suppress +from datetime import datetime + +from aiogram.dispatcher.filters.builtin import CommandStart +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.deep_linking import get_start_link +from aiogram.utils.exceptions import ( + BadRequest, + MessageCantBeDeleted, + MessageNotModified, +) +from babel.dates import format_datetime +from pymongo import ReplaceOne +from telethon.errors.rpcerrorlist import MessageDeleteForbiddenError + +from DaisyX import bot +from DaisyX.decorator import register +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis +from DaisyX.services.telethon import tbot + +from .utils.connections import chat_connection, set_connected_command +from .utils.disable import disableable_dec +from .utils.language import get_string, get_strings_dec +from .utils.message import get_arg, need_args_dec +from .utils.notes import ( + ALLOWED_COLUMNS, + BUTTONS, + get_parsed_note_list, + send_note, + t_unparse_note_item, +) +from .utils.user_details import get_user_link + +RESTRICTED_SYMBOLS_IN_NOTENAMES = [ + ":", + "**", + "__", + "`", + "#", + '"', + "[", + "]", + "'", + "$", + "||", +] + + +async def get_similar_note(chat_id, note_name): + all_notes = [] + async for note in db.notes.find({"chat_id": chat_id}): + all_notes.extend(note["names"]) + + if len(all_notes) > 0: + check = difflib.get_close_matches(note_name, all_notes) + if len(check) > 0: + return check[0] + + return None + + +def clean_notes(func): + async def wrapped_1(*args, **kwargs): + event = args[0] + + message = await func(*args, **kwargs) + if not message: + return + + if event.chat.type == "private": + return + + chat_id = event.chat.id + + data = await db.clean_notes.find_one({"chat_id": chat_id}) + if not data: + return + + if data["enabled"] is not True: + return + + if "msgs" in data: + with suppress(MessageDeleteForbiddenError): + await tbot.delete_messages(chat_id, data["msgs"]) + + msgs = [] + if hasattr(message, "message_id"): + msgs.append(message.message_id) + else: + msgs.append(message.id) + + msgs.append(event.message_id) + + await db.clean_notes.update_one({"chat_id": chat_id}, {"$set": {"msgs": msgs}}) + + return wrapped_1 + + +@register(cmds="save", user_admin=True, user_can_change_info=True) +@need_args_dec() +@chat_connection(admin=True) +@get_strings_dec("notes") +async def save_note(message, chat, strings): + chat_id = chat["chat_id"] + arg = get_arg(message).lower() + if arg[0] == "#": + arg = arg[1:] + + sym = None + if any((sym := s) in arg for s in RESTRICTED_SYMBOLS_IN_NOTENAMES): + await message.reply(strings["notename_cant_contain"].format(symbol=sym)) + return + + note_names = arg.split("|") + + note = await get_parsed_note_list(message) + + note["names"] = note_names + note["chat_id"] = chat_id + + if "text" not in note and "file" not in note: + await message.reply(strings["blank_note"]) + return + + if old_note := await db.notes.find_one( + {"chat_id": chat_id, "names": {"$in": note_names}} + ): + text = strings["note_updated"] + if "created_date" in old_note: + note["created_date"] = old_note["created_date"] + note["created_user"] = old_note["created_user"] + note["edited_date"] = datetime.now() + note["edited_user"] = message.from_user.id + else: + text = strings["note_saved"] + note["created_date"] = datetime.now() + note["created_user"] = message.from_user.id + + await db.notes.replace_one( + {"_id": old_note["_id"]} if old_note else note, note, upsert=True + ) + + text += strings["you_can_get_note"] + text = text.format(note_name=note_names[0], chat_title=chat["chat_title"]) + if len(note_names) > 1: + text += strings["note_aliases"] + for notename in note_names: + text += f" #{notename}" + + await message.reply(text) + + +@get_strings_dec("notes") +async def get_note( + message, + strings, + note_name=None, + db_item=None, + chat_id=None, + send_id=None, + rpl_id=None, + noformat=False, + event=None, + user=None, +): + if not chat_id: + chat_id = message.chat.id + + if not send_id: + send_id = message.chat.id + + if rpl_id is False: + rpl_id = None + elif not rpl_id: + rpl_id = message.message_id + + if not db_item and not ( + db_item := await db.notes.find_one( + {"chat_id": chat_id, "names": {"$in": [note_name]}} + ) + ): + await bot.send_message(chat_id, strings["no_note"], reply_to_message_id=rpl_id) + return + + text, kwargs = await t_unparse_note_item( + message, db_item, chat_id, noformat=noformat, event=event, user=user + ) + kwargs["reply_to"] = rpl_id + + return await send_note(send_id, text, **kwargs) + + +@register(cmds="get") +@disableable_dec("get") +@need_args_dec() +@chat_connection(command="get") +@get_strings_dec("notes") +@clean_notes +async def get_note_cmd(message, chat, strings): + chat_id = chat["chat_id"] + chat_name = chat["chat_title"] + + note_name = get_arg(message).lower() + if note_name[0] == "#": + note_name = note_name[1:] + + if "reply_to_message" in message: + rpl_id = message.reply_to_message.message_id + user = message.reply_to_message.from_user + else: + rpl_id = message.message_id + user = message.from_user + + if not ( + note := await db.notes.find_one( + {"chat_id": int(chat_id), "names": {"$in": [note_name]}} + ) + ): + text = strings["cant_find_note"].format(chat_name=chat_name) + if alleged_note_name := await get_similar_note(chat_id, note_name): + text += strings["u_mean"].format(note_name=alleged_note_name) + await message.reply(text) + return + + noformat = False + if len(args := message.text.split(" ")) > 2: + arg2 = args[2].lower() + noformat = arg2 in ("noformat", "raw") + + return await get_note( + message, db_item=note, rpl_id=rpl_id, noformat=noformat, user=user + ) + + +@register(regexp=r"^#([\w-]+)", allow_kwargs=True) +@disableable_dec("get") +@chat_connection(command="get") +@clean_notes +async def get_note_hashtag(message, chat, regexp=None, **kwargs): + chat_id = chat["chat_id"] + + note_name = regexp.group(1).lower() + if not ( + note := await db.notes.find_one( + {"chat_id": int(chat_id), "names": {"$in": [note_name]}} + ) + ): + return + + if "reply_to_message" in message: + rpl_id = message.reply_to_message.message_id + user = message.reply_to_message.from_user + else: + rpl_id = message.message_id + user = message.from_user + + return await get_note(message, db_item=note, rpl_id=rpl_id, user=user) + + +@register(cmds=["notes", "saved"]) +@disableable_dec("notes") +@chat_connection(command="notes") +@get_strings_dec("notes") +@clean_notes +async def get_notes_list_cmd(message, chat, strings): + if ( + await db.privatenotes.find_one({"chat_id": chat["chat_id"]}) + and message.chat.id == chat["chat_id"] + ): # Workaround to avoid sending PN to connected PM + text = strings["notes_in_private"] + if not (keyword := message.get_args()): + keyword = None + button = InlineKeyboardMarkup().add( + InlineKeyboardButton( + text="Click here", + url=await get_start_link(f"notes_{chat['chat_id']}_{keyword}"), + ) + ) + return await message.reply( + text, reply_markup=button, disable_web_page_preview=True + ) + else: + return await get_notes_list(message, chat=chat) + + +@get_strings_dec("notes") +async def get_notes_list(message, strings, chat, keyword=None, pm=False): + text = strings["notelist_header"].format(chat_name=chat["chat_title"]) + + notes = ( + await db.notes.find({"chat_id": chat["chat_id"]}) + .sort("names", 1) + .to_list(length=300) + ) + if not notes: + return await message.reply( + strings["notelist_no_notes"].format(chat_title=chat["chat_title"]) + ) + + async def search_notes(request): + nonlocal notes, text, note, note_name + text += "\n" + strings["notelist_search"].format(request=request) + all_notes = notes + notes = [] + for note in all_notes: + for note_name in note["names"]: + if re.search(request, note_name): + notes.append(note) + if len(notes) <= 0: + return await message.reply(strings["no_notes_pattern"] % request) + + # Search + if keyword: + await search_notes(keyword) + if len(keyword := message.get_args()) > 0 and pm is False: + await search_notes(keyword) + + if len(notes) > 0: + for note in notes: + text += "\n-" + for note_name in note["names"]: + text += f" #{note_name}" + text += strings["you_can_get_note"] + + try: + return await message.reply(text) + except BadRequest: + await message.answer(text) + + +@register(cmds="search") +@chat_connection() +@get_strings_dec("notes") +@clean_notes +async def search_in_note(message, chat, strings): + request = message.get_args() + text = strings["search_header"].format( + chat_name=chat["chat_title"], request=request + ) + + notes = db.notes.find( + {"chat_id": chat["chat_id"], "text": {"$regex": request, "$options": "i"}} + ).sort("names", 1) + for note in (check := await notes.to_list(length=300)): + text += "\n-" + for note_name in note["names"]: + text += f" #{note_name}" + text += strings["you_can_get_note"] + if not check: + return await message.reply( + strings["notelist_no_notes"].format(chat_title=chat["chat_title"]) + ) + return await message.reply(text) + + +@register(cmds=["clear", "delnote"], user_admin=True, user_can_change_info=True) +@chat_connection(admin=True) +@need_args_dec() +@get_strings_dec("notes") +async def clear_note(message, chat, strings): + note_names = get_arg(message).lower().split("|") + + removed = "" + not_removed = "" + for note_name in note_names: + if note_name[0] == "#": + note_name = note_name[1:] + + if not ( + note := await db.notes.find_one( + {"chat_id": chat["chat_id"], "names": {"$in": [note_name]}} + ) + ): + if len(note_names) <= 1: + text = strings["cant_find_note"].format(chat_name=chat["chat_title"]) + if alleged_note_name := await get_similar_note( + chat["chat_id"], note_name + ): + text += strings["u_mean"].format(note_name=alleged_note_name) + await message.reply(text) + return + else: + not_removed += " #" + note_name + continue + + await db.notes.delete_one({"_id": note["_id"]}) + removed += " #" + note_name + + if len(note_names) > 1: + text = strings["note_removed_multiple"].format( + chat_name=chat["chat_title"], removed=removed + ) + if not_removed: + text += strings["not_removed_multiple"].format(not_removed=not_removed) + await message.reply(text) + else: + await message.reply( + strings["note_removed"].format( + note_name=note_name, chat_name=chat["chat_title"] + ) + ) + + +@register(cmds="clearall", user_admin=True, user_can_change_info=True) +@chat_connection(admin=True) +@get_strings_dec("notes") +async def clear_all_notes(message, chat, strings): + # Ensure notes count + if not await db.notes.find_one({"chat_id": chat["chat_id"]}): + await message.reply( + strings["notelist_no_notes"].format(chat_title=chat["chat_title"]) + ) + return + + text = strings["clear_all_text"].format(chat_name=chat["chat_title"]) + buttons = InlineKeyboardMarkup() + buttons.add( + InlineKeyboardButton( + strings["clearall_btn_yes"], callback_data="clean_all_notes_cb" + ) + ) + buttons.add( + InlineKeyboardButton(strings["clearall_btn_no"], callback_data="cancel") + ) + await message.reply(text, reply_markup=buttons) + + +@register(regexp="clean_all_notes_cb", f="cb", is_admin=True, user_can_change_info=True) +@chat_connection(admin=True) +@get_strings_dec("notes") +async def clear_all_notes_cb(event, chat, strings): + num = (await db.notes.delete_many({"chat_id": chat["chat_id"]})).deleted_count + + text = strings["clearall_done"].format(num=num, chat_name=chat["chat_title"]) + await event.message.edit_text(text) + + +@register(cmds="noteinfo", user_admin=True) +@chat_connection() +@need_args_dec() +@get_strings_dec("notes") +@clean_notes +async def note_info(message, chat, strings): + note_name = get_arg(message).lower() + if note_name[0] == "#": + note_name = note_name[1:] + + if not ( + note := await db.notes.find_one( + {"chat_id": chat["chat_id"], "names": {"$in": [note_name]}} + ) + ): + text = strings["cant_find_note"].format(chat_name=chat["chat_title"]) + if alleged_note_name := await get_similar_note(chat["chat_id"], note_name): + text += strings["u_mean"].format(note_name=alleged_note_name) + return await message.reply(text) + + text = strings["note_info_title"] + + note_names = "" + for note_name in note["names"]: + note_names += f" #{note_name}" + + text += strings["note_info_note"] % note_names + text += strings["note_info_content"] % ( + "text" if "file" not in note else note["file"]["type"] + ) + + if "parse_mode" not in note or note["parse_mode"] == "md": + parse_mode = "Markdown" + elif note["parse_mode"] == "html": + parse_mode = "HTML" + elif note["parse_mode"] == "none": + parse_mode = "None" + else: + raise TypeError() + + text += strings["note_info_parsing"] % parse_mode + + if "created_date" in note: + text += strings["note_info_created"].format( + date=format_datetime( + note["created_date"], locale=strings["language_info"]["babel"] + ), + user=await get_user_link(note["created_user"]), + ) + + if "edited_date" in note: + text += strings["note_info_updated"].format( + date=format_datetime( + note["edited_date"], locale=strings["language_info"]["babel"] + ), + user=await get_user_link(note["edited_user"]), + ) + + return await message.reply(text) + + +BUTTONS.update({"note": "btnnotesm", "#": "btnnotesm"}) + + +@register(regexp=r"btnnotesm_(\w+)_(.*)", f="cb", allow_kwargs=True) +@get_strings_dec("notes") +async def note_btn(event, strings, regexp=None, **kwargs): + chat_id = int(regexp.group(2)) + user_id = event.from_user.id + note_name = regexp.group(1).lower() + + if not ( + note := await db.notes.find_one( + {"chat_id": chat_id, "names": {"$in": [note_name]}} + ) + ): + await event.answer(strings["no_note"]) + return + + with suppress(MessageCantBeDeleted): + await event.message.delete() + await get_note( + event.message, + db_item=note, + chat_id=chat_id, + send_id=user_id, + rpl_id=None, + event=event, + ) + + +@register(CommandStart(re.compile(r"btnnotesm")), allow_kwargs=True) +@get_strings_dec("notes") +async def note_start(message, strings, regexp=None, **kwargs): + # Don't even ask what it means, mostly it workaround to support note names with _ + args = re.search(r"^([a-zA-Z0-9]+)_(.*?)(-\d+)$", message.get_args()) + chat_id = int(args.group(3)) + user_id = message.from_user.id + note_name = args.group(2).strip("_") + + if not ( + note := await db.notes.find_one( + {"chat_id": chat_id, "names": {"$in": [note_name]}} + ) + ): + await message.reply(strings["no_note"]) + return + + await get_note(message, db_item=note, chat_id=chat_id, send_id=user_id, rpl_id=None) + + +@register(cmds="start", only_pm=True) +@get_strings_dec("connections") +async def btn_note_start_state(message, strings): + key = "btn_note_start_state:" + str(message.from_user.id) + if not (cached := redis.hgetall(key)): + return + + chat_id = int(cached["chat_id"]) + user_id = message.from_user.id + note_name = cached["notename"] + + note = await db.notes.find_one({"chat_id": chat_id, "names": {"$in": [note_name]}}) + await get_note(message, db_item=note, chat_id=chat_id, send_id=user_id, rpl_id=None) + + redis.delete(key) + + +@register(cmds="privatenotes", is_admin=True, user_can_change_info=True) +@chat_connection(admin=True) +@get_strings_dec("notes") +async def private_notes_cmd(message, chat, strings): + chat_id = chat["chat_id"] + chat_name = chat["chat_title"] + text = str + + try: + (text := "".join(message.text.split()[1]).lower()) + except IndexError: + pass + + enabling = ["true", "enable", "on"] + disabling = ["false", "disable", "off"] + if database := await db.privatenotes.find_one({"chat_id": chat_id}): + if text in enabling: + await message.reply(strings["already_enabled"] % chat_name) + return + if text in enabling: + await db.privatenotes.insert_one({"chat_id": chat_id}) + await message.reply(strings["enabled_successfully"] % chat_name) + elif text in disabling: + if not database: + await message.reply(strings["not_enabled"]) + return + await db.privatenotes.delete_one({"_id": database["_id"]}) + await message.reply(strings["disabled_successfully"] % chat_name) + else: + # Assume admin asked for current state + if database: + state = strings["enabled"] + else: + state = strings["disabled"] + await message.reply( + strings["current_state_info"].format(state=state, chat=chat_name) + ) + + +@register(cmds="cleannotes", is_admin=True, user_can_change_info=True) +@chat_connection(admin=True) +@get_strings_dec("notes") +async def clean_notes(message, chat, strings): + disable = ["no", "off", "0", "false", "disable"] + enable = ["yes", "on", "1", "true", "enable"] + + chat_id = chat["chat_id"] + + arg = get_arg(message) + if arg and arg.lower() in enable: + await db.clean_notes.update_one( + {"chat_id": chat_id}, {"$set": {"enabled": True}}, upsert=True + ) + text = strings["clean_notes_enable"].format(chat_name=chat["chat_title"]) + elif arg and arg.lower() in disable: + await db.clean_notes.update_one( + {"chat_id": chat_id}, {"$set": {"enabled": False}}, upsert=True + ) + text = strings["clean_notes_disable"].format(chat_name=chat["chat_title"]) + else: + data = await db.clean_notes.find_one({"chat_id": chat_id}) + if data and data["enabled"] is True: + text = strings["clean_notes_enabled"].format(chat_name=chat["chat_title"]) + else: + text = strings["clean_notes_disabled"].format(chat_name=chat["chat_title"]) + + await message.reply(text) + + +@register(CommandStart(re.compile("notes"))) +@get_strings_dec("notes") +async def private_notes_func(message, strings): + args = message.get_args().split("_") + chat_id = args[1] + keyword = args[2] if args[2] != "None" else None + await set_connected_command(message.from_user.id, int(chat_id), ["get", "notes"]) + chat = await db.chat_list.find_one({"chat_id": int(chat_id)}) + await message.answer(strings["privatenotes_notif"].format(chat=chat["chat_title"])) + await get_notes_list(message, chat=chat, keyword=keyword, pm=True) + + +async def __stats__(): + text = "* {} total notes\n".format(await db.notes.count_documents({})) + return text + + +async def __export__(chat_id): + data = [] + notes = ( + await db.notes.find({"chat_id": chat_id}).sort("names", 1).to_list(length=300) + ) + for note in notes: + del note["_id"] + del note["chat_id"] + note["created_date"] = str(note["created_date"]) + if "edited_date" in note: + note["edited_date"] = str(note["edited_date"]) + data.append(note) + + return {"notes": data} + + +ALLOWED_COLUMNS_NOTES = ALLOWED_COLUMNS + [ + "names", + "created_date", + "created_user", + "edited_date", + "edited_user", +] + + +async def __import__(chat_id, data): + if not data: + return + + new = [] + for note in data: + + # File ver 1 to 2 + if "name" in note: + note["names"] = [note["name"]] + del note["name"] + + for item in [i for i in note if i not in ALLOWED_COLUMNS_NOTES]: + del note[item] + + note["chat_id"] = chat_id + note["created_date"] = datetime.fromisoformat(note["created_date"]) + if "edited_date" in note: + note["edited_date"] = datetime.fromisoformat(note["edited_date"]) + new.append( + ReplaceOne( + {"chat_id": note["chat_id"], "names": {"$in": [note["names"][0]]}}, + note, + upsert=True, + ) + ) + + await db.notes.bulk_write(new) + + +async def filter_handle(message, chat, data): + chat_id = chat["chat_id"] + read_chat_id = message.chat.id + note_name = data["note_name"] + note = await db.notes.find_one({"chat_id": chat_id, "names": {"$in": [note_name]}}) + await get_note( + message, db_item=note, chat_id=chat_id, send_id=read_chat_id, rpl_id=None + ) + + +async def setup_start(message): + text = await get_string(message.chat.id, "notes", "filters_setup_start") + with suppress(MessageNotModified): + await message.edit_text(text) + + +async def setup_finish(message, data): + note_name = message.text.split(" ", 1)[0].split()[0] + + if not (await db.notes.find_one({"chat_id": data["chat_id"], "names": note_name})): + await message.reply("no such note!") + return + + return {"note_name": note_name} + + +__filters__ = { + "get_note": { + "title": {"module": "notes", "string": "filters_title"}, + "handle": filter_handle, + "setup": {"start": setup_start, "finish": setup_finish}, + "del_btn_name": lambda msg, data: f"Get note: {data['note_name']}", + } +} + + +__mod_name__ = "Notes" + +__help__ = """ +Sometimes you need to save some data, like text or pictures. With notes, you can save any types of Telegram's data in your chats. +Also notes perfectly working in PM with Daisy. + +Available commands: +- /save (name) (data): Saves the note. +- #(name) or /get (name): Get the note registered to that word. +- /clear (name): deletes the note. +- /notes or /saved: Lists all notes. +- /noteinfo (name): Shows detailed info about the note. +- /search (search pattern): Search text in notes +- /clearall: Clears all notes + +Only in groups: +- /privatenotes (on/off): Redirect user in PM to see notes +- /cleannotes (on/off): Will clean old notes messages + +Examples: +An example of how to save a note would be via: +/save data This is example note! +Now, anyone using /get data, or #data will be replied to with This is example note!. + +Saving pictures and other non-text data: +If you want to save an image, gif, or sticker, or any other data, do the following: +/save word while replying to a sticker or whatever data you'd like. Now, the note at #word contains a sticker which will be sent as a reply. + +Removing many notes per one request: +To remove many notes you can use the /clear command, just place all note names which you want to remove as argument of the command, use | as seprator, for example: +/clear note1|note2|note3 + +Notes aliases: +You can save note with many names, example: +/save name1|name2|name3 +That will save a note with 3 different names, by any you can /get note, that can be useful if users in your chat trying to get notes which exits by other names. + +Notes buttons and variables: +Notes support inline buttons, send /buttonshelp to get started with using it. +Variables are special words which will be replaced by actual info like if you add {id} in your note it will be replaced by user ID which asked note. Send /variableshelp to get started with using it. + +Notes formatting and settings: +Every note can contain special settings, for example you can change formatting method to HTML by %PARSEMODE_HTML and fully disable it by %PARSEMODE_NONE ( By default formatting is Markdown or the same formatting Telegram supports ) + +%PARSEMODE_(HTML, NONE): Change the note formatting +%PREVIEW: Enables the links preview in saved note + +Saving notes from other Marie style bots: +Daisy can save notes from other bots, just reply /save on the saved message from another bot, saving pictures and buttons supported aswell. + +Retrieving notes without the formatting: +To retrieve a note without the formatting, use /get (name) raw or /get (name) noformat +This will retrieve the note and send it without formatting it; getting you the raw note, allowing you to make easy edits. +""" diff --git a/DaisyX/modules/owner_stuff.py b/DaisyX/modules/owner_stuff.py new file mode 100644 index 00000000..9d7a50cc --- /dev/null +++ b/DaisyX/modules/owner_stuff.py @@ -0,0 +1,279 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import html +import os +import sys + +import rapidjson +import requests + +from DaisyX import DAISY_VERSION, OWNER_ID, bot, dp +from DaisyX.decorator import COMMANDS_ALIASES, REGISTRED_COMMANDS, register +from DaisyX.modules import LOADED_MODULES +from DaisyX.services.mongo import db, mongodb +from DaisyX.services.redis import redis +from DaisyX.services.telethon import tbot + +from .utils.covert import convert_size +from .utils.language import get_strings_dec +from .utils.message import need_args_dec +from .utils.notes import BUTTONS, get_parsed_note_list, send_note, t_unparse_note_item +from .utils.term import chat_term + + +@register(cmds="allcommands", is_op=True) +async def all_commands_list(message): + text = "" + for cmd in REGISTRED_COMMANDS: + text += "* /" + cmd + "\n" + await message.reply(text) + + +@register(cmds="allcmdsaliases", is_op=True) +async def all_cmds_aliases_list(message): + text = "" + text = str(COMMANDS_ALIASES) + await message.reply(text) + + +@register(cmds="loadedmodules", is_op=True) +async def all_modules_list(message): + text = "" + for module in LOADED_MODULES: + text += "* " + module.__name__ + "\n" + await message.reply(text) + + +@register(cmds="avaiblebtns", is_op=True) +async def all_btns_list(message): + text = "Avaible message inline btns:\n" + for module in BUTTONS: + text += "* " + module + "\n" + await message.reply(text) + + +@register(cmds="ip", is_owner=True, only_pm=True) +async def get_bot_ip(message): + await message.reply(requests.get("http://ipinfo.io/ip").text) + + +@register(cmds="term", is_owner=True) +async def cmd_term(message): + if message.from_user.id in devs: + msg = await message.reply("Running...") + command = str(message.text.split(" ", 1)[1]) + text = "Shell:\n" + text += ( + "" + + html.escape(await chat_term(message, command), quote=False) + + "" + ) + await msg.edit_text(text) + else: + pass + + +@register(cmds="leavechat", is_owner=True) +@need_args_dec() +async def leave_chat(message): + arg = message.text.split()[1] + cname = message.chat.title + await bot.leave_chat(chat_id=arg) + await message.reply(f"Done, I left the group {cname}") + + +@register(cmds="sbroadcast", is_owner=True) +@need_args_dec() +async def sbroadcast(message): + data = await get_parsed_note_list(message, split_args=-1) + dp.register_message_handler(check_message_for_smartbroadcast) + + await db.sbroadcast.drop({}) + + chats = mongodb.chat_list.distinct("chat_id") + + data["chats_num"] = len(chats) + data["recived_chats"] = 0 + data["chats"] = chats + + await db.sbroadcast.insert_one(data) + await message.reply( + "Smart broadcast planned for {} chats".format(len(chats)) + ) + + +@register(cmds="stopsbroadcast", is_owner=True) +async def stop_sbroadcast(message): + dp.message_handlers.unregister(check_message_for_smartbroadcast) + old = await db.sbroadcast.find_one({}) + await db.sbroadcast.drop({}) + await message.reply( + "Smart broadcast stopped." + "It was sended to %d chats." % old["recived_chats"] + ) + + +@register(cmds="continuebroadcast", is_owner=True) +async def continue_sbroadcast(message): + dp.register_message_handler(check_message_for_smartbroadcast) + return await message.reply("Re-registered the broadcast handler.") + + +# Check on smart broadcast +async def check_message_for_smartbroadcast(message): + chat_id = message.chat.id + if not (db_item := await db.sbroadcast.find_one({"chats": {"$in": [chat_id]}})): + return + + text, kwargs = await t_unparse_note_item(message, db_item, chat_id) + await send_note(chat_id, text, **kwargs) + + await db.sbroadcast.update_one( + {"_id": db_item["_id"]}, + {"$pull": {"chats": chat_id}, "$inc": {"recived_chats": 1}}, + ) + + +@register(cmds="purgecache", is_owner=True) +async def purge_caches(message): + redis.flushdb() + await message.reply("Redis cache was cleaned.") + + +@register(cmds="botstop", is_owner=True) +async def bot_stop(message): + await message.reply("Goodbye...") + sys.exit(1) + + +@register(cmds="restart", is_owner=True) +async def restart_bot(message): + await message.reply("Daisy will be restarted...") + args = [sys.executable, "-m", "DaisyX"] + os.execl(sys.executable, *args) + + +@register(cmds="upgrade", is_owner=True) +async def upgrade(message): + m = await message.reply("Upgrading sources...") + proc = await asyncio.create_subprocess_shell( + "git pull --no-edit", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, + ) + stdout = (await proc.communicate())[0] + if proc.returncode == 0: + if "Already up to date." in stdout.decode(): + await m.edit_text("There's nothing to upgrade.") + else: + await m.edit_text("Restarting...") + args = [sys.executable, "-m", "DaisyX"] + os.execl(sys.executable, *args) + else: + await m.edit_text( + f"Upgrade failed (process exited with {proc.returncode}):\n{stdout.decode()}" + ) + proc = await asyncio.create_subprocess_shell("git merge --abort") + await proc.communicate() + + +@register(cmds="upload", is_owner=True) +async def upload_file(message): + input_str = message.get_args() + if not os.path.exists(input_str): + await message.reply("File not found!") + return + await message.reply("Processing ...") + caption_rts = os.path.basename(input_str) + with open(input_str, "rb") as f: + await tbot.send_file( + message.chat.id, + f, + caption=caption_rts, + force_document=False, + allow_cache=False, + reply_to=message.message_id, + ) + + +@register(cmds="logs", is_op=True) +async def upload_logs(message): + input_str = "logs/daisy.log" + with open(input_str, "rb") as f: + await tbot.send_file(message.chat.id, f, reply_to=message.message_id) + + +@register(cmds="crash", is_owner=True) +async def crash(message): + test = 2 / 0 + print(test) + + +@register(cmds="event", is_op=True) +async def get_event(message): + print(message) + event = str(rapidjson.dumps(message, indent=2)) + await message.reply(event) + + +@register(cmds="stats", is_op=True) +async def stats(message): + if message.from_user.id in OWNER_ID: + text = f"Daisy {DAISY_VERSION} stats\n" + + for module in [m for m in LOADED_MODULES if hasattr(m, "__stats__")]: + text += await module.__stats__() + + await message.reply(text) + else: + pass + + +async def __stats__(): + text = "" + if os.getenv("WEBHOOKS", False): + text += f"* Webhooks mode, listen port: {os.getenv('WEBHOOKS_PORT', 8080)}\n" + else: + text += "* Long-polling mode\n" + local_db = await db.command("dbstats") + if "fsTotalSize" in local_db: + text += "* Database size is {}, free {}\n".format( + convert_size(local_db["dataSize"]), + convert_size(local_db["fsTotalSize"] - local_db["fsUsedSize"]), + ) + else: + text += "* Database size is {}, free {}\n".format( + convert_size(local_db["storageSize"]), + convert_size(536870912 - local_db["storageSize"]), + ) + + text += "* {} total keys in Redis database\n".format(len(redis.keys())) + text += "* {} total commands registred, in {} modules\n".format( + len(REGISTRED_COMMANDS), len(LOADED_MODULES) + ) + return text + + +@get_strings_dec("owner_stuff") +async def __user_info__(message, user_id, strings): + global skemmers + if user_id in skemmers: + return strings["sudo_crown"] diff --git a/DaisyX/modules/paste.py b/DaisyX/modules/paste.py new file mode 100644 index 00000000..a674cd6f --- /dev/null +++ b/DaisyX/modules/paste.py @@ -0,0 +1,44 @@ +# Copyright (C) 2020-2021 by DevsExpo@Github, < https://github.com/DevsExpo >. +# +# This file is part of < https://github.com/DevsExpo/FridayUserBot > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/DevsExpo/blob/master/LICENSE > +# +# All rights reserved. + +import os + +import requests +from pyrogram import filters + +from DaisyX.function.pluginhelpers import edit_or_reply, get_text +from DaisyX.services.pyrogram import pbot + + +@pbot.on_message(filters.command("paste") & ~filters.edited & ~filters.bot) +async def paste(client, message): + pablo = await edit_or_reply(message, "`Please Wait.....`") + tex_t = get_text(message) + message_s = tex_t + if not tex_t: + if not message.reply_to_message: + await pablo.edit("`Reply To File / Give Me Text To Paste!`") + return + if not message.reply_to_message.text: + file = await message.reply_to_message.download() + m_list = open(file, "r").read() + message_s = m_list + print(message_s) + os.remove(file) + elif message.reply_to_message.text: + message_s = message.reply_to_message.text + key = ( + requests.post("https://nekobin.com/api/documents", json={"content": message_s}) + .json() + .get("result") + .get("key") + ) + url = f"https://nekobin.com/{key}" + raw = f"https://nekobin.com/raw/{key}" + reply_text = f"Pasted Text To [NekoBin]({url}) And For Raw [Click Here]({raw})" + await pablo.edit(reply_text) diff --git a/DaisyX/modules/phone.py b/DaisyX/modules/phone.py new file mode 100644 index 00000000..4f888c13 --- /dev/null +++ b/DaisyX/modules/phone.py @@ -0,0 +1,66 @@ +import json + +import requests +from telethon import types + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as client + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + elif isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + else: + return None + + +@register(pattern=r"^/phone (.*)") +async def phone(event): + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply("☎️ You are not admin 🚶‍♀️") + return + information = event.pattern_match.group(1) + number = information + key = "fe65b94e78fc2e3234c1c6ed1b771abd" + api = ( + "http://apilayer.net/api/validate?access_key=" + + key + + "&number=" + + number + + "&country_code=&format=1" + ) + output = requests.get(api) + content = output.text + obj = json.loads(content) + country_code = obj["country_code"] + country_name = obj["country_name"] + location = obj["location"] + carrier = obj["carrier"] + line_type = obj["line_type"] + validornot = obj["valid"] + aa = "Valid: " + str(validornot) + a = "Phone number: " + str(number) + b = "Country: " + str(country_code) + c = "Country Name: " + str(country_name) + d = "Location: " + str(location) + e = "Carrier: " + str(carrier) + f = "Device: " + str(line_type) + g = f"{aa}\n{a}\n{b}\n{c}\n{d}\n{e}\n{f}" + await event.reply(g) diff --git a/DaisyX/modules/pinmisc.py b/DaisyX/modules/pinmisc.py new file mode 100644 index 00000000..0a0ac0ee --- /dev/null +++ b/DaisyX/modules/pinmisc.py @@ -0,0 +1,391 @@ +from re import compile as compile_re + +from pyrogram import filters +from pyrogram.errors import ChatAdminRequired, RightForbidden, RPCError +from pyrogram.types import InlineKeyboardButton, InlineKeyboardMarkup, Message + +from DaisyX.function.pluginhelpers import member_permissions +from DaisyX.services.mongo import mongodb as db +from DaisyX.services.pyrogram import pbot as app + +BTN_URL_REGEX = compile_re(r"(\[([^\[]+?)\]\(buttonurl:(?:/{0,2})(.+?)(:same)?\))") + + +async def parse_button(text: str): + """Parse button from text.""" + markdown_note = text + prev = 0 + note_data = "" + buttons = [] + for match in BTN_URL_REGEX.finditer(markdown_note): + # Check if btnurl is escaped + n_escapes = 0 + to_check = match.start(1) - 1 + while to_check > 0 and markdown_note[to_check] == "\\": + n_escapes += 1 + to_check -= 1 + + # if even, not escaped -> create button + if n_escapes % 2 == 0: + # create a thruple with button label, url, and newline status + buttons.append((match.group(2), match.group(3), bool(match.group(4)))) + note_data += markdown_note[prev : match.start(1)] + prev = match.end(1) + # if odd, escaped -> move along + else: + note_data += markdown_note[prev:to_check] + prev = match.start(1) - 1 + + note_data += markdown_note[prev:] + + return note_data, buttons + + +async def build_keyboard(buttons): + """Build keyboards from provided buttons.""" + keyb = [] + for btn in buttons: + if btn[-1] and keyb: + keyb[-1].append(InlineKeyboardButton(btn[0], url=btn[1])) + else: + keyb.append([InlineKeyboardButton(btn[0], url=btn[1])]) + + return keyb + + +class MongoDB: + """Class for interacting with Bot database.""" + + def __init__(self, collection) -> None: + self.collection = db[collection] + + # Insert one entry into collection + def insert_one(self, document): + result = self.collection.insert_one(document) + return repr(result.inserted_id) + + # Find one entry from collection + def find_one(self, query): + result = self.collection.find_one(query) + if result: + return result + return False + + # Find entries from collection + def find_all(self, query=None): + if query is None: + query = {} + lst = [] + for document in self.collection.find(query): + lst.append(document) + return lst + + # Count entries from collection + def count(self, query=None): + if query is None: + query = {} + return self.collection.count_documents(query) + + # Delete entry/entries from collection + def delete_one(self, query): + self.collection.delete_many(query) + after_delete = self.collection.count_documents({}) + return after_delete + + # Replace one entry in collection + def replace(self, query, new_data): + old = self.collection.find_one(query) + _id = old["_id"] + self.collection.replace_one({"_id": _id}, new_data) + new = self.collection.find_one({"_id": _id}) + return old, new + + # Update one entry from collection + def update(self, query, update): + result = self.collection.update_one(query, {"$set": update}) + new_document = self.collection.find_one(query) + return result.modified_count, new_document + + # Close connection + @staticmethod + def close(): + return mongodb_client.close() + + +def __connect_first(): + _ = MongoDB("test") + + +__connect_first() + + +@app.on_message(filters.command("unpinall") & ~filters.private) +async def unpinall_message(_, m: Message): + try: + chat_id = m.chat.id + user_id = m.from_user.id + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_pin_messages" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_restrict_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_promote_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + try: + await _.unpin_all_chat_messages(m.chat.id) + await m.reply("I have unpinned all messages") + except ChatAdminRequired: + await m.reply("I'm not admin here") + except RightForbidden: + await m.reply("I don't have enough rights to unpin here") + except RPCError as ef: + await m.reply_text(ef) + return + + except Exception as e: + print(e) + await m.reply_text(e) + return + + +from threading import RLock + +INSERTION_LOCK = RLock() + + +class Pins: + """Class for managing antichannelpins in chats.""" + + # Database name to connect to to preform operations + db_name = "antichannelpin" + + def __init__(self, chat_id: int) -> None: + self.collection = MongoDB(self.db_name) + self.chat_id = chat_id + self.chat_info = self.__ensure_in_db() + + def get_settings(self): + with INSERTION_LOCK: + return self.chat_info + + def antichannelpin_on(self): + with INSERTION_LOCK: + return self.set_on("antichannelpin") + + def cleanlinked_on(self): + with INSERTION_LOCK: + return self.set_on("cleanlinked") + + def antichannelpin_off(self): + with INSERTION_LOCK: + return self.set_off("antichannelpin") + + def cleanlinked_off(self): + with INSERTION_LOCK: + return self.set_off("cleanlinked") + + def set_on(self, atype: str): + with INSERTION_LOCK: + otype = "cleanlinked" if atype == "antichannelpin" else "antichannelpin" + return self.collection.update( + {"_id": self.chat_id}, + {atype: True, otype: False}, + ) + + def set_off(self, atype: str): + with INSERTION_LOCK: + otype = "cleanlinked" if atype == "antichannelpin" else "antichannelpin" + return self.collection.update( + {"_id": self.chat_id}, + {atype: False, otype: False}, + ) + + def __ensure_in_db(self): + chat_data = self.collection.find_one({"_id": self.chat_id}) + if not chat_data: + new_data = { + "_id": self.chat_id, + "antichannelpin": False, + "cleanlinked": False, + } + self.collection.insert_one(new_data) + return new_data + return chat_data + + # Migrate if chat id changes! + def migrate_chat(self, new_chat_id: int): + old_chat_db = self.collection.find_one({"_id": self.chat_id}) + new_data = old_chat_db.update({"_id": new_chat_id}) + self.collection.insert_one(new_data) + self.collection.delete_one({"_id": self.chat_id}) + + # ----- Static Methods ----- + @staticmethod + def count_chats(atype: str): + with INSERTION_LOCK: + collection = MongoDB(Pins.db_name) + return collection.count({atype: True}) + + @staticmethod + def list_chats(query: str): + with INSERTION_LOCK: + collection = MongoDB(Pins.db_name) + return collection.find_all({query: True}) + + @staticmethod + def load_from_db(): + with INSERTION_LOCK: + collection = MongoDB(Pins.db_name) + return collection.findall() + + @staticmethod + def repair_db(collection): + all_data = collection.find_all() + keys = {"antichannelpin": False, "cleanlinked": False} + for data in all_data: + for key, val in keys.items(): + try: + _ = data[key] + except KeyError: + collection.update({"_id": data["_id"]}, {key: val}) + + +def __pre_req_pins_chats(): + collection = MongoDB(Pins.db_name) + Pins.repair_db(collection) + + +@app.on_message(filters.command("antichannelpin") & ~filters.private) +async def anti_channel_pin(_, m: Message): + chat_id = m.chat.id + user_id = m.from_user.id + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_pin_messages" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_restrict_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_promote_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + pinsdb = Pins(m.chat.id) + if len(m.text.split()) == 1: + status = pinsdb.get_settings()["antichannelpin"] + await m.reply_text(f"Antichannelpin currently: {status}") + return + + if len(m.text.split()) == 2: + if m.command[1] in ("yes", "on", "true"): + pinsdb.antichannelpin_on() + msg = "Antichannelpin turned on for this chat" + elif m.command[1] in ("no", "off", "false"): + pinsdb.antichannelpin_off() + msg = "Antichannelpin turned off for this chat" + else: + await m.reply_text("Invalid syntax") + return + + await m.reply_text(msg) + return + + +@app.on_message(filters.command("cleanlinked") & ~filters.private) +async def clean_linked(_, m: Message): + chat_id = m.chat.id + user_id = m.from_user.id + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_pin_messages" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_restrict_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_promote_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + pinsdb = Pins(m.chat.id) + + if len(m.text.split()) == 1: + status = pinsdb.get_settings()["cleanlinked"] + await m.reply_text(f"Cleanlinked pins currently: {status}") + return + + if len(m.text.split()) == 2: + if m.command[1] in ("yes", "on", "true"): + pinsdb.cleanlinked_on() + msg = "Turned on CleanLinked! Now all the messages from linked channel will be deleted!" + elif m.command[1] in ("no", "off", "false"): + pinsdb.cleanlinked_off() + msg = "Turned off CleanLinked! Messages from linked channel will not be deleted!" + else: + await m.reply("Invalid syntax") + return + + await m.reply(msg) + return + + +@app.on_message(filters.command("permapin") & ~filters.private) +async def perma_pin(_, m: Message): + chat_id = m.chat.id + user_id = m.from_user.id + permissions = await member_permissions(chat_id, user_id) + if "can_change_info" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_pin_messages" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_restrict_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if "can_promote_members" not in permissions: + await m.reply_text("You Don't Have Enough Permissions.") + return + if m.reply_to_message or len(m.text.split()) > 1: + if m.reply_to_message: + text = m.reply_to_message.text + elif len(m.text.split()) > 1: + text = m.text.split(None, 1)[1] + teks, button = await parse_button(text) + button = await build_keyboard(button) + button = InlineKeyboardMarkup(button) if button else None + z = await m.reply_text(teks, reply_markup=button) + await z.pin() + else: + await m.reply_text("Reply to a message or enter text to pin it.") + await m.delete() + return + + +@app.on_message(filters.linked_channel) +async def antichanpin_cleanlinked(c, m: Message): + try: + msg_id = m.message_id + pins_db = Pins(m.chat.id) + curr = pins_db.get_settings() + if curr["antichannelpin"]: + await c.unpin_chat_message(chat_id=m.chat.id, message_id=msg_id) + if curr["cleanlinked"]: + await c.delete_messages(m.chat.id, msg_id) + except ChatAdminRequired: + await m.reply_text( + "Disabled antichannelpin as I don't have enough admin rights!", + ) + pins_db.antichannelpin_off() + except Exception: + return + return diff --git a/DaisyX/modules/pins.py b/DaisyX/modules/pins.py new file mode 100644 index 00000000..d845c554 --- /dev/null +++ b/DaisyX/modules/pins.py @@ -0,0 +1,77 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from aiogram.utils.exceptions import BadRequest + +from DaisyX import bot +from DaisyX.decorator import register + +from .utils.connections import chat_connection +from .utils.language import get_strings_dec +from .utils.message import get_arg + + +@register(cmds="unpin", user_can_pin_messages=True, bot_can_pin_messages=True) +@chat_connection(admin=True) +@get_strings_dec("pins") +async def unpin_message(message, chat, strings): + # support unpinning all + if get_arg(message) in {"all"}: + return await bot.unpin_all_chat_messages(chat["chat_id"]) + + try: + await bot.unpin_chat_message(chat["chat_id"]) + except BadRequest: + await message.reply(strings["chat_not_modified_unpin"]) + return + + +@register(cmds="pin", user_can_pin_messages=True, bot_can_pin_messages=True) +@get_strings_dec("pins") +async def pin_message(message, strings): + if "reply_to_message" not in message: + await message.reply(strings["no_reply_msg"]) + return + msg = message.reply_to_message.message_id + arg = get_arg(message).lower() + + dnd = True + loud = ["loud", "notify"] + if arg in loud: + dnd = False + + try: + await bot.pin_chat_message(message.chat.id, msg, disable_notification=dnd) + except BadRequest: + await message.reply(strings["chat_not_modified_pin"]) + + +__mod_name__ = "Pinning" + +__help__ = """ +All the pin related commands can be found here; keep your chat up to date on the latest news with a simple pinned message! + + Basic Pins +- /pin: silently pins the message replied to - add 'loud' or 'notify' to give notifs to users. +- /unpin: unpins the currently pinned message - add 'all' to unpin all pinned messages. + + Other +- /permapin [reply]: Pin a custom message through the bot. This message can contain markdown, buttons, and all the other cool features. +- /unpinall: Unpins all pinned messages. +- /antichannelpin [yes/no/on/off]: Don't let telegram auto-pin linked channels. If no arguments are given, shows current setting. +- /cleanlinked [yes/no/on/off]: Delete messages sent by the linked channel. + +Note: When using antichannel pins, make sure to use the /unpin command, instead of doing it manually. Otherwise, the old message will get re-pinned when the channel sends any messages. +""" diff --git a/DaisyX/modules/pm_menu.py b/DaisyX/modules/pm_menu.py new file mode 100644 index 00000000..08398553 --- /dev/null +++ b/DaisyX/modules/pm_menu.py @@ -0,0 +1,158 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import random +from contextlib import suppress + +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.callback_data import CallbackData +from aiogram.utils.exceptions import MessageNotModified + +from DaisyX.decorator import register +from DaisyX.modules.utils.disable import disableable_dec + +from . import MOD_HELP +from .language import select_lang_keyboard +from .utils.disable import disableable_dec +from .utils.language import get_strings_dec + +helpmenu_cb = CallbackData("helpmenu", "mod") + + +def help_markup(modules): + markup = InlineKeyboardMarkup() + for module in modules: + markup.insert( + InlineKeyboardButton(module, callback_data=helpmenu_cb.new(mod=module)) + ) + return markup + + +STICKERS = ( + "CAACAgUAAxkBAAJOGmBeli95P073FKVkgc4esfKE4UlXAAIOAgACyavAVkbLMIidWYdyHgQ", + "CAACAgUAAxkBAAJOG2BeljABwlCfwzHT1gzyiciBri6_AAIsAgACXBPBVgpGQRz-1qmlHgQ", + "CAACAgUAAxkBAAJOHGBeljOJ35CQNnkpnVcgRoHuJX6DAAL3AQACN8TBVm1PIART01cWHgQ", + "CAACAgUAAxkBAAJOHWBeljXW9QzYQ51gpCjHZHCF5Ui6AAJ7AgAC3zDBVo2xenp7JYhAHgQ", + "CAACAgUAAxkBAAJOHmBeljjU0_FT_QpdUUJBqVUC0nfJAAKYAgACJ_jBVvntHY_8WF27HgQ", + "CAACAgUAAxkBAAJOH2BeljrV68mPLu8_6n4edT20Q3IQAAJ9AgACq3LBVmLuZuNPlvkfHgQ", + "CAACAgUAAxkBAAJOIGBeljttuniUPykRtzkSZj3SRwKJAAI7AgACNm_BVp8TCkE6ZqCoHgQ", + "CAACAgUAAxkBAAJOIWBelj-P_2vtVqtkF2OMlVN3M0N4AAK3AQACSm3BVkXF2voraS2tHgQ", + "CAACAgUAAxkBAAJOImBelkJxUBm2rL1iPfMZfk-_9DaOAALrAgAC4T3BVniopXQVsZ4KHgQ", + "CAACAgUAAxkBAAJOI2BelkMO0AX_wtAc7hUZz1NixuMlAAKEAwACY4TAViVuNLTBmmkgHgQ", +) + + +@register(cmds="start", no_args=True, only_groups=True) +@disableable_dec("start") +@get_strings_dec("pm_menu") +async def start_group_cmd(message, strings): + await message.reply(strings["start_hi_group"]) + + +@register(cmds="start", no_args=True, only_pm=True) +async def start_cmd(message): + await message.reply_sticker(random.choice(STICKERS)) + await get_start_func(message) + + +@get_strings_dec("pm_menu") +async def get_start_func(message, strings, edit=False): + msg = message.message if hasattr(message, "message") else message + + task = msg.edit_text if edit else msg.reply + buttons = InlineKeyboardMarkup() + buttons.add(InlineKeyboardButton(strings["btn_help"], callback_data="get_help")) + buttons.add( + InlineKeyboardButton(strings["btn_lang"], callback_data="lang_btn"), + InlineKeyboardButton( + strings["btn_source"], url="https://github.com/TeamDaisyX/" + ), + ) + buttons.add( + InlineKeyboardButton(strings["btn_channel"], url="https://t.me/DaisyXUpdates"), + InlineKeyboardButton( + strings["btn_group"], url="https://t.me/DaisySupport_Official" + ), + ) + buttons.add( + InlineKeyboardButton( + "👸🏼 Add DaisyX to your group", + url=f"https://telegram.me/daisyxbot?startgroup=true", + ) + ) + # Handle error when user click the button 2 or more times simultaneously + with suppress(MessageNotModified): + await task(strings["start_hi"], reply_markup=buttons) + + +@register(regexp="get_help", f="cb") +@get_strings_dec("pm_menu") +async def help_cb(event, strings): + button = help_markup(MOD_HELP) + button.add(InlineKeyboardButton(strings["back"], callback_data="go_to_start")) + with suppress(MessageNotModified): + await event.message.edit_text(strings["help_header"], reply_markup=button) + + +@register(regexp="lang_btn", f="cb") +async def set_lang_cb(event): + await select_lang_keyboard(event.message, edit=True) + + +@register(regexp="go_to_start", f="cb") +async def back_btn(event): + await get_start_func(event, edit=True) + + +@register(cmds="help", only_pm=True) +@disableable_dec("help") +@get_strings_dec("pm_menu") +async def help_cmd(message, strings): + button = help_markup(MOD_HELP) + button.add(InlineKeyboardButton(strings["back"], callback_data="go_to_start")) + await message.reply(strings["help_header"], reply_markup=button) + + +@register(cmds="help", only_groups=True) +@disableable_dec("help") +@get_strings_dec("pm_menu") +async def help_cmd_g(message, strings): + text = strings["btn_group_help"] + button = InlineKeyboardMarkup().add( + InlineKeyboardButton(text=text, url="https://t.me/DaisyXBOT?start") + ) + await message.reply(strings["help_header"], reply_markup=button) + + +@register(helpmenu_cb.filter(), f="cb", allow_kwargs=True) +async def helpmenu_callback(query, callback_data=None, **kwargs): + mod = callback_data["mod"] + if not mod in MOD_HELP: + await query.answer() + return + msg = f"Help for {mod} module:\n" + msg += f"{MOD_HELP[mod]}" + button = InlineKeyboardMarkup().add( + InlineKeyboardButton(text="🏃‍♂️ Back", callback_data="get_help") + ) + with suppress(MessageNotModified): + await query.message.edit_text( + msg, disable_web_page_preview=True, reply_markup=button + ) + await query.answer("Help for " + mod) diff --git a/DaisyX/modules/polling.py b/DaisyX/modules/polling.py new file mode 100644 index 00000000..7d483c71 --- /dev/null +++ b/DaisyX/modules/polling.py @@ -0,0 +1,438 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from pymongo import MongoClient +from telethon import * +from telethon.tl import * + +from DaisyX import BOT_ID +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +MONGO_DB_URI = get_str_key("MONGO_URI", required=True) +client = MongoClient() +client = MongoClient(MONGO_DB_URI) +db = client["DaisyX"] +approved_users = db.approve +dbb = client["DaisyX"] +poll_id = dbb.pollid + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/poll (.*)") +async def _(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + try: + quew = event.pattern_match.group(1) + except Exception: + await event.reply("Where is the question ?") + return + if "|" in quew: + secrets, quess, options = quew.split("|") + secret = secrets.strip() + + if not secret: + await event.reply("I need a poll id of 5 digits to make a poll") + return + + try: + secret = str(secret) + except ValueError: + await event.reply("Poll id should contain only numbers") + return + + # print(secret) + + if len(secret) != 5: + await event.reply("Poll id should be an integer of 5 digits") + return + + allpoll = poll_id.find({}) + # print(secret) + for c in allpoll: + if event.sender_id == c["user"]: + await event.reply( + "Please stop the previous poll before creating a new one !" + ) + return + poll_id.insert_one({"user": event.sender_id, "pollid": secret}) + + ques = quess.strip() + option = options.strip() + quiz = option.split(" ")[1 - 1] + if "True" in quiz: + quizy = True + if "@" in quiz: + one, two = quiz.split("@") + rightone = two.strip() + else: + await event.reply( + "You need to select the right answer with question number like True@1, True@3 etc.." + ) + return + + quizoptionss = [] + try: + ab = option.split(" ")[4 - 1] + cd = option.split(" ")[5 - 1] + quizoptionss.append(types.PollAnswer(ab, b"1")) + quizoptionss.append(types.PollAnswer(cd, b"2")) + except Exception: + await event.reply("At least need two options to create a poll") + return + try: + ef = option.split(" ")[6 - 1] + quizoptionss.append(types.PollAnswer(ef, b"3")) + except Exception: + ef = None + try: + gh = option.split(" ")[7 - 1] + quizoptionss.append(types.PollAnswer(gh, b"4")) + except Exception: + gh = None + try: + ij = option.split(" ")[8 - 1] + quizoptionss.append(types.PollAnswer(ij, b"5")) + except Exception: + ij = None + try: + kl = option.split(" ")[9 - 1] + quizoptionss.append(types.PollAnswer(kl, b"6")) + except Exception: + kl = None + try: + mn = option.split(" ")[10 - 1] + quizoptionss.append(types.PollAnswer(mn, b"7")) + except Exception: + mn = None + try: + op = option.split(" ")[11 - 1] + quizoptionss.append(types.PollAnswer(op, b"8")) + except Exception: + op = None + try: + qr = option.split(" ")[12 - 1] + quizoptionss.append(types.PollAnswer(qr, b"9")) + except Exception: + qr = None + try: + st = option.split(" ")[13 - 1] + quizoptionss.append(types.PollAnswer(st, b"10")) + except Exception: + st = None + + elif "False" in quiz: + quizy = False + else: + await event.reply("Wrong arguments provided !") + return + + pvote = option.split(" ")[2 - 1] + if "True" in pvote: + pvoty = True + elif "False" in pvote: + pvoty = False + else: + await event.reply("Wrong arguments provided !") + return + mchoice = option.split(" ")[3 - 1] + if "True" in mchoice: + mchoicee = True + elif "False" in mchoice: + mchoicee = False + else: + await event.reply("Wrong arguments provided !") + return + optionss = [] + try: + ab = option.split(" ")[4 - 1] + cd = option.split(" ")[5 - 1] + optionss.append(types.PollAnswer(ab, b"1")) + optionss.append(types.PollAnswer(cd, b"2")) + except Exception: + await event.reply("At least need two options to create a poll") + return + try: + ef = option.split(" ")[6 - 1] + optionss.append(types.PollAnswer(ef, b"3")) + except Exception: + ef = None + try: + gh = option.split(" ")[7 - 1] + optionss.append(types.PollAnswer(gh, b"4")) + except Exception: + gh = None + try: + ij = option.split(" ")[8 - 1] + optionss.append(types.PollAnswer(ij, b"5")) + except Exception: + ij = None + try: + kl = option.split(" ")[9 - 1] + optionss.append(types.PollAnswer(kl, b"6")) + except Exception: + kl = None + try: + mn = option.split(" ")[10 - 1] + optionss.append(types.PollAnswer(mn, b"7")) + except Exception: + mn = None + try: + op = option.split(" ")[11 - 1] + optionss.append(types.PollAnswer(op, b"8")) + except Exception: + op = None + try: + qr = option.split(" ")[12 - 1] + optionss.append(types.PollAnswer(qr, b"9")) + except Exception: + qr = None + try: + st = option.split(" ")[13 - 1] + optionss.append(types.PollAnswer(st, b"10")) + except Exception: + st = None + + if pvoty is False and quizy is False and mchoicee is False: + await tbot.send_file( + event.chat_id, + types.InputMediaPoll( + poll=types.Poll(id=12345, question=ques, answers=optionss, quiz=False) + ), + ) + + if pvoty is True and quizy is False and mchoicee is True: + await tbot.send_file( + event.chat_id, + types.InputMediaPoll( + poll=types.Poll( + id=12345, + question=ques, + answers=optionss, + quiz=False, + multiple_choice=True, + public_voters=True, + ) + ), + ) + + if pvoty is False and quizy is False and mchoicee is True: + await tbot.send_file( + event.chat_id, + types.InputMediaPoll( + poll=types.Poll( + id=12345, + question=ques, + answers=optionss, + quiz=False, + multiple_choice=True, + public_voters=False, + ) + ), + ) + + if pvoty is True and quizy is False and mchoicee is False: + await tbot.send_file( + event.chat_id, + types.InputMediaPoll( + poll=types.Poll( + id=12345, + question=ques, + answers=optionss, + quiz=False, + multiple_choice=False, + public_voters=True, + ) + ), + ) + + if pvoty is False and quizy is True and mchoicee is False: + await tbot.send_file( + event.chat_id, + types.InputMediaPoll( + poll=types.Poll( + id=12345, question=ques, answers=quizoptionss, quiz=True + ), + correct_answers=[f"{rightone}"], + ), + ) + + if pvoty is True and quizy is True and mchoicee is False: + await tbot.send_file( + event.chat_id, + types.InputMediaPoll( + poll=types.Poll( + id=12345, + question=ques, + answers=quizoptionss, + quiz=True, + public_voters=True, + ), + correct_answers=[f"{rightone}"], + ), + ) + + if pvoty is True and quizy is True and mchoicee is True: + await event.reply("You can't use multiple voting with quiz mode") + return + if pvoty is False and quizy is True and mchoicee is True: + await event.reply("You can't use multiple voting with quiz mode") + return + + +@register(pattern="^/stoppoll (.*)") +async def stop(event): + secret = event.pattern_match.group(1) + # print(secret) + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + + if not event.reply_to_msg_id: + await event.reply("Please reply to a poll to stop it") + return + + if input is None: + await event.reply("Where is the poll id ?") + return + + try: + secret = str(secret) + except ValueError: + await event.reply("Poll id should contain only numbers") + return + + if len(secret) != 5: + await event.reply("Poll id should be an integer of 5 digits") + return + + msg = await event.get_reply_message() + + if str(msg.sender_id) != str(BOT_ID): + await event.reply( + "I can't do this operation on this poll.\nProbably it's not created by me" + ) + return + print(secret) + if msg.poll: + allpoll = poll_id.find({}) + for c in allpoll: + if not event.sender_id == c["user"] and not secret == c["pollid"]: + await event.reply( + "Oops, either you haven't created this poll or you have given wrong poll id" + ) + return + if msg.poll.poll.closed: + await event.reply("Oops, the poll is already closed.") + return + poll_id.delete_one({"user": event.sender_id}) + pollid = msg.poll.poll.id + await msg.edit( + file=types.InputMediaPoll( + poll=types.Poll(id=pollid, question="", answers=[], closed=True) + ) + ) + await event.reply("Successfully stopped the poll") + else: + await event.reply("This isn't a poll") + + +@register(pattern="^/forgotpollid$") +async def stop(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + allpoll = poll_id.find({}) + for c in allpoll: + if event.sender_id == c["user"]: + try: + poll_id.delete_one({"user": event.sender_id}) + await event.reply("Done you can now create a new poll.") + except Exception: + await event.reply("Seems like you haven't created any poll yet !") + + +__help__ = """ +You can now send polls anonymously with Daisy +Here is how you can do it: + Parameters - + - poll-id - a poll id consists of an 5 digit random integer, this id is automatically removed from the system when you stop your previous poll + - question - the question you wanna ask + - [True@optionnumber/False](1) - quiz mode, you must state the correct answer with @ eg: True@ or True@2 + - [True/False](2) - public votes + - [True/False](3) - multiple choice + Syntax - +- /poll [poll-id] question | True@optionnumber/False [True/False] [True/False] [option1] [option2] ... upto [option10] + Examples - +- /poll 12345 | am i cool? | False False False yes no` +- /poll 12345 | am i cool? | True@1 False False yes no` + To stop a poll +Reply to the poll with `/stoppoll [poll-id]` to stop the poll + Fogot poll id +- /forgotpollid - to reset poll + +""" + + +__mod_name__ = "Polls" diff --git a/DaisyX/modules/promotes.py b/DaisyX/modules/promotes.py new file mode 100644 index 00000000..fe4d4228 --- /dev/null +++ b/DaisyX/modules/promotes.py @@ -0,0 +1,102 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import html + +from aiogram.utils.exceptions import ChatAdminRequired +from telethon.errors import AdminRankEmojiNotAllowedError + +from DaisyX import BOT_ID, bot +from DaisyX.decorator import register +from DaisyX.services.telethon import tbot + +from .utils.connections import chat_connection +from .utils.language import get_strings_dec +from .utils.user_details import ( + get_admins_rights, + get_user_and_text_dec, + get_user_dec, + get_user_link, +) + + +@register(cmds="promote", bot_can_promote_members=True, user_can_promote_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_and_text_dec() +@get_strings_dec("promotes") +async def promote(message, chat, user, args, strings): + chat_id = chat["chat_id"] + text = strings["promote_success"].format( + user=await get_user_link(user["user_id"]), chat_name=chat["chat_title"] + ) + + if user["user_id"] == BOT_ID: + return + + if user["user_id"] == message.from_user.id: + return await message.reply(strings["cant_promote_yourself"]) + + title = None + + if args: + if len(args) > 16: + await message.reply(strings["rank_to_loong"]) + return + title = args + text += strings["promote_title"].format(role=html.escape(title, quote=False)) + + try: + await tbot.edit_admin( + chat_id, + user["user_id"], + invite_users=True, + change_info=True, + ban_users=True, + delete_messages=True, + pin_messages=True, + title=title, + ) + except ValueError: + return await message.reply(strings["cant_get_user"]) + except AdminRankEmojiNotAllowedError: + return await message.reply(strings["emoji_not_allowed"]) + await get_admins_rights(chat_id, force_update=True) # Reset a cache + await message.reply(text) + + +@register(cmds="demote", bot_can_promote_members=True, user_can_promote_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_dec() +@get_strings_dec("promotes") +async def demote(message, chat, user, strings): + chat_id = chat["chat_id"] + if user["user_id"] == BOT_ID: + return + + try: + await bot.promote_chat_member(chat_id, user["user_id"]) + except ChatAdminRequired: + return await message.reply(strings["demote_failed"]) + + await get_admins_rights(chat_id, force_update=True) # Reset a cache + await message.reply( + strings["demote_success"].format( + user=await get_user_link(user["user_id"]), chat_name=chat["chat_title"] + ) + ) diff --git a/DaisyX/modules/purges.py b/DaisyX/modules/purges.py new file mode 100644 index 00000000..c9f164b0 --- /dev/null +++ b/DaisyX/modules/purges.py @@ -0,0 +1,91 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio + +from telethon.errors.rpcerrorlist import MessageDeleteForbiddenError + +from DaisyX import bot +from DaisyX.decorator import register +from DaisyX.services.telethon import tbot + +from .utils.language import get_strings_dec +from .utils.notes import BUTTONS + + +@register(cmds="del", bot_can_delete_messages=True, user_can_delete_messages=True) +@get_strings_dec("msg_deleting") +async def del_message(message, strings): + if not message.reply_to_message: + await message.reply(strings["reply_to_msg"]) + return + msgs = [message.message_id, message.reply_to_message.message_id] + await tbot.delete_messages(message.chat.id, msgs) + + +@register( + cmds="purge", + no_args=True, + bot_can_delete_messages=True, + user_can_delete_messages=True, +) +@get_strings_dec("msg_deleting") +async def fast_purge(message, strings): + if not message.reply_to_message: + await message.reply(strings["reply_to_msg"]) + return + msg_id = message.reply_to_message.message_id + delete_to = message.message_id + + chat_id = message.chat.id + msgs = [] + for m_id in range(int(delete_to), msg_id - 1, -1): + msgs.append(m_id) + if len(msgs) == 100: + await tbot.delete_messages(chat_id, msgs) + msgs = [] + + try: + await tbot.delete_messages(chat_id, msgs) + except MessageDeleteForbiddenError: + await message.reply(strings["purge_error"]) + return + + msg = await bot.send_message(chat_id, strings["fast_purge_done"]) + await asyncio.sleep(5) + await msg.delete() + + +BUTTONS.update({"delmsg": "btn_deletemsg_cb"}) + + +@register(regexp=r"btn_deletemsg:(\w+)", f="cb", allow_kwargs=True) +async def delmsg_btn(event, regexp=None, **kwargs): + await event.message.delete() + + +__mod_name__ = "Purges" + +__help__ = """ +Need to delete lots of messages? That's what purges are for! + +Available commands: +- /purge: Deletes all messages from the message you replied to, to the current message. +- /del: Deletes the message you replied to and your "/del" command message. +""" diff --git a/DaisyX/modules/qr_code.py b/DaisyX/modules/qr_code.py new file mode 100644 index 00000000..4c366745 --- /dev/null +++ b/DaisyX/modules/qr_code.py @@ -0,0 +1,132 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +from asyncio import sleep +from datetime import datetime + +from requests import get, post +from telethon.tl import functions, types + +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as client + + +def progress(current, total): + """Calculate and return the download progress with given arguments.""" + print( + "Downloaded {} of {}\nCompleted {}".format( + current, total, (current / total) * 100 + ) + ) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + elif isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + else: + return None + + +@register(pattern=r"^/getqr$") +async def parseqr(qr_e): + """For .getqr command, get QR Code content from the replied photo.""" + if qr_e.fwd_from: + return + start = datetime.now() + downloaded_file_name = await qr_e.client.download_media( + await qr_e.get_reply_message(), progress_callback=progress + ) + url = "https://api.qrserver.com/v1/read-qr-code/?outputformat=json" + file = open(downloaded_file_name, "rb") + files = {"file": file} + resp = post(url, files=files).json() + qr_contents = resp[0]["symbol"][0]["data"] + file.close() + os.remove(downloaded_file_name) + end = datetime.now() + duration = (end - start).seconds + await qr_e.reply( + "Obtained QRCode contents in {} seconds.\n{}".format(duration, qr_contents) + ) + + +@register(pattern=r"^/makeqr(?: |$)([\s\S]*)") +async def make_qr(qrcode): + """For .makeqr command, make a QR Code containing the given content.""" + if qrcode.fwd_from: + return + start = datetime.now() + input_str = qrcode.pattern_match.group(1) + message = "SYNTAX: `.makeqr `" + reply_msg_id = None + if input_str: + message = input_str + elif qrcode.reply_to_msg_id: + previous_message = await qrcode.get_reply_message() + reply_msg_id = previous_message.id + if previous_message.media: + downloaded_file_name = await qrcode.client.download_media( + previous_message, progress_callback=progress + ) + m_list = None + with open(downloaded_file_name, "rb") as file: + m_list = file.readlines() + message = "" + for media in m_list: + message += media.decode("UTF-8") + "\r\n" + os.remove(downloaded_file_name) + else: + message = previous_message.message + + url = "https://api.qrserver.com/v1/create-qr-code/?data={}&\ +size=200x200&charset-source=UTF-8&charset-target=UTF-8\ +&ecc=L&color=0-0-0&bgcolor=255-255-255\ +&margin=1&qzone=0&format=jpg" + + resp = get(url.format(message), stream=True) + required_file_name = "temp_qr.webp" + with open(required_file_name, "w+b") as file: + for chunk in resp.iter_content(chunk_size=128): + file.write(chunk) + await qrcode.client.send_file( + qrcode.chat_id, + required_file_name, + reply_to=reply_msg_id, + progress_callback=progress, + ) + os.remove(required_file_name) + duration = (datetime.now() - start).seconds + await qrcode.reply("Created QRCode in {} seconds".format(duration)) + await sleep(5) diff --git a/DaisyX/modules/quotely.py b/DaisyX/modules/quotely.py new file mode 100644 index 00000000..06e4329c --- /dev/null +++ b/DaisyX/modules/quotely.py @@ -0,0 +1,466 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import json +import os +import random +import textwrap +import urllib + +import emoji +from fontTools.ttLib import TTFont +from PIL import Image, ImageDraw, ImageFont, ImageOps +from telethon.tl import functions, types + +from DaisyX.services.events import register + +COLORS = [ + "#F07975", + "#F49F69", + "#F9C84A", + "#8CC56E", + "#6CC7DC", + "#80C1FA", + "#BCB3F9", + "#E181AC", +] + + +async def process(msg, user, client, reply, replied=None): + if not os.path.isdir("resources"): + os.mkdir("resources", 0o755) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Roboto-Regular.ttf", + "resources/Roboto-Regular.ttf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Quivira.otf", + "resources/Quivira.otf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Roboto-Medium.ttf", + "resources/Roboto-Medium.ttf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/DroidSansMono.ttf", + "resources/DroidSansMono.ttf", + ) + urllib.request.urlretrieve( + "https://github.com/erenmetesar/modules-repo/raw/master/Roboto-Italic.ttf", + "resources/Roboto-Italic.ttf", + ) + + # Importıng fonts and gettings the size of text + font = ImageFont.truetype("resources/Roboto-Medium.ttf", 43, encoding="utf-16") + font2 = ImageFont.truetype("resources/Roboto-Regular.ttf", 33, encoding="utf-16") + mono = ImageFont.truetype("resources/DroidSansMono.ttf", 30, encoding="utf-16") + italic = ImageFont.truetype("resources/Roboto-Italic.ttf", 33, encoding="utf-16") + fallback = ImageFont.truetype("resources/Quivira.otf", 43, encoding="utf-16") + + # Splitting text + maxlength = 0 + width = 0 + text = [] + for line in msg.split("\n"): + length = len(line) + if length > 43: + text += textwrap.wrap(line, 43) + maxlength = 43 + if width < fallback.getsize(line[:43])[0]: + if "MessageEntityCode" in str(reply.entities): + width = mono.getsize(line[:43])[0] + 30 + else: + width = fallback.getsize(line[:43])[0] + next + else: + text.append(line + "\n") + if width < fallback.getsize(line)[0]: + if "MessageEntityCode" in str(reply.entities): + width = mono.getsize(line)[0] + 30 + else: + width = fallback.getsize(line)[0] + if maxlength < length: + maxlength = length + + title = "" + try: + details = await client( + functions.channels.GetParticipantRequest(reply.chat_id, user.id) + ) + if isinstance(details.participant, types.ChannelParticipantCreator): + title = details.participant.rank if details.participant.rank else "Creator" + elif isinstance(details.participant, types.ChannelParticipantAdmin): + title = details.participant.rank if details.participant.rank else "Admin" + except TypeError: + pass + titlewidth = font2.getsize(title)[0] + + # Get user name + lname = "" if not user.last_name else user.last_name + tot = user.first_name + " " + lname + + namewidth = fallback.getsize(tot)[0] + 10 + + if namewidth > width: + width = namewidth + width += titlewidth + 30 if titlewidth > width - namewidth else -(titlewidth - 30) + height = len(text) * 40 + + # Profile Photo BG + pfpbg = Image.new("RGBA", (125, 600), (0, 0, 0, 0)) + + # Draw Template + top, middle, bottom = await drawer(width, height) + # Profile Photo Check and Fetch + yes = False + color = random.choice(COLORS) + async for photo in client.iter_profile_photos(user, limit=1): + yes = True + if yes: + pfp = await client.download_profile_photo(user) + paste = Image.open(pfp) + os.remove(pfp) + paste.thumbnail((105, 105)) + + # Mask + mask_im = Image.new("L", paste.size, 0) + draw = ImageDraw.Draw(mask_im) + draw.ellipse((0, 0, 105, 105), fill=255) + + # Apply Mask + pfpbg.paste(paste, (0, 0), mask_im) + else: + paste, color = await no_photo(user, tot) + pfpbg.paste(paste, (0, 0)) + + # Creating a big canvas to gather all the elements + canvassize = ( + middle.width + pfpbg.width, + top.height + middle.height + bottom.height, + ) + canvas = Image.new("RGBA", canvassize) + draw = ImageDraw.Draw(canvas) + + y = 80 + if replied: + # Creating a big canvas to gather all the elements + replname = "" if not replied.sender.last_name else replied.sender.last_name + reptot = replied.sender.first_name + " " + replname + font2.getsize(reptot)[0] + if reply.sticker: + sticker = await reply.download_media() + stimg = Image.open(sticker) + canvas = canvas.resize((stimg.width + pfpbg.width, stimg.height + 160)) + top = Image.new("RGBA", (200 + stimg.width, 300), (29, 29, 29, 255)) + draw = ImageDraw.Draw(top) + await replied_user(draw, reptot, replied.message.replace("\n", " "), 20) + top = top.crop((135, 70, top.width, 300)) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width + 10, 0)) + canvas.paste(stimg, (pfpbg.width + 10, 140)) + os.remove(sticker) + return True, canvas + canvas = canvas.resize((canvas.width + 60, canvas.height + 120)) + top, middle, bottom = await drawer(middle.width + 60, height + 105) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width, 0)) + canvas.paste(middle, (pfpbg.width, top.height)) + canvas.paste(bottom, (pfpbg.width, top.height + middle.height)) + draw = ImageDraw.Draw(canvas) + if replied.sticker: + replied.text = "Sticker" + elif replied.photo: + replied.text = "Photo" + elif replied.audio: + replied.text = "Audio" + elif replied.voice: + replied.text = "Voice Message" + elif replied.document: + replied.text = "Document" + await replied_user( + draw, + reptot, + replied.message.replace("\n", " "), + maxlength + len(title), + len(title), + ) + y = 200 + elif reply.sticker: + sticker = await reply.download_media() + stimg = Image.open(sticker) + canvas = canvas.resize((stimg.width + pfpbg.width + 30, stimg.height + 10)) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(stimg, (pfpbg.width + 10, 10)) + os.remove(sticker) + return True, canvas + elif reply.document and not reply.audio and not reply.audio: + docname = ".".join(reply.document.attributes[-1].file_name.split(".")[:-1]) + doctype = reply.document.attributes[-1].file_name.split(".")[-1].upper() + if reply.document.size < 1024: + docsize = str(reply.document.size) + " Bytes" + elif reply.document.size < 1048576: + docsize = str(round(reply.document.size / 1024, 2)) + " KB " + elif reply.document.size < 1073741824: + docsize = str(round(reply.document.size / 1024 ** 2, 2)) + " MB " + else: + docsize = str(round(reply.document.size / 1024 ** 3, 2)) + " GB " + docbglen = ( + font.getsize(docsize)[0] + if font.getsize(docsize)[0] > font.getsize(docname)[0] + else font.getsize(docname)[0] + ) + canvas = canvas.resize((pfpbg.width + width + docbglen, 160 + height)) + top, middle, bottom = await drawer(width + docbglen, height + 30) + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width, 0)) + canvas.paste(middle, (pfpbg.width, top.height)) + canvas.paste(bottom, (pfpbg.width, top.height + middle.height)) + canvas = await doctype(docname, docsize, doctype, canvas) + y = 80 if text else 0 + else: + canvas.paste(pfpbg, (0, 0)) + canvas.paste(top, (pfpbg.width, 0)) + canvas.paste(middle, (pfpbg.width, top.height)) + canvas.paste(bottom, (pfpbg.width, top.height + middle.height)) + y = 85 + + # Writing User's Name + space = pfpbg.width + 30 + namefallback = ImageFont.truetype("resources/Quivira.otf", 43, encoding="utf-16") + for letter in tot: + if letter in emoji.UNICODE_EMOJI: + newemoji, mask = await emoji_fetch(letter) + canvas.paste(newemoji, (space, 24), mask) + space += 40 + else: + if not await fontTest(letter): + draw.text((space, 20), letter, font=namefallback, fill=color) + space += namefallback.getsize(letter)[0] + else: + draw.text((space, 20), letter, font=font, fill=color) + space += font.getsize(letter)[0] + + if title: + draw.text( + (canvas.width - titlewidth - 20, 25), title, font=font2, fill="#898989" + ) + + # Writing all separating emojis and regular texts + x = pfpbg.width + 30 + bold, mono, italic, link = await get_entity(reply) + index = 0 + emojicount = 0 + textfallback = ImageFont.truetype("resources/Quivira.otf", 33, encoding="utf-16") + textcolor = "white" + for line in text: + for letter in line: + index = ( + msg.find(letter) if emojicount == 0 else msg.find(letter) + emojicount + ) + for offset, length in bold.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/Roboto-Medium.ttf", 33, encoding="utf-16" + ) + textcolor = "white" + for offset, length in italic.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/Roboto-Italic.ttf", 33, encoding="utf-16" + ) + textcolor = "white" + for offset, length in mono.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/DroidSansMono.ttf", 30, encoding="utf-16" + ) + textcolor = "white" + for offset, length in link.items(): + if index in range(offset, length): + font2 = ImageFont.truetype( + "resources/Roboto-Regular.ttf", 30, encoding="utf-16" + ) + textcolor = "#898989" + if letter in emoji.UNICODE_EMOJI: + newemoji, mask = await emoji_fetch(letter) + canvas.paste(newemoji, (x, y - 2), mask) + x += 45 + emojicount += 1 + else: + if not await fontTest(letter): + draw.text((x, y), letter, font=textfallback, fill=textcolor) + x += textfallback.getsize(letter)[0] + else: + draw.text((x, y), letter, font=font2, fill=textcolor) + x += font2.getsize(letter)[0] + msg = msg.replace(letter, "¶", 1) + y += 40 + x = pfpbg.width + 30 + return True, canvas + + +async def drawer(width, height): + # Top part + top = Image.new("RGBA", (width, 20), (0, 0, 0, 0)) + draw = ImageDraw.Draw(top) + draw.line((10, 0, top.width - 20, 0), fill=(29, 29, 29, 255), width=50) + draw.pieslice((0, 0, 30, 50), 180, 270, fill=(29, 29, 29, 255)) + draw.pieslice((top.width - 75, 0, top.width, 50), 270, 360, fill=(29, 29, 29, 255)) + + # Middle part + middle = Image.new("RGBA", (top.width, height + 75), (29, 29, 29, 255)) + + # Bottom part + bottom = ImageOps.flip(top) + + return top, middle, bottom + + +async def fontTest(letter): + test = TTFont("resources/Roboto-Medium.ttf") + for table in test["cmap"].tables: + if ord(letter) in table.cmap.keys(): + return True + + +async def get_entity(msg): + bold = {0: 0} + italic = {0: 0} + mono = {0: 0} + link = {0: 0} + if not msg.entities: + return bold, mono, italic, link + for entity in msg.entities: + if isinstance(entity, types.MessageEntityBold): + bold[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityItalic): + italic[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityCode): + mono[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityUrl): + link[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityTextUrl): + link[entity.offset] = entity.offset + entity.length + elif isinstance(entity, types.MessageEntityMention): + link[entity.offset] = entity.offset + entity.length + return bold, mono, italic, link + + +async def doctype(name, size, type, canvas): + font = ImageFont.truetype("resources/Roboto-Medium.ttf", 38) + doc = Image.new("RGBA", (130, 130), (29, 29, 29, 255)) + draw = ImageDraw.Draw(doc) + draw.ellipse((0, 0, 130, 130), fill="#434343") + draw.line((66, 28, 66, 53), width=14, fill="white") + draw.polygon([(67, 77), (90, 53), (42, 53)], fill="white") + draw.line((40, 87, 90, 87), width=8, fill="white") + canvas.paste(doc, (160, 23)) + draw2 = ImageDraw.Draw(canvas) + draw2.text((320, 40), name, font=font, fill="white") + draw2.text((320, 97), size + type, font=font, fill="#AAAAAA") + return canvas + + +async def no_photo(reply, tot): + pfp = Image.new("RGBA", (105, 105), (0, 0, 0, 0)) + pen = ImageDraw.Draw(pfp) + color = random.choice(COLORS) + pen.ellipse((0, 0, 105, 105), fill=color) + letter = "" if not tot else tot[0] + font = ImageFont.truetype("resources/Roboto-Regular.ttf", 60) + pen.text((32, 17), letter, font=font, fill="white") + return pfp, color + + +async def emoji_fetch(emoji): + emojis = json.loads( + urllib.request.urlopen( + "https://github.com/erenmetesar/modules-repo/raw/master/emojis.txt" + ) + .read() + .decode() + ) + if emoji in emojis: + img = emojis[emoji] + return await transparent( + urllib.request.urlretrieve(img, "resources/emoji.png")[0] + ) + else: + img = emojis["⛔"] + return await transparent( + urllib.request.urlretrieve(img, "resources/emoji.png")[0] + ) + + +async def transparent(emoji): + emoji = Image.open(emoji).convert("RGBA") + emoji.thumbnail((40, 40)) + + # Mask + mask = Image.new("L", (40, 40), 0) + draw = ImageDraw.Draw(mask) + draw.ellipse((0, 0, 40, 40), fill=255) + return emoji, mask + + +async def replied_user(draw, tot, text, maxlength, title): + namefont = ImageFont.truetype("resources/Roboto-Medium.ttf", 38) + namefallback = ImageFont.truetype("resources/Quivira.otf", 38) + textfont = ImageFont.truetype("resources/Roboto-Regular.ttf", 32) + textfallback = ImageFont.truetype("resources/Roboto-Medium.ttf", 38) + maxlength = maxlength + 7 if maxlength < 10 else maxlength + text = text[: maxlength - 2] + ".." if len(text) > maxlength else text + draw.line((165, 90, 165, 170), width=5, fill="white") + space = 0 + for letter in tot: + if not await fontTest(letter): + draw.text((180 + space, 86), letter, font=namefallback, fill="#888888") + space += namefallback.getsize(letter)[0] + else: + draw.text((180 + space, 86), letter, font=namefont, fill="#888888") + space += namefont.getsize(letter)[0] + space = 0 + for letter in text: + if not await fontTest(letter): + draw.text((180 + space, 132), letter, font=textfallback, fill="#888888") + space += textfallback.getsize(letter)[0] + else: + draw.text((180 + space, 132), letter, font=textfont, fill="white") + space += textfont.getsize(letter)[0] + + +@register(pattern="^/q") +async def _(event): + if event.fwd_from: + return + reply = await event.get_reply_message() + msg = reply.message + repliedreply = await reply.get_reply_message() + user = ( + await event.client.get_entity(reply.forward.sender) + if reply.fwd_from + else reply.sender + ) + res, canvas = await process(msg, user, event.client, reply, repliedreply) + if not res: + return + canvas.save("sticker.webp") + await event.client.send_file( + event.chat_id, "sticker.webp", reply_to=event.reply_to_msg_id + ) + os.remove("sticker.webp") diff --git a/DaisyX/modules/readme.md b/DaisyX/modules/readme.md new file mode 100644 index 00000000..cb81a92c --- /dev/null +++ b/DaisyX/modules/readme.md @@ -0,0 +1,84 @@ +# DaisyX Example plugin format + +## Basic: Simple Plugins +```python3 + +from DaisyX.decorator import register +from .utils.disable import disableable_dec +from .utils.message import get_args_str + +@register(cmds="Hi") +@disableable_dec("Hi") +async def _(message): + j = "Hello there" + await message.reply(j) + +__mod_name__ = "Hi" +__help__ = """ +Hi +- /hi: Say Hello There +""" +``` + +## Basic: Env Vars +```python3 +# You can import env like this. If config present auto use config + +from DaisyX.decorator import register +from .utils.disable import disableable_dec +from .utils.message import get_args_str +from DaisyX.config import get_int_key, get_str_key + +HI_STRING = get_str_key("HI_STRING", required=True) # String +MULTI = get_int_key("MULTI", required=True) #Intiger + +@register(cmds="Hi") +@disableable_dec("Hi") +async def _(message): + j = HI_STRING*MULTI + await message.reply(j) + +__mod_name__ = "Hi" +__help__ = """ +Hi +- /hi: Say Hello There +""" +``` + + + +## Advanced: Pyrogram +```python3 +from DaisyX.function.pluginhelpers import admins_only +from DaisyX.services.pyrogram import pbot + +@pbot.on_message(filters.command("hi") & ~filters.edited & ~filters.bot) +@admins_only +async def hmm(client, message): + j = "Hello there" + await message.reply(j) + +__mod_name__ = "Hi" +__help__ = """ +Hi +- /hi: Say Hello There +""" +``` + +## Advanced: Telethon +```python3 + +from DaisyX.services.telethon import tbot +from DaisyX.services.events import register + +@register(pattern="^/hi$") +async def hmm(event): + j = "Hello there" + await event.reply(j) + +__mod_name__ = "Hi" +__help__ = """ +Hi +- /hi: Say Hello There +""" +``` diff --git a/DaisyX/modules/reports.py b/DaisyX/modules/reports.py new file mode 100644 index 00000000..00855cef --- /dev/null +++ b/DaisyX/modules/reports.py @@ -0,0 +1,92 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from DaisyX.decorator import register +from DaisyX.services.mongo import db + +from .utils.connections import chat_connection +from .utils.disable import disableable_dec +from .utils.language import get_strings_dec +from .utils.user_details import get_admins_rights, get_user_link, is_user_admin + + +@register(regexp="^@admin$") +@chat_connection(only_groups=True) +@get_strings_dec("reports") +async def report1_cmd(message, chat, strings): + # Checking whether report is disabled in chat! + check = await db.disabled.find_one({"chat_id": chat["chat_id"]}) + if check: + if "report" in check["cmds"]: + return + await report(message, chat, strings) + + +@register(cmds="report") +@chat_connection(only_groups=True) +@disableable_dec("report") +@get_strings_dec("reports") +async def report2_cmd(message, chat, strings): + await report(message, chat, strings) + + +async def report(message, chat, strings): + user = message.from_user.id + + if (await is_user_admin(chat["chat_id"], user)) is True: + return await message.reply(strings["user_user_admin"]) + + if "reply_to_message" not in message: + return await message.reply(strings["no_user_to_report"]) + + offender_id = message.reply_to_message.from_user.id + if (await is_user_admin(chat["chat_id"], offender_id)) is True: + return await message.reply(strings["report_admin"]) + + admins = await get_admins_rights(chat["chat_id"]) + + offender = await get_user_link(offender_id) + text = strings["reported_user"].format(user=offender) + + try: + if message.text.split(None, 2)[1]: + reason = " ".join(message.text.split(None, 2)[1:]) + text += strings["reported_reason"].format(reason=reason) + except IndexError: + pass + + for admin in admins: + text += await get_user_link(admin, custom_name="​") + + await message.reply(text) + + +__mod_name__ = "Reports" + +__help__ = """ +We're all busy people who don't have time to monitor our groups 24/7. But how do you react if someone in your group is spamming? + +Presenting reports; if someone in your group thinks someone needs reporting, they now have an easy way to call all admins. + +Available commands: +- /report (?text): Reports +- @admins: Same as above, but not a clickable + +TIP: You always can disable reporting by disabling module +""" diff --git a/DaisyX/modules/restrictions.py b/DaisyX/modules/restrictions.py new file mode 100644 index 00000000..de09f740 --- /dev/null +++ b/DaisyX/modules/restrictions.py @@ -0,0 +1,502 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import datetime # noqa: F401 +from contextlib import suppress + +from aiogram.utils.exceptions import MessageNotModified +from babel.dates import format_timedelta + +from DaisyX import BOT_ID, bot +from DaisyX.decorator import register +from DaisyX.services.redis import redis +from DaisyX.services.telethon import tbot + +from .misc import customise_reason_finish, customise_reason_start +from .utils.connections import chat_connection +from .utils.language import get_strings_dec +from .utils.message import InvalidTimeUnit, convert_time, get_cmd +from .utils.restrictions import ban_user, kick_user, mute_user, unban_user, unmute_user +from .utils.user_details import ( + get_user_and_text_dec, + get_user_dec, + get_user_link, + is_user_admin, +) + + +@register( + cmds=["kick", "skick"], + bot_can_restrict_members=True, + user_can_restrict_members=True, +) +@chat_connection(admin=True, only_groups=True) +@get_user_and_text_dec() +@get_strings_dec("restrictions") +async def kick_user_cmd(message, chat, user, args, strings): + chat_id = chat["chat_id"] + user_id = user["user_id"] + + if user_id == BOT_ID: + await message.reply(strings["kick_DaisyX"]) + return + + elif user_id == message.from_user.id: + await message.reply(strings["kick_self"]) + return + + elif await is_user_admin(chat_id, user_id): + await message.reply(strings["kick_admin"]) + return + + text = strings["user_kicked"].format( + user=await get_user_link(user_id), + admin=await get_user_link(message.from_user.id), + chat_name=chat["chat_title"], + ) + + # Add reason + if args: + text += strings["reason"] % args + + # Check if silent + silent = False + if get_cmd(message) == "skick": + silent = True + key = "leave_silent:" + str(chat_id) + redis.set(key, user_id) + redis.expire(key, 30) + text += strings["purge"] + + await kick_user(chat_id, user_id) + + msg = await message.reply(text) + + # Del msgs if silent + if silent: + to_del = [msg.message_id, message.message_id] + if ( + "reply_to_message" in message + and message.reply_to_message.from_user.id == user_id + ): + to_del.append(message.reply_to_message.message_id) + await asyncio.sleep(5) + await tbot.delete_messages(chat_id, to_del) + + +@register( + cmds=["mute", "smute", "tmute", "stmute"], + bot_can_restrict_members=True, + user_can_restrict_members=True, +) +@chat_connection(admin=True, only_groups=True) +@get_user_and_text_dec() +@get_strings_dec("restrictions") +async def mute_user_cmd(message, chat, user, args, strings): + chat_id = chat["chat_id"] + user_id = user["user_id"] + + if user_id == BOT_ID: + await message.reply(strings["mute_DaisyX"]) + return + + elif user_id == message.from_user.id: + await message.reply(strings["mute_self"]) + return + + elif await is_user_admin(chat_id, user_id): + await message.reply(strings["mute_admin"]) + return + + text = strings["user_muted"].format( + user=await get_user_link(user_id), + admin=await get_user_link(message.from_user.id), + chat_name=chat["chat_title"], + ) + + curr_cmd = get_cmd(message) + + # Check if temprotary + until_date = None + if curr_cmd in ("tmute", "stmute"): + if args is not None and len(args := args.split()) > 0: + try: + until_date = convert_time(args[0]) + except (InvalidTimeUnit, TypeError, ValueError): + await message.reply(strings["invalid_time"]) + return + + text += strings["on_time"] % format_timedelta( + until_date, locale=strings["language_info"]["babel"] + ) + + # Add reason + if len(args) > 1: + text += strings["reason"] % " ".join(args[1:]) + else: + await message.reply(strings["enter_time"]) + return + else: + # Add reason + if args is not None and len(args := args.split()) > 0: + text += strings["reason"] % " ".join(args[0:]) + + # Check if silent + silent = False + if curr_cmd in ("smute", "stmute"): + silent = True + key = "leave_silent:" + str(chat_id) + redis.set(key, user_id) + redis.expire(key, 30) + text += strings["purge"] + + await mute_user(chat_id, user_id, until_date=until_date) + + msg = await message.reply(text) + + # Del msgs if silent + if silent: + to_del = [msg.message_id, message.message_id] + if ( + "reply_to_message" in message + and message.reply_to_message.from_user.id == user_id + ): + to_del.append(message.reply_to_message.message_id) + await asyncio.sleep(5) + await tbot.delete_messages(chat_id, to_del) + + +@register(cmds="unmute", bot_can_restrict_members=True, user_can_restrict_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_dec() +@get_strings_dec("restrictions") +async def unmute_user_cmd(message, chat, user, strings): + chat_id = chat["chat_id"] + user_id = user["user_id"] + + if user_id == BOT_ID: + await message.reply(strings["unmute_DaisyX"]) + return + + elif user_id == message.from_user.id: + await message.reply(strings["unmute_self"]) + return + + elif await is_user_admin(chat_id, user_id): + await message.reply(strings["unmute_admin"]) + return + + await unmute_user(chat_id, user_id) + + text = strings["user_unmuted"].format( + user=await get_user_link(user_id), + admin=await get_user_link(message.from_user.id), + chat_name=chat["chat_title"], + ) + + await message.reply(text) + + +@register( + cmds=["ban", "sban", "tban", "stban"], + bot_can_restrict_members=True, + user_can_restrict_members=True, +) +@chat_connection(admin=True, only_groups=True) +@get_user_and_text_dec() +@get_strings_dec("restrictions") +async def ban_user_cmd(message, chat, user, args, strings): + chat_id = chat["chat_id"] + user_id = user["user_id"] + + if user_id == BOT_ID: + await message.reply(strings["ban_DaisyX"]) + return + + elif user_id == message.from_user.id: + await message.reply(strings["ban_self"]) + return + + elif await is_user_admin(chat_id, user_id): + await message.reply(strings["ban_admin"]) + return + + text = strings["user_banned"].format( + user=await get_user_link(user_id), + admin=await get_user_link(message.from_user.id), + chat_name=chat["chat_title"], + ) + + curr_cmd = get_cmd(message) + + # Check if temprotary + until_date = None + if curr_cmd in ("tban", "stban"): + if args is not None and len(args := args.split()) > 0: + try: + until_date = convert_time(args[0]) + except (InvalidTimeUnit, TypeError, ValueError): + await message.reply(strings["invalid_time"]) + return + + text += strings["on_time"] % format_timedelta( + until_date, locale=strings["language_info"]["babel"] + ) + + # Add reason + if len(args) > 1: + text += strings["reason"] % " ".join(args[1:]) + else: + await message.reply(strings["enter_time"]) + return + else: + # Add reason + if args is not None and len(args := args.split()) > 0: + text += strings["reason"] % " ".join(args[0:]) + + # Check if silent + silent = False + if curr_cmd in ("sban", "stban"): + silent = True + key = "leave_silent:" + str(chat_id) + redis.set(key, user_id) + redis.expire(key, 30) + text += strings["purge"] + + await ban_user(chat_id, user_id, until_date=until_date) + + msg = await message.reply(text) + + # Del msgs if silent + if silent: + to_del = [msg.message_id, message.message_id] + if ( + "reply_to_message" in message + and message.reply_to_message.from_user.id == user_id + ): + to_del.append(message.reply_to_message.message_id) + await asyncio.sleep(5) + await tbot.delete_messages(chat_id, to_del) + + +@register(cmds="unban", bot_can_restrict_members=True, user_can_restrict_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_dec() +@get_strings_dec("restrictions") +async def unban_user_cmd(message, chat, user, strings): + chat_id = chat["chat_id"] + user_id = user["user_id"] + + if user_id == BOT_ID: + await message.reply(strings["unban_DaisyX"]) + return + + elif user_id == message.from_user.id: + await message.reply(strings["unban_self"]) + return + + elif await is_user_admin(chat_id, user_id): + await message.reply(strings["unban_admin"]) + return + + await unban_user(chat_id, user_id) + + text = strings["user_unband"].format( + user=await get_user_link(user_id), + admin=await get_user_link(message.from_user.id), + chat_name=chat["chat_title"], + ) + + await message.reply(text) + + +@register(f="leave") +async def leave_silent(message): + if not message.from_user.id == BOT_ID: + return + + if redis.get("leave_silent:" + str(message.chat.id)) == message.left_chat_member.id: + await message.delete() + + +@get_strings_dec("restrictions") +async def filter_handle_ban(message, chat, data: dict, strings=None): + if await is_user_admin(chat["chat_id"], message.from_user.id): + return + if await ban_user(chat["chat_id"], message.from_user.id): + reason = data.get("reason", None) or strings["filter_action_rsn"] + text = strings["filtr_ban_success"] % ( + await get_user_link(BOT_ID), + await get_user_link(message.from_user.id), + reason, + ) + await bot.send_message(chat["chat_id"], text) + + +@get_strings_dec("restrictions") +async def filter_handle_mute(message, chat, data, strings=None): + if await is_user_admin(chat["chat_id"], message.from_user.id): + return + if await mute_user(chat["chat_id"], message.from_user.id): + reason = data.get("reason", None) or strings["filter_action_rsn"] + text = strings["filtr_mute_success"] % ( + await get_user_link(BOT_ID), + await get_user_link(message.from_user.id), + reason, + ) + await bot.send_message(chat["chat_id"], text) + + +@get_strings_dec("restrictions") +async def filter_handle_tmute(message, chat, data, strings=None): + if await is_user_admin(chat["chat_id"], message.from_user.id): + return + if await mute_user( + chat["chat_id"], message.from_user.id, until_date=eval(data["time"]) + ): + reason = data.get("reason", None) or strings["filter_action_rsn"] + time = format_timedelta( + eval(data["time"]), locale=strings["language_info"]["babel"] + ) + text = strings["filtr_tmute_success"] % ( + await get_user_link(BOT_ID), + await get_user_link(message.from_user.id), + time, + reason, + ) + await bot.send_message(chat["chat_id"], text) + + +@get_strings_dec("restrictions") +async def filter_handle_tban(message, chat, data, strings=None): + if await is_user_admin(chat["chat_id"], message.from_user.id): + return + if await ban_user( + chat["chat_id"], message.from_user.id, until_date=eval(data["time"]) + ): + reason = data.get("reason", None) or strings["filter_action_rsn"] + time = format_timedelta( + eval(data["time"]), locale=strings["language_info"]["babel"] + ) + text = strings["filtr_tban_success"] % ( + await get_user_link(BOT_ID), + await get_user_link(message.from_user.id), + time, + reason, + ) + await bot.send_message(chat["chat_id"], text) + + +@get_strings_dec("restrictions") +async def time_setup_start(message, strings): + with suppress(MessageNotModified): + await message.edit_text(strings["time_setup_start"]) + + +@get_strings_dec("restrictions") +async def time_setup_finish(message, data, strings): + try: + time = convert_time(message.text) + except (InvalidTimeUnit, TypeError, ValueError): + await message.reply(strings["invalid_time"]) + return None + else: + return {"time": repr(time)} + + +@get_strings_dec("restrictions") +async def filter_handle_kick(message, chat, data, strings=None): + if await is_user_admin(chat["chat_id"], message.from_user.id): + return + if await kick_user(chat["chat_id"], message.from_user.id): + await bot.send_message( + chat["chat_id"], + strings["user_kicked"].format( + user=await get_user_link(message.from_user.id), + admin=await get_user_link(BOT_ID), + chat_name=chat["chat_title"], + ), + ) + + +__filters__ = { + "ban_user": { + "title": {"module": "restrictions", "string": "filter_title_ban"}, + "setup": {"start": customise_reason_start, "finish": customise_reason_finish}, + "handle": filter_handle_ban, + }, + "mute_user": { + "title": {"module": "restrictions", "string": "filter_title_mute"}, + "setup": {"start": customise_reason_start, "finish": customise_reason_finish}, + "handle": filter_handle_mute, + }, + "tmute_user": { + "title": {"module": "restrictions", "string": "filter_title_tmute"}, + "handle": filter_handle_tmute, + "setup": [ + {"start": time_setup_start, "finish": time_setup_finish}, + {"start": customise_reason_start, "finish": customise_reason_finish}, + ], + }, + "tban_user": { + "title": {"module": "restrictions", "string": "filter_title_tban"}, + "handle": filter_handle_tban, + "setup": [ + {"start": time_setup_start, "finish": time_setup_finish}, + {"start": customise_reason_start, "finish": customise_reason_finish}, + ], + }, + "kick_user": { + "title": {"module": "restrictions", "string": "filter_title_kick"}, + "handle": filter_handle_kick, + }, +} + + +__mod_name__ = "Restrictions" + +__help__ = """ +General admin's rights is restrict users and control their rules with this module you can easely do it. + +Available commands: +Kicks: +- /kick: Kicks a user +- /skick: Silently kicks + +Mutes: +- /mute: Mutes a user +- /smute: Silently mutes +- /tmute (time): Temprotary mute a user +- /stmute (time): Silently temprotary mute a user +- /unmute: Unmutes the user + +Bans: +- /ban: Bans a user +- /sban: Silently bans +- /tban (time): Temprotary ban a user +-/stban (time): Silently temprotary ban a user +- /unban: Unbans the user + +Examples: +- Mute a user for two hours. +-> /tmute @username 2h + + +""" diff --git a/DaisyX/modules/rmbg.py b/DaisyX/modules/rmbg.py new file mode 100644 index 00000000..950dbfb4 --- /dev/null +++ b/DaisyX/modules/rmbg.py @@ -0,0 +1,117 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import io +import os +from datetime import datetime + +import requests +from telethon import types +from telethon.tl import functions + +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +REM_BG_API_KEY = get_str_key("REM_BG_API_KEY", required=False) +TEMP_DOWNLOAD_DIRECTORY = "./" + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/rmbg") +async def _(event): + HELP_STR = "use `/rmbg` as reply to a media" + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + if REM_BG_API_KEY is None: + await event.reply("You need API token from remove.bg to use this plugin.") + return False + start = datetime.now() + message_id = event.message.id + if event.reply_to_msg_id: + message_id = event.reply_to_msg_id + reply_message = await event.get_reply_message() + await event.reply("Processing...") + try: + downloaded_file_name = await tbot.download_media( + reply_message, TEMP_DOWNLOAD_DIRECTORY + ) + except Exception as e: + await event.reply(str(e)) + return + else: + output_file_name = ReTrieveFile(downloaded_file_name) + os.remove(downloaded_file_name) + else: + await event.reply(HELP_STR) + return + contentType = output_file_name.headers.get("content-type") + if "image" in contentType: + with io.BytesIO(output_file_name.content) as remove_bg_image: + remove_bg_image.name = "rmbg.png" + await tbot.send_file( + event.chat_id, + remove_bg_image, + force_document=True, + supports_streaming=False, + allow_cache=False, + reply_to=message_id, + ) + end = datetime.now() + ms = (end - start).seconds + await event.reply("Background Removed in {} seconds".format(ms)) + else: + await event.reply( + "remove.bg API returned Errors. Please report to @DaisySupport_Official\n`{}".format( + output_file_name.content.decode("UTF-8") + ) + ) + + +def ReTrieveFile(input_file_name): + headers = { + "X-API-Key": REM_BG_API_KEY, + } + files = { + "image_file": (input_file_name, open(input_file_name, "rb")), + } + r = requests.post( + "https://api.remove.bg/v1.0/removebg", + headers=headers, + files=files, + allow_redirects=True, + stream=True, + ) + return r diff --git a/DaisyX/modules/rss.py b/DaisyX/modules/rss.py new file mode 100644 index 00000000..5227a059 --- /dev/null +++ b/DaisyX/modules/rss.py @@ -0,0 +1,178 @@ +# Copyright (C) 2020-2021 by DevsExpo@Github, < https://github.com/DevsExpo >. +# +# This file is part of < https://github.com/DevsExpo/FridayUserBot > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/DevsExpo/blob/master/LICENSE > +# +# All rights reserved. + +import asyncio + +import feedparser +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from pyrogram import filters + +from DaisyX.db.mongo_helpers.rss_db import ( + add_rss, + basic_check, + del_rss, + delete_all, + get_all, + get_chat_rss, + is_get_chat_rss, + overall_check, + update_rss, +) +from DaisyX.function.pluginhelpers import admins_only, edit_or_reply, get_text +from DaisyX.services.pyrogram import pbot + + +@pbot.on_message(filters.command("addrss") & ~filters.edited & ~filters.bot) +@admins_only +async def addrss(client, message): + pablo = await edit_or_reply(message, "`Processing....`") + lenk = get_text(message) + if not lenk: + await pablo.edit("Invalid Command Syntax, Please Check Help Menu To Know More!") + return + try: + rss_d = feedparser.parse(lenk) + rss_d.entries[0].title + except: + await pablo.edit( + "ERROR: The link does not seem to be a RSS feed or is not supported" + ) + return + lol = is_get_chat_rss(message.chat.id, lenk) + if lol: + await pablo.edit("This Link Already Added") + return + content = "" + content += f"**{rss_d.entries[0].title}**" + content += f"\n\n{rss_d.entries[0].link}" + try: + content += f"\n{rss_d.entries[0].description}" + except: + pass + await client.send_message(message.chat.id, content) + add_rss(message.chat.id, lenk, rss_d.entries[0].link) + await pablo.edit("Successfully Added Link To RSS Watch") + + +@pbot.on_message( + filters.command("testrss") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def testrss(client, message): + pablo = await edit_or_reply(message, "`Processing....`") + damn = basic_check(message.chat.id) + if not damn: + URL = "https://www.reddit.com/r/funny/new/.rss" + rss_d = feedparser.parse(URL) + Content = rss_d.entries[0]["title"] + "\n\n" + rss_d.entries[0]["link"] + await client.send_message(message.chat.id, Content) + await pablo.edit("This Chat Has No RSS So Sent Reddit RSS") + else: + all = get_chat_rss(message.chat.id) + for x in all: + link = x.get("rss_link") + rss_d = feedparser.parse(link) + content = "" + content += f"**{rss_d.entries[0].title}**" + content += f"\n\nLink : {rss_d.entries[0].link}" + try: + content += f"\n{rss_d.entries[0].description}" + except: + pass + await client.send_message(message.chat.id, content) + await pablo.delete() + + +@pbot.on_message( + filters.command("listrss") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def listrss(client, message): + pablo = await edit_or_reply(message, "`Processing....`") + damn = basic_check(message.chat.id) + if not damn: + await pablo.edit("This Chat Has No RSS!") + return + links = "" + all = get_chat_rss(message.chat.id) + for x in all: + l = x.get("rss_link") + links += f"{l}\n" + content = f"Rss Found In The Chat Are : \n\n{links}" + await client.send_message(message.chat.id, content) + await pablo.delete() + + +@pbot.on_message( + filters.command("delrss") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def delrss(client, message): + pablo = await edit_or_reply(message, "`Processing....`") + lenk = get_text(message) + if not lenk: + await pablo.edit("Invalid Command Syntax, Please Check Help Menu To Know More!") + return + lol = is_get_chat_rss(message.chat.id, lenk) + if not lol: + await pablo.edit("This Link Was Never Added") + return + del_rss(message.chat.id, lenk) + await pablo.edit(f"Successfully Removed `{lenk}` From Chat RSS") + + +@pbot.on_message( + filters.command("delallrss") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def delrss(client, message): + pablo = await edit_or_reply(message, "`Processing....`") + if not basic_check(message.chat.id): + await pablo.edit("This Chat Has No RSS To Delete") + return + await delete_all() + await pablo.edit("Successfully Deleted All RSS From The Chat") + + +async def check_rss(): + if not overall_check(): + return + all = get_all() + for one in all: + link = one.get("rss_link") + old = one.get("latest_rss") + rss_d = feedparser.parse(link) + if rss_d.entries[0].link != old: + message = one.get("chat_id") + content = "" + content += f"**{rss_d.entries[0].title}**" + content += f"\n\nLink : {rss_d.entries[0].link}" + try: + content += f"\n{rss_d.entries[0].description}" + except: + pass + update_rss(message, link, rss_d.entries[0].link) + try: + await pbot.send_message(message, content) + await asyncio.sleep(2) + except: + return + + +scheduler = AsyncIOScheduler() +scheduler.add_job(check_rss, "interval", minutes=10) +scheduler.start() + +__mod_name__ = "RSS Feed" +__help__ = """ +- /addrss : Add Rss to the chat +- /testrss : Test RSS Of The Chat +- /listrss : List all RSS Of The Chat +- /delrss : Delete RSS From The Chat +- /delallrss : Deletes All RSS From The Chat +""" diff --git a/DaisyX/modules/rules.py b/DaisyX/modules/rules.py new file mode 100644 index 00000000..43f42c7f --- /dev/null +++ b/DaisyX/modules/rules.py @@ -0,0 +1,144 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import re + +from aiogram.dispatcher.filters import CommandStart + +from DaisyX.decorator import register +from DaisyX.services.mongo import db + +from .utils.connections import chat_connection +from .utils.disable import disableable_dec +from .utils.language import get_strings_dec +from .utils.notes import ( + ALLOWED_COLUMNS, + BUTTONS, + get_parsed_note_list, + send_note, + t_unparse_note_item, +) + + +@register(cmds=["setrules", "saverules"], user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("rules") +async def set_rules(message, chat, strings): + chat_id = chat["chat_id"] + + # FIXME: documents are allow to saved (why?), check for args if no 'reply_to_message' + note = await get_parsed_note_list(message, allow_reply_message=True, split_args=-1) + note["chat_id"] = chat_id + + if ( + await db.rules.replace_one({"chat_id": chat_id}, note, upsert=True) + ).modified_count > 0: + text = strings["updated"] + else: + text = strings["saved"] + + await message.reply(text % chat["chat_title"]) + + +@register(cmds="rules") +@disableable_dec("rules") +@chat_connection(only_groups=True) +@get_strings_dec("rules") +async def rules(message, chat, strings): + chat_id = chat["chat_id"] + send_id = message.chat.id + + if "reply_to_message" in message: + rpl_id = message.reply_to_message.message_id + else: + rpl_id = message.message_id + + if len(args := message.get_args().split()) > 0: + arg1 = args[0].lower() + else: + arg1 = None + noformat = arg1 in ("noformat", "raw") + + if not (db_item := await db.rules.find_one({"chat_id": chat_id})): + await message.reply(strings["not_found"]) + return + + text, kwargs = await t_unparse_note_item( + message, db_item, chat_id, noformat=noformat + ) + kwargs["reply_to"] = rpl_id + + await send_note(send_id, text, **kwargs) + + +@register(cmds="resetrules", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("rules") +async def reset_rules(message, chat, strings): + chat_id = chat["chat_id"] + + if (await db.rules.delete_one({"chat_id": chat_id})).deleted_count < 1: + await message.reply(strings["not_found"]) + return + + await message.reply(strings["deleted"]) + + +BUTTONS.update({"rules": "btn_rules"}) + + +@register(CommandStart(re.compile("btn_rules"))) +@get_strings_dec("rules") +async def rules_btn(message, strings): + chat_id = (message.get_args().split("_"))[2] + user_id = message.chat.id + if not (db_item := await db.rules.find_one({"chat_id": int(chat_id)})): + await message.answer(strings["not_found"]) + return + + text, kwargs = await t_unparse_note_item(message, db_item, chat_id) + await send_note(user_id, text, **kwargs) + + +async def __export__(chat_id): + rules = await db.rules.find_one({"chat_id": chat_id}) + if rules: + del rules["_id"] + del rules["chat_id"] + + return {"rules": rules} + + +async def __import__(chat_id, data): + rules = data + for column in [i for i in data if i not in ALLOWED_COLUMNS]: + del rules[column] + + rules["chat_id"] = chat_id + await db.rules.replace_one({"chat_id": rules["chat_id"]}, rules, upsert=True) + + +__mod_name__ = "Rules" + +__help__ = """ +Available Commands: +- /setrules (rules): saves the rules (also works with reply) +- /rules: Shows the rules of chat if any! +- /resetrules: Resets group's rules +""" diff --git a/DaisyX/modules/search.py b/DaisyX/modules/search.py new file mode 100644 index 00000000..40c5c8ac --- /dev/null +++ b/DaisyX/modules/search.py @@ -0,0 +1,290 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import re +import urllib +import urllib.request + +import bs4 +import requests +from bs4 import BeautifulSoup +from pyrogram import filters + +# This plugin is ported from https://github.com/thehamkercat/WilliamButcherBot +from search_engine_parser import GoogleSearch + +from DaisyX.modules.utils.fetch import fetch +from DaisyX.services.events import register +from DaisyX.services.pyrogram import pbot as app + +ARQ = "https://thearq.tech/" + + +@app.on_message(filters.command("ud") & ~filters.edited) +async def urbandict(_, message): + if len(message.command) < 2: + await message.reply_text('"/ud" Needs An Argument.') + return + text = message.text.split(None, 1)[1] + try: + results = await fetch(f"{ARQ}ud?query={text}") + reply_text = f"""**Definition:** __{results["list"][0]["definition"]}__ +**Example:** __{results["list"][0]["example"]}__""" + except IndexError: + reply_text = "Sorry could not find any matching results!" + ignore_chars = "[]" + reply = reply_text + for chars in ignore_chars: + reply = reply.replace(chars, "") + if len(reply) >= 4096: + reply = reply[:4096] + await message.reply_text(reply) + + +# google + + +@app.on_message(filters.command("google") & ~filters.edited) +async def google(_, message): + try: + if len(message.command) < 2: + await message.reply_text("/google Needs An Argument") + return + text = message.text.split(None, 1)[1] + gresults = await GoogleSearch().async_search(text, 1) + result = "" + for i in range(4): + try: + title = gresults["titles"][i].replace("\n", " ") + source = gresults["links"][i] + description = gresults["descriptions"][i] + result += f"[{title}]({source})\n" + result += f"`{description}`\n\n" + except IndexError: + pass + await message.reply_text(result, disable_web_page_preview=True) + except Exception as e: + await message.reply_text(str(e)) + + +# StackOverflow [This is also a google search with some added args] + + +@app.on_message(filters.command("so") & ~filters.edited) +async def stack(_, message): + try: + if len(message.command) < 2: + await message.reply_text('"/so" Needs An Argument') + return + gett = message.text.split(None, 1)[1] + text = gett + ' "site:stackoverflow.com"' + gresults = await GoogleSearch().async_search(text, 1) + result = "" + for i in range(4): + try: + title = gresults["titles"][i].replace("\n", " ") + source = gresults["links"][i] + description = gresults["descriptions"][i] + result += f"[{title}]({source})\n" + result += f"`{description}`\n\n" + except IndexError: + pass + await message.reply_text(result, disable_web_page_preview=True) + except Exception as e: + await message.reply_text(str(e)) + + +# Github [This is also a google search with some added args] + + +@app.on_message(filters.command("gh") & ~filters.edited) +async def github(_, message): + try: + if len(message.command) < 2: + await message.reply_text('"/gh" Needs An Argument') + return + gett = message.text.split(None, 1)[1] + text = gett + ' "site:github.com"' + gresults = await GoogleSearch().async_search(text, 1) + result = "" + for i in range(4): + try: + title = gresults["titles"][i].replace("\n", " ") + source = gresults["links"][i] + description = gresults["descriptions"][i] + result += f"[{title}]({source})\n" + result += f"`{description}`\n\n" + except IndexError: + pass + await message.reply_text(result, disable_web_page_preview=True) + except Exception as e: + await message.reply_text(str(e)) + + +# YouTube + + +@app.on_message(filters.command("yts") & ~filters.edited) +async def ytsearch(_, message): + try: + if len(message.command) < 2: + await message.reply_text("/yt needs an argument") + return + query = message.text.split(None, 1)[1] + m = await message.reply_text("Searching....") + results = await fetch(f"{ARQ}youtube?query={query}&count=3") + i = 0 + text = "" + while i < 3: + text += f"Title - {results[i]['title']}\n" + text += f"Duration - {results[i]['duration']}\n" + text += f"Views - {results[i]['views']}\n" + text += f"Channel - {results[i]['channel']}\n" + text += f"https://youtube.com{results[i]['url_suffix']}\n\n" + i += 1 + await m.edit(text, disable_web_page_preview=True) + except Exception as e: + await message.reply_text(str(e)) + + +opener = urllib.request.build_opener() +useragent = "Mozilla/5.0 (Linux; Android 9; SM-G960F Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.157 Mobile Safari/537.36" +opener.addheaders = [("User-agent", useragent)] + + +async def ParseSauce(googleurl): + """Parse/Scrape the HTML code for the info we want.""" + + source = opener.open(googleurl).read() + soup = BeautifulSoup(source, "html.parser") + + results = {"similar_images": "", "best_guess": ""} + + try: + for similar_image in soup.findAll("input", {"class": "gLFyf"}): + url = "https://www.google.com/search?tbm=isch&q=" + urllib.parse.quote_plus( + similar_image.get("value") + ) + results["similar_images"] = url + except BaseException: + pass + + for best_guess in soup.findAll("div", attrs={"class": "r5a77d"}): + results["best_guess"] = best_guess.get_text() + + return results + + +async def scam(results, lim): + + single = opener.open(results["similar_images"]).read() + decoded = single.decode("utf-8") + + imglinks = [] + counter = 0 + + pattern = r"^,\[\"(.*[.png|.jpg|.jpeg])\",[0-9]+,[0-9]+\]$" + oboi = re.findall(pattern, decoded, re.I | re.M) + + for imglink in oboi: + counter += 1 + if counter < int(lim): + imglinks.append(imglink) + else: + break + + return imglinks + + +@register(pattern="^/app (.*)") +async def apk(e): + try: + app_name = e.pattern_match.group(1) + remove_space = app_name.split(" ") + final_name = "+".join(remove_space) + page = requests.get( + "https://play.google.com/store/search?q=" + final_name + "&c=apps" + ) + str(page.status_code) + soup = bs4.BeautifulSoup(page.content, "lxml", from_encoding="utf-8") + results = soup.findAll("div", "ZmHEEd") + app_name = ( + results[0].findNext("div", "Vpfmgd").findNext("div", "WsMG1c nnK0zc").text + ) + app_dev = results[0].findNext("div", "Vpfmgd").findNext("div", "KoLSrc").text + app_dev_link = ( + "https://play.google.com" + + results[0].findNext("div", "Vpfmgd").findNext("a", "mnKHRc")["href"] + ) + app_rating = ( + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "pf5lIe") + .find("div")["aria-label"] + ) + app_link = ( + "https://play.google.com" + + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "vU6FJ p63iDd") + .a["href"] + ) + app_icon = ( + results[0] + .findNext("div", "Vpfmgd") + .findNext("div", "uzcko") + .img["data-src"] + ) + app_details = "📲​" + app_details += " " + app_name + "" + app_details += ( + "\n\nDeveloper : " + + app_dev + + "" + ) + app_details += "\nRating : " + app_rating.replace( + "Rated ", "⭐ " + ).replace(" out of ", "/").replace(" stars", "", 1).replace( + " stars", "⭐ " + ).replace( + "five", "5" + ) + app_details += ( + "\nFeatures : View in Play Store" + ) + app_details += "\n\n===> @DaisySupport_Official <===" + await e.reply(app_details, link_preview=True, parse_mode="HTML") + except IndexError: + await e.reply("No result found in search. Please enter **Valid app name**") + except Exception as err: + await e.reply("Exception Occured:- " + str(err)) + + +__help__ = """ + - /google text: Perform a google search + - /so - Search For Something On Stack OverFlow + - /gh - Search For Something On GitHub + - /yts - Search For Something On YouTub + - /app appname: Searches for an app in Play Store and returns its details. +""" + +__mod_name__ = "Search" diff --git a/DaisyX/modules/send.py b/DaisyX/modules/send.py new file mode 100644 index 00000000..bfe6af3e --- /dev/null +++ b/DaisyX/modules/send.py @@ -0,0 +1,31 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from pyrogram import filters + +from DaisyX.function.pluginhelpers import admins_only, get_text +from DaisyX.services.pyrogram import pbot + + +@pbot.on_message( + filters.command("send") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def send(client, message): + args = get_text(message) + await client.send_message(message.chat.id, text=args) diff --git a/DaisyX/modules/shazam.py b/DaisyX/modules/shazam.py new file mode 100644 index 00000000..9c9d39ee --- /dev/null +++ b/DaisyX/modules/shazam.py @@ -0,0 +1,64 @@ +import os +from json import JSONDecodeError + +import requests + +# import ffmpeg +from pyrogram import filters + +from DaisyX.function.pluginhelpers import admins_only, edit_or_reply, fetch_audio +from DaisyX.services.pyrogram import pbot + + +@pbot.on_message(filters.command(["identify", "shazam"])) +@admins_only +async def shazamm(client, message): + kek = await edit_or_reply(message, "`Shazaming In Progress!`") + if not message.reply_to_message: + await kek.edit("Reply To The Audio.") + return + if os.path.exists("friday.mp3"): + os.remove("friday.mp3") + kkk = await fetch_audio(client, message) + downloaded_file_name = kkk + f = {"file": (downloaded_file_name, open(downloaded_file_name, "rb"))} + await kek.edit("**Searching For This Song In Friday's DataBase.**") + r = requests.post("https://starkapi.herokuapp.com/shazam/", files=f) + try: + xo = r.json() + except JSONDecodeError: + await kek.edit( + "`Seems Like Our Server Has Some Issues, Please Try Again Later!`" + ) + return + if xo.get("success") is False: + await kek.edit("`Song Not Found IN Database. Please Try Again.`") + os.remove(downloaded_file_name) + return + xoo = xo.get("response") + zz = xoo[1] + zzz = zz.get("track") + zzz.get("sections")[3] + nt = zzz.get("images") + image = nt.get("coverarthq") + by = zzz.get("subtitle") + title = zzz.get("title") + messageo = f"""Song Shazamed. +Song Name : {title} +Song By : {by} +Identified Using @DaisyXBot - Join our support @DaisySupport_Official +Powered by @FridayOT +""" + await client.send_photo(message.chat.id, image, messageo, parse_mode="HTML") + os.remove(downloaded_file_name) + await kek.delete() + + +# __mod_name__ = "Shazam" +# __help__ = """ +# SHAZAMMER +# Find any song with it's music or part of song +# - /shazam : identify the song from Friday's Database + +# Special credits to friday userbot +# """ diff --git a/DaisyX/modules/shortify.py b/DaisyX/modules/shortify.py new file mode 100644 index 00000000..75dc8e0b --- /dev/null +++ b/DaisyX/modules/shortify.py @@ -0,0 +1,51 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import json + +import aiohttp +from pyrogram import filters + +from DaisyX.function.pluginhelpers import admins_only, get_text +from DaisyX.services.pyrogram import pbot + + +# Used my api key here, don't fuck with it +@pbot.on_message( + filters.command("short") & ~filters.edited & ~filters.bot & ~filters.private +) +@admins_only +async def shortify(client, message): + lel = await client.send_message(message.chat.id, "`Wait a sec....`") + url = get_text(message) + if "." not in url: + await lel.edit("Defuq!. Is it a url?") + return + header = { + "Authorization": "Bearer ad39983fa42d0b19e4534f33671629a4940298dc", + "Content-Type": "application/json", + } + payload = {"long_url": f"{url}"} + payload = json.dumps(payload) + async with aiohttp.ClientSession() as session: + async with session.post( + "https://api-ssl.bitly.com/v4/shorten", headers=header, data=payload + ) as resp: + data = await resp.json() + msg = f"**Original Url:** {url}\n**Shortened Url:** {data['link']}" + await lel.edit(msg) diff --git a/DaisyX/modules/song.py b/DaisyX/modules/song.py new file mode 100644 index 00000000..2b954714 --- /dev/null +++ b/DaisyX/modules/song.py @@ -0,0 +1,355 @@ +# Daisyxmusic (Telegram bot project ) +# Copyright (C) 2021 Inukaasith + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from __future__ import unicode_literals + +import asyncio +import os +import time +from random import randint +from urllib.parse import urlparse + +import aiofiles +import aiohttp +import wget +import youtube_dl +from pyrogram import filters +from pyrogram.types import Message +from youtube_dl import YoutubeDL +from youtubesearchpython import SearchVideos + +from DaisyX.function.inlinehelper import arq +from DaisyX.function.pluginhelpers import get_text, progress +from DaisyX.services.pyrogram import pbot as Client + +dl_limit = 0 + + +@Client.on_message(filters.command(["music", "song"])) +async def ytmusic(client, message: Message): + urlissed = get_text(message) + if not urlissed: + await client.send_message( + message.chat.id, + "Invalid Command Syntax, Please Check Help Menu To Know More!", + ) + return + global dl_limit + if dl_limit >= 4: + await message.reply_text( + "Daisy's server busy due to too many downloads, try again after sometime." + ) + return + pablo = await client.send_message( + message.chat.id, f"`Getting {urlissed} From Youtube Servers. Please Wait.`" + ) + search = SearchVideos(f"{urlissed}", offset=1, mode="dict", max_results=1) + try: + mi = search.result() + mio = mi["search_result"] + mo = mio[0]["link"] + mio[0]["duration"] + thum = mio[0]["title"] + fridayz = mio[0]["id"] + thums = mio[0]["channel"] + kekme = f"https://img.youtube.com/vi/{fridayz}/hqdefault.jpg" + except: + await message.reply_text( + "Sorry I accounted an error.\n Unkown error raised while getting search result" + ) + return + + await asyncio.sleep(0.6) + sedlyf = wget.download(kekme) + opts = { + "format": "bestaudio", + "addmetadata": True, + "key": "FFmpegMetadata", + "writethumbnail": True, + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "mp3", + "preferredquality": "720", + } + ], + "outtmpl": "%(id)s.mp3", + "quiet": True, + "logtostderr": False, + } + try: + dl_limit = dl_limit + 1 + with YoutubeDL(opts) as ytdl: + ytdl_data = ytdl.extract_info(mo, download=True) + + except Exception as e: + await pablo.edit(f"**Failed To Download** \n**Error :** `{str(e)}`") + # dl_limit = dl_limit-1 + return + c_time = time.time() + capy = f"**Song Name :** `{thum}` \n**Requested For :** `{urlissed}` \n**Channel :** `{thums}` \n**Link :** `{mo}`" + file_stark = f"{ytdl_data['id']}.mp3" + try: + await client.send_audio( + message.chat.id, + audio=open(file_stark, "rb"), + duration=int(ytdl_data["duration"]), + title=str(ytdl_data["title"]), + performer=str(ytdl_data["uploader"]), + thumb=sedlyf, + caption=capy, + progress=progress, + progress_args=( + pablo, + c_time, + f"`Uploading {urlissed} Song From YouTube Music!`", + file_stark, + ), + ) + dl_limit = dl_limit - 1 + except: + dl_limit = dl_limit - 1 + return + await pablo.delete() + for files in (sedlyf, file_stark): + if files and os.path.exists(files): + os.remove(files) + + +ydl_opts = { + "format": "bestaudio/best", + "writethumbnail": True, + "postprocessors": [ + { + "key": "FFmpegExtractAudio", + "preferredcodec": "mp3", + "preferredquality": "192", + } + ], +} + + +def get_file_extension_from_url(url): + url_path = urlparse(url).path + basename = os.path.basename(url_path) + return basename.split(".")[-1] + + +# Funtion To Download Song +async def download_song(url): + song_name = f"{randint(6969, 6999)}.mp3" + async with aiohttp.ClientSession() as session: + async with session.get(url) as resp: + if resp.status == 200: + f = await aiofiles.open(song_name, mode="wb") + await f.write(await resp.read()) + await f.close() + return song_name + + +is_downloading = False + + +def time_to_seconds(time): + stringt = str(time) + return sum(int(x) * 60 ** i for i, x in enumerate(reversed(stringt.split(":")))) + + +@Client.on_message(filters.command("saavn") & ~filters.edited) +async def jssong(_, message): + global is_downloading + global dl_limit + if len(message.command) < 2: + await message.reply_text("/saavn requires an argument.") + return + if dl_limit >= 3: + await message.reply_text( + "Daisy's server busy due to too many downloads, try again after sometime." + ) + return + if is_downloading: + await message.reply_text( + "Another download is in progress, try again after sometime." + ) + return + is_downloading = True + text = message.text.split(None, 1)[1] + query = text.replace(" ", "%20") + m = await message.reply_text("Searching...") + try: + songs = await arq.saavn(query) + if not songs.ok: + await message.reply_text(songs.result) + return + sname = songs.result[0].song + slink = songs.result[0].media_url + ssingers = songs.result[0].singers + await m.edit("Downloading") + song = await download_song(slink) + await m.edit("Uploading") + await message.reply_audio(audio=song, title=sname, performer=ssingers) + os.remove(song) + await m.delete() + except Exception as e: + is_downloading = False + await m.edit(str(e)) + return + is_downloading = False + + +# Deezer Music + + +@Client.on_message(filters.command("deezer") & ~filters.edited) +async def deezsong(_, message): + global is_downloading + if len(message.command) < 2: + await message.reply_text("/deezer requires an argument.") + return + if is_downloading: + await message.reply_text( + "Another download is in progress, try again after sometime." + ) + return + if dl_limit >= 3: + await message.reply_text( + "Daisy's server busy due to too many downloads, try again after sometime." + ) + return + is_downloading = True + text = message.text.split(None, 1)[1] + query = text.replace(" ", "%20") + m = await message.reply_text("Searching...") + try: + songs = await arq.deezer(query, 1) + if not songs.ok: + await message.reply_text(songs.result) + return + title = songs.result[0].title + url = songs.result[0].url + artist = songs.result[0].artist + await m.edit("Downloading") + song = await download_song(url) + await m.edit("Uploading") + await message.reply_audio(audio=song, title=title, performer=artist) + os.remove(song) + await m.delete() + except Exception as e: + is_downloading = False + await m.edit(str(e)) + return + is_downloading = False + + +@Client.on_message(filters.command(["vsong", "video"])) +async def ytmusic(client, message: Message): + global is_downloading + if is_downloading: + await message.reply_text( + "Another download is in progress, try again after sometime." + ) + return + global dl_limit + if dl_limit >= 4: + await message.reply_text( + "Daisy s server busy due to too many downloads, try again after sometime." + ) + return + urlissed = get_text(message) + + pablo = await client.send_message( + message.chat.id, f"`Getting {urlissed} From Youtube Servers. Please Wait.`" + ) + if not urlissed: + await pablo.edit("Invalid Command Syntax, Please Check Help Menu To Know More!") + return + + search = SearchVideos(f"{urlissed}", offset=1, mode="dict", max_results=1) + try: + mi = search.result() + mio = mi["search_result"] + mo = mio[0]["link"] + thum = mio[0]["title"] + fridayz = mio[0]["id"] + thums = mio[0]["channel"] + kekme = f"https://img.youtube.com/vi/{fridayz}/hqdefault.jpg" + except: + await message.reply_text( + "Unknown error raised while getting result from youtube" + ) + return + await asyncio.sleep(0.6) + url = mo + sedlyf = wget.download(kekme) + opts = { + "format": "best", + "addmetadata": True, + "key": "FFmpegMetadata", + "prefer_ffmpeg": True, + "geo_bypass": True, + "nocheckcertificate": True, + "postprocessors": [{"key": "FFmpegVideoConvertor", "preferedformat": "mp4"}], + "outtmpl": "%(id)s.mp4", + "logtostderr": False, + "quiet": True, + } + try: + is_downloading = True + with youtube_dl.YoutubeDL(opts) as ytdl: + infoo = ytdl.extract_info(url, False) + duration = round(infoo["duration"] / 60) + + if duration > 8: + await pablo.edit( + f"❌ Videos longer than 8 minute(s) aren t allowed, the provided video is {duration} minute(s)" + ) + is_downloading = False + return + ytdl_data = ytdl.extract_info(url, download=True) + + except Exception: + # await pablo.edit(event, f"**Failed To Download** \n**Error :** `{str(e)}`") + is_downloading = False + return + + c_time = time.time() + file_stark = f"{ytdl_data['id']}.mp4" + capy = f"**Video Name ➠** `{thum}` \n**Requested For :** `{urlissed}` \n**Channel :** `{thums}` \n**Link :** `{mo}`" + await client.send_video( + message.chat.id, + video=open(file_stark, "rb"), + duration=int(ytdl_data["duration"]), + file_name=str(ytdl_data["title"]), + thumb=sedlyf, + caption=capy, + supports_streaming=True, + progress=progress, + progress_args=( + pablo, + c_time, + f"`Uploading {urlissed} Song From YouTube Music!`", + file_stark, + ), + ) + await pablo.delete() + is_downloading = False + for files in (sedlyf, file_stark): + if files and os.path.exists(files): + os.remove(files) diff --git a/DaisyX/modules/spwinfo.py b/DaisyX/modules/spwinfo.py new file mode 100644 index 00000000..71d11f13 --- /dev/null +++ b/DaisyX/modules/spwinfo.py @@ -0,0 +1,109 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from asyncio import sleep +from datetime import datetime + +import aiohttp +from pyrogram import filters +from pyrogram.errors import PeerIdInvalid + +from DaisyX.services.pyrogram import pbot + + +class AioHttp: + @staticmethod + async def get_json(link): + async with aiohttp.ClientSession() as session: + async with session.get(link) as resp: + return await resp.json() + + @staticmethod + async def get_text(link): + async with aiohttp.ClientSession() as session: + async with session.get(link) as resp: + return await resp.text() + + @staticmethod + async def get_raw(link): + async with aiohttp.ClientSession() as session: + async with session.get(link) as resp: + return await resp.read() + + +@pbot.on_message(filters.command("spwinfo") & ~filters.edited & ~filters.bot) +async def lookup(client, message): + cmd = message.command + if not message.reply_to_message and len(cmd) == 1: + get_user = message.from_user.id + elif len(cmd) == 1: + if message.reply_to_message.forward_from: + get_user = message.reply_to_message.forward_from.id + else: + get_user = message.reply_to_message.from_user.id + elif len(cmd) > 1: + get_user = cmd[1] + try: + get_user = int(cmd[1]) + except ValueError: + pass + try: + user = await client.get_chat(get_user) + except PeerIdInvalid: + await message.reply_text("I don't know that User.") + sleep(2) + return + url = f"https://api.intellivoid.net/spamprotection/v1/lookup?query={user.id}" + a = await AioHttp().get_json(url) + response = a["success"] + if response is True: + date = a["results"]["last_updated"] + stats = f"**◢ Intellivoid• SpamProtection Info**:\n" + stats += f' • **Updated on**: `{datetime.fromtimestamp(date).strftime("%Y-%m-%d %I:%M:%S %p")}`\n' + stats += ( + f" • **Chat Info**: [Link](t.me/SpamProtectionBot/?start=00_{user.id})\n" + ) + + if a["results"]["attributes"]["is_potential_spammer"] is True: + stats += f" • **User**: `USERxSPAM`\n" + elif a["results"]["attributes"]["is_operator"] is True: + stats += f" • **User**: `USERxOPERATOR`\n" + elif a["results"]["attributes"]["is_agent"] is True: + stats += f" • **User**: `USERxAGENT`\n" + elif a["results"]["attributes"]["is_whitelisted"] is True: + stats += f" • **User**: `USERxWHITELISTED`\n" + + stats += f' • **Type**: `{a["results"]["entity_type"]}`\n' + stats += ( + f' • **Language**: `{a["results"]["language_prediction"]["language"]}`\n' + ) + stats += f' • **Language Probability**: `{a["results"]["language_prediction"]["probability"]}`\n' + stats += f"**Spam Prediction**:\n" + stats += f' • **Ham Prediction**: `{a["results"]["spam_prediction"]["ham_prediction"]}`\n' + stats += f' • **Spam Prediction**: `{a["results"]["spam_prediction"]["spam_prediction"]}`\n' + stats += f'**Blacklisted**: `{a["results"]["attributes"]["is_blacklisted"]}`\n' + if a["results"]["attributes"]["is_blacklisted"] is True: + stats += ( + f' • **Reason**: `{a["results"]["attributes"]["blacklist_reason"]}`\n' + ) + stats += f' • **Flag**: `{a["results"]["attributes"]["blacklist_flag"]}`\n' + stats += f'**PTID**:\n`{a["results"]["private_telegram_id"]}`\n' + await message.reply_text(stats, disable_web_page_preview=True) + else: + await message.reply_text("`Cannot reach SpamProtection API`") + await sleep(3) diff --git a/DaisyX/modules/stickers.py b/DaisyX/modules/stickers.py new file mode 100644 index 00000000..6d10a5c9 --- /dev/null +++ b/DaisyX/modules/stickers.py @@ -0,0 +1,534 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import datetime +import io +import math +import os +from io import BytesIO + +import requests +from aiogram.types.input_file import InputFile +from bs4 import BeautifulSoup as bs +from PIL import Image +from pyrogram import filters +from telethon import * +from telethon.errors.rpcerrorlist import StickersetInvalidError +from telethon.tl.functions.messages import GetStickerSetRequest +from telethon.tl.types import ( + DocumentAttributeSticker, + InputStickerSetID, + InputStickerSetShortName, + MessageMediaPhoto, +) + +from DaisyX import bot +from DaisyX.decorator import register +from DaisyX.services.events import register as Daisy +from DaisyX.services.pyrogram import pbot +from DaisyX.services.telethon import tbot +from DaisyX.services.telethonuserbot import ubot + +from .utils.disable import disableable_dec +from .utils.language import get_strings_dec + + +def is_it_animated_sticker(message): + try: + if message.media and message.media.document: + mime_type = message.media.document.mime_type + if "tgsticker" in mime_type: + return True + return False + return False + except BaseException: + return False + + +def is_message_image(message): + if message.media: + if isinstance(message.media, MessageMediaPhoto): + return True + if message.media.document: + if message.media.document.mime_type.split("/")[0] == "image": + return True + return False + return False + + +async def silently_send_message(conv, text): + await conv.send_message(text) + response = await conv.get_response() + await conv.mark_read(message=response) + return response + + +async def stickerset_exists(conv, setname): + try: + await tbot(GetStickerSetRequest(InputStickerSetShortName(setname))) + response = await silently_send_message(conv, "/addsticker") + if response.text == "Invalid pack selected.": + await silently_send_message(conv, "/cancel") + return False + await silently_send_message(conv, "/cancel") + return True + except StickersetInvalidError: + return False + + +def resize_image(image, save_locaton): + """Copyright Rhyse Simpson: + https://github.com/skittles9823/SkittBot/blob/master/tg_bot/modules/stickers.py + """ + im = Image.open(image) + maxsize = (512, 512) + if (im.width and im.height) < 512: + size1 = im.width + size2 = im.height + if im.width > im.height: + scale = 512 / size1 + size1new = 512 + size2new = size2 * scale + else: + scale = 512 / size2 + size1new = size1 * scale + size2new = 512 + size1new = math.floor(size1new) + size2new = math.floor(size2new) + sizenew = (size1new, size2new) + im = im.resize(sizenew) + else: + im.thumbnail(maxsize) + im.save(save_locaton, "PNG") + + +def find_instance(items, class_or_tuple): + for item in items: + if isinstance(item, class_or_tuple): + return item + return None + + +@Daisy(pattern="^/searchsticker (.*)") +async def _(event): + input_str = event.pattern_match.group(1) + combot_stickers_url = "https://combot.org/telegram/stickers?q=" + text = requests.get(combot_stickers_url + input_str) + soup = bs(text.text, "lxml") + results = soup.find_all("a", {"class": "sticker-pack__btn"}) + titles = soup.find_all("div", "sticker-pack__title") + if not results: + await event.reply("No results found :(") + return + reply = f"Stickers Related to **{input_str}**:" + for result, title in zip(results, titles): + link = result["href"] + reply += f"\n• [{title.get_text()}]({link})" + await event.reply(reply) + + +@Daisy(pattern="^/packinfo$") +async def _(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + + if not event.is_reply: + await event.reply("Reply to any sticker to get it's pack info.") + return + rep_msg = await event.get_reply_message() + if not rep_msg.document: + await event.reply("Reply to any sticker to get it's pack info.") + return + stickerset_attr_s = rep_msg.document.attributes + stickerset_attr = find_instance(stickerset_attr_s, DocumentAttributeSticker) + if not stickerset_attr.stickerset: + await event.reply("sticker does not belong to a pack.") + return + get_stickerset = await tbot( + GetStickerSetRequest( + InputStickerSetID( + id=stickerset_attr.stickerset.id, + access_hash=stickerset_attr.stickerset.access_hash, + ) + ) + ) + pack_emojis = [] + for document_sticker in get_stickerset.packs: + if document_sticker.emoticon not in pack_emojis: + pack_emojis.append(document_sticker.emoticon) + await event.reply( + f"**Sticker Title:** `{get_stickerset.set.title}\n`" + f"**Sticker Short Name:** `{get_stickerset.set.short_name}`\n" + f"**Official:** `{get_stickerset.set.official}`\n" + f"**Archived:** `{get_stickerset.set.archived}`\n" + f"**Stickers In Pack:** `{len(get_stickerset.packs)}`\n" + f"**Emojis In Pack:** {' '.join(pack_emojis)}" + ) + + +def find_instance(items, class_or_tuple): + for item in items: + if isinstance(item, class_or_tuple): + return item + return None + + +DEFAULTUSER = "DaisyX" +FILLED_UP_DADDY = "Invalid pack selected." + + +async def get_sticker_emoji(event): + reply_message = await event.get_reply_message() + try: + final_emoji = reply_message.media.document.attributes[1].alt + except: + final_emoji = "😎" + return final_emoji + + +@Daisy(pattern="^/kang ?(.*)") +async def _(event): + if not event.is_reply: + await event.reply("PLease, Reply To A Sticker / Image To Add It Your Pack") + return + reply_message = await event.get_reply_message() + sticker_emoji = await get_sticker_emoji(event) + input_str = event.pattern_match.group(1) + if input_str: + sticker_emoji = input_str + user = await event.get_sender() + if not user.first_name: + user.first_name = user.id + pack = 1 + userid = event.sender_id + first_name = user.first_name + packname = f"{first_name}'s Sticker Vol.{pack}" + packshortname = f"DaisyX_stickers_{userid}" + kanga = await event.reply("Hello, This Sticker Looks Noice. Mind if Daisy steal it") + is_a_s = is_it_animated_sticker(reply_message) + file_ext_ns_ion = "Stickers.png" + file = await event.client.download_file(reply_message.media) + uploaded_sticker = None + if is_a_s: + file_ext_ns_ion = "AnimatedSticker.tgs" + uploaded_sticker = await ubot.upload_file(file, file_name=file_ext_ns_ion) + packname = f"{first_name}'s Animated Sticker Vol.{pack}" + packshortname = f"DaisyX_animated_{userid}" + elif not is_message_image(reply_message): + await kanga.edit("Oh no.. This Message type is invalid") + return + else: + with BytesIO(file) as mem_file, BytesIO() as sticker: + resize_image(mem_file, sticker) + sticker.seek(0) + uploaded_sticker = await ubot.upload_file( + sticker, file_name=file_ext_ns_ion + ) + + await kanga.edit("This Sticker is Gonna Get Stolen.....") + + async with ubot.conversation("@Stickers") as d_conv: + now = datetime.datetime.now() + dt = now + datetime.timedelta(minutes=1) + if not await stickerset_exists(d_conv, packshortname): + + await silently_send_message(d_conv, "/cancel") + if is_a_s: + response = await silently_send_message(d_conv, "/newanimated") + else: + response = await silently_send_message(d_conv, "/newpack") + if "Yay!" not in response.text: + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + response = await silently_send_message(d_conv, packname) + if not response.text.startswith("Alright!"): + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + w = await d_conv.send_file( + file=uploaded_sticker, allow_cache=False, force_document=True + ) + response = await d_conv.get_response() + if "Sorry" in response.text: + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + await silently_send_message(d_conv, sticker_emoji) + await silently_send_message(d_conv, "/publish") + response = await silently_send_message(d_conv, f"<{packname}>") + await silently_send_message(d_conv, "/skip") + response = await silently_send_message(d_conv, packshortname) + if response.text == "Sorry, this short name is already taken.": + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + else: + await silently_send_message(d_conv, "/cancel") + await silently_send_message(d_conv, "/addsticker") + await silently_send_message(d_conv, packshortname) + await d_conv.send_file( + file=uploaded_sticker, allow_cache=False, force_document=True + ) + response = await d_conv.get_response() + if response.text == FILLED_UP_DADDY: + while response.text == FILLED_UP_DADDY: + pack += 1 + prevv = int(pack) - 1 + packname = f"{first_name}'s Sticker Vol.{pack}" + packshortname = f"Vol_{pack}_with_{userid}" + + if not await stickerset_exists(d_conv, packshortname): + await tbot.edit_message( + kanga, + "**Pack No. **" + + str(prevv) + + "** is full! Making a new Pack, Vol. **" + + str(pack), + ) + if is_a_s: + response = await silently_send_message( + d_conv, "/newanimated" + ) + else: + response = await silently_send_message(d_conv, "/newpack") + if "Yay!" not in response.text: + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + response = await silently_send_message(d_conv, packname) + if not response.text.startswith("Alright!"): + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + w = await d_conv.send_file( + file=uploaded_sticker, + allow_cache=False, + force_document=True, + ) + response = await d_conv.get_response() + if "Sorry" in response.text: + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + await silently_send_message(d_conv, sticker_emoji) + await silently_send_message(d_conv, "/publish") + response = await silently_send_message( + bot_conv, f"<{packname}>" + ) + await silently_send_message(d_conv, "/skip") + response = await silently_send_message(d_conv, packshortname) + if response.text == "Sorry, this short name is already taken.": + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + else: + await tbot.edit_message( + kanga, + "**Pack No. **" + + str(prevv) + + "** is full! Switching to Vol. **" + + str(pack), + ) + await silently_send_message(d_conv, "/addsticker") + await silently_send_message(d_conv, packshortname) + await d_conv.send_file( + file=uploaded_sticker, + allow_cache=False, + force_document=True, + ) + response = await d_conv.get_response() + if "Sorry" in response.text: + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + await silently_send_message(d_conv, sticker_emoji) + await silently_send_message(d_conv, "/done") + else: + if "Sorry" in response.text: + await tbot.edit_message( + kanga, f"**Error**! @Stickers replied: {response.text}" + ) + return + await silently_send_message(d_conv, response) + await silently_send_message(d_conv, sticker_emoji) + await silently_send_message(d_conv, "/done") + await kanga.edit("Inviting This Sticker To Your Pack 🚶") + await kanga.edit( + f"This Sticker Has Came To Your Pack.` \n**Check It Out** [Here](t.me/addstickers/{packshortname})" + ) + os.system("rm -rf Stickers.png") + os.system("rm -rf AnimatedSticker.tgs") + os.system("rm -rf *.webp") + + +@Daisy(pattern="^/rmkang$") +async def _(event): + try: + if not event.is_reply: + await event.reply( + "Reply to a sticker to remove it from your personal sticker pack." + ) + return + reply_message = await event.get_reply_message() + kanga = await event.reply("`Deleting .`") + + if not is_message_image(reply_message): + await kanga.edit("Please reply to a sticker.") + return + + rmsticker = await ubot.get_messages(event.chat_id, ids=reply_message.id) + + stickerset_attr_s = reply_message.document.attributes + stickerset_attr = find_instance(stickerset_attr_s, DocumentAttributeSticker) + if not stickerset_attr.stickerset: + await event.reply("Sticker does not belong to a pack.") + return + + get_stickerset = await tbot( + GetStickerSetRequest( + InputStickerSetID( + id=stickerset_attr.stickerset.id, + access_hash=stickerset_attr.stickerset.access_hash, + ) + ) + ) + + packname = get_stickerset.set.short_name + + sresult = ( + await ubot( + functions.messages.GetStickerSetRequest( + InputStickerSetShortName(packname) + ) + ) + ).documents + for c in sresult: + if int(c.id) == int(stickerset_attr.stickerset.id): + pass + else: + await kanga.edit( + "This sticker is already removed from your personal sticker pack." + ) + return + + await kanga.edit("`Deleting ..`") + + async with ubot.conversation("@Stickers") as bot_conv: + + await silently_send_message(bot_conv, "/cancel") + response = await silently_send_message(bot_conv, "/delsticker") + if "Choose" not in response.text: + await tbot.edit_message( + kanga, f"**FAILED**! @Stickers replied: {response.text}" + ) + return + response = await silently_send_message(bot_conv, packname) + if not response.text.startswith("Please"): + await tbot.edit_message( + kanga, f"**FAILED**! @Stickers replied: {response.text}" + ) + return + try: + await rmsticker.forward_to("@Stickers") + except Exception as e: + print(e) + if response.text.startswith("This pack has only"): + await silently_send_message(bot_conv, "Delete anyway") + + await kanga.edit("`Deleting ...`") + response = await bot_conv.get_response() + if not "I have deleted" in response.text: + await tbot.edit_message( + kanga, f"**FAILED**! @Stickers replied: {response.text}" + ) + return + + await kanga.edit( + "Successfully deleted that sticker from your personal pack." + ) + except Exception as e: + os.remove("sticker.webp") + print(e) + + +@register(cmds="getsticker") +@disableable_dec("getsticker") +@get_strings_dec("stickers") +async def get_sticker(message, strings): + if "reply_to_message" not in message or "sticker" not in message.reply_to_message: + await message.reply(strings["rpl_to_sticker"]) + return + + sticker = message.reply_to_message.sticker + file_id = sticker.file_id + text = strings["ur_sticker"].format(emoji=sticker.emoji, id=file_id) + + sticker_file = await bot.download_file_by_id(file_id, io.BytesIO()) + + await message.reply_document( + InputFile( + sticker_file, filename=f"{sticker.set_name}_{sticker.file_id[:5]}.png" + ), + text, + ) + + +@pbot.on_message(filters.command("sticker_id") & ~filters.edited) +async def sticker_id(_, message): + if not message.reply_to_message: + await message.reply_text("Reply to a sticker.") + return + if not message.reply_to_message.sticker: + await message.reply_text("Reply to a sticker.") + return + file_id = message.reply_to_message.sticker.file_id + await message.reply_text(f"`{file_id}`") + + +__mod_name__ = "Stickers" + +__help__ = """ +Stickers are the best way to show emotion. + +Available commands: +- /searchsticker: Search stickers for given query. +- /packinfo: Reply to a sticker to get it's pack info +- /getsticker: Uploads the .png of the sticker you've replied to +- /sticker_id : Reply to Sticker for getting sticker Id. +- /kang [Emoji for sticker] [reply to Image/Sticker]: Kang replied sticker/image. +- /rmkang [REPLY]: Remove replied sticker from your kang pack. +""" diff --git a/DaisyX/modules/tagall.py b/DaisyX/modules/tagall.py new file mode 100644 index 00000000..72f3a1e2 --- /dev/null +++ b/DaisyX/modules/tagall.py @@ -0,0 +1,36 @@ +# Copyright (C) 2020-2021 by DevsExpo@Github, < https://github.com/DevsExpo >. +# +# This file is part of < https://github.com/DevsExpo/FridayUserBot > project, +# and is released under the "GNU v3.0 License Agreement". +# Please see < https://github.com/DevsExpo/blob/master/LICENSE > +# +# All rights reserved. + + +from pyrogram import filters + +from DaisyX.function.pluginhelpers import admins_only, get_text +from DaisyX.services.pyrogram import pbot + + +@pbot.on_message(filters.command("tagall") & ~filters.edited & ~filters.bot) +@admins_only +async def tagall(client, message): + await message.reply("`Processing.....`") + sh = get_text(message) + if not sh: + sh = "Hi!" + mentions = "" + async for member in client.iter_chat_members(message.chat.id): + mentions += member.user.mention + " " + n = 4096 + kk = [mentions[i : i + n] for i in range(0, len(mentions), n)] + for i in kk: + j = f"{sh} \n{i}" + await client.send_message(message.chat.id, j, parse_mode="html") + + +_mod_name_ = "Tagall" +_help_ = """ +- /tagall : Tag everyone in a chat +""" diff --git a/DaisyX/modules/telegraph.py b/DaisyX/modules/telegraph.py new file mode 100644 index 00000000..341d8864 --- /dev/null +++ b/DaisyX/modules/telegraph.py @@ -0,0 +1,119 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import os +from datetime import datetime + +from PIL import Image +from telegraph import Telegraph, exceptions, upload_file +from telethon import events + +from DaisyX.services.telethon import tbot as borg + +telegraph = Telegraph() +r = telegraph.create_account(short_name="DaisyX") +auth_url = r["auth_url"] + +# Will change later +TMP_DOWNLOAD_DIRECTORY = "./" + +BOTLOG = False + + +@borg.on(events.NewMessage(pattern="/telegraph (media|text) ?(.*)")) +async def _(event): + if event.fwd_from: + return + optional_title = event.pattern_match.group(2) + if event.reply_to_msg_id: + start = datetime.now() + r_message = await event.get_reply_message() + input_str = event.pattern_match.group(1) + if input_str == "media": + downloaded_file_name = await borg.download_media( + r_message, TMP_DOWNLOAD_DIRECTORY + ) + end = datetime.now() + ms = (end - start).seconds + await event.reply( + "Downloaded to {} in {} seconds.".format(downloaded_file_name, ms) + ) + if downloaded_file_name.endswith((".webp")): + resize_image(downloaded_file_name) + try: + start = datetime.now() + media_urls = upload_file(downloaded_file_name) + except exceptions.TelegraphException as exc: + await event.edit("ERROR: " + str(exc)) + os.remove(downloaded_file_name) + else: + end = datetime.now() + ms_two = (end - start).seconds + os.remove(downloaded_file_name) + await event.reply( + "Uploaded to https://telegra.ph{} in {} seconds.".format( + media_urls[0], (ms + ms_two) + ), + link_preview=True, + ) + elif input_str == "text": + user_object = await borg.get_entity(r_message.sender_id) + title_of_page = user_object.first_name # + " " + user_object.last_name + # apparently, all Users do not have last_name field + if optional_title: + title_of_page = optional_title + page_content = r_message.message + if r_message.media: + if page_content != "": + title_of_page = page_content + downloaded_file_name = await borg.download_media( + r_message, TMP_DOWNLOAD_DIRECTORY + ) + m_list = None + with open(downloaded_file_name, "rb") as fd: + m_list = fd.readlines() + for m in m_list: + page_content += m.decode("UTF-8") + "\n" + os.remove(downloaded_file_name) + page_content = page_content.replace("\n", "
") + response = telegraph.create_page(title_of_page, html_content=page_content) + end = datetime.now() + ms = (end - start).seconds + await event.reply( + "Pasted to https://telegra.ph/{} in {} seconds.".format( + response["path"], ms + ), + link_preview=True, + ) + else: + await event.reply("Reply to a message to get a permanent telegra.ph link. ") + + +def resize_image(image): + im = Image.open(image) + im.save(image, "PNG") + + +__mod_name__ = """ + Telegraph text/video upload plugin + - /telegraph media reply to image or video : Upload image and video directly to telegraph. + - /telegraph text reply to text : upload text directly to telegraph . +""" + +__mod_name__ = "Telegraph" diff --git a/DaisyX/modules/text_filters.py b/DaisyX/modules/text_filters.py new file mode 100644 index 00000000..d94a72c8 --- /dev/null +++ b/DaisyX/modules/text_filters.py @@ -0,0 +1,110 @@ +# This filte is ported from WilliamButcherBot +# Credits goes to TheHamkerCat + +# Don't edit these lines + +from pyrogram import filters + +from DaisyX.db.mongo_helpers.filterdb import ( + delete_filter, + get_filter, + get_filters_names, + save_filter, +) +from DaisyX.function.pluginhelpers import member_permissions +from DaisyX.services.pyrogram import pbot as app + + +@app.on_message(filters.command("filter") & ~filters.edited & ~filters.private) +async def save_filters(_, message): + if len(message.command) < 2 or not message.reply_to_message: + await message.reply_text( + "Usage:\nReply to a text or sticker with /filter to save it. \n\n NOTE: **TRY OUR NEW FILTER SYSTEM WITH /addfilter**" + ) + + elif not message.reply_to_message.text and not message.reply_to_message.sticker: + await message.reply_text( + "__**You can only save text or stickers as text filters.**__\n\n NOTE: **TRY /addfilter FOR OTHER FILE TYPES**" + ) + + elif len(await member_permissions(message.chat.id, message.from_user.id)) < 1: + await message.reply_text("**You don't have enough permissions**") + elif not "can_change_info" in ( + await member_permissions(message.chat.id, message.from_user.id) + ): + await message.reply_text("**You don't have enough permissions**") + else: + name = message.text.split(None, 1)[1].strip() + if not name: + await message.reply_text("**Usage**\n__/filter __") + return + _type = "text" if message.reply_to_message.text else "sticker" + _filter = { + "type": _type, + "data": message.reply_to_message.text.markdown + if _type == "text" + else message.reply_to_message.sticker.file_id, + } + await save_filter(message.chat.id, name, _filter) + await message.reply_text(f"__**Saved filter {name}.**__") + + +@app.on_message(filters.command("filters") & ~filters.edited & ~filters.private) +async def get_filterss(_, message): + _filters = await get_filters_names(message.chat.id) + if not _filters: + return + else: + msg = f"Text filters in {message.chat.title}\n" + for _filter in _filters: + msg += f"**-** `{_filter}`\n" + await message.reply_text(msg) + + +@app.on_message(filters.command("stop") & ~filters.edited & ~filters.private) +async def del_filter(_, message): + if len(message.command) < 2: + await message.reply_text( + "**Usage**\n__/stop \nIf filter /delfilter __" + ) + + elif len(await member_permissions(message.chat.id, message.from_user.id)) < 1: + await message.reply_text("**You don't have enough permissions**") + + else: + name = message.text.split(None, 1)[1].strip() + if not name: + await message.reply_text( + "**Usage**\n__/stop \nIf filter /delfilter __" + ) + return + chat_id = message.chat.id + deleted = await delete_filter(chat_id, name) + if deleted: + await message.reply_text(f"**Deleted filter {name}.**") + else: + await message.reply_text(f"**No such filter.**") + + +@app.on_message( + filters.incoming & filters.text & ~filters.private & ~filters.channel & ~filters.bot +) +async def filters_re(_, message): + try: + if message.text[0] != "/": + text = message.text.lower().strip().split(" ") + if text: + chat_id = message.chat.id + list_of_filters = await get_filters_names(chat_id) + for word in text: + if word in list_of_filters: + _filter = await get_filter(chat_id, word) + data_type = _filter["type"] + data = _filter["data"] + if data_type == "text": + await message.reply_text(data) + else: + await message.reply_sticker(data) + message.continue_propagation() + except Exception: + pass diff --git a/DaisyX/modules/torrent.py b/DaisyX/modules/torrent.py new file mode 100644 index 00000000..4bb6037e --- /dev/null +++ b/DaisyX/modules/torrent.py @@ -0,0 +1,419 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import requests +from telethon import * +from telethon import events +from telethon.tl import functions, types +from telethon.tl.types import * + +from DaisyX.services.mongo import mongodb as db +from DaisyX.services.telethon import tbot + +approved_users = db.approve + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return False + + +@tbot.on(events.NewMessage(pattern="^/torrent (.*)")) +async def _(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + sender = event.sender_id + search = event.pattern_match.group(1) + index = 0 + chatid = event.chat_id + msg = await tbot.send_message(chatid, "Loading ...") + msgid = msg.id + await tbot.edit_message( + chatid, + msgid, + "Daisy found some torrents for you. Take a look 👇", + buttons=[ + [ + Button.inline( + "📤 Get Torrents from Sumanjay's API", + data=f"torrent-{sender}|{search}|{index}|{chatid}|{msgid}", + ) + ], + [ + Button.inline( + "❌ Cancel Search", data=f"torrentstop-{sender}|{chatid}|{msgid}" + ) + ], + ], + ) + + +@tbot.on(events.CallbackQuery(pattern=r"torrent(\-(.*))")) +async def paginate_news(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + tata = event.pattern_match.group(1) + data = tata.decode() + meta = data.split("-", 1)[1] + # print(meta) + if "|" in meta: + sender, search, index, chatid, msgid = meta.split("|") + sender = int(sender.strip()) + if not event.sender_id == sender: + await event.answer("You haven't send that command !") + return + search = search.strip() + index = int(index.strip()) + num = index + chatid = int(chatid.strip()) + msgid = int(msgid.strip()) + url = f"https://api.sumanjay.cf/torrent/?query={search}" + try: + results = requests.get(url).json() + except Exception as e: + await event.reply( + "Sorry, either the server is down or no results found for your query." + ) + print(e) + return + # print(results) + age = results[int(num)].get("age") + leech = results[int(num)].get("leecher") + mag = results[int(num)].get("magnet") + name = results[int(num)].get("name") + seed = results[int(num)].get("seeder") + size = results[int(num)].get("size") + typ = results[int(num)].get("type") + header = f"**#{num} **" + lastisthis = f"{header} **Name:** {name}\n**Uploaded:** {age} ago\n**Seeders:** {seed}\n**Leechers:** {leech}\n**Size:** {size}\n**Type:** {typ}\n**Magnet Link:** `{mag}`" + await tbot.edit_message( + chatid, + msgid, + lastisthis, + link_preview=False, + buttons=[ + [ + Button.inline( + "◀️", data=f"prevtorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + Button.inline("❌", data=f"torrentstop-{sender}|{chatid}|{msgid}"), + Button.inline( + "▶️", data=f"nexttorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + ], + [ + Button.inline( + "Refresh 🔁", data=f"newtorrent-{sender}|{search}|{chatid}|{msgid}" + ) + ], + ], + ) + + +@tbot.on(events.CallbackQuery(pattern=r"prevtorrent(\-(.*))")) +async def paginate_prevtorrent(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + tata = event.pattern_match.group(1) + data = tata.decode() + meta = data.split("-", 1)[1] + # print(meta) + if "|" in meta: + sender, search, index, chatid, msgid = meta.split("|") + sender = int(sender.strip()) + if not event.sender_id == sender: + await event.answer("You haven't send that command !") + return + search = search.strip() + index = int(index.strip()) + num = index - 1 + chatid = int(chatid.strip()) + msgid = int(msgid.strip()) + url = f"https://api.sumanjay.cf/torrent/?query={search}" + try: + results = requests.get(url).json() + except Exception as e: + await event.reply("Sorry, Daisy Cant found any torrents for that word") + print(e) + return + vector = len(results) + if num < 0: + num = vector - 1 + # print(results) + age = results[int(num)].get("age") + leech = results[int(num)].get("leecher") + mag = results[int(num)].get("magnet") + name = results[int(num)].get("name") + seed = results[int(num)].get("seeder") + size = results[int(num)].get("size") + typ = results[int(num)].get("type") + header = f"**#{num} **" + lastisthis = f"{header} **Name:** {name}\n**Uploaded:** {age} ago\n**Seeders:** {seed}\n**Leechers:** {leech}\n**Size:** {size}\n**Type:** {typ}\n**Magnet Link:** `{mag}`" + await tbot.edit_message( + chatid, + msgid, + lastisthis, + link_preview=False, + buttons=[ + [ + Button.inline( + "◀️", data=f"prevtorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + Button.inline("❌", data=f"torrentstop-{sender}|{chatid}|{msgid}"), + Button.inline( + "▶️", data=f"nexttorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + ], + [ + Button.inline( + "Refresh 🔁", data=f"newtorrent-{sender}|{search}|{chatid}|{msgid}" + ) + ], + ], + ) + + +@tbot.on(events.CallbackQuery(pattern=r"nexttorrent(\-(.*))")) +async def paginate_nexttorrent(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + tata = event.pattern_match.group(1) + data = tata.decode() + meta = data.split("-", 1)[1] + # print(meta) + if "|" in meta: + sender, search, index, chatid, msgid = meta.split("|") + sender = int(sender.strip()) + if not event.sender_id == sender: + await event.answer("You haven't send that command !") + return + search = search.strip() + index = int(index.strip()) + num = index + 1 + chatid = int(chatid.strip()) + msgid = int(msgid.strip()) + url = f"https://api.sumanjay.cf/torrent/?query={search}" + try: + results = requests.get(url).json() + except Exception as e: + await event.reply( + "Sorry, either the server is down or no results found for your query." + ) + print(e) + return + vector = len(results) + if num > vector - 1: + num = 0 + # print(results) + age = results[int(num)].get("age") + leech = results[int(num)].get("leecher") + mag = results[int(num)].get("magnet") + name = results[int(num)].get("name") + seed = results[int(num)].get("seeder") + size = results[int(num)].get("size") + typ = results[int(num)].get("type") + header = f"**#{num} **" + lastisthis = f"{header} **Name:** {name}\n**Uploaded:** {age} ago\n**Seeders:** {seed}\n**Leechers:** {leech}\n**Size:** {size}\n**Type:** {typ}\n**Magnet Link:** `{mag}`" + await tbot.edit_message( + chatid, + msgid, + lastisthis, + link_preview=False, + buttons=[ + [ + Button.inline( + "◀️", data=f"prevtorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + Button.inline("❌", data=f"torrentstop-{sender}|{chatid}|{msgid}"), + Button.inline( + "▶️", data=f"nexttorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + ], + [ + Button.inline( + "Refresh 🔁", data=f"newtorrent-{sender}|{search}|{chatid}|{msgid}" + ) + ], + ], + ) + + +@tbot.on(events.CallbackQuery(pattern=r"torrentstop(\-(.*))")) +async def torrentstop(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + tata = event.pattern_match.group(1) + data = tata.decode() + meta = data.split("-", 1)[1] + # print(meta) + if "|" in meta: + sender, chatid, msgid = meta.split("|") + sender = int(sender.strip()) + chatid = int(chatid.strip()) + msgid = int(msgid.strip()) + if not event.sender_id == sender: + await event.answer("You haven't send that command !") + return + await tbot.edit_message( + chatid, + msgid, + "Thanks for using.\n❤️ from [Daisy X](t.me/DaisyXBot) !", + link_preview=False, + ) + + +@tbot.on(events.CallbackQuery(pattern=r"newtorrent(\-(.*))")) +async def paginate_nexttorrent(event): + approved_userss = approved_users.find({}) + for ch in approved_userss: + iid = ch["id"] + userss = ch["user"] + if event.is_group: + if await is_register_admin(event.input_chat, event.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + tata = event.pattern_match.group(1) + data = tata.decode() + meta = data.split("-", 1)[1] + # print(meta) + if "|" in meta: + sender, search, chatid, msgid = meta.split("|") + sender = int(sender.strip()) + if not event.sender_id == sender: + await event.answer("You haven't send that command !") + return + search = search.strip() + num = 0 + chatid = int(chatid.strip()) + msgid = int(msgid.strip()) + url = f"https://api.sumanjay.cf/torrent/?query={search}" + try: + results = requests.get(url).json() + except Exception as e: + await event.reply( + "Sorry, either the server is down or no results found for your query." + ) + print(e) + return + vector = len(results) + if num > vector - 1: + num = 0 + # print(results) + age = results[int(num)].get("age") + leech = results[int(num)].get("leecher") + mag = results[int(num)].get("magnet") + name = results[int(num)].get("name") + seed = results[int(num)].get("seeder") + size = results[int(num)].get("size") + typ = results[int(num)].get("type") + header = f"**#{num} **" + lastisthis = f"{header} **Name:** {name}\n**Uploaded:** {age} ago\n**Seeders:** {seed}\n**Leechers:** {leech}\n**Size:** {size}\n**Type:** {typ}\n**Magnet Link:** `{mag}`" + await tbot.edit_message( + chatid, + msgid, + lastisthis, + link_preview=False, + buttons=[ + [ + Button.inline( + "◀️", data=f"prevtorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + Button.inline("❌", data=f"torrentstop-{sender}|{chatid}|{msgid}"), + Button.inline( + "▶️", data=f"nexttorrent-{sender}|{search}|{num}|{chatid}|{msgid}" + ), + ], + [ + Button.inline( + "Refresh 🔁", data=f"newtorrent-{sender}|{search}|{chatid}|{msgid}" + ) + ], + ], + ) + + +_help_ = """ + - /torrent text: Search for torrent links + +Special Credits to Sumanjay for api and also for julia project +""" + +_mod_name_ = "Torrent" diff --git a/DaisyX/modules/tts-stt.py b/DaisyX/modules/tts-stt.py new file mode 100644 index 00000000..04af00e9 --- /dev/null +++ b/DaisyX/modules/tts-stt.py @@ -0,0 +1,171 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import os +from datetime import datetime + +import requests +from gtts import gTTS, gTTSError +from telethon.tl import functions, types + +from DaisyX.config import get_str_key +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + +IBM_WATSON_CRED_PASSWORD = get_str_key("IBM_WATSON_CRED_PASSWORD", required=False) +IBM_WATSON_CRED_URL = get_str_key("IBM_WATSON_CRED_URL", required=False) +TEMP_DOWNLOAD_DIRECTORY = "./" + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/tts (.*)") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + input_str = event.pattern_match.group(1) + reply_to_id = event.message.id + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + text = previous_message.message + lan = input_str + elif "|" in input_str: + lan, text = input_str.split("|") + else: + await event.reply( + "Invalid Syntax\nFormat `/tts lang | text`\nFor eg: `/tts en | hello`" + ) + return + text = text.strip() + lan = lan.strip() + try: + tts = gTTS(text, tld="com", lang=lan) + tts.save("k.mp3") + except AssertionError: + await event.reply( + "The text is empty.\n" + "Nothing left to speak after pre-precessing, " + "tokenizing and cleaning." + ) + return + except ValueError: + await event.reply("Language is not supported.") + return + except RuntimeError: + await event.reply("Error loading the languages dictionary.") + return + except gTTSError: + await event.reply("Error in Google Text-to-Speech API request !") + return + with open("k.mp3", "r"): + await tbot.send_file( + event.chat_id, "k.mp3", voice_note=True, reply_to=reply_to_id + ) + os.remove("k.mp3") + + +# ------ THANKS TO LONAMI ------# + + +@register(pattern="^/stt$") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + start = datetime.now() + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + + if event.reply_to_msg_id: + previous_message = await event.get_reply_message() + required_file_name = await event.client.download_media( + previous_message, TEMP_DOWNLOAD_DIRECTORY + ) + if IBM_WATSON_CRED_URL is None or IBM_WATSON_CRED_PASSWORD is None: + await event.reply( + "You need to set the required ENV variables for this module. \nModule stopping" + ) + else: + # await event.reply("Starting analysis") + headers = { + "Content-Type": previous_message.media.document.mime_type, + } + data = open(required_file_name, "rb").read() + response = requests.post( + IBM_WATSON_CRED_URL + "/v1/recognize", + headers=headers, + data=data, + auth=("apikey", IBM_WATSON_CRED_PASSWORD), + ) + r = response.json() + if "results" in r: + # process the json to appropriate string format + results = r["results"] + transcript_response = "" + transcript_confidence = "" + for alternative in results: + alternatives = alternative["alternatives"][0] + transcript_response += " " + str(alternatives["transcript"]) + transcript_confidence += ( + " " + str(alternatives["confidence"]) + " + " + ) + end = datetime.now() + ms = (end - start).seconds + if transcript_response != "": + string_to_show = "TRANSCRIPT: `{}`\nTime Taken: {} seconds\nConfidence: `{}`".format( + transcript_response, ms, transcript_confidence + ) + else: + string_to_show = "TRANSCRIPT: `Nil`\nTime Taken: {} seconds\n\n**No Results Found**".format( + ms + ) + await event.reply(string_to_show) + else: + await event.reply(r["error"]) + # now, remove the temporary file + os.remove(required_file_name) + else: + await event.reply("Reply to a voice message, to get the text out of it.") + + +_mod_name_ = "Text to Speech" + +_help_ = """ + - /tts: Reply to any message to get text to speech output + - /stt: Type in reply to a voice message(english only) to extract text from it. +""" diff --git a/DaisyX/modules/userinfo.py b/DaisyX/modules/userinfo.py new file mode 100644 index 00000000..4cdc8118 --- /dev/null +++ b/DaisyX/modules/userinfo.py @@ -0,0 +1,106 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from datetime import datetime + +from pyrogram import filters +from pyrogram.errors import PeerIdInvalid +from pyrogram.types import Message, User + +from DaisyX.services.pyrogram import pbot + + +def ReplyCheck(message: Message): + reply_id = None + + if message.reply_to_message: + reply_id = message.reply_to_message.message_id + + elif not message.from_user.is_self: + reply_id = message.message_id + + return reply_id + + +infotext = ( + "**[{full_name}](tg://user?id={user_id})**\n" + " * UserID: `{user_id}`\n" + " * First Name: `{first_name}`\n" + " * Last Name: `{last_name}`\n" + " * Username: `{username}`\n" + " * Last Online: `{last_online}`\n" + " * Bio: {bio}" +) + + +def LastOnline(user: User): + if user.is_bot: + return "" + elif user.status == "recently": + return "Recently" + elif user.status == "within_week": + return "Within the last week" + elif user.status == "within_month": + return "Within the last month" + elif user.status == "long_time_ago": + return "A long time ago :(" + elif user.status == "online": + return "Currently Online" + elif user.status == "offline": + return datetime.fromtimestamp(user.status.date).strftime( + "%a, %d %b %Y, %H:%M:%S" + ) + + +def FullName(user: User): + return user.first_name + " " + user.last_name if user.last_name else user.first_name + + +@pbot.on_message(filters.command("whois") & ~filters.edited & ~filters.bot) +async def whois(client, message): + cmd = message.command + if not message.reply_to_message and len(cmd) == 1: + get_user = message.from_user.id + elif len(cmd) == 1: + get_user = message.reply_to_message.from_user.id + elif len(cmd) > 1: + get_user = cmd[1] + try: + get_user = int(cmd[1]) + except ValueError: + pass + try: + user = await client.get_users(get_user) + except PeerIdInvalid: + await message.reply("I don't know that User.") + return + desc = await client.get_chat(get_user) + desc = desc.description + await message.reply_text( + infotext.format( + full_name=FullName(user), + user_id=user.id, + user_dc=user.dc_id, + first_name=user.first_name, + last_name=user.last_name if user.last_name else "", + username=user.username if user.username else "", + last_online=LastOnline(user), + bio=desc if desc else "`No bio set up.`", + ), + disable_web_page_preview=True, + ) diff --git a/DaisyX/modules/username.py b/DaisyX/modules/username.py new file mode 100644 index 00000000..fd2795a9 --- /dev/null +++ b/DaisyX/modules/username.py @@ -0,0 +1,113 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from telethon.errors.rpcerrorlist import YouBlockedUserError +from telethon.tl import functions, types + +from DaisyX.services.events import register as Daisy +from DaisyX.services.telethon import tbot +from DaisyX.services.telethonuserbot import ubot + + +async def is_register_admin(chat, user): + + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +async def silently_send_message(conv, text): + await conv.send_message(text) + response = await conv.get_response() + await conv.mark_read(message=response) + return response + + +@Daisy(pattern="^/namehistory ?(.*)") +async def _(event): + + if event.fwd_from: + + return + + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + if not event.reply_to_msg_id: + + await event.reply("```Reply to any user message.```") + + return + + reply_message = await event.get_reply_message() + + if not reply_message.text: + + await event.reply("```reply to text message```") + + return + + chat = "@DetectiveInfoBot" + uid = reply_message.sender_id + reply_message.sender + + if reply_message.sender.bot: + + await event.edit("```Reply to actual users message.```") + + return + + lol = await event.reply("```Processing```") + + async with ubot.conversation(chat) as conv: + + try: + + # response = conv.wait_event( + # events.NewMessage(incoming=True, from_users=1706537835) + # ) + + await silently_send_message(conv, f"/detect_id {uid}") + + # response = await response + responses = await silently_send_message(conv, f"/detect_id {uid}") + except YouBlockedUserError: + + await event.reply("```Please unblock @DetectiveInfoBot and try again```") + + return + await lol.edit(f"{responses.text}") + # await lol.edit(f"{response.message.message}") diff --git a/DaisyX/modules/users.py b/DaisyX/modules/users.py new file mode 100644 index 00000000..b37e9307 --- /dev/null +++ b/DaisyX/modules/users.py @@ -0,0 +1,283 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2019 Aiogram +# +# This file is part of Daisy (Telegram Bot) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import datetime +import html + +from aiogram.dispatcher.middlewares import BaseMiddleware + +from DaisyX import dp +from DaisyX.decorator import register +from DaisyX.modules import LOADED_MODULES +from DaisyX.services.mongo import db +from DaisyX.utils.logger import log + +from .utils.connections import chat_connection +from .utils.disable import disableable_dec +from .utils.language import get_strings_dec +from .utils.user_details import ( + get_admins_rights, + get_user_dec, + get_user_link, + is_user_admin, +) + + +async def update_users_handler(message): + chat_id = message.chat.id + + # Update chat + new_chat = message.chat + if not new_chat.type == "private": + + old_chat = await db.chat_list.find_one({"chat_id": chat_id}) + + if not hasattr(new_chat, "username"): + chatnick = None + else: + chatnick = new_chat.username + + if old_chat and "first_detected_date" in old_chat: + first_detected_date = old_chat["first_detected_date"] + else: + first_detected_date = datetime.datetime.now() + + chat_new = { + "chat_id": chat_id, + "chat_title": html.escape(new_chat.title, quote=False), + "chat_nick": chatnick, + "type": new_chat.type, + "first_detected_date": first_detected_date, + } + + # Check on old chat in DB with same username + find_old_chat = { + "chat_nick": chat_new["chat_nick"], + "chat_id": {"$ne": chat_new["chat_id"]}, + } + if chat_new["chat_nick"] and ( + check := await db.chat_list.find_one(find_old_chat) + ): + await db.chat_list.delete_one({"_id": check["_id"]}) + log.info( + f"Found chat ({check['chat_id']}) with same username as ({chat_new['chat_id']}), old chat was deleted." + ) + + await db.chat_list.update_one( + {"chat_id": chat_id}, {"$set": chat_new}, upsert=True + ) + + log.debug(f"Users: Chat {chat_id} updated") + + # Update users + await update_user(chat_id, message.from_user) + + if ( + "reply_to_message" in message + and hasattr(message.reply_to_message.from_user, "chat_id") + and message.reply_to_message.from_user.chat_id + ): + await update_user(chat_id, message.reply_to_message.from_user) + + if "forward_from" in message: + await update_user(chat_id, message.forward_from) + + +async def update_user(chat_id, new_user): + old_user = await db.user_list.find_one({"user_id": new_user.id}) + + new_chat = [chat_id] + + if old_user and "chats" in old_user: + if old_user["chats"]: + new_chat = old_user["chats"] + if not new_chat or chat_id not in new_chat: + new_chat.append(chat_id) + + if old_user and "first_detected_date" in old_user: + first_detected_date = old_user["first_detected_date"] + else: + first_detected_date = datetime.datetime.now() + + if new_user.username: + username = new_user.username.lower() + else: + username = None + + if hasattr(new_user, "last_name") and new_user.last_name: + last_name = html.escape(new_user.last_name, quote=False) + else: + last_name = None + + first_name = html.escape(new_user.first_name, quote=False) + + user_new = { + "user_id": new_user.id, + "first_name": first_name, + "last_name": last_name, + "username": username, + "user_lang": new_user.language_code, + "chats": new_chat, + "first_detected_date": first_detected_date, + } + + # Check on old user in DB with same username + find_old_user = { + "username": user_new["username"], + "user_id": {"$ne": user_new["user_id"]}, + } + if user_new["username"] and (check := await db.user_list.find_one(find_old_user)): + await db.user_list.delete_one({"_id": check["_id"]}) + log.info( + f"Found user ({check['user_id']}) with same username as ({user_new['user_id']}), old user was deleted." + ) + + await db.user_list.update_one( + {"user_id": new_user.id}, {"$set": user_new}, upsert=True + ) + + log.debug(f"Users: User {new_user.id} updated") + + return user_new + + +@register(cmds="info") +@disableable_dec("info") +@get_user_dec(allow_self=True) +@get_strings_dec("users") +async def user_info(message, user, strings): + chat_id = message.chat.id + + text = strings["user_info"] + text += strings["info_id"].format(id=user["user_id"]) + text += strings["info_first"].format(first_name=str(user["first_name"])) + + if user["last_name"] is not None: + text += strings["info_last"].format(last_name=str(user["last_name"])) + + if user["username"] is not None: + text += strings["info_username"].format(username="@" + str(user["username"])) + + text += strings["info_link"].format( + user_link=str(await get_user_link(user["user_id"])) + ) + + text += "\n" + + if await is_user_admin(chat_id, user["user_id"]) is True: + text += strings["info_admeme"] + + for module in [m for m in LOADED_MODULES if hasattr(m, "__user_info__")]: + if txt := await module.__user_info__(message, user["user_id"]): + text += txt + + text += strings["info_saw"].format(num=len(user["chats"]) if "chats" in user else 0) + + await message.reply(text) + + +@register(cmds="admincache", is_admin=True) +@chat_connection(only_groups=True, admin=True) +@get_strings_dec("users") +async def reset_admins_cache(message, chat, strings): + # Reset a cache + await get_admins_rights(chat["chat_id"], force_update=True) + await message.reply(strings["upd_cache_done"]) + + +@register(cmds=["id", "chatid", "userid"]) +@disableable_dec("id") +@get_user_dec(allow_self=True) +@get_strings_dec("misc") +@chat_connection() +async def get_id(message, user, strings, chat): + user_id = message.from_user.id + + text = strings["your_id"].format(id=user_id) + if message.chat.id != user_id: + text += strings["chat_id"].format(id=message.chat.id) + + if chat["status"] is True: + text += strings["conn_chat_id"].format(id=chat["chat_id"]) + + if not user["user_id"] == user_id: + text += strings["user_id"].format( + user=await get_user_link(user["user_id"]), id=user["user_id"] + ) + + if ( + "reply_to_message" in message + and "forward_from" in message.reply_to_message + and not message.reply_to_message.forward_from.id + == message.reply_to_message.from_user.id + ): + text += strings["user_id"].format( + user=await get_user_link(message.reply_to_message.forward_from.id), + id=message.reply_to_message.forward_from.id, + ) + + await message.reply(text) + + +@register(cmds=["adminlist", "admins"]) +@disableable_dec("adminlist") +@chat_connection(only_groups=True) +@get_strings_dec("users") +async def adminlist(message, chat, strings): + admins = await get_admins_rights(chat["chat_id"]) + text = strings["admins"] + for admin, rights in admins.items(): + if rights["anonymous"]: + continue + text += "- {} ({})\n".format(await get_user_link(admin), admin) + + await message.reply(text, disable_notification=True) + + +class SaveUser(BaseMiddleware): + async def on_process_message(self, message, data): + await update_users_handler(message) + + +async def __before_serving__(loop): + dp.middleware.setup(SaveUser()) + + +async def __stats__(): + text = "* {} total users, in {} chats\n".format( + await db.user_list.count_documents({}), await db.chat_list.count_documents({}) + ) + + text += "* {} new users and {} new chats in the last 48 hours\n".format( + await db.user_list.count_documents( + { + "first_detected_date": { + "$gte": datetime.datetime.now() - datetime.timedelta(days=2) + } + } + ), + await db.chat_list.count_documents( + { + "first_detected_date": { + "$gte": datetime.datetime.now() - datetime.timedelta(days=2) + } + } + ), + ) + + return text diff --git a/DaisyX/modules/utils/android.py b/DaisyX/modules/utils/android.py new file mode 100644 index 00000000..9df2217f --- /dev/null +++ b/DaisyX/modules/utils/android.py @@ -0,0 +1,63 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import httpx +import rapidjson as json + +# This file is an adaptation / port from the Galaxy Helper Bot. +# Copyright (C) KassemSYR. All rights reserved. + + +class GetDevice: + def __init__(self, device): + """Get device info by codename or model!""" + self.device = device + + async def get(self): + if self.device.lower().startswith("sm-"): + async with httpx.AsyncClient(http2=True) as http: + data = await http.get( + "https://raw.githubusercontent.com/androidtrackers/certified-android-devices/master/by_model.json" + ) + db = json.loads(data.content) + await http.aclose() + try: + name = db[self.device.upper()][0]["name"] + device = db[self.device.upper()][0]["device"] + brand = db[self.device.upper()][0]["brand"] + model = self.device.lower() + return {"name": name, "device": device, "model": model, "brand": brand} + except KeyError: + return False + else: + async with httpx.AsyncClient(http2=True) as http: + data = await http.get( + "https://raw.githubusercontent.com/androidtrackers/certified-android-devices/master/by_device.json" + ) + db = json.loads(data.content) + await http.aclose() + newdevice = ( + self.device.strip("lte").lower() + if self.device.startswith("beyond") + else self.device.lower() + ) + try: + name = db[newdevice][0]["name"] + model = db[newdevice][0]["model"] + brand = db[newdevice][0]["brand"] + device = self.device.lower() + return {"name": name, "device": device, "model": model, "brand": brand} + except KeyError: + return False diff --git a/DaisyX/modules/utils/anime.py b/DaisyX/modules/utils/anime.py new file mode 100644 index 00000000..0f8d1fa8 --- /dev/null +++ b/DaisyX/modules/utils/anime.py @@ -0,0 +1,162 @@ +# This file is part of DaisyXBot (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +def shorten(description, info="anilist.co"): + ms_g = "" + if len(description) > 700: + description = description[0:500] + "..." + ms_g += ( + f"\nDescription: {description} Read More" + ) + else: + ms_g += f"\nDescription: {description}" + return ( + ms_g.replace("
", "") + .replace("
", "") + .replace("", "") + .replace("", "") + ) + + +def t(milliseconds: int) -> str: + """Inputs time in milliseconds, to get beautified time, + as string""" + seconds, milliseconds = divmod(int(milliseconds), 1000) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + tmp = ( + ((str(days) + " Days, ") if days else "") + + ((str(hours) + " Hours, ") if hours else "") + + ((str(minutes) + " Minutes, ") if minutes else "") + + ((str(seconds) + " Seconds, ") if seconds else "") + + ((str(milliseconds) + " ms, ") if milliseconds else "") + ) + return tmp[:-2] + + +airing_query = """ + query ($id: Int,$search: String) { + Media (id: $id, type: ANIME,search: $search) { + id + episodes + title { + romaji + english + native + } + siteUrl + nextAiringEpisode { + airingAt + timeUntilAiring + episode + } + } + } + """ + +fav_query = """ +query ($id: Int) { + Media (id: $id, type: ANIME) { + id + title { + romaji + english + native + } + } +} +""" + +anime_query = """ + query ($id: Int,$search: String) { + Media (id: $id, type: ANIME,search: $search) { + id + idMal + title { + romaji + english + native + } + description (asHtml: false) + startDate{ + year + } + episodes + season + type + format + status + duration + siteUrl + studios{ + nodes{ + name + } + } + trailer{ + id + site + thumbnail + } + averageScore + genres + bannerImage + } + } +""" +character_query = """ + query ($query: String) { + Character (search: $query) { + id + name { + first + last + full + } + siteUrl + favourites + image { + large + } + description + } + } +""" + +manga_query = """ +query ($id: Int,$search: String) { + Media (id: $id, type: MANGA,search: $search) { + id + title { + romaji + english + native + } + description (asHtml: false) + startDate{ + year + } + type + format + status + siteUrl + averageScore + genres + bannerImage + } + } +""" diff --git a/DaisyX/modules/utils/buttonhelper.py b/DaisyX/modules/utils/buttonhelper.py new file mode 100644 index 00000000..92497d41 --- /dev/null +++ b/DaisyX/modules/utils/buttonhelper.py @@ -0,0 +1,134 @@ +import re +from typing import List + +from pyrogram.types import InlineKeyboardButton + +BTN_URL_REGEX = re.compile( + r"(\[([^\[]+?)\]\((buttonurl|buttonalert):(?:/{0,2})(.+?)(:same)?\))" +) + +SMART_OPEN = "“" +SMART_CLOSE = "”" +START_CHAR = ("'", '"', SMART_OPEN) + + +def split_quotes(text: str) -> List: + if any(text.startswith(char) for char in START_CHAR): + counter = 1 # ignore first char -> is some kind of quote + while counter < len(text): + if text[counter] == "\\": + counter += 1 + elif text[counter] == text[0] or ( + text[0] == SMART_OPEN and text[counter] == SMART_CLOSE + ): + break + counter += 1 + else: + return text.split(None, 1) + + # 1 to avoid starting quote, and counter is exclusive so avoids ending + key = remove_escapes(text[1:counter].strip()) + # index will be in range, or `else` would have been executed and returned + rest = text[counter + 1 :].strip() + if not key: + key = text[0] + text[0] + return list(filter(None, [key, rest])) + else: + return text.split(None, 1) + + +def parser(text, keyword): + if "buttonalert" in text: + text = text.replace("\n", "\\n").replace("\t", "\\t") + buttons = [] + note_data = "" + prev = 0 + i = 0 + alerts = [] + for match in BTN_URL_REGEX.finditer(text): + # Check if btnurl is escaped + n_escapes = 0 + to_check = match.start(1) - 1 + while to_check > 0 and text[to_check] == "\\": + n_escapes += 1 + to_check -= 1 + + # if even, not escaped -> create button + if n_escapes % 2 == 0: + note_data += text[prev : match.start(1)] + prev = match.end(1) + if match.group(3) == "buttonalert": + # create a thruple with button label, url, and newline status + if bool(match.group(5)) and buttons: + buttons[-1].append( + InlineKeyboardButton( + text=match.group(2), + callback_data=f"alertmessage:{i}:{keyword}", + ) + ) + else: + buttons.append( + [ + InlineKeyboardButton( + text=match.group(2), + callback_data=f"alertmessage:{i}:{keyword}", + ) + ] + ) + i = i + 1 + alerts.append(match.group(4)) + else: + if bool(match.group(5)) and buttons: + buttons[-1].append( + InlineKeyboardButton( + text=match.group(2), url=match.group(4).replace(" ", "") + ) + ) + else: + buttons.append( + [ + InlineKeyboardButton( + text=match.group(2), url=match.group(4).replace(" ", "") + ) + ] + ) + + # if odd, escaped -> move along + else: + note_data += text[prev:to_check] + prev = match.start(1) - 1 + else: + note_data += text[prev:] + + try: + return note_data, buttons, alerts + except: + return note_data, buttons, None + + +def remove_escapes(text: str) -> str: + counter = 0 + res = "" + is_escaped = False + while counter < len(text): + if is_escaped: + res += text[counter] + is_escaped = False + elif text[counter] == "\\": + is_escaped = True + else: + res += text[counter] + counter += 1 + return res + + +def humanbytes(size): + if not size: + return "" + power = 2 ** 10 + n = 0 + Dic_powerN = {0: " ", 1: "Ki", 2: "Mi", 3: "Gi", 4: "Ti"} + while size > power: + size /= power + n += 1 + return str(round(size, 2)) + " " + Dic_powerN[n] + "B" diff --git a/DaisyX/modules/utils/connections.py b/DaisyX/modules/utils/connections.py new file mode 100644 index 00000000..825f5a55 --- /dev/null +++ b/DaisyX/modules/utils/connections.py @@ -0,0 +1,161 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +from aiogram.utils.exceptions import Unauthorized + +from DaisyX.modules.utils.user_details import is_user_admin +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis +from DaisyX.utils.cached import cached + + +async def get_connected_chat( + message, admin=False, only_groups=False, from_id=None, command=None +): + # admin - Require admin rights in connected chat + # only_in_groups - disable command when bot's pm not connected to any chat + real_chat_id = message.chat.id + user_id = from_id or message.from_user.id + key = "connection_cache_" + str(user_id) + + if not message.chat.type == "private": + _chat = await db.chat_list.find_one({"chat_id": real_chat_id}) + chat_title = _chat["chat_title"] if _chat is not None else message.chat.title + # On some strange cases such as Database is fresh or new ; it doesn't contain chat data + # Only to "handle" the error, we do the above workaround - getting chat title from the update + return {"status": "chat", "chat_id": real_chat_id, "chat_title": chat_title} + + # Cached + if cached := redis.hgetall(key): + cached["status"] = True + cached["chat_id"] = int(cached["chat_id"]) + # return cached + + # if pm and not connected + if ( + not (connected := await get_connection_data(user_id)) + or "chat_id" not in connected + ): + if only_groups: + return {"status": None, "err_msg": "usage_only_in_groups"} + else: + return {"status": "private", "chat_id": user_id, "chat_title": "Local chat"} + + chat_id = connected["chat_id"] + + # Get chats where user was detected and check if user in connected chat + # TODO: Really get the user and check on banned + user_chats = (await db.user_list.find_one({"user_id": user_id}))["chats"] + if chat_id not in user_chats: + return {"status": None, "err_msg": "not_in_chat"} + + chat_title = (await db.chat_list.find_one({"chat_id": chat_id}))["chat_title"] + + # Admin rights check if admin=True + try: + user_admin = await is_user_admin(chat_id, user_id) + except Unauthorized: + return {"status": None, "err_msg": "bot_not_in_chat, please /disconnect"} + + if admin: + if not user_admin: + return {"status": None, "err_msg": "u_should_be_admin"} + + if "command" in connected: + if command in connected["command"]: + return {"status": True, "chat_id": chat_id, "chat_title": chat_title} + else: + # Return local chat if user is accessing non connected command + return {"status": "private", "chat_id": user_id, "chat_title": "Local chat"} + + # Check on /allowusersconnect enabled + if settings := await db.chat_connection_settings.find_one({"chat_id": chat_id}): + if ( + "allow_users_connect" in settings + and settings["allow_users_connect"] is False + and not user_admin + ): + return {"status": None, "err_msg": "conn_not_allowed"} + + data = {"status": True, "chat_id": chat_id, "chat_title": chat_title} + + # Cache connection status for 15 minutes + cached = data + cached["status"] = 1 + redis.hmset(key, cached) + redis.expire(key, 900) + + return data + + +def chat_connection(**dec_kwargs): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + + message = args[0] + from_id = None + if hasattr(message, "message"): + from_id = message.from_user.id + message = message.message + + if ( + check := await get_connected_chat( + message, from_id=from_id, **dec_kwargs + ) + )["status"] is None: + await message.reply(check["err_msg"]) + return + else: + return await func(*args, check, **kwargs) + + return wrapped_1 + + return wrapped + + +async def set_connected_chat(user_id, chat_id): + key = f"connection_cache_{user_id}" + redis.delete(key) + if not chat_id: + await db.connections.update_one( + {"user_id": user_id}, {"$unset": {"chat_id": 1, "command": 1}}, upsert=True + ) + await get_connection_data.reset_cache(user_id) + return + + await db.connections.update_one( + {"user_id": user_id}, + { + "$set": {"user_id": user_id, "chat_id": chat_id}, + "$unset": {"command": 1}, + "$addToSet": {"history": {"$each": [chat_id]}}, + }, + upsert=True, + ) + return await get_connection_data.reset_cache(user_id) + + +async def set_connected_command(user_id, chat_id, command): + command.append("disconnect") + await db.connections.update_one( + {"user_id": user_id}, + {"$set": {"user_id": user_id, "chat_id": chat_id, "command": list(command)}}, + upsert=True, + ) + return await get_connection_data.reset_cache(user_id) + + +@cached() +async def get_connection_data(user_id: int) -> dict: + return await db.connections.find_one({"user_id": user_id}) diff --git a/DaisyX/modules/utils/covert.py b/DaisyX/modules/utils/covert.py new file mode 100644 index 00000000..720ad95f --- /dev/null +++ b/DaisyX/modules/utils/covert.py @@ -0,0 +1,26 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import math + + +def convert_size(size_bytes): + if size_bytes == 0: + return "0B" + size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB") + i = int(math.floor(math.log(size_bytes, 1024))) + p = math.pow(1024, i) + s = round(size_bytes / p, 2) + return "%s %s" % (s, size_name[i]) diff --git a/DaisyX/modules/utils/disable.py b/DaisyX/modules/utils/disable.py new file mode 100644 index 00000000..169a7b29 --- /dev/null +++ b/DaisyX/modules/utils/disable.py @@ -0,0 +1,52 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from contextlib import suppress + +from DaisyX.modules.utils.user_details import is_user_admin +from DaisyX.services.mongo import db +from DaisyX.utils.logger import log + +DISABLABLE_COMMANDS = [] + + +def disableable_dec(command): + log.debug(f"Adding {command} to the disableable commands...") + + if command not in DISABLABLE_COMMANDS: + DISABLABLE_COMMANDS.append(command) + + def wrapped(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + + chat_id = message.chat.id + user_id = message.from_user.id + cmd = command + + with suppress(KeyError): + if command in (aliases := message.conf["cmds"]): + cmd = aliases[0] + + check = await db.disabled.find_one( + {"chat_id": chat_id, "cmds": {"$in": [cmd]}} + ) + if check and not await is_user_admin(chat_id, user_id): + return + return await func(*args, **kwargs) + + return wrapped_1 + + return wrapped diff --git a/DaisyX/modules/utils/fetch.py b/DaisyX/modules/utils/fetch.py new file mode 100644 index 00000000..c33af655 --- /dev/null +++ b/DaisyX/modules/utils/fetch.py @@ -0,0 +1,14 @@ +import requests + + +async def fetch(url): + try: + r = requests.request("GET", url=url) + except: + return + + try: + data = r.json() + except: + data = r.text() + return data diff --git a/DaisyX/modules/utils/httpx.py b/DaisyX/modules/utils/httpx.py new file mode 100644 index 00000000..4382c086 --- /dev/null +++ b/DaisyX/modules/utils/httpx.py @@ -0,0 +1,18 @@ +# This file is part of DaisyXBot (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import httpx + +http = httpx.AsyncClient(http2=True) diff --git a/DaisyX/modules/utils/language.py b/DaisyX/modules/utils/language.py new file mode 100644 index 00000000..8929c2a2 --- /dev/null +++ b/DaisyX/modules/utils/language.py @@ -0,0 +1,135 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os + +import yaml +from babel.core import Locale + +from DaisyX.services.mongo import db +from DaisyX.services.redis import redis +from DaisyX.utils.logger import log + +LANGUAGES = {} + +log.info("Loading localizations...") + +for filename in os.listdir("DaisyX/localization"): + log.debug("Loading language file " + filename) + with open("DaisyX/localization/" + filename, "r", encoding="utf8") as f: + lang = yaml.load(f, Loader=yaml.CLoader) + + lang_code = lang["language_info"]["code"] + lang["language_info"]["babel"] = Locale(lang_code) + + LANGUAGES[lang_code] = lang + +log.info( + "Languages loaded: {}".format( + [ + language["language_info"]["babel"].display_name + for language in LANGUAGES.values() + ] + ) +) + + +async def get_chat_lang(chat_id): + r = redis.get("lang_cache_{}".format(chat_id)) + if r: + return r + else: + db_lang = await db.lang.find_one({"chat_id": chat_id}) + if db_lang: + # Rebuild lang cache + redis.set("lang_cache_{}".format(chat_id), db_lang["lang"]) + return db_lang["lang"] + user_lang = await db.user_list.find_one({"user_id": chat_id}) + if user_lang and user_lang["user_lang"] in LANGUAGES: + # Add telegram language in lang cache + redis.set("lang_cache_{}".format(chat_id), user_lang["user_lang"]) + return user_lang["user_lang"] + else: + return "en" + + +async def change_chat_lang(chat_id, lang): + redis.set("lang_cache_{}".format(chat_id), lang) + await db.lang.update_one( + {"chat_id": chat_id}, {"$set": {"chat_id": chat_id, "lang": lang}}, upsert=True + ) + + +async def get_strings(chat_id, module, mas_name="STRINGS"): + chat_lang = await get_chat_lang(chat_id) + if chat_lang not in LANGUAGES: + await change_chat_lang(chat_id, "en") + + class Strings: + @staticmethod + def get_strings(lang, mas_name, module): + + if ( + mas_name not in LANGUAGES[lang] + or module not in LANGUAGES[lang][mas_name] + ): + return {} + + data = LANGUAGES[lang][mas_name][module] + + if mas_name == "STRINGS": + data["language_info"] = LANGUAGES[chat_lang]["language_info"] + return data + + def get_string(self, name): + data = self.get_strings(chat_lang, mas_name, module) + if name not in data: + data = self.get_strings("en", mas_name, module) + + return data[name] + + def __getitem__(self, key): + return self.get_string(key) + + return Strings() + + +async def get_string(chat_id, module, name, mas_name="STRINGS"): + strings = await get_strings(chat_id, module, mas_name=mas_name) + return strings[name] + + +def get_strings_dec(module, mas_name="STRINGS"): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + if hasattr(message, "chat"): + chat_id = message.chat.id + elif hasattr(message, "message"): + chat_id = message.message.chat.id + else: + chat_id = None + + strings = await get_strings(chat_id, module, mas_name=mas_name) + return await func(*args, strings, **kwargs) + + return wrapped_1 + + return wrapped + + +async def get_chat_lang_info(chat_id): + chat_lang = await get_chat_lang(chat_id) + return LANGUAGES[chat_lang]["language_info"] diff --git a/DaisyX/modules/utils/message.py b/DaisyX/modules/utils/message.py new file mode 100644 index 00000000..3a2d5807 --- /dev/null +++ b/DaisyX/modules/utils/message.py @@ -0,0 +1,91 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from datetime import timedelta + +# elif raw_button[1] == 'note': +# t = InlineKeyboardButton(raw_button[0], callback_data='get_note_{}_{}'.format(chat_id, raw_button[2])) +# elif raw_button[1] == 'alert': +# t = InlineKeyboardButton(raw_button[0], callback_data='get_alert_{}_{}'.format(chat_id, raw_button[2])) +# elif raw_button[1] == 'deletemsg': +# t = InlineKeyboardButton(raw_button[0], callback_data='get_delete_msg_{}_{}'.format(chat_id, raw_button[2])) + + +class InvalidTimeUnit(Exception): + pass + + +def get_arg(message): + try: + return message.get_args().split()[0] + except IndexError: + return "" + + +def get_args(message): + args = message.get_args().split() + if args is None: + # getting args from non-command + args = message.text.split() + return args + + +def get_args_str(message): + return " ".join(get_args(message)) + + +def get_cmd(message): + cmd = message.get_command().lower()[1:].split("@")[0] + return cmd + + +def convert_time(time_val): + if not any(time_val.endswith(unit) for unit in ("m", "h", "d")): + raise TypeError + + time_num = int(time_val[:-1]) + unit = time_val[-1] + kwargs = {} + + if unit == "m": + kwargs["minutes"] = time_num + elif unit == "h": + kwargs["hours"] = time_num + elif unit == "d": + kwargs["days"] = time_num + else: + raise InvalidTimeUnit() + + val = timedelta(**kwargs) + + return val + + +def convert_timedelta(time): + return {"days": time.days, "seconds": time.seconds} + + +def need_args_dec(num=1): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + if len(message.text.split(" ")) > num: + return await func(*args, **kwargs) + else: + await message.reply("Give me args!") + + return wrapped_1 + + return wrapped diff --git a/DaisyX/modules/utils/notes.py b/DaisyX/modules/utils/notes.py new file mode 100644 index 00000000..67f1d5e3 --- /dev/null +++ b/DaisyX/modules/utils/notes.py @@ -0,0 +1,503 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2019 Aiogram +# +# This file is part of AllMightBot. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import html +import re +import sys +from datetime import datetime + +from aiogram.types import Message +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils import markdown +from babel.dates import format_date, format_datetime, format_time +from telethon.errors import ( + BadRequestError, + ButtonUrlInvalidError, + MediaEmptyError, + MessageEmptyError, + RPCError, +) +from telethon.errors.rpcerrorlist import ChatWriteForbiddenError +from telethon.tl.custom import Button + +import DaisyX.modules.utils.tmarkdown as tmarkdown +from DaisyX import BOT_USERNAME +from DaisyX.services.telethon import tbot + +from ...utils.logger import log +from .language import get_chat_lang +from .message import get_args +from .tmarkdown import tbold, tcode, titalic, tlink, tpre, tstrikethrough, tunderline +from .user_details import get_user_link + +BUTTONS = {} + +ALLOWED_COLUMNS = ["parse_mode", "file", "text", "preview"] + + +def tparse_ent(ent, text, as_html=True): + if not text: + return text + + etype = ent.type + offset = ent.offset + length = ent.length + + if sys.maxunicode == 0xFFFF: + return text[offset : offset + length] + + if not isinstance(text, bytes): + entity_text = text.encode("utf-16-le") + else: + entity_text = text + + entity_text = entity_text[offset * 2 : (offset + length) * 2].decode("utf-16-le") + + if etype == "bold": + method = markdown.hbold if as_html else tbold + return method(entity_text) + if etype == "italic": + method = markdown.hitalic if as_html else titalic + return method(entity_text) + if etype == "pre": + method = markdown.hpre if as_html else tpre + return method(entity_text) + if etype == "code": + method = markdown.hcode if as_html else tcode + return method(entity_text) + if etype == "strikethrough": + method = markdown.hstrikethrough if as_html else tstrikethrough + return method(entity_text) + if etype == "underline": + method = markdown.hunderline if as_html else tunderline + return method(entity_text) + if etype == "url": + return entity_text + if etype == "text_link": + method = markdown.hlink if as_html else tlink + return method(entity_text, ent.url) + if etype == "text_mention" and ent.user: + return ent.user.get_mention(entity_text, as_html=as_html) + + return entity_text + + +def get_parsed_msg(message): + if not message.text and not message.caption: + return "", "md" + + text = message.caption or message.text + + mode = get_msg_parse(text) + if mode == "html": + as_html = True + else: + as_html = False + + entities = message.caption_entities or message.entities + + if not entities: + return text, mode + + if not sys.maxunicode == 0xFFFF: + text = text.encode("utf-16-le") + + result = "" + offset = 0 + + for entity in sorted(entities, key=lambda item: item.offset): + entity_text = tparse_ent(entity, text, as_html=as_html) + + if sys.maxunicode == 0xFFFF: + part = text[offset : entity.offset] + result += part + entity_text + else: + part = text[offset * 2 : entity.offset * 2].decode("utf-16-le") + result += part + entity_text + + offset = entity.offset + entity.length + + if sys.maxunicode == 0xFFFF: + result += text[offset:] + else: + result += text[offset * 2 :].decode("utf-16-le") + + result = re.sub(r"\[format:(\w+)\]", "", result) + result = re.sub(r"%PARSEMODE_(\w+)", "", result) + + if not result: + result = "" + + return result, mode + + +def get_msg_parse(text, default_md=True): + if "[format:html]" in text or "%PARSEMODE_HTML" in text: + return "html" + elif "[format:none]" in text or "%PARSEMODE_NONE" in text: + return "none" + elif "[format:md]" in text or "%PARSEMODE_MD" in text: + return "md" + else: + if not default_md: + return None + return "md" + + +def parse_button(data, name): + raw_button = data.split("_") + raw_btn_type = raw_button[0] + + pattern = re.match(r"btn(.+)(sm|cb|start)", raw_btn_type) + if not pattern: + return "" + + action = pattern.group(1) + args = raw_button[1] + + if action in BUTTONS: + text = f"\n[{name}](btn{action}:{args}*!repl!*)" + else: + if args: + text = f"\n[{name}].(btn{action}:{args})" + else: + text = f"\n[{name}].(btn{action})" + + return text + + +def get_reply_msg_btns_text(message): + text = "" + for column in message.reply_markup.inline_keyboard: + btn_num = 0 + for btn in column: + btn_num += 1 + name = btn["text"] + + if "url" in btn: + url = btn["url"] + if "?start=" in url: + raw_btn = url.split("?start=")[1] + text += parse_button(raw_btn, name) + else: + text += f"\n[{btn['text']}](btnurl:{btn['url']}*!repl!*)" + elif "callback_data" in btn: + text += parse_button(btn["callback_data"], name) + + if btn_num > 1: + text = text.replace("*!repl!*", ":same") + else: + text = text.replace("*!repl!*", "") + return text + + +async def get_msg_file(message): + message_id = message.message_id + + tmsg = await tbot.get_messages(message.chat.id, ids=message_id) + + file_types = [ + "sticker", + "photo", + "document", + "video", + "audio", + "video_note", + "voice", + ] + for file_type in file_types: + if file_type not in message: + continue + return {"id": tmsg.file.id, "type": file_type} + return None + + +async def get_parsed_note_list(message, allow_reply_message=True, split_args=1): + note = {} + if "reply_to_message" in message and allow_reply_message: + # Get parsed reply msg text + text, note["parse_mode"] = get_parsed_msg(message.reply_to_message) + # Get parsed origin msg text + text += " " + to_split = "".join([" " + q for q in get_args(message)[:split_args]]) + if not to_split: + to_split = " " + text += get_parsed_msg(message)[0].partition(message.get_command() + to_split)[ + 2 + ][1:] + # Set parse_mode if origin msg override it + if mode := get_msg_parse(message.text, default_md=False): + note["parse_mode"] = mode + + # Get message keyboard + if ( + "reply_markup" in message.reply_to_message + and "inline_keyboard" in message.reply_to_message.reply_markup + ): + text += get_reply_msg_btns_text(message.reply_to_message) + + # Check on attachment + if msg_file := await get_msg_file(message.reply_to_message): + note["file"] = msg_file + else: + text, note["parse_mode"] = get_parsed_msg(message) + if message.get_command() and message.get_args(): + # Remove cmd and arg from message's text + text = re.sub(message.get_command() + r"\s?", "", text, 1) + if split_args > 0: + text = re.sub(re.escape(get_args(message)[0]) + r"\s?", "", text, 1) + # Check on attachment + if msg_file := await get_msg_file(message): + note["file"] = msg_file + + if text.replace(" ", ""): + note["text"] = text + + # Preview + if "text" in note and re.search(r"[$|%]PREVIEW", note["text"]): + note["text"] = re.sub(r"[$|%]PREVIEW", "", note["text"]) + note["preview"] = True + + return note + + +async def t_unparse_note_item( + message, db_item, chat_id, noformat=None, event=None, user=None +): + text = db_item["text"] if "text" in db_item else "" + + file_id = None + preview = None + + if not user: + user = message.from_user + + if "file" in db_item: + file_id = db_item["file"]["id"] + + if noformat: + markup = None + if "parse_mode" not in db_item or db_item["parse_mode"] == "none": + text += "\n%PARSEMODE_NONE" + elif db_item["parse_mode"] == "html": + text += "\n%PARSEMODE_HTML" + + if "preview" in db_item and db_item["preview"]: + text += "\n%PREVIEW" + + db_item["parse_mode"] = None + + else: + pm = True if message.chat.type == "private" else False + text, markup = button_parser(chat_id, text, pm=pm) + + if not text and not file_id: + text = ("#" + db_item["names"][0]) if "names" in db_item else "404" + + if "parse_mode" not in db_item or db_item["parse_mode"] == "none": + db_item["parse_mode"] = None + elif db_item["parse_mode"] == "md": + text = await vars_parser( + text, message, chat_id, md=True, event=event, user=user + ) + elif db_item["parse_mode"] == "html": + text = await vars_parser( + text, message, chat_id, md=False, event=event, user=user + ) + + if "preview" in db_item and db_item["preview"]: + preview = True + + return text, { + "buttons": markup, + "parse_mode": db_item["parse_mode"], + "file": file_id, + "link_preview": preview, + } + + +async def send_note(send_id, text, **kwargs): + if text: + text = text[:4090] + + if "parse_mode" in kwargs and kwargs["parse_mode"] == "md": + kwargs["parse_mode"] = tmarkdown + + try: + return await tbot.send_message(send_id, text, **kwargs) + + except (ButtonUrlInvalidError, MessageEmptyError, MediaEmptyError): + return await tbot.send_message( + send_id, "I found this note invalid! Please update it (read help)." + ) + except RPCError: + log.error("Send Note Error bot is Kicked/Muted in chat [IGNORE]") + return + except ChatWriteForbiddenError: + log.error("Send Note Error bot is Kicked/Muted in chat [IGNORE]") + return + except BadRequestError: # if reply message deleted + del kwargs["reply_to"] + return await tbot.send_message(send_id, text, **kwargs) + except Exception as err: + log.error("Something happened on sending note", exc_info=err) + + +def button_parser(chat_id, texts, pm=False, aio=False, row_width=None): + buttons = InlineKeyboardMarkup(row_width=row_width) if aio else [] + pattern = r"\[(.+?)\]\((button|btn|#)(.+?)(:.+?|)(:same|)\)(\n|)" + raw_buttons = re.findall(pattern, texts) + text = re.sub(pattern, "", texts) + btn = None + for raw_button in raw_buttons: + name = raw_button[0] + action = ( + raw_button[1] if raw_button[1] not in ("button", "btn") else raw_button[2] + ) + + if raw_button[3]: + argument = raw_button[3][1:].lower().replace("`", "") + elif action in ("#"): + argument = raw_button[2] + print(raw_button[2]) + else: + argument = "" + + if action in BUTTONS.keys(): + cb = BUTTONS[action] + string = f"{cb}_{argument}_{chat_id}" if argument else f"{cb}_{chat_id}" + if aio: + start_btn = InlineKeyboardButton( + name, url=f"https://t.me/{BOT_USERNAME}?start=" + string + ) + cb_btn = InlineKeyboardButton(name, callback_data=string) + else: + start_btn = Button.url( + name, f"https://t.me/{BOT_USERNAME}?start=" + string + ) + cb_btn = Button.inline(name, string) + + if cb.endswith("sm"): + btn = cb_btn if pm else start_btn + elif cb.endswith("cb"): + btn = cb_btn + elif cb.endswith("start"): + btn = start_btn + elif cb.startswith("url"): + # Workaround to make URLs case-sensitive TODO: make better + argument = raw_button[3][1:].replace("`", "") if raw_button[3] else "" + btn = Button.url(name, argument) + elif cb.endswith("rules"): + btn = start_btn + elif action == "url": + argument = raw_button[3][1:].replace("`", "") if raw_button[3] else "" + if argument[0] == "/" and argument[1] == "/": + argument = argument[2:] + btn = ( + InlineKeyboardButton(name, url=argument) + if aio + else Button.url(name, argument) + ) + else: + # If btn not registred + btn = None + if argument: + text += f"\n[{name}].(btn{action}:{argument})" + else: + text += f"\n[{name}].(btn{action})" + continue + + if btn: + if aio: + buttons.insert(btn) if raw_button[4] else buttons.add(btn) + else: + if len(buttons) < 1 and raw_button[4]: + buttons.add(btn) if aio else buttons.append([btn]) + else: + buttons[-1].append(btn) if raw_button[4] else buttons.append([btn]) + + if not aio and len(buttons) == 0: + buttons = None + + if not text or text.isspace(): # TODO: Sometimes we can return text == ' ' + text = None + + return text, buttons + + +async def vars_parser( + text, message, chat_id, md=False, event: Message = None, user=None +): + if event is None: + event = message + + if not text: + return text + + language_code = await get_chat_lang(chat_id) + current_datetime = datetime.now() + + first_name = html.escape(user.first_name, quote=False) + last_name = html.escape(user.last_name or "", quote=False) + user_id = ( + [user.id for user in event.new_chat_members][0] + if "new_chat_members" in event and event.new_chat_members != [] + else user.id + ) + mention = await get_user_link(user_id, md=md) + + if ( + hasattr(event, "new_chat_members") + and event.new_chat_members + and event.new_chat_members[0].username + ): + username = "@" + event.new_chat_members[0].username + elif user.username: + username = "@" + user.username + else: + username = mention + + chat_id = message.chat.id + chat_name = html.escape(message.chat.title or "Local", quote=False) + chat_nick = message.chat.username or chat_name + + current_date = html.escape( + format_date(date=current_datetime, locale=language_code), quote=False + ) + current_time = html.escape( + format_time(time=current_datetime, locale=language_code), quote=False + ) + current_timedate = html.escape( + format_datetime(datetime=current_datetime, locale=language_code), quote=False + ) + + text = ( + text.replace("{first}", first_name) + .replace("{last}", last_name) + .replace("{fullname}", first_name + " " + last_name) + .replace("{id}", str(user_id).replace("{userid}", str(user_id))) + .replace("{mention}", mention) + .replace("{username}", username) + .replace("{chatid}", str(chat_id)) + .replace("{chatname}", str(chat_name)) + .replace("{chatnick}", str(chat_nick)) + .replace("{date}", str(current_date)) + .replace("{time}", str(current_time)) + .replace("{timedate}", str(current_timedate)) + ) + return text diff --git a/DaisyX/modules/utils/restrictions.py b/DaisyX/modules/utils/restrictions.py new file mode 100644 index 00000000..8597e624 --- /dev/null +++ b/DaisyX/modules/utils/restrictions.py @@ -0,0 +1,77 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from aiogram.types.chat_permissions import ChatPermissions +from aiogram.utils.exceptions import BadRequest, MigrateToChat, Unauthorized + +from DaisyX import bot + + +async def ban_user(chat_id, user_id, until_date=None): + try: + await bot.kick_chat_member(chat_id, user_id, until_date=until_date) + except (BadRequest, MigrateToChat, Unauthorized): + return False + return True + + +async def kick_user(chat_id, user_id): + await bot.unban_chat_member(chat_id, user_id) + return True + + +async def mute_user(chat_id, user_id, until_date=None): + await bot.restrict_chat_member( + chat_id, + user_id, + permissions=ChatPermissions(can_send_messages=False, until_date=until_date), + until_date=until_date, + ) + return True + + +async def restrict_user(chat_id, user_id, until_date=None): + await bot.restrict_chat_member( + chat_id, + user_id, + permissions=ChatPermissions( + can_send_messages=True, + can_send_media_messages=False, + can_send_other_messages=False, + can_add_web_page_previews=False, + until_date=until_date, + ), + until_date=until_date, + ) + return True + + +async def unmute_user(chat_id, user_id): + await bot.restrict_chat_member( + chat_id, + user_id, + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True, + can_add_web_page_previews=True, + ) + return True + + +async def unban_user(chat_id, user_id): + try: + return await bot.unban_chat_member(chat_id, user_id, only_if_banned=True) + except (BadRequest, Unauthorized): + return False diff --git a/DaisyX/modules/utils/term.py b/DaisyX/modules/utils/term.py new file mode 100644 index 00000000..ecd01f1d --- /dev/null +++ b/DaisyX/modules/utils/term.py @@ -0,0 +1,44 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import subprocess + +from DaisyX.services.telethon import tbot + + +async def chat_term(message, command): + result = await term(command) + if len(result) > 4096: + output = open("output.txt", "w+") + output.write(result) + output.close() + await tbot.send_file( + message.chat.id, + "output.txt", + reply_to=message["message_id"], + caption="`Output too large, sending as file`", + ) + subprocess.run(["rm", "output.txt"], stdout=subprocess.PIPE) + return result + + +async def term(command): + process = await asyncio.create_subprocess_shell( + command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + result = str(stdout.decode().strip()) + str(stderr.decode().strip()) + return result diff --git a/DaisyX/modules/utils/text.py b/DaisyX/modules/utils/text.py new file mode 100644 index 00000000..c21969c3 --- /dev/null +++ b/DaisyX/modules/utils/text.py @@ -0,0 +1,154 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This file is part of Daisy. + +from typing import Union + + +class SanTeXDoc: + def __init__(self, *args): + self.items = list(args) + + def __str__(self) -> str: + return "\n".join([str(items) for items in self.items]) + + def __add__(self, other): + self.items.append(other) + return self + + +class StyleFormationCore: + start: str + end: str + + def __init__(self, text: str): + self.text = f"{self.start}{text}{self.end}" + + def __str__(self) -> str: + return self.text + + +class Bold(StyleFormationCore): + start = "" + end = "" + + +class Italic(StyleFormationCore): + start = "" + end = "" + + +class Code(StyleFormationCore): + start = "" + end = "" + + +class Pre(StyleFormationCore): + start = "
"
+    end = "
" + + +class Strikethrough(StyleFormationCore): + start = "" + end = "" + + +class Underline(StyleFormationCore): + start = "" + end = "" + + +class Section: + def __init__(self, *args, title="", indent=3, bold=True, postfix=":"): + self.title_text = title + self.items = list(args) + self.indent = indent + self.bold = bold + self.postfix = postfix + + @property + def title(self) -> str: + title = self.title_text + text = str(Bold(title)) if self.bold else title + text += self.postfix + return text + + def __str__(self) -> str: + text = self.title + space = " " * self.indent + for item in self.items: + text += "\n" + + if type(item) is Section: + item.indent *= 2 + if type(item) is SList: + item.indent = self.indent + else: + text += space + + text += str(item) + + return text + + def __add__(self, other): + self.items.append(other) + return self + + +class SList: + def __init__(self, *args, indent=0, prefix="- "): + self.items = list(args) + self.prefix = prefix + self.indent = indent + + def __str__(self) -> str: + space = " " * self.indent if self.indent else " " + text = "" + for idx, item in enumerate(self.items): + if idx > 0: + text += "\n" + text += f"{space}{self.prefix}{item}" + + return text + + +class KeyValue: + def __init__(self, title, value, suffix=": "): + self.title = title + self.value = value + self.suffix = suffix + + def __str__(self) -> str: + text = f"{self.title}{self.suffix}{self.value}" + return text + + +class MultiKeyValue: + def __init__(self, *items: Union[list, tuple], suffix=": ", separator=", "): + self.items: list = items + self.suffix = suffix + self.separator = separator + + def __str__(self) -> str: + text = "" + items_count = len(self.items) + for idx, item in enumerate(self.items): + text += f"{item[0]}{self.suffix}{item[1]}" + + if items_count - 1 != idx: + text += self.separator + + return text diff --git a/DaisyX/modules/utils/tmarkdown.py b/DaisyX/modules/utils/tmarkdown.py new file mode 100644 index 00000000..77ba8167 --- /dev/null +++ b/DaisyX/modules/utils/tmarkdown.py @@ -0,0 +1,248 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import re +import warnings + +from telethon.helpers import add_surrogate, del_surrogate, strip_text +from telethon.tl import TLObject +from telethon.tl.types import ( + MessageEntityBold, + MessageEntityCode, + MessageEntityItalic, + MessageEntityMentionName, + MessageEntityPre, + MessageEntityStrike, + MessageEntityTextUrl, + MessageEntityUnderline, +) + +DEFAULT_DELIMITERS = { + "**": MessageEntityBold, + "__": MessageEntityItalic, + "~~": MessageEntityStrike, + "++": MessageEntityUnderline, + "`": MessageEntityCode, + "```": MessageEntityPre, +} + +DEFAULT_URL_RE = re.compile(r"\[([\S\s]+?)\]\((.+?)\)") +DEFAULT_URL_FORMAT = "[{0}]({1})" + + +def overlap(a, b, x, y): + return max(a, x) < min(b, y) + + +def parse(message, delimiters=None, url_re=None): + """ + Parses the given markdown message and returns its stripped representation + plus a list of the MessageEntity's that were found. + :param message: the message with markdown-like syntax to be parsed. + :param delimiters: the delimiters to be used, {delimiter: type}. + :param url_re: the URL bytes regex to be used. Must have two groups. + :return: a tuple consisting of (clean message, [message entities]). + """ + if not message: + return message, [] + + if url_re is None: + url_re = DEFAULT_URL_RE + elif isinstance(url_re, str): + url_re = re.compile(url_re) + + if not delimiters: + if delimiters is not None: + return message, [] + delimiters = DEFAULT_DELIMITERS + + # Build a regex to efficiently test all delimiters at once. + # Note that the largest delimiter should go first, we don't + # want ``` to be interpreted as a single back-tick in a code block. + delim_re = re.compile( + "|".join( + "({})".format(re.escape(k)) + for k in sorted(delimiters, key=len, reverse=True) + ) + ) + + # Cannot use a for loop because we need to skip some indices + i = 0 + result = [] + + # Work on byte level with the utf-16le encoding to get the offsets right. + # The offset will just be half the index we're at. + message = add_surrogate(message) + while i < len(message): + m = delim_re.match(message, pos=i) + + # Did we find some delimiter here at `i`? + if m: + delim = next(filter(None, m.groups())) + + # +1 to avoid matching right after (e.g. "****") + end = message.find(delim, i + len(delim) + 1) + + # Did we find the earliest closing tag? + if end != -1: + + # Remove the delimiter from the string + message = "".join( + ( + message[:i], + message[i + len(delim) : end], + message[end + len(delim) :], + ) + ) + + # Check other affected entities + for ent in result: + # If the end is after our start, it is affected + if ent.offset + ent.length > i: + # If the old start is also before ours, it is fully enclosed + if ent.offset <= i: + ent.length -= len(delim) * 2 + else: + ent.length -= len(delim) + + # Append the found entity + ent = delimiters[delim] + if ent == MessageEntityPre: + result.append(ent(i, end - i - len(delim), "")) # has 'lang' + else: + result.append(ent(i, end - i - len(delim))) + + # No nested entities inside code blocks + if ent in (MessageEntityCode, MessageEntityPre): + i = end - len(delim) + + continue + + elif url_re: + m = url_re.match(message, pos=i) + if m: + # Replace the whole match with only the inline URL text. + message = "".join( + (message[: m.start()], m.group(1), message[m.end() :]) + ) + + delim_size = m.end() - m.start() - len(m.group()) + for ent in result: + # If the end is after our start, it is affected + if ent.offset + ent.length > m.start(): + ent.length -= delim_size + + result.append( + MessageEntityTextUrl( + offset=m.start(), + length=len(m.group(1)), + url=del_surrogate(m.group(2)), + ) + ) + i += len(m.group(1)) + continue + + i += 1 + + message = strip_text(message, result) + return del_surrogate(message), result + + +def unparse(text, entities, delimiters=None, url_fmt=None): + """ + Performs the reverse operation to .parse(), effectively returning + markdown-like syntax given a normal text and its MessageEntity's. + :param text: the text to be reconverted into markdown. + :param entities: the MessageEntity's applied to the text. + :return: a markdown-like text representing the combination of both inputs. + """ + if not text or not entities: + return text + + if not delimiters: + if delimiters is not None: + return text + delimiters = DEFAULT_DELIMITERS + + if url_fmt is not None: + warnings.warn( + "url_fmt is deprecated" + ) # since it complicates everything *a lot* + + if isinstance(entities, TLObject): + entities = (entities,) + + text = add_surrogate(text) + delimiters = {v: k for k, v in delimiters.items()} + insert_at = [] + for entity in entities: + s = entity.offset + e = entity.offset + entity.length + delimiter = delimiters.get(type(entity), None) + if delimiter: + insert_at.append((s, delimiter)) + insert_at.append((e, delimiter)) + else: + url = None + if isinstance(entity, MessageEntityTextUrl): + url = entity.url + elif isinstance(entity, MessageEntityMentionName): + url = "tg://user?id={}".format(entity.user_id) + if url: + insert_at.append((s, "[")) + insert_at.append((e, "]({})".format(url))) + + insert_at.sort(key=lambda t: t[0]) + while insert_at: + at, what = insert_at.pop() + + # If we are in the middle of a surrogate nudge the position by +1. + # Otherwise we would end up with malformed text and fail to encode. + # For example of bad input: "Hi \ud83d\ude1c" + # https://en.wikipedia.org/wiki/UTF-16#U+010000_to_U+10FFFF + while at < len(text) and "\ud800" <= text[at] <= "\udfff": + at += 1 + + text = text[:at] + what + text[at:] + + return del_surrogate(text) + + +def tbold(text, sep=" "): + return f"**{text}**" + + +def titalic(text, sep=" "): + return f"__{text}__" + + +def tcode(text, sep=" "): + return f"`{text}`" + + +def tpre(text, sep=" "): + return f"```{text}```" + + +def tstrikethrough(text, sep=" "): + return f"~~{text}~~" + + +def tunderline(text, sep=" "): + return f"++{text}++" + + +def tlink(title, url): + return "[{0}]({1})".format(title, url) diff --git a/DaisyX/modules/utils/user_details.py b/DaisyX/modules/utils/user_details.py new file mode 100644 index 00000000..bc62c44e --- /dev/null +++ b/DaisyX/modules/utils/user_details.py @@ -0,0 +1,444 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import pickle +import re +from contextlib import suppress +from typing import Union + +from aiogram.dispatcher.handler import SkipHandler +from aiogram.types import CallbackQuery, Message +from aiogram.utils.exceptions import BadRequest, ChatNotFound, Unauthorized +from telethon.tl.functions.users import GetFullUserRequest + +from DaisyX import OPERATORS, bot +from DaisyX.services.mongo import db +from DaisyX.services.redis import bredis +from DaisyX.services.telethon import tbot + +from .language import get_string +from .message import get_arg + + +async def add_user_to_db(user): + if hasattr(user, "user"): + user = user.user + + new_user = { + "user_id": user.id, + "first_name": user.first_name, + "last_name": user.last_name, + "username": user.username, + } + + user = await db.user_list.find_one({"user_id": new_user["user_id"]}) + if not user or user is None: + user = new_user + + if "chats" not in user: + new_user["chats"] = [] + if "user_lang" not in user: + new_user["user_lang"] = "en" + if hasattr(user, "user_lang"): + new_user["user_lang"] = user.user_lang + + await db.user_list.update_one( + {"user_id": user["user_id"]}, {"$set": new_user}, upsert=True + ) + + return new_user + + +async def get_user_by_id(user_id: int): + if not user_id <= 2147483647: + return None + + user = await db.user_list.find_one({"user_id": user_id}) + if not user: + try: + user = await add_user_to_db(await tbot(GetFullUserRequest(user_id))) + except (ValueError, TypeError): + user = None + + return user + + +async def get_id_by_nick(data): + # Check if data is user_id + user = await db.user_list.find_one({"username": data.replace("@", "")}) + if user: + return user["user_id"] + + user = await tbot(GetFullUserRequest(data)) + return user + + +async def get_user_by_username(username): + # Search username in database + if "@" in username: + # Remove '@' + username = username[1:] + + user = await db.user_list.find_one({"username": username.lower()}) + + # Ohnu, we don't have this user in DB + if not user: + try: + user = await add_user_to_db(await tbot(GetFullUserRequest(username))) + except (ValueError, TypeError): + user = None + + return user + + +async def get_user_link(user_id, custom_name=None, md=False): + user = await db.user_list.find_one({"user_id": user_id}) + + if user: + user_name = user["first_name"] + else: + try: + user = await add_user_to_db(await tbot(GetFullUserRequest(int(user_id)))) + except (ValueError, TypeError): + user_name = str(user_id) + else: + user_name = user["first_name"] + + if custom_name: + user_name = custom_name + + if md: + return "[{name}](tg://user?id={id})".format(name=user_name, id=user_id) + else: + return '{name}'.format( + name=user_name, id=user_id + ) + + +async def get_admins_rights(chat_id, force_update=False): + key = "admin_cache:" + str(chat_id) + if (alist := bredis.get(key)) and not force_update: + return pickle.loads(alist) + else: + alist = {} + admins = await bot.get_chat_administrators(chat_id) + for admin in admins: + user_id = admin["user"]["id"] + alist[user_id] = { + "status": admin["status"], + "admin": True, + "title": admin["custom_title"], + "anonymous": admin["is_anonymous"], + "can_change_info": admin["can_change_info"], + "can_delete_messages": admin["can_delete_messages"], + "can_invite_users": admin["can_invite_users"], + "can_restrict_members": admin["can_restrict_members"], + "can_pin_messages": admin["can_pin_messages"], + "can_promote_members": admin["can_promote_members"], + } + + with suppress(KeyError): # Optional permissions + alist[user_id]["can_post_messages"] = admin["can_post_messages"] + + bredis.set(key, pickle.dumps(alist)) + bredis.expire(key, 900) + return alist + + +async def is_user_admin(chat_id, user_id): + # User's pm should have admin rights + if chat_id == user_id: + return True + + if user_id in OPERATORS: + return True + + # Workaround to support anonymous admins + if user_id == 1087968824: + return True + + try: + admins = await get_admins_rights(chat_id) + except BadRequest: + return False + else: + if user_id in admins: + return True + else: + return False + + +async def check_admin_rights( + event: Union[Message, CallbackQuery], chat_id, user_id, rights +): + # User's pm should have admin rights + if chat_id == user_id: + return True + + if user_id in OPERATORS: + return True + + # Workaround to support anonymous admins + if user_id == 1087968824: + if not isinstance(event, Message): + raise ValueError( + f"Cannot extract signuature of anonymous admin from {type(event)}" + ) + + if not event.author_signature: + return True + + for admin in (await get_admins_rights(chat_id)).values(): + if "title" in admin and admin["title"] == event.author_signature: + for permission in rights: + if not admin[permission]: + return permission + return True + + admin_rights = await get_admins_rights(chat_id) + if user_id not in admin_rights: + return False + + if admin_rights[user_id]["status"] == "creator": + return True + + for permission in rights: + if not admin_rights[user_id][permission]: + return permission + + return True + + +async def check_group_admin(event, user_id, no_msg=False): + if hasattr(event, "chat_id"): + chat_id = event.chat_id + elif hasattr(event, "chat"): + chat_id = event.chat.id + if await is_user_admin(chat_id, user_id) is True: + return True + else: + if no_msg is False: + await event.reply("You should be a admin to do it!") + return False + + +async def is_chat_creator(event: Union[Message, CallbackQuery], chat_id, user_id): + admin_rights = await get_admins_rights(chat_id) + + if user_id == 1087968824: + _co, possible_creator = 0, None + for admin in admin_rights.values(): + if admin["title"] == event.author_signature: + _co += 1 + possible_creator = admin + + if _co > 1: + await event.answer( + await get_string(chat_id, "global", "unable_identify_creator") + ) + raise SkipHandler + + if possible_creator["status"] == "creator": + return True + return False + + if user_id not in admin_rights: + return False + + if admin_rights[user_id]["status"] == "creator": + return True + + return False + + +async def get_user_by_text(message, text: str): + # Get all entities + entities = filter( + lambda ent: ent["type"] == "text_mention" or ent["type"] == "mention", + message.entities, + ) + for entity in entities: + # If username matches entity's text + if text in entity.get_text(message.text): + if entity.type == "mention": + # This one entity is comes with mention by username, like @rDaisyBot + return await get_user_by_username(text) + elif entity.type == "text_mention": + # This one is link mention, mostly used for users without an username + return await get_user_by_id(entity.user.id) + + # Now let's try get user with user_id + # We trying this not first because user link mention also can have numbers + if text.isdigit(): + user_id = int(text) + if user := await get_user_by_id(user_id): + return user + + # Not found anything 😞 + return None + + +async def get_user(message, allow_self=False): + args = message.text.split(None, 2) + user = None + + # Only 1 way + if len(args) < 2 and "reply_to_message" in message: + return await get_user_by_id(message.reply_to_message.from_user.id) + + # Use default function to get user + if len(args) > 1: + user = await get_user_by_text(message, args[1]) + + if not user and bool(message.reply_to_message): + user = await get_user_by_id(message.reply_to_message.from_user.id) + + if not user and allow_self: + # TODO: Fetch user from message instead of db?! less overhead + return await get_user_by_id(message.from_user.id) + + # No args and no way to get user + if not user and len(args) < 2: + return None + + return user + + +async def get_user_and_text(message, **kwargs): + args = message.text.split(" ", 2) + user = await get_user(message, **kwargs) + + if len(args) > 1: + if (test_user := await get_user_by_text(message, args[1])) == user: + if test_user: + print(len(args)) + if len(args) > 2: + return user, args[2] + else: + return user, "" + + if len(args) > 1: + return user, message.text.split(" ", 1)[1] + else: + return user, "" + + +async def get_users(message): + args = message.text.split(None, 2) + text = args[1] + users = [] + + for text in text.split("|"): + if user := await get_user_by_text(message, text): + users.append(user) + + return users + + +async def get_users_and_text(message): + users = await get_users(message) + args = message.text.split(None, 2) + + if len(args) > 1: + return users, args[1] + else: + return users, "" + + +def get_user_and_text_dec(**dec_kwargs): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + if hasattr(message, "message"): + message = message.message + + user, text = await get_user_and_text(message, **dec_kwargs) + if not user: + await message.reply("I can't get the user!") + return + else: + return await func(*args, user, text, **kwargs) + + return wrapped_1 + + return wrapped + + +def get_user_dec(**dec_kwargs): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + if hasattr(message, "message"): + message = message.message + + user, text = await get_user_and_text(message, **dec_kwargs) + if not bool(user): + await message.reply("I can't get the user!") + return + else: + return await func(*args, user, **kwargs) + + return wrapped_1 + + return wrapped + + +def get_chat_dec(allow_self=False, fed=False): + def wrapped(func): + async def wrapped_1(*args, **kwargs): + message = args[0] + if hasattr(message, "message"): + message = message.message + + arg = get_arg(message) + if fed is True: + if len(text := message.get_args().split()) > 1: + if text[0].count("-") == 4: + arg = text[1] + else: + arg = text[0] + + if arg.startswith("-") or arg.isdigit(): + chat = await db.chat_list.find_one({"chat_id": int(arg)}) + if not chat: + try: + chat = await bot.get_chat(arg) + except ChatNotFound: + return await message.reply( + "I couldn't find the chat/channel! Maybe I am not there!" + ) + except Unauthorized: + return await message.reply( + "I couldn't access chat/channel! Maybe I was kicked from there!" + ) + elif arg.startswith("@"): + chat = await db.chat_list.find_one( + {"chat_nick": re.compile(arg.strip("@"), re.IGNORECASE)} + ) + elif allow_self is True: + chat = await db.chat_list.find_one({"chat_id": message.chat.id}) + else: + await message.reply("Please give me valid chat ID/username") + return + + if not chat: + await message.reply("I can't find any chats on given information!") + return + + return await func(*args, chat, **kwargs) + + return wrapped_1 + + return wrapped diff --git a/DaisyX/modules/warns.py b/DaisyX/modules/warns.py new file mode 100644 index 00000000..1260151e --- /dev/null +++ b/DaisyX/modules/warns.py @@ -0,0 +1,412 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2021 TeamDaisyX +# Copyright (C) 2020 Inuka Asith + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import functools +import re +from contextlib import suppress + +from aiogram.types import Message +from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils.deep_linking import get_start_link +from aiogram.utils.exceptions import MessageNotModified +from babel.dates import format_timedelta +from bson.objectid import ObjectId + +from DaisyX import BOT_ID, bot +from DaisyX.decorator import register +from DaisyX.services.mongo import db +from DaisyX.services.telethon import tbot + +from .misc import customise_reason_finish, customise_reason_start +from .utils.connections import chat_connection +from .utils.language import get_strings_dec +from .utils.message import InvalidTimeUnit, convert_time +from .utils.restrictions import ban_user, mute_user +from .utils.user_details import ( + get_user_and_text_dec, + get_user_dec, + get_user_link, + is_user_admin, +) + + +@register(cmds="warn", user_can_restrict_members=True, bot_can_restrict_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_and_text_dec() +async def warn_cmd(message, chat, user, text): + await warn_func(message, chat, user, text) + + +@register(cmds="dwarn", user_can_restrict_members=True, bot_can_restrict_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_and_text_dec() +async def warn_cmd(message, chat, user, text): + if not message.reply_to_message: + await message.reply(strings["reply_to_msg"]) + return + await warn_func(message, chat, user, text) + msgs = [message.message_id, message.reply_to_message.message_id] + await tbot.delete_messages(message.chat.id, msgs) + + +@get_strings_dec("warns") +async def warn_func(message: Message, chat, user, text, strings, filter_action=False): + chat_id = chat["chat_id"] + chat_title = chat["chat_title"] + by_id = BOT_ID if filter_action is True else message.from_user.id + user_id = user["user_id"] if filter_action is False else user + + if user_id == BOT_ID: + await message.reply(strings["warn_sofi"]) + return + + if not filter_action: + if user_id == message.from_user.id: + await message.reply(strings["warn_self"]) + return + + if await is_user_admin(chat_id, user_id): + if not filter_action: + await message.reply(strings["warn_admin"]) + return + + reason = text + warn_id = str( + ( + await db.warns.insert_one( + { + "user_id": user_id, + "chat_id": chat_id, + "reason": str(reason), + "by": by_id, + } + ) + ).inserted_id + ) + + admin = await get_user_link(by_id) + member = await get_user_link(user_id) + text = strings["warn"].format(admin=admin, user=member, chat_name=chat_title) + + if reason: + text += strings["warn_rsn"].format(reason=reason) + + warns_count = await db.warns.count_documents( + {"chat_id": chat_id, "user_id": user_id} + ) + + buttons = InlineKeyboardMarkup().add( + InlineKeyboardButton( + "⚠️ Remove warn", callback_data="remove_warn_{}".format(warn_id) + ) + ) + + if await db.rules.find_one({"chat_id": chat_id}): + buttons.insert( + InlineKeyboardButton( + "📝 Rules", url=await get_start_link(f"btn_rules_{chat_id}") + ) + ) + + if warn_limit := await db.warnlimit.find_one({"chat_id": chat_id}): + max_warn = int(warn_limit["num"]) + else: + max_warn = 3 + + if filter_action: + action = functools.partial(bot.send_message, chat_id=chat_id) + elif message.reply_to_message: + action = message.reply_to_message.reply + else: + action = functools.partial(message.reply, disable_notification=True) + + if warns_count >= max_warn: + if await max_warn_func(chat_id, user_id): + await db.warns.delete_many({"user_id": user_id, "chat_id": chat_id}) + data = await db.warnmode.find_one({"chat_id": chat_id}) + if data is not None: + if data["mode"] == "tmute": + text = strings["max_warn_exceeded:tmute"].format( + user=member, + time=format_timedelta( + convert_time(data["time"]), + locale=strings["language_info"]["babel"], + ), + ) + else: + text = strings["max_warn_exceeded"].format( + user=member, + action=strings["banned"] + if data["mode"] == "ban" + else strings["muted"], + ) + return await action(text=text) + return await action( + text=strings["max_warn_exceeded"].format( + user=member, action=strings["banned"] + ) + ) + text += strings["warn_num"].format(curr_warns=warns_count, max_warns=max_warn) + return await action(text=text, reply_markup=buttons, disable_web_page_preview=True) + + +@register( + regexp=r"remove_warn_(.*)", + f="cb", + allow_kwargs=True, + user_can_restrict_members=True, +) +@get_strings_dec("warns") +async def rmv_warn_btn(event, strings, regexp=None, **kwargs): + warn_id = ObjectId(re.search(r"remove_warn_(.*)", str(regexp)).group(1)[:-2]) + user_id = event.from_user.id + admin_link = await get_user_link(user_id) + await db.warns.delete_one({"_id": warn_id}) + with suppress(MessageNotModified): + await event.message.edit_text( + strings["warn_btn_rmvl_success"].format(admin=admin_link) + ) + + +@register(cmds="warns") +@chat_connection(admin=True, only_groups=True) +@get_user_dec(allow_self=True) +@get_strings_dec("warns") +async def warns(message, chat, user, strings): + chat_id = chat["chat_id"] + user_id = user["user_id"] + text = strings["warns_header"] + user_link = await get_user_link(user_id) + + count = 0 + async for warn in db.warns.find({"user_id": user_id, "chat_id": chat_id}): + count += 1 + by = await get_user_link(warn["by"]) + rsn = warn["reason"] + reason = f"{rsn}" + if not rsn or rsn == "None": + reason = "No Reason" + text += strings["warns"].format(count=count, reason=reason, admin=by) + + if count == 0: + await message.reply(strings["no_warns"].format(user=user_link)) + return + + await message.reply(text, disable_notification=True) + + +@register(cmds="warnlimit", user_admin=True) +@chat_connection(admin=True, only_groups=True) +@get_strings_dec("warns") +async def warnlimit(message, chat, strings): + chat_id = chat["chat_id"] + chat_title = chat["chat_title"] + arg = message.get_args().split() + + if not arg: + if current_limit := await db.warnlimit.find_one({"chat_id": chat_id}): + num = current_limit["num"] + else: + num = 3 # Default value + await message.reply(strings["warn_limit"].format(chat_name=chat_title, num=num)) + elif not arg[0].isdigit(): + return await message.reply(strings["not_digit"]) + else: + if int(arg[0]) < 2: + return await message.reply(strings["warnlimit_short"]) + + elif int(arg[0]) > 10000: # Max value + return await message.reply(strings["warnlimit_long"]) + + new = {"chat_id": chat_id, "num": int(arg[0])} + + await db.warnlimit.update_one({"chat_id": chat_id}, {"$set": new}, upsert=True) + await message.reply(strings["warnlimit_updated"].format(num=int(arg[0]))) + + +@register(cmds=["resetwarns", "delwarns"], user_can_restrict_members=True) +@chat_connection(admin=True, only_groups=True) +@get_user_dec() +@get_strings_dec("warns") +async def reset_warn(message, chat, user, strings): + chat_id = chat["chat_id"] + chat_title = chat["chat_title"] + user_id = user["user_id"] + user_link = await get_user_link(user_id) + admin_link = await get_user_link(message.from_user.id) + + if user_id == BOT_ID: + await message.reply(strings["rst_wrn_sofi"]) + return + + if await db.warns.find_one({"chat_id": chat_id, "user_id": user_id}): + deleted = await db.warns.delete_many({"chat_id": chat_id, "user_id": user_id}) + purged = deleted.deleted_count + await message.reply( + strings["purged_warns"].format( + admin=admin_link, num=purged, user=user_link, chat_title=chat_title + ) + ) + else: + await message.reply(strings["usr_no_wrn"].format(user=user_link)) + + +@register( + cmds=["warnmode", "warnaction"], user_admin=True, bot_can_restrict_members=True +) +@chat_connection(admin=True) +@get_strings_dec("warns") +async def warnmode(message, chat, strings): + chat_id = chat["chat_id"] + acceptable_args = ["ban", "tmute", "mute"] + arg = str(message.get_args()).split() + new = {"chat_id": chat_id} + + if arg and arg[0] in acceptable_args: + option = "".join(arg[0]) + if ( + data := await db.warnmode.find_one({"chat_id": chat_id}) + ) is not None and data["mode"] == option: + return await message.reply(strings["same_mode"]) + if arg[0] == acceptable_args[0]: + new["mode"] = option + await db.warnmode.update_one( + {"chat_id": chat_id}, {"$set": new}, upsert=True + ) + elif arg[0] == acceptable_args[1]: + try: + time = arg[1] + except IndexError: + return await message.reply(strings["no_time"]) + else: + try: + # TODO: For better UX we have to show until time of tmute when action is done. + # We can't store timedelta class in mongodb; Here we check validity of given time. + convert_time(time) + except (InvalidTimeUnit, TypeError, ValueError): + return await message.reply(strings["invalid_time"]) + else: + new.update(mode=option, time=time) + await db.warnmode.update_one( + {"chat_id": chat_id}, {"$set": new}, upsert=True + ) + elif arg[0] == acceptable_args[2]: + new["mode"] = option + await db.warnmode.update_one( + {"chat_id": chat_id}, {"$set": new}, upsert=True + ) + await message.reply(strings["warnmode_success"] % (chat["chat_title"], option)) + else: + text = "" + if (curr_mode := await db.warnmode.find_one({"chat_id": chat_id})) is not None: + mode = curr_mode["mode"] + text += strings["mode_info"] % mode + text += strings["wrng_args"] + text += "\n".join([f"- {i}" for i in acceptable_args]) + await message.reply(text) + + +async def max_warn_func(chat_id, user_id): + if (data := await db.warnmode.find_one({"chat_id": chat_id})) is not None: + if data["mode"] == "ban": + return await ban_user(chat_id, user_id) + elif data["mode"] == "tmute": + time = convert_time(data["time"]) + return await mute_user(chat_id, user_id, time) + elif data["mode"] == "mute": + return await mute_user(chat_id, user_id) + else: # Default + return await ban_user(chat_id, user_id) + + +async def __export__(chat_id): + if data := await db.warnlimit.find_one({"chat_id": chat_id}): + number = data["num"] + else: + number = 3 + + if warnmode_data := await db.warnmode.find_one({"chat_id": chat_id}): + del warnmode_data["chat_id"], warnmode_data["_id"] + else: + warnmode_data = None + + return {"warns": {"warns_limit": number, "warn_mode": warnmode_data}} + + +async def __import__(chat_id, data): + if "warns_limit" in data: + number = data["warns_limit"] + if number < 2: + return + + elif number > 10000: # Max value + return + + await db.warnlimit.update_one( + {"chat_id": chat_id}, {"$set": {"num": number}}, upsert=True + ) + + if (data := data["warn_mode"]) is not None: + await db.warnmode.update_one({"chat_id": chat_id}, {"$set": data}, upsert=True) + + +@get_strings_dec("warns") +async def filter_handle(message, chat, data, string=None): + if await is_user_admin(chat["chat_id"], message.from_user.id): + return + target_user = message.from_user.id + text = data.get("reason", None) or string["filter_handle_rsn"] + await warn_func(message, chat, target_user, text, filter_action=True) + + +__filters__ = { + "warn_user": { + "title": {"module": "warns", "string": "filters_title"}, + "setup": {"start": customise_reason_start, "finish": customise_reason_finish}, + "handle": filter_handle, + } +} + + +__mod_name__ = "Warnings" + +__help__ = """ +You can keep your members from getting out of control using this feature! + +Available commands: +General (Admins): +- /warn (?user) (?reason): Use this command to warn the user! you can mention or reply to the offended user and add reason if needed +- /delwarns or /resetwarns: This command is used to delete all the warns user got so far in the chat +- /dwarn [reply]: Delete the replied message and warn him +Warnlimt (Admins): +- /warnlimit (new limit): Sets a warnlimit +Not all chats want to give same maximum warns to the user, right? This command will help you to modify default maximum warns. Default is 3 + +The warnlimit should be greater than 1 and less than 10,000 + +Warnaction (Admins): +/warnaction (mode) (?time) +Well again, not all chats want to ban (default) users when exceed maximum warns so this command will able to modify that. +Current supported actions are ban (default one), mute, tmute. The tmute mode require time argument as you guessed. + +Available for all users: +/warns (?user) +Use this command to know number of warns and information about warns you got so far in the chat. To use yourself you doesn't require user argument. +""" diff --git a/DaisyX/modules/weather.py b/DaisyX/modules/weather.py new file mode 100644 index 00000000..6fc44be3 --- /dev/null +++ b/DaisyX/modules/weather.py @@ -0,0 +1,126 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import io +import time + +import aiohttp +from telethon.tl import functions, types +from telethon.tl.types import * + +from DaisyX.config import get_str_key + +OPENWEATHERMAP_ID = get_str_key("OPENWEATHERMAP_ID", "") +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + if isinstance(chat, types.InputPeerUser): + return True + + +@register(pattern="^/weather (.*)") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + sample_url = ( + "https://api.openweathermap.org/data/2.5/weather?q={}&APPID={}&units=metric" + ) + input_str = event.pattern_match.group(1) + async with aiohttp.ClientSession() as session: + response_api_zero = await session.get( + sample_url.format(input_str, OPENWEATHERMAP_ID) + ) + response_api = await response_api_zero.json() + if response_api["cod"] == 200: + country_code = response_api["sys"]["country"] + country_time_zone = int(response_api["timezone"]) + sun_rise_time = int(response_api["sys"]["sunrise"]) + country_time_zone + sun_set_time = int(response_api["sys"]["sunset"]) + country_time_zone + await event.reply( + """**Location**: {} +**Temperature ☀️**: {}°С + __minimium__: {}°С + __maximum__ : {}°С +**Humidity 🌤**: {}% +**Wind** 💨: {}m/s +**Clouds** ☁️: {}hpa +**Sunrise** 🌤: {} {} +**Sunset** 🌝: {} {}""".format( + input_str, + response_api["main"]["temp"], + response_api["main"]["temp_min"], + response_api["main"]["temp_max"], + response_api["main"]["humidity"], + response_api["wind"]["speed"], + response_api["clouds"]["all"], + # response_api["main"]["pressure"], + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(sun_rise_time)), + country_code, + time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(sun_set_time)), + country_code, + ) + ) + else: + await event.reply(response_api["message"]) + + +@register(pattern="^/weatherimg (.*)") +async def _(event): + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + elif event.chat_id == iid and event.sender_id == userss: + pass + else: + return + sample_url = "https://wttr.in/{}.png" + # logger.info(sample_url) + input_str = event.pattern_match.group(1) + async with aiohttp.ClientSession() as session: + response_api_zero = await session.get(sample_url.format(input_str)) + # logger.info(response_api_zero) + response_api = await response_api_zero.read() + with io.BytesIO(response_api) as out_file: + await event.reply(file=out_file) diff --git a/DaisyX/modules/zip.py b/DaisyX/modules/zip.py new file mode 100644 index 00000000..5ccda7bb --- /dev/null +++ b/DaisyX/modules/zip.py @@ -0,0 +1,227 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +import time +import zipfile + +from telethon import types +from telethon.tl import functions + +from DaisyX import TEMP_DOWNLOAD_DIRECTORY +from DaisyX.services.events import register +from DaisyX.services.telethon import tbot as client + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/zip") +async def _(event): + if event.fwd_from: + return + + if not event.is_reply: + await event.reply("Reply to a file to compress it.") + return + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply( + "Hai.. You are not admin.. You can't use this command.. But you can use in my pm" + ) + return + + mone = await event.reply("`⏳️ Please wait...`") + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + if event.reply_to_msg_id: + reply_message = await event.get_reply_message() + try: + time.time() + downloaded_file_name = await event.client.download_media( + reply_message, TEMP_DOWNLOAD_DIRECTORY + ) + directory_name = downloaded_file_name + except Exception as e: # pylint:disable=C0103,W0703 + await mone.reply(str(e)) + zipfile.ZipFile(directory_name + ".zip", "w", zipfile.ZIP_DEFLATED).write( + directory_name + ) + await event.client.send_file( + event.chat_id, + directory_name + ".zip", + force_document=True, + allow_cache=False, + reply_to=event.message.id, + ) + + +def zipdir(path, ziph): + # ziph is zipfile handle + for root, dirs, files in os.walk(path): + for file in files: + ziph.write(os.path.join(root, file)) + os.remove(os.path.join(root, file)) + + +from datetime import datetime + +from hachoir.metadata import extractMetadata +from hachoir.parser import createParser +from telethon.tl.types import DocumentAttributeVideo + +extracted = TEMP_DOWNLOAD_DIRECTORY + "extracted/" +thumb_image_path = TEMP_DOWNLOAD_DIRECTORY + "/thumb_image.jpg" +if not os.path.isdir(extracted): + os.makedirs(extracted) + + +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await client(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await client.get_peer_id(user) + ps = ( + await client(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@register(pattern="^/unzip") +async def _(event): + if event.fwd_from: + return + + if not event.is_reply: + await event.reply("Reply to a zip file.") + return + if event.is_group: + if not (await is_register_admin(event.input_chat, event.message.sender_id)): + await event.reply( + " Hai.. You are not admin.. You can't use this command.. But you can use in my pm🙈" + ) + return + + mone = await event.reply("Processing ...") + if not os.path.isdir(TEMP_DOWNLOAD_DIRECTORY): + os.makedirs(TEMP_DOWNLOAD_DIRECTORY) + if event.reply_to_msg_id: + start = datetime.now() + reply_message = await event.get_reply_message() + try: + time.time() + downloaded_file_name = await client.download_media( + reply_message, TEMP_DOWNLOAD_DIRECTORY + ) + except Exception as e: + await mone.reply(str(e)) + else: + end = datetime.now() + (end - start).seconds + + with zipfile.ZipFile(downloaded_file_name, "r") as zip_ref: + zip_ref.extractall(extracted) + filename = sorted(get_lst_of_files(extracted, [])) + await event.reply("Unzipping now") + for single_file in filename: + if os.path.exists(single_file): + caption_rts = os.path.basename(single_file) + force_document = True + supports_streaming = False + document_attributes = [] + if single_file.endswith((".mp4", ".mp3", ".flac", ".webm")): + metadata = extractMetadata(createParser(single_file)) + duration = 0 + width = 0 + height = 0 + if metadata.has("duration"): + duration = metadata.get("duration").seconds + if os.path.exists(thumb_image_path): + metadata = extractMetadata(createParser(thumb_image_path)) + if metadata.has("width"): + width = metadata.get("width") + if metadata.has("height"): + height = metadata.get("height") + document_attributes = [ + DocumentAttributeVideo( + duration=duration, + w=width, + h=height, + round_message=False, + supports_streaming=True, + ) + ] + try: + await client.send_file( + event.chat_id, + single_file, + force_document=force_document, + supports_streaming=supports_streaming, + allow_cache=False, + reply_to=event.message.id, + attributes=document_attributes, + ) + except Exception as e: + await client.send_message( + event.chat_id, + "{} caused `{}`".format(caption_rts, str(e)), + reply_to=event.message.id, + ) + continue + os.remove(single_file) + os.remove(downloaded_file_name) + + +def get_lst_of_files(input_directory, output_lst): + filesinfolder = os.listdir(input_directory) + for file_name in filesinfolder: + current_file_name = os.path.join(input_directory, file_name) + if os.path.isdir(current_file_name): + return get_lst_of_files(current_file_name, output_lst) + output_lst.append(current_file_name) + return output_lst diff --git a/DaisyX/modules/zombies.py b/DaisyX/modules/zombies.py new file mode 100644 index 00000000..b71539d6 --- /dev/null +++ b/DaisyX/modules/zombies.py @@ -0,0 +1,140 @@ +# Copyright (C) 2021 TeamDaisyX + + +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from asyncio import sleep + +from telethon import events +from telethon.errors import ChatAdminRequiredError, UserAdminInvalidError +from telethon.tl.functions.channels import EditBannedRequest +from telethon.tl.types import ChatBannedRights + +from DaisyX import OWNER_ID +from DaisyX.services.telethon import tbot as client + +# =================== CONSTANT =================== + +BANNED_RIGHTS = ChatBannedRights( + until_date=None, + view_messages=True, + send_messages=True, + send_media=True, + send_stickers=True, + send_gifs=True, + send_games=True, + send_inline=True, + embed_links=True, +) + + +UNBAN_RIGHTS = ChatBannedRights( + until_date=None, + send_messages=None, + send_media=None, + send_stickers=None, + send_gifs=None, + send_games=None, + send_inline=None, + embed_links=None, +) + +OFFICERS = OWNER_ID + +# Check if user has admin rights +async def is_register_admin(chat, user): + if isinstance(chat, (types.InputPeerChannel, types.InputChannel)): + + return isinstance( + ( + await tbot(functions.channels.GetParticipantRequest(chat, user)) + ).participant, + (types.ChannelParticipantAdmin, types.ChannelParticipantCreator), + ) + if isinstance(chat, types.InputPeerChat): + + ui = await tbot.get_peer_id(user) + ps = ( + await tbot(functions.messages.GetFullChatRequest(chat.chat_id)) + ).full_chat.participants.participants + return isinstance( + next((p for p in ps if p.user_id == ui), None), + (types.ChatParticipantAdmin, types.ChatParticipantCreator), + ) + return None + + +@client.on(events.NewMessage(pattern=f"^[!/]zombies ?(.*)")) +async def zombies(event): + """For .zombies command, list all the zombies in a chat.""" + if event.fwd_from: + return + if event.is_group: + if await is_register_admin(event.input_chat, event.message.sender_id): + pass + else: + return + con = event.pattern_match.group(1).lower() + del_u = 0 + del_status = "No Deleted Accounts Found, Group Is Clean." + + if con != "clean": + find_zombies = await event.respond("Searching For Zombies...") + async for user in event.client.iter_participants(event.chat_id): + + if user.deleted: + del_u += 1 + await sleep(1) + if del_u > 0: + del_status = f"Found **{del_u}** Zombies In This Group.\ + \nClean Them By Using - `/zombies clean`" + await find_zombies.edit(del_status) + return + + # Here laying the sanity check + chat = await event.get_chat() + chat.admin_rights + chat.creator + + # Well + + cleaning_zombies = await event.respond("Cleaning Zombies...") + del_u = 0 + del_a = 0 + + async for user in event.client.iter_participants(event.chat_id): + if user.deleted: + try: + await event.client( + EditBannedRequest(event.chat_id, user.id, BANNED_RIGHTS) + ) + except ChatAdminRequiredError: + await cleaning_zombies.edit("I Don't Have Ban Rights In This Group.") + return + except UserAdminInvalidError: + del_u -= 1 + del_a += 1 + await event.client(EditBannedRequest(event.chat_id, user.id, UNBAN_RIGHTS)) + del_u += 1 + + if del_u > 0: + del_status = f"Cleaned `{del_u}` Zombies" + + if del_a > 0: + del_status = f"Cleaned `{del_u}` Zombies \ + \n`{del_a}` Zombie Admin Accounts Are Not Removed!" + + await cleaning_zombies.edit(del_status) diff --git a/DaisyX/modules/zz_extras.py b/DaisyX/modules/zz_extras.py new file mode 100644 index 00000000..61d2271d --- /dev/null +++ b/DaisyX/modules/zz_extras.py @@ -0,0 +1,58 @@ +__mod_name__ = "🛠 Extras" +__help__ = """ +The module that contains extra tools that help you to do many cool stuff. + + Available Commands + + AFK +- /afk reason : mark yourself as AFK(Away From Keyboard) + + URL LOCK + Block links sent by users in your group +- /urllock [on/off]: Enable/Disable URL Lock + + DATE TIME +- /datetime [timezone]: Get the present date and time information +You can check out this [link](https://timezonedb.com/time-zones) for the available timezones + + PASTE +- /paste [reply] +Usage: Create a paste or a shortened url using nekobin (https://nekobin.com) + + TELEGRAPH +- /telegraph media : To Make Link of Any Image Or MP4 video. +- /telegraph text : To make Link of Any Text Written. + + TORRENT +- /torrent [QUERY]: Search for torrent links + + TEXT TO SPEECH +- /tts: Reply to any message to get text to speech output +- /stt: Type in reply to a voice message(english only) to extract text from it. + + VIRUS SCAN +- /scanit: Scan a file for virus (MAX SIZE = 3MB) + +COUNTRY +- /country [country name]*:* Gathering info about given country + +COVID +- /covid - To Get Global Stats of Covid. +- /covid [COUNTRY] - To Get Stats of A Single Country. + +MATCH INFO +- /cs - Gathers match information (globally) + +QR CODE +- /getqr - Get text in qr. +- /makeqr - Make a qr code. + + KARMA +[UPVOTE] - Use upvote keywords like "+", "+1", "thanks" etc to upvote a message. +[DOWNVOTE] - Use downvote keywords like "-", "-1", etc to downvote a message. + +- /karma [ON/OFF]: Enable/Disable karma in group. +- /karma [Reply to a message]: Check user's karma +- /karma: Chek karma list of top 10 users + +""" diff --git a/DaisyX/modules/zz_misc.py b/DaisyX/modules/zz_misc.py new file mode 100644 index 00000000..7f8fac2a --- /dev/null +++ b/DaisyX/modules/zz_misc.py @@ -0,0 +1,62 @@ +__mod_name__ = "Misc ⚙️" + +__help__ = """ +An "odds and ends" module for small, simple commands which don't really fit anywhere. + +Available commands: + +BASIC +- /github (username): Returns info about a GitHub user or organization. +- /wiki (keywords): Get wikipedia articles just using this bot. +- /imdb: Search for a movie +- /cancel: Disables current state. Can help in cases if DaisyXBot not responing on your message. +- /id: get the current group id. If used by replying to a message, gets that user's id. +- /info: get information about a user. +- /paste: Pase the text/file in nekobin +- /gps: Find a location + +BOOK DOWNLOAD +- /book book name : Usage :Gets Instant Download Link Of Given Book. + +FAKE INFO +- /fakegen : Generates Fake Information +- /picgen : generate a fake pic + +ZIPPER +- /zip: reply to a telegram file to compress it in .zip format +- /unzip: reply to a telegram file to decompress it from the .zip format + +WEATHER +- /weather: Gives weather forcast +- /wheatherimg: Gives weather image + +PHONE INFO +- /phone [phone no]: Gathers no info + +CURRENCY + - /cash : currency converter +Example syntax: `/cash 1 USD INR` + +NAME HISTORY +- /namehistory [REPLY]: Get the Username and Name history of user. + +SEND +- /send [MESSAGE]: Send given text by bot. + +CC CHECKER +- /au [cc]: Stripe Auth given CC +- /pp [cc]: Paypal 1$ Guest Charge +- /ss [cc]: Speedy Stripe Auth +- /ch [cc]: Check If CC is Live +- /bin [bin]: Gather's Info About the bin +- /gen [bin]: Generates CC with given bin +- /key [sk]: Checks if Stripe key is Live + + Note: Format of cc is ccnum|mm|yy|cvv + Privacy warning: Don't check any of your personal CC's. + +URL TOOLS +- /short (url): Shortify given url. +- /ip (url): Displays information about an IP / domain. +- /direct (url): Generates direct links from the sourceforge.net +""" diff --git a/DaisyX/services/__init__.py b/DaisyX/services/__init__.py new file mode 100644 index 00000000..b423a296 --- /dev/null +++ b/DaisyX/services/__init__.py @@ -0,0 +1,14 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . diff --git a/DaisyX/services/apscheduller.py b/DaisyX/services/apscheduller.py new file mode 100644 index 00000000..cdd8efb9 --- /dev/null +++ b/DaisyX/services/apscheduller.py @@ -0,0 +1,41 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from apscheduler.executors.asyncio import AsyncIOExecutor +from apscheduler.jobstores.redis import RedisJobStore +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from pytz import utc + +from DaisyX.config import get_str_key +from DaisyX.utils.logger import log + +DEFAULT = "default" + +jobstores = { + DEFAULT: RedisJobStore( + host=get_str_key("REDIS_URI"), + port=get_str_key("REDIS_PORT"), + password=get_str_key("REDIS_PASS"), + ) +} +executors = {DEFAULT: AsyncIOExecutor()} +job_defaults = {"coalesce": False, "max_instances": 3} + +scheduler = AsyncIOScheduler( + jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc +) + +log.info("Starting apscheduller...") +scheduler.start() diff --git a/DaisyX/services/errors.py b/DaisyX/services/errors.py new file mode 100644 index 00000000..dd237750 --- /dev/null +++ b/DaisyX/services/errors.py @@ -0,0 +1,52 @@ +import sys +import traceback +from functools import wraps + +from DaisyX import SUPPORT_CHAT +from DaisyX.services.pyrogram import pbot + + +def split_limits(text): + if len(text) < 2048: + return [text] + + lines = text.splitlines(True) + small_msg = "" + result = [] + for line in lines: + if len(small_msg) + len(line) < 2048: + small_msg += line + else: + result.append(small_msg) + small_msg = line + else: + result.append(small_msg) + + return result + + +def capture_err(func): + @wraps(func) + async def capture(client, message, *args, **kwargs): + try: + return await func(client, message, *args, **kwargs) + except Exception as err: + exc_type, exc_obj, exc_tb = sys.exc_info() + errors = traceback.format_exception( + etype=exc_type, + value=exc_obj, + tb=exc_tb, + ) + error_feedback = split_limits( + "**ERROR** | `{}` | `{}`\n\n```{}```\n\n```{}```\n".format( + 0 if not message.from_user else message.from_user.id, + 0 if not message.chat else message.chat.id, + message.text or message.caption, + "".join(errors), + ), + ) + for x in error_feedback: + await pbot.send_message(SUPPORT_CHAT, x) + raise err + + return capture diff --git a/DaisyX/services/events.py b/DaisyX/services/events.py new file mode 100644 index 00000000..99f525e8 --- /dev/null +++ b/DaisyX/services/events.py @@ -0,0 +1,116 @@ +import inspect +import re +from pathlib import Path + +from telethon import events + +from DaisyX.services.mongo import mongodb as db +from DaisyX.services.telethon import tbot + +gbanned = db.gban +CMD_LIST = {} + + +def register(**args): + pattern = args.get("pattern") + r_pattern = r"^[/]" + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = "(?i)" + pattern + + args["pattern"] = pattern.replace("^/", r_pattern, 1) + stack = inspect.stack() + previous_stack_frame = stack[1] + file_test = Path(previous_stack_frame.filename) + file_test = file_test.stem.replace(".py", "") + reg = re.compile("(.*)") + + if pattern is not None: + try: + cmd = re.search(reg, pattern) + try: + cmd = cmd.group(1).replace("$", "").replace("\\", "").replace("^", "") + except BaseException: + pass + + try: + CMD_LIST[file_test].append(cmd) + except BaseException: + CMD_LIST.update({file_test: [cmd]}) + except BaseException: + pass + + def decorator(func): + async def wrapper(check): + if check.edit_date: + return + if check.fwd_from: + return + if check.is_group or check.is_private: + pass + else: + # print("i don't work in channels") + return + users = gbanned.find({}) + for c in users: + if check.sender_id == c["user"]: + return + try: + await func(check) + try: + LOAD_PLUG[file_test].append(func) + except Exception: + LOAD_PLUG.update({file_test: [func]}) + except BaseException: + return + else: + pass + + tbot.add_event_handler(wrapper, events.NewMessage(**args)) + return wrapper + + return decorator + + +def chataction(**args): + """Registers chat actions.""" + + def decorator(func): + tbot.add_event_handler(func, events.ChatAction(**args)) + return func + + return decorator + + +def userupdate(**args): + """Registers user updates.""" + + def decorator(func): + tbot.add_event_handler(func, events.UserUpdate(**args)) + return func + + return decorator + + +def inlinequery(**args): + """Registers inline query.""" + pattern = args.get("pattern", None) + + if pattern is not None and not pattern.startswith("(?i)"): + args["pattern"] = "(?i)" + pattern + + def decorator(func): + tbot.add_event_handler(func, events.InlineQuery(**args)) + return func + + return decorator + + +def callbackquery(**args): + """Registers inline query.""" + + def decorator(func): + tbot.add_event_handler(func, events.CallbackQuery(**args)) + return func + + return decorator diff --git a/DaisyX/services/mongo.py b/DaisyX/services/mongo.py new file mode 100644 index 00000000..3c1eb50c --- /dev/null +++ b/DaisyX/services/mongo.py @@ -0,0 +1,41 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import asyncio +import sys + +from motor import motor_asyncio +from odmantic import AIOEngine +from pymongo import MongoClient +from pymongo.errors import ServerSelectionTimeoutError + +from DaisyX import log +from DaisyX.config import get_int_key, get_str_key + +MONGO_URI = get_str_key("MONGO_URI") +MONGO_PORT = get_int_key("MONGO_PORT") +MONGO_DB = get_str_key("MONGO_DB") + +# Init MongoDB +mongodb = MongoClient(MONGO_URI, MONGO_PORT)[MONGO_DB] +motor = motor_asyncio.AsyncIOMotorClient(MONGO_URI, MONGO_PORT) +db = motor[MONGO_DB] + +engine = AIOEngine(motor, MONGO_DB) + +try: + asyncio.get_event_loop().run_until_complete(motor.server_info()) +except ServerSelectionTimeoutError: + sys.exit(log.critical("Can't connect to mongodb! Exiting...")) diff --git a/DaisyX/services/mongo2.py b/DaisyX/services/mongo2.py new file mode 100644 index 00000000..4115b53b --- /dev/null +++ b/DaisyX/services/mongo2.py @@ -0,0 +1,14 @@ +# Support Dual Mongo DB now +# For free users + +from motor.motor_asyncio import AsyncIOMotorClient as MongoClient + +from DaisyX.config import get_str_key + +MONGO2 = get_str_key("MONGO_URI_2", None) +MONGO = get_str_key("MONGO_URI", required=True) +if MONGO2 == None: + MONGO2 = MONGO + +mongo_client = MongoClient(MONGO2) +db = mongo_client.daisy diff --git a/DaisyX/services/pyrogram.py b/DaisyX/services/pyrogram.py new file mode 100644 index 00000000..b8162f4f --- /dev/null +++ b/DaisyX/services/pyrogram.py @@ -0,0 +1,37 @@ +# This file is part of DaisyXBot (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +import logging + +from pyrogram import Client + +# from pyromod import listen +from DaisyX.config import get_int_key, get_str_key + +TOKEN = get_str_key("TOKEN", required=True) +APP_ID = get_int_key("APP_ID", required=True) +APP_HASH = get_str_key("APP_HASH", required=True) +session_name = TOKEN.split(":")[0] +pbot = Client( + session_name, + api_id=APP_ID, + api_hash=APP_HASH, + bot_token=TOKEN, +) + +# disable logging for pyrogram [not for ERROR logging] +logging.getLogger("pyrogram").setLevel(level=logging.ERROR) + +pbot.start() diff --git a/DaisyX/services/readme.md b/DaisyX/services/readme.md new file mode 100644 index 00000000..a0e35981 --- /dev/null +++ b/DaisyX/services/readme.md @@ -0,0 +1,3 @@ +## Services Section + +## Main connections & SQL DB here diff --git a/DaisyX/services/redis.py b/DaisyX/services/redis.py new file mode 100644 index 00000000..8ff31b88 --- /dev/null +++ b/DaisyX/services/redis.py @@ -0,0 +1,40 @@ +# This file is part of Utah (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import sys + +import redis as redis_lib + +from DaisyX import log +from DaisyX.config import get_str_key + +# Init Redis +redis = redis_lib.Redis( + host=get_str_key("REDIS_URI"), + port=get_str_key("REDIS_PORT"), + password=get_str_key("REDIS_PASS"), + decode_responses=True, +) + +bredis = redis_lib.Redis( + host=get_str_key("REDIS_URI"), + port=get_str_key("REDIS_PORT"), + password=get_str_key("REDIS_PASS"), +) + +try: + redis.ping() +except redis_lib.ConnectionError: + sys.exit(log.critical("Can't connect to RedisDB! Exiting...")) diff --git a/DaisyX/services/sql/__init__.py b/DaisyX/services/sql/__init__.py new file mode 100644 index 00000000..c1a09469 --- /dev/null +++ b/DaisyX/services/sql/__init__.py @@ -0,0 +1,16 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import scoped_session, sessionmaker + +from DaisyX import POSTGRESS_URL as DB_URI + + +def start() -> scoped_session: + engine = create_engine(DB_URI, client_encoding="utf8") + BASE.metadata.bind = engine + BASE.metadata.create_all(engine) + return scoped_session(sessionmaker(bind=engine, autoflush=False)) + + +BASE = declarative_base() +SESSION = start() diff --git a/DaisyX/services/sql/afk_sql.py b/DaisyX/services/sql/afk_sql.py new file mode 100644 index 00000000..e00c5dc9 --- /dev/null +++ b/DaisyX/services/sql/afk_sql.py @@ -0,0 +1,89 @@ +# Reconfigured with AioGram by DaisyDevTeam +# Timer added by MissJuliaRobot +import threading +import time + +from sqlalchemy import Boolean, Column, Integer, String, UnicodeText + +from DaisyX.services.sql import BASE, SESSION + + +class AFK(BASE): + __tablename__ = "afk_usrs" + + user_id = Column(Integer, primary_key=True) + is_afk = Column(Boolean) + reason = Column(UnicodeText) + start_time = Column(String) + + def __init__(self, user_id, reason="", is_afk=True, start_time=""): + self.user_id = user_id + self.reason = reason + self.is_afk = is_afk + self.start_time = start_time + + def __repr__(self): + return "afk_status for {}".format(self.user_id) + + +AFK.__table__.create(checkfirst=True) +INSERTION_LOCK = threading.RLock() + +AFK_USERS = {} +AFK_USERSS = {} + + +def is_afk(user_id): + return user_id in AFK_USERS + return user_id in AFK_USERSS + + +def check_afk_status(user_id): + try: + return SESSION.query(AFK).get(user_id) + finally: + SESSION.close() + + +def set_afk(user_id, reason, start_time=""): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if not curr: + curr = AFK(user_id, reason, True, start_time) + else: + curr.is_afk = True + curr.reason = reason + curr.start_time = time.time() + AFK_USERS[user_id] = reason + AFK_USERSS[user_id] = start_time + SESSION.add(curr) + SESSION.commit() + + +def rm_afk(user_id): + with INSERTION_LOCK: + curr = SESSION.query(AFK).get(user_id) + if curr: + if user_id in AFK_USERS: # sanity check + del AFK_USERS[user_id] + del AFK_USERSS[user_id] + SESSION.delete(curr) + SESSION.commit() + return True + + SESSION.close() + return False + + +def __load_afk_users(): + global AFK_USERS + global AFK_USERSS + try: + all_afk = SESSION.query(AFK).all() + AFK_USERS = {user.user_id: user.reason for user in all_afk if user.is_afk} + AFK_USERSS = {user.user_id: user.start_time for user in all_afk if user.is_afk} + finally: + SESSION.close() + + +__load_afk_users() diff --git a/DaisyX/services/sql/chatbot_sql.py b/DaisyX/services/sql/chatbot_sql.py new file mode 100644 index 00000000..dc08cd8d --- /dev/null +++ b/DaisyX/services/sql/chatbot_sql.py @@ -0,0 +1,73 @@ +import threading + +from sqlalchemy import Column, String + +from DaisyX.services.sql import BASE, SESSION + + +class ChatbotChats(BASE): + __tablename__ = "chatbot_chats" + chat_id = Column(String(14), primary_key=True) + ses_id = Column(String(70)) + expires = Column(String(15)) + + def __init__(self, chat_id, ses_id, expires): + self.chat_id = chat_id + self.ses_id = ses_id + self.expires = expires + + +ChatbotChats.__table__.create(checkfirst=True) + +INSERTION_LOCK = threading.RLock() + + +def is_chat(chat_id): + try: + chat = SESSION.query(ChatbotChats).get(str(chat_id)) + if chat: + return True + return False + finally: + SESSION.close() + + +def set_ses(chat_id, ses_id, expires): + with INSERTION_LOCK: + autochat = SESSION.query(ChatbotChats).get(str(chat_id)) + if not autochat: + autochat = ChatbotChats(str(chat_id), str(ses_id), str(expires)) + else: + autochat.ses_id = str(ses_id) + autochat.expires = str(expires) + + SESSION.add(autochat) + SESSION.commit() + + +def get_ses(chat_id): + autochat = SESSION.query(ChatbotChats).get(str(chat_id)) + sesh = "" + exp = "" + if autochat: + sesh = str(autochat.ses_id) + exp = str(autochat.expires) + + SESSION.close() + return sesh, exp + + +def rem_chat(chat_id): + with INSERTION_LOCK: + autochat = SESSION.query(ChatbotChats).get(str(chat_id)) + if autochat: + SESSION.delete(autochat) + + SESSION.commit() + + +def get_all_chats(): + try: + return SESSION.query(ChatbotChats.chat_id).all() + finally: + SESSION.close() diff --git a/DaisyX/services/sql/filters_sql.py b/DaisyX/services/sql/filters_sql.py new file mode 100644 index 00000000..c8485999 --- /dev/null +++ b/DaisyX/services/sql/filters_sql.py @@ -0,0 +1,97 @@ +from sqlalchemy import Column, LargeBinary, Numeric, String, UnicodeText + +from DaisyX.services.sql import BASE, SESSION + + +class Filters(BASE): + __tablename__ = "cust_filters" + chat_id = Column(String(14), primary_key=True) + keyword = Column(UnicodeText, primary_key=True) + reply = Column(UnicodeText) + snip_type = Column(Numeric) + media_id = Column(UnicodeText) + media_access_hash = Column(UnicodeText) + media_file_reference = Column(LargeBinary) + + def __init__( + self, + chat_id, + keyword, + reply, + snip_type, + media_id=None, + media_access_hash=None, + media_file_reference=None, + ): + self.chat_id = chat_id + self.keyword = keyword + self.reply = reply + self.snip_type = snip_type + self.media_id = media_id + self.media_access_hash = media_access_hash + self.media_file_reference = media_file_reference + + +Filters.__table__.create(checkfirst=True) + + +def get_filter(chat_id, keyword): + try: + return SESSION.query(Filters).get((str(chat_id), keyword)) + except BaseException: + return None + finally: + SESSION.close() + + +def get_all_filters(chat_id): + try: + return SESSION.query(Filters).filter(Filters.chat_id == str(chat_id)).all() + except BaseException: + return None + finally: + SESSION.close() + + +def add_filter( + chat_id, + keyword, + reply, + snip_type, + media_id, + media_access_hash, + media_file_reference, +): + adder = SESSION.query(Filters).get((str(chat_id), keyword)) + if adder: + adder.reply = reply + adder.snip_type = snip_type + adder.media_id = media_id + adder.media_access_hash = media_access_hash + adder.media_file_reference = media_file_reference + else: + adder = Filters( + chat_id, + keyword, + reply, + snip_type, + media_id, + media_access_hash, + media_file_reference, + ) + SESSION.add(adder) + SESSION.commit() + + +def remove_filter(chat_id, keyword): + saved_filter = SESSION.query(Filters).get((str(chat_id), keyword)) + if saved_filter: + SESSION.delete(saved_filter) + SESSION.commit() + + +def remove_all_filters(chat_id): + saved_filter = SESSION.query(Filters).filter(Filters.chat_id == str(chat_id)) + if saved_filter: + saved_filter.delete() + SESSION.commit() diff --git a/DaisyX/services/sql/forceSubscribe_sql.py b/DaisyX/services/sql/forceSubscribe_sql.py new file mode 100644 index 00000000..25f59591 --- /dev/null +++ b/DaisyX/services/sql/forceSubscribe_sql.py @@ -0,0 +1,46 @@ +from sqlalchemy import Column, Numeric, String + +from DaisyX.services.sql import BASE, SESSION + + +class forceSubscribe(BASE): + __tablename__ = "forceSubscribe" + chat_id = Column(Numeric, primary_key=True) + channel = Column(String) + + def __init__(self, chat_id, channel): + self.chat_id = chat_id + self.channel = channel + + +forceSubscribe.__table__.create(checkfirst=True) + + +def fs_settings(chat_id): + try: + return ( + SESSION.query(forceSubscribe) + .filter(forceSubscribe.chat_id == chat_id) + .one() + ) + except: + return None + finally: + SESSION.close() + + +def add_channel(chat_id, channel): + adder = SESSION.query(forceSubscribe).get(chat_id) + if adder: + adder.channel = channel + else: + adder = forceSubscribe(chat_id, channel) + SESSION.add(adder) + SESSION.commit() + + +def disapprove(chat_id): + rem = SESSION.query(forceSubscribe).get(chat_id) + if rem: + SESSION.delete(rem) + SESSION.commit() diff --git a/DaisyX/services/sql/night_mode_sql.py b/DaisyX/services/sql/night_mode_sql.py new file mode 100644 index 00000000..157560d3 --- /dev/null +++ b/DaisyX/services/sql/night_mode_sql.py @@ -0,0 +1,55 @@ +# Copyright (C) Midhun KM 2020-2021 +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from sqlalchemy import Column, String + +from DaisyX.services.sql import BASE, SESSION + + +class Nightmode(BASE): + __tablename__ = "nightmode" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nightmode.__table__.create(checkfirst=True) + + +def add_nightmode(chat_id: str): + nightmoddy = Nightmode(str(chat_id)) + SESSION.add(nightmoddy) + SESSION.commit() + + +def rmnightmode(chat_id: str): + rmnightmoddy = SESSION.query(Nightmode).get(str(chat_id)) + if rmnightmoddy: + SESSION.delete(rmnightmoddy) + SESSION.commit() + + +def get_all_chat_id(): + stark = SESSION.query(Nightmode).all() + SESSION.close() + return stark + + +def is_nightmode_indb(chat_id: str): + try: + s__ = SESSION.query(Nightmode).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/DaisyX/services/sql/nsfw_watch_sql.py b/DaisyX/services/sql/nsfw_watch_sql.py new file mode 100644 index 00000000..091444db --- /dev/null +++ b/DaisyX/services/sql/nsfw_watch_sql.py @@ -0,0 +1,55 @@ +# Copyright (C) Midhun KM 2020-2021 +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from sqlalchemy import Column, String + +from DaisyX.services.sql import BASE, SESSION + + +class Nsfwatch(BASE): + __tablename__ = "nsfwatch" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Nsfwatch.__table__.create(checkfirst=True) + + +def add_nsfwatch(chat_id: str): + nsfws = Nsfwatch(str(chat_id)) + SESSION.add(nsfws) + SESSION.commit() + + +def rmnsfwatch(chat_id: str): + nsfwm = SESSION.query(Nsfwatch).get(str(chat_id)) + if nsfwm: + SESSION.delete(nsfwm) + SESSION.commit() + + +def get_all_nsfw_enabled_chat(): + stark = SESSION.query(Nsfwatch).all() + SESSION.close() + return stark + + +def is_nsfwatch_indb(chat_id: str): + try: + s__ = SESSION.query(Nsfwatch).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/DaisyX/services/sql/talk_mode_sql.py b/DaisyX/services/sql/talk_mode_sql.py new file mode 100644 index 00000000..0cb1c432 --- /dev/null +++ b/DaisyX/services/sql/talk_mode_sql.py @@ -0,0 +1,55 @@ +# Copyright (C) InukaAsith 2021 +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from sqlalchemy import Column, String + +from DaisyX.services.sql import BASE, SESSION + + +class Talkmode(BASE): + __tablename__ = "talkmode" + chat_id = Column(String(14), primary_key=True) + + def __init__(self, chat_id): + self.chat_id = chat_id + + +Talkmode.__table__.create(checkfirst=True) + + +def add_talkmode(chat_id: str): + talkmoddy = Talkmode(str(chat_id)) + SESSION.add(talkmoddy) + SESSION.commit() + + +def rmtalkmode(chat_id: str): + rmtalkmoddy = SESSION.query(Talkmode).get(str(chat_id)) + if rmtalkmoddy: + SESSION.delete(rmtalkmoddy) + SESSION.commit() + + +def get_all_chat_id(): + stark = SESSION.query(Talkmode).all() + SESSION.close() + return stark + + +def is_talkmode_indb(chat_id: str): + try: + s__ = SESSION.query(Talkmode).get(str(chat_id)) + if s__: + return str(s__.chat_id) + finally: + SESSION.close() diff --git a/DaisyX/services/sql/urlblacklist_sql.py b/DaisyX/services/sql/urlblacklist_sql.py new file mode 100644 index 00000000..107d06c7 --- /dev/null +++ b/DaisyX/services/sql/urlblacklist_sql.py @@ -0,0 +1,83 @@ +# MissJuliaRobot (A Telegram Bot Project) +# Copyright (C) 2019-Present Anonymous (https://t.me/MissJulia_Robot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, in version 3 of the License. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see < https://www.gnu.org/licenses/agpl-3.0.en.html > + + +import threading + +from sqlalchemy import Column, String, UnicodeText + +from DaisyX.services.sql import BASE, SESSION + + +class URLBlackListFilters(BASE): + __tablename__ = "url_blacklist" + chat_id = Column(String(14), primary_key=True) + domain = Column(UnicodeText, primary_key=True, nullable=False) + + def __init__(self, chat_id, domain): + self.chat_id = str(chat_id) + self.domain = str(domain) + + +URLBlackListFilters.__table__.create(checkfirst=True) + +URL_BLACKLIST_FILTER_INSERTION_LOCK = threading.RLock() + +CHAT_URL_BLACKLISTS = {} + + +def blacklist_url(chat_id, domain): + with URL_BLACKLIST_FILTER_INSERTION_LOCK: + domain_filt = URLBlackListFilters(str(chat_id), domain) + + SESSION.merge(domain_filt) + SESSION.commit() + CHAT_URL_BLACKLISTS.setdefault(str(chat_id), set()).add(domain) + + +def rm_url_from_blacklist(chat_id, domain): + with URL_BLACKLIST_FILTER_INSERTION_LOCK: + domain_filt = SESSION.query(URLBlackListFilters).get((str(chat_id), domain)) + if domain_filt: + if domain in CHAT_URL_BLACKLISTS.get(str(chat_id), set()): + CHAT_URL_BLACKLISTS.get(str(chat_id), set()).remove(domain) + SESSION.delete(domain_filt) + SESSION.commit() + return True + + SESSION.close() + return False + + +def get_blacklisted_urls(chat_id): + return CHAT_URL_BLACKLISTS.get(str(chat_id), set()) + + +def _load_chat_blacklist(): + global CHAT_URL_BLACKLISTS + try: + chats = SESSION.query(URLBlackListFilters.chat_id).distinct().all() + for (chat_id,) in chats: + CHAT_URL_BLACKLISTS[chat_id] = [] + + all_urls = SESSION.query(URLBlackListFilters).all() + for url in all_urls: + CHAT_URL_BLACKLISTS[url.chat_id] += [url.domain] + CHAT_URL_BLACKLISTS = {k: set(v) for k, v in CHAT_URL_BLACKLISTS.items()} + finally: + SESSION.close() + + +_load_chat_blacklist() diff --git a/DaisyX/services/sql/welcome_sql.py b/DaisyX/services/sql/welcome_sql.py new file mode 100644 index 00000000..85b131ab --- /dev/null +++ b/DaisyX/services/sql/welcome_sql.py @@ -0,0 +1,71 @@ +from sqlalchemy import BigInteger, Boolean, Column, String, UnicodeText + +from DaisyX.services.sql import BASE, SESSION + + +class Goodbye(BASE): + __tablename__ = "goodbye" + chat_id = Column(String(14), primary_key=True) + custom_goodbye_message = Column(UnicodeText) + media_file_id = Column(UnicodeText) + should_clean_goodbye = Column(Boolean, default=False) + previous_goodbye = Column(BigInteger) + + def __init__( + self, + chat_id, + custom_goodbye_message, + should_clean_goodbye, + previous_goodbye, + media_file_id=None, + ): + self.chat_id = chat_id + self.custom_goodbye_message = custom_goodbye_message + self.media_file_id = media_file_id + self.should_clean_goodbye = should_clean_goodbye + self.previous_goodbye = previous_goodbye + + +Goodbye.__table__.create(checkfirst=True) + + +def get_current_goodbye_settings(chat_id): + try: + return SESSION.query(Goodbye).filter(Goodbye.chat_id == str(chat_id)).one() + except: + return None + finally: + SESSION.close() + + +def add_goodbye_setting( + chat_id, + custom_goodbye_message, + should_clean_goodbye, + previous_goodbye, + media_file_id, +): + # adder = SESSION.query(Goodbye).get(chat_id) + adder = Goodbye( + chat_id, + custom_goodbye_message, + should_clean_goodbye, + previous_goodbye, + media_file_id, + ) + SESSION.add(adder) + SESSION.commit() + + +def rm_goodbye_setting(chat_id): + rem = SESSION.query(Goodbye).get(str(chat_id)) + if rem: + SESSION.delete(rem) + SESSION.commit() + + +def update_previous_goodbye(chat_id, previous_goodbye): + row = SESSION.query(Goodbye).get(str(chat_id)) + row.previous_goodbye = previous_goodbye + # commit the changes to the DB + SESSION.commit() diff --git a/DaisyX/services/telethon.py b/DaisyX/services/telethon.py new file mode 100644 index 00000000..d4e3bce6 --- /dev/null +++ b/DaisyX/services/telethon.py @@ -0,0 +1,29 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +from telethon import TelegramClient + +from DaisyX.config import get_int_key, get_str_key + +TOKEN = get_str_key("TOKEN", required=True) +NAME = TOKEN.split(":")[0] + +tbot = TelegramClient( + NAME, get_int_key("APP_ID", required=True), get_str_key("APP_HASH", required=True) +) + +# Telethon +tbot.start(bot_token=TOKEN) diff --git a/DaisyX/services/telethonuserbot.py b/DaisyX/services/telethonuserbot.py new file mode 100644 index 00000000..95b4456d --- /dev/null +++ b/DaisyX/services/telethonuserbot.py @@ -0,0 +1,32 @@ +# This file is part of DaisyXBot (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import sys + +from telethon import TelegramClient +from telethon.sessions import StringSession + +from DaisyX.config import get_int_key, get_str_key + +STRING_SESSION = get_str_key("STRING_SESSION", required=True) +API_ID = get_int_key("APP_ID", required=True) +API_HASH = get_str_key("APP_HASH", required=True) + +ubot = TelegramClient(StringSession(STRING_SESSION), API_ID, API_HASH) +try: + ubot.start() +except BaseException: + print("Userbot Error ! Have you added a STRING_SESSION in deploying??") + sys.exit(1) diff --git a/DaisyX/stuff/daisy.jpg b/DaisyX/stuff/daisy.jpg new file mode 100644 index 00000000..2be3c72b Binary files /dev/null and b/DaisyX/stuff/daisy.jpg differ diff --git a/DaisyX/stuff/fonts/CaveatBrush-Regular.ttf b/DaisyX/stuff/fonts/CaveatBrush-Regular.ttf new file mode 100644 index 00000000..4bf588d1 Binary files /dev/null and b/DaisyX/stuff/fonts/CaveatBrush-Regular.ttf differ diff --git a/DaisyX/stuff/fonts/MaShanZheng-Regular.ttf b/DaisyX/stuff/fonts/MaShanZheng-Regular.ttf new file mode 100644 index 00000000..400499d5 Binary files /dev/null and b/DaisyX/stuff/fonts/MaShanZheng-Regular.ttf differ diff --git a/DaisyX/stuff/fonts/OFL.txt b/DaisyX/stuff/fonts/OFL.txt new file mode 100644 index 00000000..2099bf3c --- /dev/null +++ b/DaisyX/stuff/fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2015 Google Inc. All Rights Reserved. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/DaisyX/stuff/fonts/RobotoMono-Medium.ttf b/DaisyX/stuff/fonts/RobotoMono-Medium.ttf new file mode 100644 index 00000000..c496725e Binary files /dev/null and b/DaisyX/stuff/fonts/RobotoMono-Medium.ttf differ diff --git a/DaisyX/stuff/fonts/VT323-Regular.ttf b/DaisyX/stuff/fonts/VT323-Regular.ttf new file mode 100644 index 00000000..e8385811 Binary files /dev/null and b/DaisyX/stuff/fonts/VT323-Regular.ttf differ diff --git a/DaisyX/stuff/fonts/__init__.py b/DaisyX/stuff/fonts/__init__.py new file mode 100644 index 00000000..48d43f0f --- /dev/null +++ b/DaisyX/stuff/fonts/__init__.py @@ -0,0 +1,31 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +def list_all_fonts(): + import glob + from os.path import basename, dirname, isfile + + mod_paths = glob.glob(dirname(__file__) + "/*.ttf") + all_fonts = [ + dirname(f) + "/" + basename(f) + for f in mod_paths + if isfile(f) and f.endswith(".ttf") + ] + return all_fonts + + +ALL_FONTS = sorted(list_all_fonts()) +__all__ = ALL_FONTS + ["ALL_FONTS"] diff --git a/DaisyX/utils/Cache/lol b/DaisyX/utils/Cache/lol new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/DaisyX/utils/Cache/lol @@ -0,0 +1 @@ + diff --git a/DaisyX/utils/cached.py b/DaisyX/utils/cached.py new file mode 100644 index 00000000..48603e28 --- /dev/null +++ b/DaisyX/utils/cached.py @@ -0,0 +1,103 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + + +import asyncio +import functools +import pickle +from typing import Optional, Union + +from DaisyX.services.redis import bredis +from DaisyX.utils.logger import log + + +async def set_value(key, value, ttl): + value = pickle.dumps(value) + bredis.set(key, value) + if ttl: + bredis.expire(key, ttl) + + +class cached: + def __init__( + self, + ttl: Optional[Union[int, float]] = None, + key: Optional[str] = None, + no_self: bool = False, + ): + self.ttl = ttl + self.key = key + self.no_self = no_self + + def __call__(self, *args, **kwargs): + if not hasattr(self, "func"): + self.func = args[0] + # wrap + functools.update_wrapper(self, self.func) + # return ``cached`` object when function is not being called + return self + return self._set(*args, **kwargs) + + async def _set(self, *args: dict, **kwargs: dict): + key = self.__build_key(*args, **kwargs) + + if bredis.exists(key): + value = pickle.loads(bredis.get(key)) + return value if type(value) is not _NotSet else value.real_value + + result = await self.func(*args, **kwargs) + if result is None: + result = _NotSet() + asyncio.ensure_future(set_value(key, result, ttl=self.ttl)) + log.debug(f"Cached: writing new data for key - {key}") + return result if type(result) is not _NotSet else result.real_value + + def __build_key(self, *args: dict, **kwargs: dict) -> str: + ordered_kwargs = sorted(kwargs.items()) + + new_key = ( + self.key if self.key else (self.func.__module__ or "") + self.func.__name__ + ) + new_key += str(args[1:] if self.no_self else args) + + if ordered_kwargs: + new_key += str(ordered_kwargs) + + return new_key + + async def reset_cache(self, *args, new_value=None, **kwargs): + """ + >>> @cached() + >>> def somefunction(arg): + >>> pass + >>> + >>> [...] + >>> arg = ... # same thing ^^ + >>> await somefunction.reset_cache(arg, new_value='Something') + + :param new_value: new/ updated value to be set [optional] + """ + + key = self.__build_key(*args, **kwargs) + if new_value: + return set_value(key, new_value, ttl=self.ttl) + return bredis.delete(key) + + +class _NotSet: + real_value = None + + def __repr__(self) -> str: + return "NotSet" diff --git a/DaisyX/utils/channel_logs.py b/DaisyX/utils/channel_logs.py new file mode 100644 index 00000000..30bd828b --- /dev/null +++ b/DaisyX/utils/channel_logs.py @@ -0,0 +1,28 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import html + +from DaisyX import bot +from DaisyX.config import get_int_key +from DaisyX.utils.logger import log + + +async def channel_log(msg, info_log=True): + chat_id = get_int_key("LOGS_CHANNEL_ID") + if info_log: + log.info(msg) + + await bot.send_message(chat_id, html.escape(msg, quote=False)) diff --git a/DaisyX/utils/exit_gracefully.py b/DaisyX/utils/exit_gracefully.py new file mode 100644 index 00000000..872e909d --- /dev/null +++ b/DaisyX/utils/exit_gracefully.py @@ -0,0 +1,35 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import os +import signal + +from DaisyX.services.redis import redis +from DaisyX.utils.logger import log + + +def exit_gracefully(signum, frame): + log.warning("Bye!") + + try: + redis.save() + except Exception: + log.error("Exiting immediately!") + os.kill(os.getpid(), signal.SIGUSR1) + + +# Signal exit +log.info("Setting exit_gracefully task...") +signal.signal(signal.SIGINT, exit_gracefully) diff --git a/DaisyX/utils/filters/__init__.py b/DaisyX/utils/filters/__init__.py new file mode 100644 index 00000000..f29e82ac --- /dev/null +++ b/DaisyX/utils/filters/__init__.py @@ -0,0 +1,33 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import glob +import os.path + + +def list_all_filters(): + mod_paths = glob.glob(os.path.dirname(__file__) + "/*.py") + all_filters = [ + os.path.basename(f)[:-3] + for f in mod_paths + if os.path.isfile(f) and f.endswith(".py") and not f.endswith("__init__.py") + ] + + return all_filters + + +ALL_FILTERS = sorted(list(list_all_filters())) + +__all__ = ALL_FILTERS + ["ALL_FILTERS"] diff --git a/DaisyX/utils/filters/admin_rights.py b/DaisyX/utils/filters/admin_rights.py new file mode 100644 index 00000000..660f6098 --- /dev/null +++ b/DaisyX/utils/filters/admin_rights.py @@ -0,0 +1,152 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from dataclasses import dataclass + +from aiogram.dispatcher.filters import Filter +from aiogram.types.callback_query import CallbackQuery +from aiogram.utils.exceptions import BadRequest + +from DaisyX import BOT_ID, dp +from DaisyX.modules.utils.language import get_strings +from DaisyX.modules.utils.user_details import check_admin_rights + + +@dataclass +class UserRestricting(Filter): + admin: bool = False + can_post_messages: bool = False + can_edit_messages: bool = False + can_delete_messages: bool = False + can_restrict_members: bool = False + can_promote_members: bool = False + can_change_info: bool = False + can_invite_users: bool = False + can_pin_messages: bool = False + + ARGUMENTS = { + "user_admin": "admin", + "user_can_post_messages": "can_post_messages", + "user_can_edit_messages": "can_edit_messages", + "user_can_delete_messages": "can_delete_messages", + "user_can_restrict_members": "can_restrict_members", + "user_can_promote_members": "can_promote_members", + "user_can_change_info": "can_change_info", + "user_can_invite_users": "can_invite_users", + "user_can_pin_messages": "can_pin_messages", + } + PAYLOAD_ARGUMENT_NAME = "user_member" + + def __post_init__(self): + self.required_permissions = { + arg: True for arg in self.ARGUMENTS.values() if getattr(self, arg) + } + + @classmethod + def validate(cls, full_config): + config = {} + for alias, argument in cls.ARGUMENTS.items(): + if alias in full_config: + config[argument] = full_config.pop(alias) + return config + + async def check(self, event): + user_id = await self.get_target_id(event) + message = event.message if hasattr(event, "message") else event + # If pm skip checks + if message.chat.type == "private": + return True + + check = await check_admin_rights( + message, message.chat.id, user_id, self.required_permissions.keys() + ) + if check is not True: + # check = missing permission in this scope + await self.no_rights_msg(event, check) + return False + + return True + + async def get_target_id(self, message): + return message.from_user.id + + async def no_rights_msg(self, message, required_permissions): + strings = await get_strings( + message.message.chat.id if hasattr(message, "message") else message.chat.id, + "global", + ) + task = message.answer if hasattr(message, "message") else message.reply + if not isinstance( + required_permissions, bool + ): # Check if check_admin_rights func returned missing perm + required_permissions = " ".join( + required_permissions.strip("can_").split("_") + ) + try: + await task( + strings["user_no_right"].format(permission=required_permissions) + ) + except BadRequest as error: + if error.args == "Reply message not found": + return await message.answer(strings["user_no_right"]) + else: + try: + await task(strings["user_no_right:not_admin"]) + except BadRequest as error: + if error.args == "Reply message not found": + return await message.answer(strings["user_no_right:not_admin"]) + + +class BotHasPermissions(UserRestricting): + ARGUMENTS = { + "bot_admin": "admin", + "bot_can_post_messages": "can_post_messages", + "bot_can_edit_messages": "can_edit_messages", + "bot_can_delete_messages": "can_delete_messages", + "bot_can_restrict_members": "can_restrict_members", + "bot_can_promote_members": "can_promote_members", + "bot_can_change_info": "can_change_info", + "bot_can_invite_users": "can_invite_users", + "bot_can_pin_messages": "can_pin_messages", + } + PAYLOAD_ARGUMENT_NAME = "bot_member" + + async def get_target_id(self, message): + return BOT_ID + + async def no_rights_msg(self, message, required_permissions): + message = message.message if isinstance(message, CallbackQuery) else message + strings = await get_strings(message.chat.id, "global") + if not isinstance(required_permissions, bool): + required_permissions = " ".join( + required_permissions.strip("can_").split("_") + ) + try: + await message.reply( + strings["bot_no_right"].format(permission=required_permissions) + ) + except BadRequest as error: + if error.args == "Reply message not found": + return await message.answer(strings["bot_no_right"]) + else: + try: + await message.reply(strings["bot_no_right:not_admin"]) + except BadRequest as error: + if error.args == "Reply message not found": + return await message.answer(strings["bot_no_right:not_admin"]) + + +dp.filters_factory.bind(UserRestricting) +dp.filters_factory.bind(BotHasPermissions) diff --git a/DaisyX/utils/filters/chat_status.py b/DaisyX/utils/filters/chat_status.py new file mode 100644 index 00000000..7c44a63b --- /dev/null +++ b/DaisyX/utils/filters/chat_status.py @@ -0,0 +1,45 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from aiogram import types +from aiogram.dispatcher.filters import BoundFilter + +from DaisyX import dp + + +class OnlyPM(BoundFilter): + key = "only_pm" + + def __init__(self, only_pm): + self.only_pm = only_pm + + async def check(self, message: types.Message): + if message.from_user.id == message.chat.id: + return True + + +class OnlyGroups(BoundFilter): + key = "only_groups" + + def __init__(self, only_groups): + self.only_groups = only_groups + + async def check(self, message: types.Message): + if not message.from_user.id == message.chat.id: + return True + + +dp.filters_factory.bind(OnlyPM) +dp.filters_factory.bind(OnlyGroups) diff --git a/DaisyX/utils/filters/message_status.py b/DaisyX/utils/filters/message_status.py new file mode 100644 index 00000000..cf200f37 --- /dev/null +++ b/DaisyX/utils/filters/message_status.py @@ -0,0 +1,77 @@ +# Copyright (C) 2018 - 2020 MrYacha. All rights reserved. Source code available under the AGPL. +# Copyright (C) 2019 Aiogram +# +# This file is part of DaisyBot. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from aiogram import types +from aiogram.dispatcher.filters import BoundFilter + +from DaisyX import dp + + +class NotForwarded(BoundFilter): + key = "not_forwarded" + + def __init__(self, not_forwarded): + self.not_forwarded = not_forwarded + + async def check(self, message: types.Message): + if "forward_from" not in message: + return True + + +class NoArgs(BoundFilter): + key = "no_args" + + def __init__(self, no_args): + self.no_args = no_args + + async def check(self, message: types.Message): + if not len(message.text.split(" ")) > 1: + return True + + +class HasArgs(BoundFilter): + key = "has_args" + + def __init__(self, has_args): + self.has_args = has_args + + async def check(self, message: types.Message): + if len(message.text.split(" ")) > 1: + return True + + +class CmdNotMonospaced(BoundFilter): + key = "cmd_not_mono" + + def __init__(self, cmd_not_mono): + self.cmd_not_mono = cmd_not_mono + + async def check(self, message: types.Message): + if ( + message.entities + and message.entities[0]["type"] == "code" + and message.entities[0]["offset"] < 1 + ): + return False + return True + + +dp.filters_factory.bind(NotForwarded) +dp.filters_factory.bind(NoArgs) +dp.filters_factory.bind(HasArgs) +dp.filters_factory.bind(CmdNotMonospaced) diff --git a/DaisyX/utils/filters/user_status.py b/DaisyX/utils/filters/user_status.py new file mode 100644 index 00000000..91a9080c --- /dev/null +++ b/DaisyX/utils/filters/user_status.py @@ -0,0 +1,84 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from aiogram import types +from aiogram.dispatcher.filters import BoundFilter + +from DaisyX import OPERATORS, dp +from DaisyX.config import get_int_key +from DaisyX.modules.utils.language import get_strings_dec +from DaisyX.modules.utils.user_details import is_user_admin +from DaisyX.services.mongo import mongodb + + +class IsAdmin(BoundFilter): + key = "is_admin" + + def __init__(self, is_admin): + self.is_admin = is_admin + + @get_strings_dec("global") + async def check(self, event, strings): + + if hasattr(event, "message"): + chat_id = event.message.chat.id + else: + chat_id = event.chat.id + + if not await is_user_admin(chat_id, event.from_user.id): + task = event.answer if hasattr(event, "message") else event.reply + await task(strings["u_not_admin"]) + return False + return True + + +class IsOwner(BoundFilter): + key = "is_owner" + + def __init__(self, is_owner): + self.is_owner = is_owner + + async def check(self, message: types.Message): + if message.from_user.id == get_int_key("OWNER_ID"): + return True + + +class IsOP(BoundFilter): + key = "is_op" + + def __init__(self, is_op): + self.is_owner = is_op + + async def check(self, message: types.Message): + if message.from_user.id in OPERATORS: + return True + + +class NotGbanned(BoundFilter): + key = "not_gbanned" + + def __init__(self, not_gbanned): + self.not_gbanned = not_gbanned + + async def check(self, message: types.Message): + check = mongodb.blacklisted_users.find_one({"user": message.from_user.id}) + if not check: + return True + + +dp.filters_factory.bind(IsAdmin) +dp.filters_factory.bind(IsOwner) +dp.filters_factory.bind(NotGbanned) +dp.filters_factory.bind(IsOP) diff --git a/DaisyX/utils/logger.py b/DaisyX/utils/logger.py new file mode 100644 index 00000000..10781055 --- /dev/null +++ b/DaisyX/utils/logger.py @@ -0,0 +1,50 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import logging + +from loguru import logger + + +class InterceptHandler(logging.Handler): + LEVELS_MAP = { + logging.CRITICAL: "CRITICAL", + logging.ERROR: "ERROR", + logging.WARNING: "WARNING", + logging.INFO: "INFO", + logging.DEBUG: "DEBUG", + } + + def _get_level(self, record): + return self.LEVELS_MAP.get(record.levelno, record.levelno) + + def emit(self, record): + logger_opt = logger.opt( + depth=6, exception=record.exc_info, ansi=True, lazy=True + ) + logger_opt.log(self._get_level(record), record.getMessage()) + + +logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO) +log = logging.getLogger(__name__) +logger.add( + "logs/daisy.log", + rotation="1 d", + compression="tar.xz", + backtrace=True, + diagnose=True, + level="INFO", +) +log.info("Enabled logging intro daisy.log file.") diff --git a/DaisyX/utils/sentry.py b/DaisyX/utils/sentry.py new file mode 100644 index 00000000..dd3182d9 --- /dev/null +++ b/DaisyX/utils/sentry.py @@ -0,0 +1,24 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import sentry_sdk +from sentry_sdk.integrations.redis import RedisIntegration + +from DaisyX.config import get_str_key +from DaisyX.utils.logger import log + +log.info("Starting sentry.io integraion...") + +sentry_sdk.init(get_str_key("SENTRY_API_KEY"), integrations=[RedisIntegration()]) diff --git a/DaisyX/utils/term.py b/DaisyX/utils/term.py new file mode 100644 index 00000000..4ac30fce --- /dev/null +++ b/DaisyX/utils/term.py @@ -0,0 +1,27 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import subprocess + +from DaisyX.utils.logger import log + + +def term(cmd): + p = subprocess.Popen( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) + if p.stderr: + log.error(p.stderr.readlines()) + return p.stdout.readlines() diff --git a/DaisyX/versions.py b/DaisyX/versions.py new file mode 100644 index 00000000..1771f543 --- /dev/null +++ b/DaisyX/versions.py @@ -0,0 +1,16 @@ +# This file is part of Daisy (Telegram Bot) + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +DAISY_VERSION = "v2.2.8-H4.0" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..4834d94e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM python:3.9 +WORKDIR . +ENV PYTHONUNBUFFERED=1 +COPY requirements.txt . +COPY deploy.sh . +RUN bash deploy.sh +COPY . . +CMD ["python3", "-m", "DaisyX"] diff --git a/Downloads/lol b/Downloads/lol new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Downloads/lol @@ -0,0 +1 @@ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..62b2c8ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,660 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Copyright (C) 2018 - 2020 MrYacha. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..742f8922 --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +DAISYX: python3 -m DaisyX +ps:scale DAISYX=1 diff --git a/README.md b/README.md new file mode 100644 index 00000000..a570806a --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ + +

❤️ DaisyX 2.0 ❤️

+ +

A Powerful, Smart And Simple Group Manager
... Written with AioGram , Pyrogram and Telethon...

+

+ + +

+ +

+

+ Codacy + + + + + +

+ +> ⭐️ Thanks to everyone who starred Daisy, That is the greatest pleasure we have ! + +### https://daisyproject.studio +## Avaiilable on Telegram as [@DaisyXBot](https://t.me/daisyxbot) + +# 🧙‍♀️ Deploy Guide +Complete guide on deploying @DaisyXBot's clone on Heroku. + +[![Tutorial](https://yt-embed.herokuapp.com/embed?v=yar61k_hEHQ)](https://www.youtube.com/watch?v=yar61k_hEHQ) + +☆ Video by [ANJANA MADUSHANKA](https://www.youtube.com/channel/UCApXYZNiMdW6UG48-syX7wQ) ☆ + + +# 🏃‍♂️ Easy Deploy +[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/TeamofDaisyX/DaisyX.git) + +[Generate String Session](https://replit.com/@SpEcHiDe/GenerateStringSession) << Select telethon + + + +# ❤️ Support + + + +### Self-hosting (For Devs) ⚔ +```sh +# Install Git First (apt-instll git) +$ git clone https://github.com/TeamOfDaisyX/DaisyX +$ cd DaisyX +# Upgrade sources +$ bash deploy.sh +# Install All Requirements +$ pip3 install -r requirements.txt +# Rename ./DaisyX/data/bot_conf.yaml.example to bot_conf.yaml and fill +# Start Bot +$ python3 -m DaisyX +``` + +### Mandatory Vars 📒 +``` +[+] Make Sure You Add All These Mandatory Vars. + [-] API_ID: You can get this value from https://my.telegram.org + [-] API_HASH : You can get this value from https://my.telegram.org + [-] STRINGSESSION : Your String Session, You can get this From Repl or BY running String_Gen File Locally + [-] MONGO_URI : Your Mongo DB DataBase Url. . + [-] TOKEN: Get from botfarther + [-] DATABASE_URL: from elephantsql.com + [-] OWNER_ID: ur id + [-] MONGO_PORT: 27017 + [-] MONGO_DB': 'DaisyX' + [-] REDIS_URI: from redislabs.com (remove port) + [-] REDIS_PORT: At the end of uri + [-] REDIS_PASS: pass +[+] The DaisyX won't run without setting the mandatory vars. +``` + +# 😍 Credits + + - [Hitsuki](https://github.com/HitsukiNetwork/Hitsukix) + - [SophieBot](https://gitlab.com/SophieBot/sophie) + - [Alita_Robot](https://github.com/Divkix/Alita_Robot/) + - [FridayUserbot](https://github.com/DevsExpo/FridayUserbot) + - [MissJuliaRobot](https://github.com/MissJuliaRobot/MissJuliaRobot) + - [DaisyX](https://github.com/teamdaisyx/daisy-x) + - [ADV-Auto-Filter-Bot-V2](https://github.com/AlbertEinsteinTG/Adv-Auto-Filter-Bot-V2) + - [Image-Editor](https://github.com/TroJanzHEX/Image-Editor/) + - [Utah](https://github.com/minatouzuki/utah). + - [WilliamButcherBot](https://github.com/thehamkercat/WilliamButcherBot) + - [LEGENDX](https://github.com/LEGENDXOP/LEGEND-X) + +## Special Credits +- [SophieBot Devs](https://gitlab.com/SophieBot) +- [TroJanzHEX](https://github.com/TroJanzHEX) +- [infotechbro](https://github.com/infotechbro/) +- [AlbertEinsteinTG](https://github.com/AlbertEinsteinTG) +- [Amarnath c](https://github.com/Amarnathcdj) +- [Thehamkercat](https://github.com/thehamkercat) +- [StarkGang](https://github.com/StarkGang) +- [chsaiujwal](https://github.com/chsaiujwal) +- [LEGENDX](https://github.com/LEGENDXOP) +- [MissJuliaRobot](https://github.com/MissJuliaRobot) +- [HitsukiNetwork](https://github.com/HitsukiNetwork) +- [AnimeKaizoku](https://github.com/AnimeKaizoku) +- [Dan](https://github.com/delivrance) +- [Lonami](https://github.com/Lonami) +- [AioGram Project group](https://github.com/aiogram) + +The bot is based on the original work done by [SophieBot](https://gitlab.com/SophieBot/sophie) and many other projects +This repo was just revamped to suit an Anime-centric & comedy loving community. All original credits go to SophieBot and their dedication, Without his efforts, this fork would not have been possible! + +All other credits mentioned on top of scripts + +Anything missing kindly let us know at [Daisy Support](https://t.me/DaisySupport_Official) or simply submit a pull request on the readme. + + +## Devs & Contributors + +#### • LUCIFER_MORNINGSTAR »» (OWNER) +#### • INUKA ASITH »» (DEV) +#### • ROSELOVERX »» (DEV) +#### • INFOTECHBRO »» (DEV) +#### • ANJANA_MA »» +#### • DARK PRINCE »» +#### • OFFICIAL_BAWWA »» +#### • Annihilatorrrr »» +#### • LEGENDX »» ( Fuck this Scammer ) + + +## All who helped at a glance + +> This project exists thanks to these awesome developers and their codes and contributions. + + + +> And credits goes to all who supported, all who helped and API & environmental equirement package devs and all projects helped in making this project. +> Special thanks to you for using bot diff --git a/app.json b/app.json new file mode 100644 index 00000000..c45b18c6 --- /dev/null +++ b/app.json @@ -0,0 +1,194 @@ +{ + "name": "DaisyX", + "description": "?", + "keywords": [ + "telegram", + "Daisy", + "plugin", + "telegram-DaisyX", + "fuck-TG" + ], + "repository": "https://github.com/TeamDaisyX/Daisy-x", + "website": "daisyproject.studio", + "success_url": "t.me/DaisyXUpdates", + "buildpacks": [ + { + "url": "https://github.com/jonathanong/heroku-buildpack-ffmpeg-latest" + }, + { + "url": "heroku/python" + } + ], + "env": { + "APP_ID": { + "description": "Get this value from my.telegram.org! Please do not steal", + "value": "", + "required": true + }, + "APP_HASH": { + "description": "Get this value from my.telegram.org! Please do not steal", + "value": "", + "required": true + }, + "MONGO_URI": { + "description": "Create A Database In Mongodb And Get URL", + "value": "", + "required": true + + }, + "ARQ_API": { + "description": "Get this from @ARQRobot", + "value": "", + "required": true + + }, + "MONGO_PORT": { + "description": "Dont change", + "value": "27017", + "required": false + }, + "MONGO_DB": { + "description": "Don't change.", + "value": "DaisyX", + "required": false + }, + "HEROKU_APP_NAME": { + "description": "Your heroku app name (not compulsory)", + "value": "", + "required": false + }, + "HEROKU_API_KEY": { + "description": "Your heroku api key (not compulsory)", + "value": "", + "required": false + }, + "UPSTREAM_REPO_URL": { + "description": "Only for fork users else don't change", + "value": "https://github.com/TeamDaisyX/DaisyX-v2.0", + "required": false + }, + "API_PORT": { + "description": "Don't change.", + "value": "8080" + }, + "API_PORT": { + "description": "Your Bot Token Obtained From @BotFather.", + "value": "" + }, + "REDIS_URI": { + "description": "Get from redislabs.com", + "value": "", + "required": true + }, + "REDIS_PORT": { + "description": "Get from redislabs.com", + "value": "", + "required": true + }, + "REDIS_PASS": { + "description": "Get from redislabs.com", + "value": "", + "required": true + }, + "OWNER_ID": { + "description": "Your Id", + "value": "", + "required": true + }, + "OPERATORS": { + "description": "Space seperated list of sudoes", + "value": "", + "required": true + }, + "SUPPORT_CHAT": { + "description": "support chat id", + "value": "", + "required": true + }, + "LYDIA_API_KEY": { + "description": "AI chat", + "value": "", + "required": false + }, + "DATABASE_URL": { + "description": "get from elephantsql.com", + "value": "", + "required": true + }, + "VIRUS_API_KEY": { + "description": "Cloudmersive api (no need)", + "value": "", + "required": false + }, + "TIME_API_KEY": { + "description": "Time api key (no need)", + "value": "", + "required": false + }, + "REM_BG_API_KEY": { + "description": "remove.bg api (no need)", + "value": "", + "required": false + }, + "SW_API": { + "description": "Spamwatch api @SpamWatchBot", + "value": "", + "required": true + }, + "IBM_WATSON_CRED_URL": { + "description": "IBM_WATSON_CRED_URL (no need)", + "value": "", + "required": false + }, + "IBM_WATSON_CRED_PASSWORD": { + "description": "IBM_WATSON_CRED_PASSWORD (no need)", + "value": "", + "required": false + }, + "OPENWEATHERMAP_ID": { + "description": "weather api (no need)", + "value": "", + "required": false + }, + "WOLFRAM_ID": { + "description": "WOLFRAM ai api (no need)", + "value": "", + "required": false + }, + "DEBUG_MODE": { + "description": "DEBUG_MODE (no need)", + "value": "False", + "required": false + }, + "DEBUG_MODE": { + "description": "DEBUG_MODE (no need)", + "value": "False", + "required": false + }, + "LOAD_MODULES": { + "description": "Don't edit", + "value": "True", + "required": false + }, + "LOGS_CHANNEL_ID": { + "description": "Id of your log group/channel", + "value": "", + "required": true + }, + "ALLOW_FORWARDS_COMMANDS": { + "description": "ALLOW_FORWARDS_COMMANDS", + "value": "False", + "required": false + }, + "TOKEN": { + "description": "Tg bot token by @BotFarther", + "value": "", + "required": true + }, + "STRING_SESSION": { + "description": "run repl run And get your String Session", + "value": "", + "required": true + } + } +} diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 00000000..e9b3baf1 --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: DaisyX/localization/en.yaml + translation: /**/DaisyX/localization/%locale_with_underscore%.yaml diff --git a/data/bot_conf.yaml.example b/data/bot_conf.yaml.example new file mode 100644 index 00000000..64395487 --- /dev/null +++ b/data/bot_conf.yaml.example @@ -0,0 +1,48 @@ +# DaisyXBot example config + + +# Basic +TOKEN: "" + +# Get they in https://my.telegram.org/ +APP_ID: 000000 +APP_HASH: "" + +#Generate a telethon str session with https://repl.it/@SpEcHiDe/GenerateStringSession (telethon one) +STRING_SESSION: "" + + +MONGO_URI: "mongodb+srv://dfsdfX:dfsfdX@cluster0.df4j.mongodb.net/myFirstDatabase?retryWrites=true&w=majority" + +MONGO_URI2: "" +MONGO_PORT: 27017 +MONGO_DB': 'DaisyX' + +API_PORT': 8080 + +REDIS_URI: "redis-1324.c234.us-east-1-4.ec4.cloud.redislabs.com" +REDIS_PORT: 15514 +REDIS_PASS: "sdfgg" +TEMP_MAIL_KEY: "" +OWNER_ID: 1141839926 +OPERATORS: [1141839926, 00000000] +SUPPORT_CHAT: -000000000 +LYDIA_API_KEY: "" + +DATABASE_URL: "" +VIRUS_API_KEY: "" +TIME_API_KEY: "" +REM_BG_API_KEY: "" +ARQ_API: "" + +# Advanced +SW_API: "" +IBM_WATSON_CRED_URL: "" +IBM_WATSON_CRED_PASSWORD: "" +OPENWEATHERMAP_ID: "" +WOLFRAM_ID: "" +DEBUG_MODE: False +LOAD_MODULES: True +LOGS_CHANNEL_ID: -000000000 +ALLOW_FORWARDS_COMMANDS: False +ALLOW_EXCEL: False diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 00000000..defbeb62 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,55 @@ + +echo " +*********** STARTING DEPLOY *********** + +DAISYX v2 -Base Aiogram +(C) 2020-2021 by @TEAMDAISYX +Support Chat is @DAISYSUPPORT_OFFICIAL. + +*************************************** +" +update_and_install_packages () { + apt -qq update -y + apt -qq install -y --no-install-recommends \ + git \ + ffmpeg \ + mediainfo \ + unzip \ + wget \ + gifsicle + } + +install_helper_packages () { + wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb && apt -fqqy install ./google-chrome-stable_current_amd64.deb && rm google-chrome-stable_current_amd64.deb + wget https://chromedriver.storage.googleapis.com/88.0.4324.96/chromedriver_linux64.zip && unzip chromedriver_linux64.zip && chmod +x chromedriver && mv -f chromedriver /usr/bin/ && rm chromedriver_linux64.zip + wget -O opencv.zip https://github.com/opencv/opencv/archive/master.zip && unzip opencv.zip && mv -f opencv-master /usr/bin/ && rm opencv.zip + wget https://people.eecs.berkeley.edu/~rich.zhang/projects/2016_colorization/files/demo_v2/colorization_release_v2.caffemodel -P ./bot_utils_files/ai_helpers/ +} + +ech_final () { + echo " + +=++---------------------------------------------++= +DAISYX. Deployed Successfully + + *************************** + * |D| |A| |I| |S| |Y| |X| * + ******************* v2.0 ** + +Thanks for deploying DaisyX +(C) 2020-2021 by @TEAMDAISYX +Support Chat is @DAISYSUPPORT_OFFICIAL. +=++---------------------------------------------++= + Greetings from dev team :) + " +} + +_run_all () { + UPDATE + install_helper_packages + pip3 install –upgrade pip + pip3 install --no-cache-dir -r requirements.txt + ech_final +} + +_run_all diff --git a/fortune.py b/fortune.py new file mode 100644 index 00000000..8744d43b --- /dev/null +++ b/fortune.py @@ -0,0 +1,111 @@ +import codecs +import random +import re +import sys +from optparse import OptionParser + +# --------------------------------------------------------------------------- +# Exports +# --------------------------------------------------------------------------- + +__all__ = ["main", "get_random_fortune"] + +# Info about the module +__version__ = "1.1.0" +__author__ = "Brian M. Clapper" +__email__ = "bmc@clapper.org" +__url__ = "http://software.clapper.org/fortune/" +__copyright__ = "2008-2019 Brian M. Clapper" +__license__ = "BSD-style license" + +# --------------------------------------------------------------------------- +# Functions +# --------------------------------------------------------------------------- + + +def _random_int(start, end): + try: + # Use SystemRandom, if it's available, since it's likely to have + # more entropy. + r = random.SystemRandom() + except BaseException: + r = random + + return r.randint(start, end) + + +def _read_fortunes(fortune_file): + with codecs.open(fortune_file, mode="r", encoding="utf-8") as f: + contents = f.read() + + lines = [line.rstrip() for line in contents.split("\n")] + + delim = re.compile(r"^%$") + + fortunes = [] + cur = [] + + def save_if_nonempty(buf): + fortune = "\n".join(buf) + if fortune.strip(): + fortunes.append(fortune) + + for line in lines: + if delim.match(line): + save_if_nonempty(cur) + cur = [] + continue + + cur.append(line) + + if cur: + save_if_nonempty(cur) + + return fortunes + + +def get_random_fortune(fortune_file): + fortunes = list(_read_fortunes(fortune_file)) + randomRecord = _random_int(0, len(fortunes) - 1) + return fortunes[randomRecord] + + +def main(): + usage = "Usage: %prog [OPTIONS] [fortune_file]" + arg_parser = OptionParser(usage=usage) + arg_parser.add_option( + "-V", + "--version", + action="store_true", + dest="show_version", + help="Show version and exit.", + ) + arg_parser.epilog = ( + "If fortune_file is omitted, fortune looks at the " + "FORTUNE_FILE environment variable for the path." + ) + + options, args = arg_parser.parse_args(sys.argv) + if len(args) == 2: + fortune_file = args[1] + + else: + try: + fortune_file = "notes.txt" + except KeyError: + print("Missing fortune file.", file=sys.stderr) + print(usage, file=sys.stderr) + sys.exit(1) + + try: + if options.show_version: + print("fortune, version {}".format(__version__)) + else: + print(get_random_fortune(fortune_file)) + except ValueError as msg: + print(msg, file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/heroku.yml b/heroku.yml new file mode 100644 index 00000000..09b09fc7 --- /dev/null +++ b/heroku.yml @@ -0,0 +1,3 @@ +build: + docker: + worker: Dockerfile diff --git a/profanity_wordlist.txt b/profanity_wordlist.txt new file mode 100644 index 00000000..04201c8e --- /dev/null +++ b/profanity_wordlist.txt @@ -0,0 +1,335 @@ +2g1c +2 girls 1 cup +4r5e +anal +anus +arse +ass +asses +assfucker +assfukka +asshole +arsehole +asswhole +assmunch +auto erotic +autoerotic +ballsack +bastard +beastial +bestial +bhen ka lode +betichod +bhenchod +bellend +bdsm +beastiality +bestiality +bitch +bitches +bitchin +bitching +bimbo +bimbos +blow job +blowjob +blowjobs +blue waffle +boob +boobs +booobs +boooobs +booooobs +booooooobs +breasts +booty call +brown shower +brown showers +boner +bondage +buceta +bukake +bukkake +bullshit +bull shit +busty +butthole +carpet muncher +cawk +chink +chut +chutiya +cipa +clit +clits +clitoris +cnut +cock +chod dunga +cocks +cockface +cockhead +cockmunch +cockmuncher +cocksuck +cocksucked +cocksucking +cocksucks +cocksucker +cokmuncher +coon +cow girl +cow girls +cowgirl +cowgirls +crap +crotch +cum +cummer +cumming +cuming +cums +cumshot +cunilingus +cunillingus +cunnilingus +cunt +cuntlicker +cuntlicking +cunts +damn +dick +dickhead +dildo +dildos +dink +dinks +deepthroat +deep throat +dog style +doggie style +doggiestyle +doggy style +doggystyle +donkeyribber +doosh +douche +duche +dyke +ejaculate +ejaculated +ejaculates +ejaculating +ejaculatings +ejaculation +ejakulate +erotic +erotism +fag +fuddi +fuddu +faggot +fagging +faggit +faggitt +faggs +fagot +fagots +fags +fatass +femdom +fingering +footjob +foot job +fuck +fucks +fucker +fuckers +fucked +fuckhead +fuckheads +fuckin +fucking +fcuk +fcuker +fcuking +felching +fellate +fellatio +fingerfuck +fingerfucked +fingerfucker +fingerfuckers +fingerfucking +fingerfucks +fistfuck +fistfucked +fistfucker +fistfuckers +fistfucking +fistfuckings +fistfucks +flange +fook +fooker +fucka +fuk +fuks +fuker +fukker +fukkin +fukking +futanari +futanary +gangbang +gangbanged +gang bang +gokkun +golden shower +goldenshower +gaysex +gand +gand mara +goatse +handjob +hand job +hentai +hooker +hoer +homo +horny +incest +jackoff +jack off +jerkoff +jerk off +jizz +knob +kinbaku +labia +lund +lun +lawda +lavda +masturbate +masochist +mofo +mothafuck +motherfuck +motherfucker +mothafucka +mothafuckas +mothafuckaz +mothafucked +mothafucker +mothafuckers +mothafuckin +mothafucking +mothafuckings +mothafucks +mother fucker +motherfucked +motherfucker +motherfuckers +motherfuckin +motherfucking +motherfuckings +motherfuckka +motherfucks +milf +muff +nigga +nigger +nigg +nipple +nipples +nob +nob jokey +nobhead +nobjocky +nobjokey +numbnuts +nutsack +nude +nudes +orgy +orgasm +orgasms +panty +panties +penis +playboy +porn +porno +pornography +pron +pussy +pussies +rape +raping +rapist +rectum +retard +rimming +sadist +sadism +schlong +scrotum +sex +semen +shemale +she male +shibari +shibary +shit +shitdick +shitfuck +shitfull +shithead +shiting +shitings +shits +shitted +shitters +shitting +shittings +shitty +shota +skank +slut +sluts +smut +smegma +spunk +strip club +stripclub +tit +tits +titties +titty +titfuck +tittiefucker +titties +tittyfuck +tittywank +titwank +threesome +three some +throating +twat +twathead +twatty +twunt +viagra +vagina +vulva +wank +wanker +wanky +whore +whoar +xxx +xx +yaoi +yury +sexy diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..2ee01e32 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,128 @@ +# General +telethon +pyrogram>=1.0.7 +aiogram + +# DBs +redis +aioredis # Redis memery storage fom aiogram +pymongo +motor +odmantic # MongoDB ORM +sqlalchemy==1.3.20 # For old modules +dnspython # needed to connect to cloud MongoDB instances + +# Optional deps to make bot faster +aiohttp[speedups] +cryptg +cryptography + +# Other +jikanpy +envparse +hypercorn +aiocron +apscheduler +requests +python-rapidjson +PyYAML>5.0 +coloredlogs +loguru +babel +captcha +async-timeout +regex +bs4 +lxml +spamwatch +httpx[http2] +wikipedia +coffeehouse +PyDictionary==2.0.1 +google-trans-new==1.1.9 +hachoir +telegraph +faker +gtts +geopy +tswift +lyricsgenius +sentry_sdk +lxml +html5lib +feedparser +tldextract +dateparser +twistdl + +#Pyrogram +tgCrypto>=1.2.2 + +#Old DB Processes +psycopg2-binary==2.8.6 + +#YTDL +youtube_dl +youtube_search_python + +#Search +bs4 +html2text +bing_image_downloader +search_engine_parser +pornhub_api +youtube_search + +#Memes +selenium +zalgo_text +cowpy +fontTools +nltk +emoji + +#Deezer +wget +asyncio + +#Markup +Markdown>=3.3.4 + +#profanity (Credits to Julia) +better_profanity +textblob +nudepy + +#ImageEditor +glitch_this +NumPy +opencv-python-headless +Pillow + +#Bassboost +pydub + +#Lel +cloudmersive_virus_api_client==3.0.1 +chromedriver==2.24.1 + +#Updator +gitpython==3.1.11 +heroku3==4.2.3 + +pykeyboard +countryinfo +flag +Python_ARQ +googletrans==4.0.0-rc1 +pyromod +aiofiles +pygithub + + +# +Skem==0.3.3 +gogoanime-api + + + diff --git a/runtime.txt b/runtime.txt new file mode 100644 index 00000000..43b47fb4 --- /dev/null +++ b/runtime.txt @@ -0,0 +1 @@ +python-3.8.5 diff --git a/string_gen.py b/string_gen.py new file mode 100644 index 00000000..be295230 --- /dev/null +++ b/string_gen.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# (c) https://t.me/TelethonChat/37677 +# This Source Code Form is subject to the terms of the GNU +# General Public License, v.3.0. If a copy of the GPL was not distributed with this +# file, You can obtain one at https://www.gnu.org/licenses/gpl-3.0.en.html. + +try: + from telethon.sessions import StringSession + from telethon.sync import TelegramClient +except BaseException: + print("Telethon Not Found. Installing Now.") + import os + + os.system("pip3 install telethon") + from telethon.sessions import StringSession + from telethon.sync import TelegramClient +ok = """ ____ ____ __ ____ __ _ _ +Thunder +""" +print(ok) +APP_ID = int(input("Enter APP ID here: \n")) +API_HASH = input("Enter API HASH here: \n") + +client = TelegramClient(StringSession(), APP_ID, API_HASH) +with client: + session_str = client.session.save() + client.send_message("me", f"`{session_str}`") + client.send_message( + "THIS IS YOUR STRING SESSION \nJoin @DaisySupport_Official For More Support." + ) + print("⬆ Please Check Your Telegram Saved Message For Your String.")