import isodate import yt_dlp from files.classes.chats import * from files.classes.orgy import * from files.routes.wrappers import * from files.helpers.config.const import * from files.helpers.get import * from files.__main__ import app, limiter @app.get("/chat") @app.get("/orgy") def chat_redirect(): return redirect("/chat/1") @app.post("/@/chat") @limiter.limit('1/second', scope=rpath) @limiter.limit('1/second', scope=rpath, key_func=get_ID) @limiter.limit("10/minute;20/hour;50/day", deduct_when=lambda response: response.status_code < 400) @limiter.limit("10/minute;20/hour;50/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID) @auth_required def chat_user(v, username): user = get_user(username, v=v, include_blocks=True) if hasattr(user, 'is_blocking') and user.is_blocking: abort(403, f"You're blocking @{user.username}") if v.admin_level <= PERMS['MESSAGE_BLOCKED_USERS'] and hasattr(user, 'is_blocked') and user.is_blocked: abort(403, f"@{user.username} is blocking you!") if user.has_muted(v): abort(403, f"@{user.username} is muting notifications from you, so you can't chat with them!") sq = g.db.query(Chat.id).join(ChatMembership, ChatMembership.chat_id == Chat.id).filter(ChatMembership.user_id.in_((v.id, user.id))).group_by(Chat.id).having(func.count(Chat.id) == 2).subquery() existing = g.db.query(Chat.id).join(ChatMembership, ChatMembership.chat_id == Chat.id).filter(Chat.id == sq.c.id).group_by(Chat.id).having(func.count(Chat.id) == 2).one_or_none() if existing: return redirect(f"/chat/{existing.id}") chat = Chat(name=f"@{v.username}, @{user.username}") g.db.add(chat) g.db.flush() chat_membership = ChatMembership( user_id=v.id, chat_id=chat.id, ) g.db.add(chat_membership) chat_membership = ChatMembership( user_id=user.id, chat_id=chat.id, ) chat_membership.created_utc += 1 g.db.add(chat_membership) return redirect(f"/chat/{chat.id}") @app.get("/chat/") @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400) @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID) @auth_required def chat(v, chat_id): chat = g.db.get(Chat, chat_id) if not chat: abort(404, "Chat not found!") if chat.id == 1: if not v.allowed_in_chat: abort(403, f"To prevent spam, you'll need {TRUESCORE_MINIMUM} truescore (this is {TRUESCORE_MINIMUM} votes, either up or down, on any threads or comments you've made) in order to access chat. Sorry! I love you 💖") else: membership = g.db.query(ChatMembership).filter_by(user_id=v.id, chat_id=chat_id).one_or_none() if v.admin_level < PERMS['VIEW_CHATS'] and not membership: abort(403, "You're not a member of this chat!") displayed_messages = reversed(g.db.query(ChatMessage).join(ChatMessage.user).filter(or_(User.id == v.id, User.shadowbanned == None)).options(joinedload(ChatMessage.quoted_message)).filter(ChatMessage.chat_id == chat.id).order_by(ChatMessage.id.desc()).limit(250).all()) displayed_messages = {m.id: m for m in displayed_messages} if chat.id == 1: sorted_memberships = None else: if not session.get("GLOBAL") and membership: membership.notification = False g.db.add(membership) g.db.commit() #to clear notif count query = g.db.query(ChatMembership).filter_by(chat_id=chat.id) sorted_memberships = query.filter(ChatMembership.user_id != chat.owner_id).join(ChatMembership.user).order_by(func.lower(User.username)).all() owner_membership = query.filter_by(user_id=chat.owner_id).one_or_none() if owner_membership: sorted_memberships = [owner_membership] + sorted_memberships orgy = get_running_orgy(v, chat_id) if orgy: orgies = g.db.query(Orgy).filter_by(chat_id=chat_id).order_by(Orgy.start_utc).all() return render_template("orgy.html", v=v, messages=displayed_messages, chat=chat, sorted_memberships=sorted_memberships, orgy=orgy, orgies=orgies) return render_template("chat.html", v=v, messages=displayed_messages, chat=chat, sorted_memberships=sorted_memberships) @app.post("/chat//name") @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400) @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID) @auth_required def change_chat_name(v, chat_id): chat = g.db.get(Chat, chat_id) if not chat: abort(404, "Chat not found!") if v.id != chat.owner_id: abort(403, "Only the chat owner can change its name!") new_name = request.values.get("new_name").strip() if len(new_name) > 40: abort(400, "New name is too long (max 40 characters)") chat.name = new_name g.db.add(chat) return redirect(f"/chat/{chat.id}") @app.post("/chat//leave") @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400) @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID) @auth_required def leave_chat(v, chat_id): chat = g.db.get(Chat, chat_id) if not chat: abort(404, "Chat not found!") if v.id == chat.owner_id: abort(403, "The chat owner can't leave it!") membership = g.db.query(ChatMembership).filter_by(user_id=v.id, chat_id=chat_id).one_or_none() if not membership: abort(400, "You're not a member of this chat!") g.db.delete(membership) chat_leave = ChatLeave( user_id=v.id, chat_id=chat_id, ) g.db.add(chat_leave) return {"message": "Chat left successfully!"} @app.get("/chat//orgies") @auth_required def orgy_control(v, chat_id): chat = g.db.get(Chat, chat_id) if not chat: abort(404, "Chat not found!") if chat.id == 1: if v.admin_level < PERMS["ORGIES"]: abort(403, "Your admin-level is not sufficient enough for this action!") elif v.id != chat.owner_id: abort(403, "Only the chat owner can manage its orgies!") orgies = g.db.query(Orgy).filter_by(chat_id=chat_id).order_by(Orgy.start_utc).all() return render_template("orgy_control.html", v=v, orgies=orgies, chat=chat) @app.post("/chat//schedule_orgy") @auth_required def schedule_orgy(v, chat_id): chat = g.db.get(Chat, chat_id) if not chat: abort(404, "Chat not found!") if chat.id == 1: if v.admin_level < PERMS["ORGIES"]: abort(403, "Your admin-level is not sufficient enough for this action!") elif v.id != chat.owner_id: abort(403, "Only the chat owner can manage its orgies!") link = request.values.get("link", "").strip() title = request.values.get("title", "").strip() start_utc = request.values.get("start_utc", "").strip() if not link: abort(400, "A link is required!") if not title: abort(400, "A title is required!") normalized_link = normalize_url(link) if start_utc: start_utc = int(start_utc) else: last_orgy = g.db.query(Orgy).order_by(Orgy.start_utc.desc()).first() if last_orgy and last_orgy.end_utc: start_utc = last_orgy.end_utc else: start_utc = int(time.time()) end_utc = None if bare_youtube_regex.match(normalized_link): orgy_type = 'youtube' data, _ = get_youtube_id_and_t(normalized_link) if YOUTUBE_KEY != DEFAULT_CONFIG_VALUE: req = requests.get(f"https://www.googleapis.com/youtube/v3/videos?id={data}&key={YOUTUBE_KEY}&part=contentDetails", headers=HEADERS, timeout=5).json() duration = req['items'][0]['contentDetails']['duration'] if duration != 'P0D': duration = isodate.parse_duration(duration).total_seconds() end_utc = int(start_utc + duration) orgy_type = 'file' ydl_opts = { "quiet": True, "simulate": True, "forceurl": True, 'format': 'b', 'proxy': PROXY_URL } with yt_dlp.YoutubeDL(ydl_opts) as ydl: info = ydl.extract_info(f"https://www.youtube.com/watch?v={data}") data = info["url"] elif rumble_regex.match(normalized_link): orgy_type = 'rumble' data = normalized_link elif twitch_regex.match(normalized_link): orgy_type = 'twitch' data = twitch_regex.search(normalized_link).group(3) elif any((normalized_link.lower().endswith(f'.{x}') for x in VIDEO_FORMATS)): domain = tldextract.extract(normalized_link).registered_domain if domain != 'archive.org' and not is_safe_url(normalized_link): abort(400, "For linking an mp4 file, you can only use archive.org or one of the approved media hosts outlined in https://rdrama.net/formatting#approved") orgy_type = 'file' data = normalized_link video_info = ffmpeg.probe(data, headers=f'referer:{SITE_FULL}/chat') duration = float(video_info['streams'][0]['duration']) if duration == 2.0: raise if duration > 3000: duration += 300 #account for break end_utc = int(start_utc + duration) else: abort(400) data = data.strip() orgy = Orgy( title=title, type=orgy_type, data=data, start_utc=start_utc, end_utc=end_utc, chat_id=chat_id, ) g.db.add(orgy) if chat.id == 1: ma = ModAction( kind="schedule_orgy", user_id=v.id, _note=f'{title}', ) g.db.add(ma) if AEVANN_ID and v.id != AEVANN_ID: text = f"@{v.username} has started [{title}]({normalized_link}) in [{chat.name}](/chat/{chat.id})" send_repeatable_notification(AEVANN_ID, text) return redirect(f"/chat/{chat_id}/orgies") @app.post("/chat//remove_orgy/") @auth_required def remove_orgy(v, created_utc, chat_id): chat = g.db.get(Chat, chat_id) if not chat: abort(404, "Chat not found!") if chat.id == 1: if v.admin_level < PERMS["ORGIES"]: abort(403, "Your admin-level is not sufficient enough for this action!") elif v.id != chat.owner_id: abort(403, "Only the chat owner can manage its orgies!") orgy = g.db.query(Orgy).filter_by(created_utc=created_utc).one_or_none() if orgy: if chat.id == 1: ma = ModAction( kind="remove_orgy", user_id=v.id, _note=f'{orgy.title}', ) g.db.add(ma) started = orgy.started g.db.delete(orgy) g.db.commit() if started: requests.post(f'http://localhost:5001/chat/{chat_id}/refresh_chat', headers={"User-Agent": "refreshing_chat", "Host": SITE}) return {"message": "Orgy stopped successfully!"}