def notify_user_auth(user, code): if user.telegram_id: send_telegram_message( chat=Chat(id=user.telegram_id), text= f"<pre>{code.code}</pre> — ваш одноразовый код для входа в Клуб", )
def notify_profile_needs_review(user, intro): admin_profile_url = settings.APP_HOST + reverse("admin_profile", kwargs={"user_slug": user.slug}) send_telegram_message( chat=ADMIN_CHAT, text=render_html_message("moderator_new_member_review.html", user=user, intro=intro), reply_markup=telegram.InlineKeyboardMarkup([ [ telegram.InlineKeyboardButton("👍 Впустить", callback_data=f"approve_user:{user.id}") ], [ telegram.InlineKeyboardButton("❌️ Плохое интро", callback_data=f"reject_user_intro:{user.id}"), ], [ telegram.InlineKeyboardButton("❌️ Недостаточно данных", callback_data=f"reject_user_data:{user.id}"), ], [ telegram.InlineKeyboardButton("❌️ Агрессия", callback_data=f"reject_user_aggression:{user.id}"), ], [ telegram.InlineKeyboardButton("❌️ Слишком общее", callback_data=f"reject_user_general:{user.id}"), ], [ telegram.InlineKeyboardButton("❌️ Плохое имя", callback_data=f"reject_user_name:{user.id}"), ], [ telegram.InlineKeyboardButton("✏️ Написать юзеру", url=admin_profile_url), ] ]) )
def notify_post_approved(post): if post.author.telegram_id: send_telegram_message( chat=Chat(id=post.author.telegram_id), text=render_html_message("post_approved.html", post=post), parse_mode=telegram.ParseMode.HTML, )
def notify_users(users, template, post): for username in users: user = User.objects.filter(slug=username).first() if user and user.telegram_id: send_telegram_message( chat=Chat(id=user.telegram_id), text=render_html_message(template, post=post), )
def notify_admin_user_on_ban(user, days, reason): banned_user_profile_url = settings.APP_HOST + reverse("profile", kwargs={"user_slug": user.slug}) send_telegram_message( chat=ADMIN_CHAT, text=f"⛔️ <b>Юзер <a href=\"{banned_user_profile_url}\">{user.full_name}</a> " f"({user.slug}) забанен на {days} дней</b>" f"\n\nПричина: <i>{reason}</i>" )
def notify_admin_user_on_mute(user_from, user_to, comment): user_from_profile_url = settings.APP_HOST + reverse("profile", kwargs={"user_slug": user_from.slug}) user_to_profile_url = settings.APP_HOST + reverse("profile", kwargs={"user_slug": user_to.slug}) send_telegram_message( chat=ADMIN_CHAT, text=f"<b>Кого-то замьютили</b> 🤕" f"\n\n<a href=\"{user_from_profile_url}\">{user_from.full_name}</a> ({user_from.slug}) считает, " f"что <a href=\"{user_to_profile_url}\">{user_to.full_name}</a> ({user_to.slug}) не место в Клубе " f"и замьютил его. \n\nВот почему: <i>{comment}</i>" )
def notify_user_profile_approved(user): user_profile_url = settings.APP_HOST + reverse("profile", kwargs={"user_slug": user.slug}) if user.telegram_id: send_telegram_message( chat=Chat(id=user.telegram_id), text=f"🚀 Подравляем, вы прошли модерацию. Добро пожаловать в Клуб!" f"\n\nМожно пойти заполнить другие смешные поля в профиле:" f"\n\n{user_profile_url}" )
def notify_user_profile_rejected(user: User, reason: UserRejectReason): try: text = render_html_message(f"rejected/{reason.value}.html", user=user) except TemplateDoesNotExist: text = render_html_message(f"rejected/intro.html", user=user) if user.telegram_id: send_telegram_message( chat=Chat(id=user.telegram_id), text=text, )
def async_label_changed(post): moderator_template = "moderator_label_removed.html" if post.label_code is None else "moderator_label_set.html" send_telegram_message( chat=ADMIN_CHAT, text=render_html_message(moderator_template, post=post), parse_mode=telegram.ParseMode.HTML, ) if post.label_code is not None and post.label['notify'] and post.author.telegram_id: send_telegram_message( chat=Chat(id=post.author.telegram_id), text=render_html_message("post_label.html", post=post), parse_mode=telegram.ParseMode.HTML, )
def notify_post_rejected(post, reason): try: text = render_html_message(f"post_rejected/{reason.value}.html", post=post) except TemplateDoesNotExist: text = render_html_message(f"post_rejected/draft.html", post=post) if post.author.telegram_id: send_telegram_message( chat=Chat(id=post.author.telegram_id), text=text, parse_mode=telegram.ParseMode.HTML, )
def async_create_or_update_comment(comment): notified_user_ids = set() # notify post subscribers post_subscribers = PostSubscription.post_subscribers(comment.post) for post_subscriber in post_subscribers: if post_subscriber.user.telegram_id and comment.author != post_subscriber.user: template = "comment_to_post.html" if post_subscriber.user == comment.post.author else "comment_to_post_announce.html" send_telegram_message( chat=Chat(id=post_subscriber.user.telegram_id), text=render_html_message(template, comment=comment), ) notified_user_ids.add(post_subscriber.user.id) # on reply — notify thread author (do not notify yourself) if comment.reply_to: thread_author = comment.reply_to.author if thread_author.telegram_id and comment.author != thread_author and thread_author.id not in notified_user_ids: send_telegram_message( chat=Chat(id=thread_author.telegram_id), text=render_html_message("comment_to_thread.html", comment=comment), ) notified_user_ids.add(thread_author.id) # post top level comments to online channel if not comment.reply_to: send_telegram_message( chat=CLUB_ONLINE, text=render_html_message("comment_to_post_announce.html", comment=comment), ) # parse @nicknames and notify their users for username in USERNAME_RE.findall(comment.text): if username == settings.MODERATOR_USERNAME: send_telegram_message( chat=ADMIN_CHAT, text=render_html_message("moderator_mention.html", comment=comment), ) continue user = User.objects.filter(slug=username).first() if user and user.telegram_id and user.id not in notified_user_ids: send_telegram_message( chat=Chat(id=user.telegram_id), text=render_html_message("comment_mention.html", comment=comment), ) notified_user_ids.add(user.id)
def announce_in_club_chats(post): if post.topic and post.topic.chat_id: # announce to the topic chat send_telegram_message( chat=Chat(id=post.topic.chat_id), text=render_html_message("channel_post_announce.html", post=post), parse_mode=telegram.ParseMode.HTML, disable_preview=True, ) else: # announce to public chat send_telegram_message( chat=CLUB_CHAT, text=render_html_message("channel_post_announce.html", post=post), parse_mode=telegram.ParseMode.HTML, disable_preview=True, )
def handle(self, *args, **options): new_posts = Post.visible_objects()\ .filter( is_approved_by_moderator=True, published_at__gte=datetime.utcnow() - timedelta(hours=24), )\ .exclude(type=Post.TYPE_INTRO)\ .order_by("-upvotes")[:6] send_telegram_message( chat=ADMIN_CHAT, text=render_html_message("good_morning.html", posts=new_posts, greetings=random.choice(DUMB_GREETINGS)), ) self.stdout.write("Done 🥙")
def announce_in_club_channel(post, announce_text=None, image=None): if not announce_text: announce_text = render_html_message("channel_post_announce.html", post=post) if image: send_telegram_image( chat=CLUB_CHANNEL, image_url=image, text=announce_text, ) else: send_telegram_message( chat=CLUB_CHANNEL, text=announce_text, disable_preview=False, parse_mode=telegram.ParseMode.HTML, )
def async_create_or_update_post(post, is_created): if not post.is_approved_by_moderator: send_telegram_message( chat=ADMIN_CHAT, text=render_html_message("moderator_new_post_review.html", post=post), reply_markup=telegram.InlineKeyboardMarkup([ *[ [telegram.InlineKeyboardButton(f"❌ {title}", callback_data=f"reject_post_{reason}:{post.id}")] for reason, title in REJECT_POST_REASONS.get(post.type) or [] ], [ telegram.InlineKeyboardButton("❌ В черновики", callback_data=f"reject_post:{post.id}"), telegram.InlineKeyboardButton("😕 Так себе", callback_data=f"forgive_post:{post.id}"), ], [ telegram.InlineKeyboardButton("👍 Одобрить", callback_data=f"approve_post:{post.id}"), ], ]) ) # post to online channel send_telegram_message( chat=CLUB_ONLINE, text=render_html_message("channel_post_announce.html", post=post), parse_mode=telegram.ParseMode.HTML, disable_preview=True, ) # only for newly created posts if post.is_visible and (is_created or "is_visible" in post.changed_fields): notified_user_ids = set() # parse @nicknames and notify mentioned users for username in USERNAME_RE.findall(post.text): user = User.objects.filter(slug=username).first() if user and user.telegram_id and user.id not in notified_user_ids: send_telegram_message( chat=Chat(id=user.telegram_id), text=render_html_message("post_mention.html", post=post), ) notified_user_ids.add(user.id) # notify friends about new posts friends = Friend.friends_for_user(post.author) for friend in friends: if friend.user_from.telegram_id \ and friend.is_subscribed_to_posts \ and friend.user_from.id not in notified_user_ids: send_telegram_message( chat=Chat(id=friend.user_from.telegram_id), text=render_html_message("friend_post.html", post=post), ) notified_user_ids.add(friend.user_from.id)
def async_create_or_update_post(post, is_created): if not post.is_approved_by_moderator: send_telegram_message( chat=ADMIN_CHAT, text=render_html_message("moderator_new_post_review.html", post=post), reply_markup=telegram.InlineKeyboardMarkup( [[ telegram.InlineKeyboardButton( "👍 Одобрить", callback_data=f"approve_post:{post.id}"), telegram.InlineKeyboardButton( "😕 Так себе", callback_data=f"forgive_post:{post.id}"), ], [ telegram.InlineKeyboardButton( "❌ В черновики", callback_data=f"delete_post:{post.id}"), ]])) # post to online channel send_telegram_message( chat=CLUB_ONLINE, text=render_html_message("channel_post_announce.html", post=post), parse_mode=telegram.ParseMode.HTML, disable_preview=True, ) # parse @nicknames and notify mentioned users (only if post is visible) if post.is_visible and (is_created or "is_visible" in post.changed_fields): notified_user_ids = set() for username in USERNAME_RE.findall(post.text): user = User.objects.filter(slug=username).first() if user and user.telegram_id and user.id not in notified_user_ids: send_telegram_message( chat=Chat(id=user.telegram_id), text=render_html_message("post_mention.html", post=post), ) notified_user_ids.add(user.id)
def do_user_admin_actions(request, user, data): if not request.me.is_moderator: raise AccessDenied() # Roles if data["role"] and is_role_manageable_by_user(data["role"], request.me): role = data["role"] if data["role_action"] == "add" and role not in user.roles: user.roles.append(role) user.save() if data["role_action"] == "delete" and role in user.roles: user.roles.remove(role) user.save() # Hats if data["remove_hat"]: user.hat = None user.save() if data["add_hat"]: if data["new_hat"]: hat = HATS.get(data["new_hat"]) if hat: user.hat = {"code": data["new_hat"], **hat} user.save() else: user.hat = { "code": "custom", "title": data["new_hat_name"], "icon": data["new_hat_icon"], "color": data["new_hat_color"], } user.save() # Achievements if data["new_achievement"]: achievement = Achievement.objects.filter( code=data["new_achievement"]).first() if achievement: UserAchievement.objects.get_or_create( user=user, achievement=achievement, ) # Ban if data["is_banned"]: if not user.is_god: user.is_banned_until = datetime.utcnow() + timedelta( days=data["ban_days"]) user.save() if data["ban_days"] > 0: send_banned_email(user, days=data["ban_days"], reason=data["ban_reason"]) notify_admin_user_on_ban(user, days=data["ban_days"], reason=data["ban_reason"]) # Unmoderate if data["is_rejected"]: user.moderation_status = User.MODERATION_STATUS_REJECTED user.save() send_unmoderated_email(user) notify_admin_user_unmoderate(user) # Delete account if data["delete_account"] and request.me.is_god: user.membership_expires_at = datetime.utcnow() user.is_banned_until = datetime.utcnow() + timedelta(days=5000) # cancel recurring payments cancel_all_stripe_subscriptions(user.stripe_id) # mark user for deletion user.deleted_at = datetime.utcnow() user.save() # remove sessions Session.objects.filter(user=user).delete() # notify user send_delete_account_confirm_email(user=user, ) # notify admins send_telegram_message( chat=ADMIN_CHAT, text= f"💀 Юзер был удален админами: {settings.APP_HOST}/user/{user.slug}/", ) # Ping if data["ping"]: send_ping_email(user, message=data["ping"]) notify_user_ping(user, message=data["ping"]) notify_admin_user_ping(user, message=data["ping"]) # Add more days of membership if data["add_membership_days"] and int(data["add_membership_days"]) > 0: gift_membership_days( days=data["add_membership_days"], from_user=request.me, to_user=user, deduct_from_original_user=False, ) send_telegram_message( chat=ADMIN_CHAT, text= f"🎁 <b>Юзеру {user.slug} добавили {data['add_membership_days']} дней членства</b>", ) return redirect("profile", user.slug)
def notify_admin_user_unmoderate(user): send_telegram_message( chat=ADMIN_CHAT, text=f"💣 <b>Юзера {user.slug} размодерировали</b>" )
def notify_admin_user_ping(user, message): send_telegram_message( chat=ADMIN_CHAT, text=f"🛎 <b>Юзера {user.slug} пинганули:</b> {message}" )
def notify_user_ping(user, message): if user.telegram_id: send_telegram_message( chat=Chat(id=user.telegram_id), text=f"👋 <b>Вам письмо от модераторов Клуба:</b> {message}" )
def async_create_or_update_comment(comment): notified_user_ids = set() # notify post subscribers post_subscribers = PostSubscription.post_subscribers(comment.post) for post_subscriber in post_subscribers: if post_subscriber.user.telegram_id and comment.author != post_subscriber.user: # respect subscription type (i.e. all comments vs top level only) if post_subscriber.type == PostSubscription.TYPE_ALL_COMMENTS \ or (post_subscriber.type == PostSubscription.TYPE_TOP_LEVEL_ONLY and not comment.reply_to_id): send_telegram_message( chat=Chat(id=post_subscriber.user.telegram_id), text=render_html_message("comment_to_post.html", comment=comment), ) notified_user_ids.add(post_subscriber.user.id) # notify thread author on reply (note: do not notify yourself) if comment.reply_to: thread_author = comment.reply_to.author if thread_author.telegram_id and comment.author != thread_author and thread_author.id not in notified_user_ids: send_telegram_message( chat=Chat(id=thread_author.telegram_id), text=render_html_message("comment_to_thread.html", comment=comment), ) notified_user_ids.add(thread_author.id) # post top level comments to online channel if not comment.reply_to and comment.post.is_visible and comment.post.is_visible_in_feeds: send_telegram_message( chat=CLUB_ONLINE, text=render_html_message("comment_to_post.html", comment=comment), ) # notify friends about your comments (not replies) if not comment.reply_to: friends = Friend.friends_for_user(comment.author) for friend in friends: if friend.user_from.telegram_id \ and friend.is_subscribed_to_comments \ and friend.user_from.id not in notified_user_ids: send_telegram_message( chat=Chat(id=friend.user_from.telegram_id), text=render_html_message("friend_comment.html", comment=comment), ) notified_user_ids.add(friend.user_from.id) # parse @nicknames and notify their users for username in USERNAME_RE.findall(comment.text): if username == settings.MODERATOR_USERNAME: send_telegram_message( chat=ADMIN_CHAT, text=render_html_message("moderator_mention.html", comment=comment), ) continue user = User.objects.filter(slug=username).first() if user and user.telegram_id and user.id not in notified_user_ids: send_telegram_message( chat=Chat(id=user.telegram_id), text=render_html_message("comment_mention.html", comment=comment), ) notified_user_ids.add(user.id)
def handle(self, *args, **options): # render digest using a special html endpoint try: digest = generate_weekly_digest() except NotFound: log.error("Weekly digest is empty") return # get a version without "unsubscribe" footer for posting on home page digest_without_footer = generate_weekly_digest(no_footer=True) # save digest as a post issue = (datetime.utcnow() - settings.LAUNCH_DATE).days // 7 year, week, _ = (datetime.utcnow() - timedelta(days=7)).isocalendar() post, _ = Post.objects.update_or_create( slug=f"{year}_{week}", type=Post.TYPE_WEEKLY_DIGEST, defaults=dict( author=User.objects.filter(slug="vas3k").first(), title=f"Клубный журнал. Итоги недели. Выпуск #{issue}", html=digest_without_footer, text=digest_without_footer, is_pinned_until=datetime.utcnow() + timedelta(days=1), is_visible=True, is_public=False, )) # make it searchable SearchIndex.update_post_index(post) # sending emails subscribed_users = User.objects\ .filter( is_email_verified=True, membership_expires_at__gte=datetime.utcnow() - timedelta(days=14), moderation_status=User.MODERATION_STATUS_APPROVED, )\ .exclude(email_digest_type=User.EMAIL_DIGEST_TYPE_NOPE)\ .exclude(is_email_unsubscribed=True) for user in subscribed_users: self.stdout.write(f"Sending to {user.email}...") if not options.get("production") and user.email not in dict( settings.ADMINS).values(): self.stdout.write( "Test mode. Use --production to send the digest to all users" ) continue try: digest = digest\ .replace("%username%", user.slug)\ .replace("%user_id%", str(user.id))\ .replace("%secret_code%", base64.b64encode(user.secret_hash.encode("utf-8")).decode()) send_club_email( recipient=user.email, subject=f"🤘 Клубный журнал. Итоги недели. Выпуск #{issue}", html=digest, tags=["weekly_digest", f"weekly_digest_{issue}"]) except Exception as ex: self.stdout.write(f"Sending to {user.email} failed: {ex}") log.exception(f"Error while sending an email to {user.email}") continue if options.get("production"): # flush digest intro and title for next time GodSettings.objects.update(digest_intro=None, digest_title=None) send_telegram_message( chat=CLUB_CHANNEL, text=render_html_message("weekly_digest_announce.html", post=post), disable_preview=False, parse_mode=telegram.ParseMode.HTML, ) self.stdout.write("Done 🥙")
def handle(self, *args, **options): # render digest using a special html endpoint digest_url = "https://vas3k.club" + reverse("render_weekly_digest") self.stdout.write(f"Generating digest: {digest_url}") digest_html_response = requests.get(digest_url) if digest_html_response.status_code > 400: log.error("Weekly digest error: bad status code", extra={"html": digest_html_response.text}) return digest_html = digest_html_response.text no_footer_digest_response = requests.get(digest_url, params={"no_footer": 1}) if no_footer_digest_response.status_code > 400: log.error("Weekly digest without footer error: bad status code", extra={"html": no_footer_digest_response.text}) return no_footer_digest_html = no_footer_digest_response.text # save digest as a post issue = (datetime.utcnow() - settings.LAUNCH_DATE).days // 7 year, week, _ = (datetime.utcnow() - timedelta(days=7)).isocalendar() post, _ = Post.objects.update_or_create( slug=f"{year}_{week}", type=Post.TYPE_WEEKLY_DIGEST, defaults=dict( author=User.objects.filter(slug="vas3k").first(), title=f"Клубный журнал. Итоги недели. Выпуск #{issue}", html=no_footer_digest_html, text=no_footer_digest_html, is_pinned_until=datetime.utcnow() + timedelta(days=1), is_visible=True, is_public=False, )) SearchIndex.update_post_index(post) # sending emails subscribed_users = User.objects\ .filter( is_email_verified=True, membership_expires_at__gte=datetime.utcnow() - timedelta(days=14), moderation_status=User.MODERATION_STATUS_APPROVED )\ .exclude(email_digest_type=User.EMAIL_DIGEST_TYPE_NOPE)\ .exclude(is_email_unsubscribed=True) for user in subscribed_users: self.stdout.write(f"Sending to {user.email}...") if not options.get("production") and user.email != "*****@*****.**": self.stdout.write( "Test mode. Use --production to send the digest to all users" ) continue try: user_digest_html = str(digest_html) user_digest_html = user_digest_html\ .replace("%username%", user.slug)\ .replace("%user_id%", str(user.id))\ .replace("%secret_code%", base64.b64encode(user.secret_hash.encode("utf-8")).decode()) send_club_email( recipient=user.email, subject=f"🤘 Клубный журнал. Итоги недели. Выпуск #{issue}", html=user_digest_html, tags=["weekly_digest", f"weekly_digest_{issue}"]) except Exception as ex: self.stdout.write(f"Sending to {user.email} failed: {ex}") log.exception(f"Error while sending an email to {user.email}") continue if options.get("production"): # flush digest intro for next time GodSettings.objects.update(digest_intro=None) send_telegram_message( chat=CLUB_CHANNEL, text=render_html_message("weekly_digest_announce.html", post=post), disable_preview=True, parse_mode=telegram.ParseMode.HTML, ) self.stdout.write("Done 🥙")
def do_user_admin_actions(request, user, data): if not request.me.is_moderator: raise AccessDenied() # Hats if data["remove_hat"]: user.hat = None user.save() if data["add_hat"]: if data["new_hat"]: hat = HATS.get(data["new_hat"]) if hat: user.hat = {"code": data["new_hat"], **hat} user.save() else: user.hat = { "code": "custom", "title": data["new_hat_name"], "icon": data["new_hat_icon"], "color": data["new_hat_color"], } user.save() # Achievements if data["new_achievement"]: achievement = Achievement.objects.filter( code=data["new_achievement"]).first() if achievement: UserAchievement.objects.get_or_create( user=user, achievement=achievement, ) # Ban if data["is_banned"]: if not user.is_god: user.is_banned_until = datetime.utcnow() + timedelta( days=data["ban_days"]) user.save() if data["ban_days"] > 0: send_banned_email(user, days=data["ban_days"], reason=data["ban_reason"]) # Unban if data["is_unbanned"]: user.is_banned_until = None user.save() # Unmoderate if data["is_rejected"]: user.moderation_status = User.MODERATION_STATUS_REJECTED user.save() send_unmoderated_email(user) notify_admin_user_unmoderate(user) # Delete account if data["delete_account"] and request.me.is_god: user.membership_expires_at = datetime.utcnow() user.is_banned_until = datetime.utcnow() + timedelta(days=5000) # cancel recurring payments cancel_all_stripe_subscriptions(user.stripe_id) # mark user for deletion user.deleted_at = datetime.utcnow() user.save() # remove sessions Session.objects.filter(user=user).delete() # notify user send_delete_account_confirm_email(user=user, ) # notify admins send_telegram_message( chat=ADMIN_CHAT, text= f"💀 Юзер был удален админами: {settings.APP_HOST}/user/{user.slug}/", ) # Ping if data["ping"]: send_ping_email(user, message=data["ping"]) notify_user_ping(user, message=data["ping"]) notify_admin_user_ping(user, message=data["ping"]) return redirect("profile", user.slug)