Esempio n. 1
0
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,
        )
Esempio n. 2
0
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)
Esempio n. 3
0
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,
        )
Esempio n. 4
0
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,
        )
Esempio n. 5
0
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)
Esempio n. 6
0
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,
        )
Esempio n. 7
0
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"])
Esempio n. 8
0
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)
Esempio n. 9
0
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),
            ]
        ])
    )
Esempio n. 10
0
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),
            )
Esempio n. 11
0
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,
        )
Esempio n. 12
0
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)
Esempio n. 13
0
    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 🥙")
Esempio n. 14
0
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,
        )
Esempio n. 15
0
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"])
Esempio n. 16
0
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,
    )
Esempio n. 17
0
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,
    )
Esempio n. 18
0
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
    })
Esempio n. 19
0
    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 🥙")
Esempio n. 20
0
    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 🥙")
Esempio n. 21
0
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)