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_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_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 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 async_create_or_update_achievement(user_achievement: UserAchievement): user = user_achievement.user achievement = user_achievement.achievement # messages if user.is_club_member and user.telegram_id: if achievement.image: send_telegram_image( chat=Chat(id=user.telegram_id), image_url=achievement.image, text=render_html_message("achievement.html", user=user, achievement=achievement), ) # emails if not user.is_email_unsubscribed: email_template = loader.get_template("emails/achievement.html") send_club_email(recipient=user.email, subject=f"🏆 Вы получили ачивку «{achievement.name}»", html=email_template.render({ "user": user, "achievement": achievement }), tags=["achievement"])
def create_or_update_post(sender, instance, created, **kwargs): if instance.type == Post.TYPE_INTRO and instance.is_visible and "text" in instance.changed_fields: # send intro updates to moderators async_task( send_telegram_message, chat=ADMIN_CHAT, text=render_html_message("moderator_updated_intro.html", user=instance.author, intro=instance), ) return None if "label_code" in instance.changed_fields: async_task(async_label_changed, instance) if "coauthors" in instance.changed_fields: async_task(async_coauthors_changed, instance) if not created and "is_visible" not in instance.changed_fields: return None # we're not interested in updates, only if they change visibility if instance.type in {Post.TYPE_WEEKLY_DIGEST}: return None # skip emails if not instance.is_visible: return None # skip drafts too async_task(async_create_or_update_post, instance, created)
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_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 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 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 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_badge(user_badge: UserBadge): to_user = user_badge.to_user # messages if to_user.is_member and to_user.telegram_id: send_telegram_image( chat=Chat(id=to_user.telegram_id), image_url= f"{settings.APP_HOST}/static/images/badges/big/{user_badge.badge.code}.png", text=render_html_message("badge.html", user_badge=user_badge), ) # emails if not to_user.is_email_unsubscribed: email_template = loader.get_template("emails/badge.html") send_club_email( recipient=to_user.email, subject=f"🏅 Вам подарили награду «{user_badge.badge.title}»", html=email_template.render({"user_badge": user_badge}), tags=["badge"])
def command_top(update: Update, context: CallbackContext) -> None: # Top posts top_posts = Post.visible_objects()\ .filter(published_at__gte=datetime.utcnow() - TOP_TIMEDELTA)\ .filter(Q(is_approved_by_moderator=True) | Q(upvotes__gte=settings.COMMUNITY_APPROVE_UPVOTES)) \ .exclude(type__in=[Post.TYPE_INTRO, Post.TYPE_WEEKLY_DIGEST])\ .order_by("-upvotes")[:5] # Hot posts hot_posts = Post.visible_objects()\ .exclude(type__in=[Post.TYPE_INTRO, Post.TYPE_WEEKLY_DIGEST]) \ .exclude(id__in=[p.id for p in top_posts]) \ .order_by("-hotness")[:3] # Top intros top_intros = Post.visible_objects()\ .filter(type=Post.TYPE_INTRO, published_at__gte=datetime.utcnow() - TOP_TIMEDELTA)\ .select_related("author")\ .order_by("-upvotes")[:3] # Top comments top_comment = Comment.visible_objects() \ .filter(created_at__gte=datetime.utcnow() - TOP_TIMEDELTA) \ .filter(is_deleted=False)\ .exclude(post__type=Post.TYPE_BATTLE) \ .select_related("author") \ .order_by("-upvotes") \ .first() update.effective_chat.send_message( render_html_message( template="top.html", top_posts=top_posts, hot_posts=hot_posts, top_intros=top_intros, top_comment=top_comment, ), parse_mode=ParseMode.HTML, disable_web_page_preview=True, )
def command_random(update: Update, context: CallbackContext) -> None: post = None attempt = 0 while not post and attempt < 5: attempt += 1 random_date = settings.LAUNCH_DATE + timedelta( seconds=randint(0, int((datetime.utcnow() - settings.LAUNCH_DATE).total_seconds())), ) post = Post.visible_objects() \ .filter(published_at__lte=random_date, published_at__gte=random_date - timedelta(days=2)) \ .filter(is_approved_by_moderator=True) \ .exclude(type__in=[Post.TYPE_INTRO, Post.TYPE_WEEKLY_DIGEST]) \ .order_by("?") \ .first() update.effective_chat.send_message( render_html_message("channel_post_announce.html", post=post), parse_mode=ParseMode.HTML, disable_web_page_preview=True, )
def announce_post(request, post_slug): post = get_object_or_404(Post, slug=post_slug) initial = { "text": render_html_message("channel_post_announce.html", post=post), "image": extract_any_image(post), } if request.method == "POST": form = PostAnnounceForm(request.POST, initial=initial) if form.is_valid(): announce_in_club_channel(post=post, announce_text=form.cleaned_data["text"], image=form.cleaned_data["image"] if form.cleaned_data["with_image"] else None) return render(request, "message.html", {"title": "Запощено ✅"}) else: form = PostAnnounceForm(initial=initial) return render(request, "admin/simple_form.html", { "title": "Анонсировать пост на канале", "post": post, "form": form })
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 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)