mirror of
https://github.com/samuelclay/NewsBlur.git
synced 2025-08-31 21:41:33 +00:00
Converting replies from original messages to reply ids. Also delaying emails on new replies and reshares so authors can change typos. Also dong a much better job with checking for dupe emails so no dupes ever get sent out.
This commit is contained in:
parent
dcbb305f6e
commit
8eb106612a
10 changed files with 102 additions and 33 deletions
33
apps/social/migrations/0003_reply_id.py
Normal file
33
apps/social/migrations/0003_reply_id.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
|
from south.db import db
|
||||||
|
from south.v2 import DataMigration
|
||||||
|
from django.db import models
|
||||||
|
from bson.objectid import ObjectId
|
||||||
|
from apps.social.models import MSharedStory
|
||||||
|
|
||||||
|
class Migration(DataMigration):
|
||||||
|
|
||||||
|
def forwards(self, orm):
|
||||||
|
stories = MSharedStory.objects.filter(has_replies=True)
|
||||||
|
story_count = stories.count()
|
||||||
|
print " ---> %s stories with replies" % story_count
|
||||||
|
for i, story in enumerate(stories):
|
||||||
|
print " ---> %s/%s: %s replies" % (i+1, story_count, len(story.replies))
|
||||||
|
replies = []
|
||||||
|
for reply in story.replies:
|
||||||
|
if not reply.reply_id:
|
||||||
|
reply.reply_id = ObjectId()
|
||||||
|
replies.append(reply)
|
||||||
|
story.replies = replies
|
||||||
|
story.save()
|
||||||
|
|
||||||
|
def backwards(self, orm):
|
||||||
|
"Write your backwards methods here."
|
||||||
|
|
||||||
|
models = {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
complete_apps = ['social']
|
||||||
|
symmetrical = True
|
|
@ -1025,13 +1025,16 @@ class MSocialSubscription(mongo.Document):
|
||||||
return social_subs
|
return social_subs
|
||||||
|
|
||||||
class MCommentReply(mongo.EmbeddedDocument):
|
class MCommentReply(mongo.EmbeddedDocument):
|
||||||
user_id = mongo.IntField()
|
reply_id = mongo.ObjectIdField()
|
||||||
publish_date = mongo.DateTimeField()
|
user_id = mongo.IntField()
|
||||||
comments = mongo.StringField()
|
publish_date = mongo.DateTimeField()
|
||||||
liking_users = mongo.ListField(mongo.IntField())
|
comments = mongo.StringField()
|
||||||
|
email_sent = mongo.BooleanField(default=False)
|
||||||
|
liking_users = mongo.ListField(mongo.IntField())
|
||||||
|
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
reply = {
|
reply = {
|
||||||
|
'reply_id': self.reply_id,
|
||||||
'user_id': self.user_id,
|
'user_id': self.user_id,
|
||||||
'publish_date': relative_timesince(self.publish_date),
|
'publish_date': relative_timesince(self.publish_date),
|
||||||
'date': self.publish_date,
|
'date': self.publish_date,
|
||||||
|
@ -1041,6 +1044,8 @@ class MCommentReply(mongo.EmbeddedDocument):
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
'ordering': ['publish_date'],
|
'ordering': ['publish_date'],
|
||||||
|
'id_field': 'reply_id',
|
||||||
|
'allow_inheritance': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1069,6 +1074,7 @@ class MSharedStory(mongo.Document):
|
||||||
mute_email_users = mongo.ListField(mongo.IntField())
|
mute_email_users = mongo.ListField(mongo.IntField())
|
||||||
liking_users = mongo.ListField(mongo.IntField())
|
liking_users = mongo.ListField(mongo.IntField())
|
||||||
emailed_reshare = mongo.BooleanField(default=False)
|
emailed_reshare = mongo.BooleanField(default=False)
|
||||||
|
emailed_replies = mongo.ListField(mongo.ObjectIdField())
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
'collection': 'shared_stories',
|
'collection': 'shared_stories',
|
||||||
|
@ -1079,7 +1085,11 @@ class MSharedStory(mongo.Document):
|
||||||
'ordering': ['shared_date'],
|
'ordering': ['shared_date'],
|
||||||
'allow_inheritance': False,
|
'allow_inheritance': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
user = User.objects.get(pk=self.user_id)
|
||||||
|
return "%s: %s (%s)%s%s" % (user.username, self.story_title[:20], self.story_feed_id, ': ' if self.has_comments else '', self.comments[:20])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def guid_hash(self):
|
def guid_hash(self):
|
||||||
return hashlib.sha1(self.story_guid).hexdigest()
|
return hashlib.sha1(self.story_guid).hexdigest()
|
||||||
|
@ -1475,19 +1485,33 @@ class MSharedStory(mongo.Document):
|
||||||
user_ids.add(self.user_id)
|
user_ids.add(self.user_id)
|
||||||
|
|
||||||
return list(user_ids)
|
return list(user_ids)
|
||||||
|
|
||||||
|
def reply_for_id(self, reply_id):
|
||||||
|
for reply in self.replies:
|
||||||
|
if reply.reply_id == reply_id:
|
||||||
|
return reply
|
||||||
|
|
||||||
|
def send_emails_for_new_reply(self, reply_id):
|
||||||
|
if reply_id in self.emailed_replies:
|
||||||
|
logging.debug(" ***> Already sent reply email: %s on %s" % (reply_id, self))
|
||||||
|
return
|
||||||
|
|
||||||
def send_emails_for_new_reply(self, reply_user_id):
|
reply = self.reply_for_id(reply_id)
|
||||||
|
if not reply:
|
||||||
|
logging.debug(" ***> Reply doesn't exist: %s on %s" % (reply_id, self))
|
||||||
|
return
|
||||||
|
|
||||||
notify_user_ids = self.notify_user_ids()
|
notify_user_ids = self.notify_user_ids()
|
||||||
if reply_user_id in notify_user_ids:
|
if reply.user_id in notify_user_ids:
|
||||||
notify_user_ids.remove(reply_user_id)
|
notify_user_ids.remove(reply.user_id)
|
||||||
reply_user = User.objects.get(pk=reply_user_id)
|
reply_user = User.objects.get(pk=reply.user_id)
|
||||||
reply_user_profile = MSocialProfile.get_user(reply_user_id)
|
reply_user_profile = MSocialProfile.get_user(reply.user_id)
|
||||||
sent_emails = 0
|
sent_emails = 0
|
||||||
|
|
||||||
story_feed = Feed.objects.get(pk=self.story_feed_id)
|
story_feed = Feed.objects.get(pk=self.story_feed_id)
|
||||||
comment = self.comments_with_author()
|
comment = self.comments_with_author()
|
||||||
profile_user_ids = set([comment['user_id']])
|
profile_user_ids = set([comment['user_id']])
|
||||||
reply_user_ids = [reply['user_id'] for reply in comment['replies']]
|
reply_user_ids = list(r['user_id'] for r in comment['replies'])
|
||||||
profile_user_ids = profile_user_ids.union(reply_user_ids)
|
profile_user_ids = profile_user_ids.union(reply_user_ids)
|
||||||
if self.source_user_id:
|
if self.source_user_id:
|
||||||
profile_user_ids.add(self.source_user_id)
|
profile_user_ids.add(self.source_user_id)
|
||||||
|
@ -1534,8 +1558,15 @@ class MSharedStory(mongo.Document):
|
||||||
sent_emails, len(notify_user_ids),
|
sent_emails, len(notify_user_ids),
|
||||||
'' if len(notify_user_ids) == 1 else 's',
|
'' if len(notify_user_ids) == 1 else 's',
|
||||||
self.story_title[:30]))
|
self.story_title[:30]))
|
||||||
|
|
||||||
|
self.emailed_replies.append(reply.reply_id)
|
||||||
|
self.save()
|
||||||
|
|
||||||
def send_email_for_reshare(self):
|
def send_email_for_reshare(self):
|
||||||
|
if self.emailed_reshare:
|
||||||
|
logging.debug(" ***> Already sent reply email: %s" % self)
|
||||||
|
return
|
||||||
|
|
||||||
reshare_user = User.objects.get(pk=self.user_id)
|
reshare_user = User.objects.get(pk=self.user_id)
|
||||||
reshare_user_profile = MSocialProfile.get_user(self.user_id)
|
reshare_user_profile = MSocialProfile.get_user(self.user_id)
|
||||||
original_user = User.objects.get(pk=self.source_user_id)
|
original_user = User.objects.get(pk=self.source_user_id)
|
||||||
|
|
|
@ -19,9 +19,9 @@ class EmailNewFollower(Task):
|
||||||
|
|
||||||
class EmailCommentReplies(Task):
|
class EmailCommentReplies(Task):
|
||||||
|
|
||||||
def run(self, shared_story_id, reply_user_id):
|
def run(self, shared_story_id, reply_id):
|
||||||
shared_story = MSharedStory.objects.get(id=shared_story_id)
|
shared_story = MSharedStory.objects.get(id=shared_story_id)
|
||||||
shared_story.send_emails_for_new_reply(reply_user_id)
|
shared_story.send_emails_for_new_reply(reply_id)
|
||||||
|
|
||||||
class EmailStoryReshares(Task):
|
class EmailStoryReshares(Task):
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import time
|
||||||
import datetime
|
import datetime
|
||||||
import zlib
|
import zlib
|
||||||
import random
|
import random
|
||||||
|
from bson.objectid import ObjectId
|
||||||
from django.shortcuts import get_object_or_404, render_to_response
|
from django.shortcuts import get_object_or_404, render_to_response
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
@ -364,8 +365,8 @@ def mark_story_as_shared(request):
|
||||||
if service not in shared_story.posted_to_services:
|
if service not in shared_story.posted_to_services:
|
||||||
PostToService.delay(shared_story_id=shared_story.id, service=service)
|
PostToService.delay(shared_story_id=shared_story.id, service=service)
|
||||||
|
|
||||||
if shared_story.source_user_id and not shared_story.emailed_reshare and shared_story.comments:
|
if shared_story.source_user_id and shared_story.comments:
|
||||||
EmailStoryReshares.delay(shared_story_id=shared_story.id)
|
EmailStoryReshares.apply_async(kwargs=dict(shared_story_id=shared_story.id), countdown=60)
|
||||||
|
|
||||||
if format == 'html':
|
if format == 'html':
|
||||||
stories = MSharedStory.attach_users_to_stories(stories, profiles)
|
stories = MSharedStory.attach_users_to_stories(stories, profiles)
|
||||||
|
@ -435,6 +436,7 @@ def save_comment_reply(request):
|
||||||
comment_user_id = request.POST['comment_user_id']
|
comment_user_id = request.POST['comment_user_id']
|
||||||
reply_comments = request.POST.get('reply_comments')
|
reply_comments = request.POST.get('reply_comments')
|
||||||
original_message = request.POST.get('original_message')
|
original_message = request.POST.get('original_message')
|
||||||
|
reply_id = request.POST.get('reply_id')
|
||||||
format = request.REQUEST.get('format', 'json')
|
format = request.REQUEST.get('format', 'json')
|
||||||
|
|
||||||
if not reply_comments:
|
if not reply_comments:
|
||||||
|
@ -448,12 +450,14 @@ def save_comment_reply(request):
|
||||||
reply.publish_date = datetime.datetime.now()
|
reply.publish_date = datetime.datetime.now()
|
||||||
reply.comments = reply_comments
|
reply.comments = reply_comments
|
||||||
|
|
||||||
if original_message:
|
if reply_id:
|
||||||
replies = []
|
replies = []
|
||||||
for story_reply in shared_story.replies:
|
for story_reply in shared_story.replies:
|
||||||
if (story_reply.user_id == reply.user_id and
|
if (story_reply.user_id == reply.user_id and
|
||||||
strip_tags(story_reply.comments) == original_message):
|
story_reply.reply_id == ObjectId(reply_id)):
|
||||||
reply.publish_date = story_reply.publish_date
|
reply.publish_date = story_reply.publish_date
|
||||||
|
reply.reply_id = story_reply.reply_id
|
||||||
|
original_message = story_reply.comments
|
||||||
replies.append(reply)
|
replies.append(reply)
|
||||||
else:
|
else:
|
||||||
replies.append(story_reply)
|
replies.append(story_reply)
|
||||||
|
@ -461,6 +465,7 @@ def save_comment_reply(request):
|
||||||
logging.user(request, "~FCUpdating comment reply in ~FM%s: ~SB~FB%s~FM" % (
|
logging.user(request, "~FCUpdating comment reply in ~FM%s: ~SB~FB%s~FM" % (
|
||||||
shared_story.story_title[:20], reply_comments[:30]))
|
shared_story.story_title[:20], reply_comments[:30]))
|
||||||
else:
|
else:
|
||||||
|
reply.reply_id = ObjectId()
|
||||||
logging.user(request, "~FCReplying to comment in: ~FM%s: ~SB~FB%s~FM" % (
|
logging.user(request, "~FCReplying to comment in: ~FM%s: ~SB~FB%s~FM" % (
|
||||||
shared_story.story_title[:20], reply_comments[:30]))
|
shared_story.story_title[:20], reply_comments[:30]))
|
||||||
shared_story.replies.append(reply)
|
shared_story.replies.append(reply)
|
||||||
|
@ -468,7 +473,7 @@ def save_comment_reply(request):
|
||||||
|
|
||||||
comment = shared_story.comments_with_author()
|
comment = shared_story.comments_with_author()
|
||||||
profile_user_ids = set([comment['user_id']])
|
profile_user_ids = set([comment['user_id']])
|
||||||
reply_user_ids = [reply['user_id'] for reply in comment['replies']]
|
reply_user_ids = list(r['user_id'] for r in comment['replies'])
|
||||||
profile_user_ids = profile_user_ids.union(reply_user_ids)
|
profile_user_ids = profile_user_ids.union(reply_user_ids)
|
||||||
profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids))
|
profiles = MSocialProfile.objects.filter(user_id__in=list(profile_user_ids))
|
||||||
profiles = [profile.to_json(compact=True) for profile in profiles]
|
profiles = [profile.to_json(compact=True) for profile in profiles]
|
||||||
|
@ -496,8 +501,9 @@ def save_comment_reply(request):
|
||||||
original_message=original_message,
|
original_message=original_message,
|
||||||
social_feed_id=comment_user_id,
|
social_feed_id=comment_user_id,
|
||||||
story_id=story_id)
|
story_id=story_id)
|
||||||
if not original_message:
|
|
||||||
EmailCommentReplies.delay(shared_story_id=shared_story.id, reply_user_id=request.user.pk)
|
EmailCommentReplies.apply_async(kwargs=dict(shared_story_id=shared_story.id,
|
||||||
|
reply_id=reply.reply_id), countdown=60)
|
||||||
|
|
||||||
if format == 'html':
|
if format == 'html':
|
||||||
comment = MSharedStory.attach_users_to_comment(comment, profiles)
|
comment = MSharedStory.attach_users_to_comment(comment, profiles)
|
||||||
|
|
|
@ -260,7 +260,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, original_message, callback, error_callback) {
|
save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, reply_id, callback, error_callback) {
|
||||||
var pre_callback = _.bind(function(data) {
|
var pre_callback = _.bind(function(data) {
|
||||||
if (data.user_profiles) {
|
if (data.user_profiles) {
|
||||||
this.add_user_profiles(data.user_profiles);
|
this.add_user_profiles(data.user_profiles);
|
||||||
|
@ -273,7 +273,7 @@ NEWSBLUR.AssetModel = Backbone.Router.extend({
|
||||||
story_feed_id: story_feed_id,
|
story_feed_id: story_feed_id,
|
||||||
comment_user_id: comment_user_id,
|
comment_user_id: comment_user_id,
|
||||||
reply_comments: reply_comments,
|
reply_comments: reply_comments,
|
||||||
original_message: original_message
|
reply_id: reply_id
|
||||||
}, pre_callback, error_callback);
|
}, pre_callback, error_callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -95,13 +95,13 @@ NEWSBLUR.SocialPageAssets = Backbone.Router.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, original_message, callback, error_callback) {
|
save_comment_reply: function(story_id, story_feed_id, comment_user_id, reply_comments, reply_id, callback, error_callback) {
|
||||||
this.make_request('/social/save_comment_reply', {
|
this.make_request('/social/save_comment_reply', {
|
||||||
story_id: story_id,
|
story_id: story_id,
|
||||||
story_feed_id: story_feed_id,
|
story_feed_id: story_feed_id,
|
||||||
comment_user_id: comment_user_id,
|
comment_user_id: comment_user_id,
|
||||||
reply_comments: reply_comments,
|
reply_comments: reply_comments,
|
||||||
original_message: original_message,
|
reply_id: reply_id,
|
||||||
format: 'html'
|
format: 'html'
|
||||||
}, callback, error_callback, {
|
}, callback, error_callback, {
|
||||||
request_type: 'POST'
|
request_type: 'POST'
|
||||||
|
|
|
@ -42,7 +42,7 @@ NEWSBLUR.Views.StoryCommentReply = Backbone.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
edit_reply: function() {
|
edit_reply: function() {
|
||||||
this.options.comment.open_reply({is_editing: true, $reply: this.$el});
|
this.options.comment.open_reply({is_editing: true, reply: this.model, $reply: this.$el});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -132,15 +132,13 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
|
||||||
var $form = $.make('div', { className: 'NB-story-comment-reply NB-story-comment-reply-form' }, [
|
var $form = $.make('div', { className: 'NB-story-comment-reply NB-story-comment-reply-form' }, [
|
||||||
$.make('img', { className: 'NB-story-comment-reply-photo', src: current_user.get('photo_url') }),
|
$.make('img', { className: 'NB-story-comment-reply-photo', src: current_user.get('photo_url') }),
|
||||||
$.make('div', { className: 'NB-story-comment-username NB-story-comment-reply-username' }, current_user.get('username')),
|
$.make('div', { className: 'NB-story-comment-username NB-story-comment-reply-username' }, current_user.get('username')),
|
||||||
$.make('input', { type: 'text', className: 'NB-input NB-story-comment-reply-comments' }),
|
$.make('input', { type: 'text', className: 'NB-input NB-story-comment-reply-comments', value: options.reply && options.reply.get("comments") }),
|
||||||
$.make('div', { className: 'NB-modal-submit-button NB-modal-submit-green' }, options.is_editing ? 'Save' : 'Post')
|
$.make('div', { className: 'NB-modal-submit-button NB-modal-submit-green' }, options.is_editing ? 'Save' : 'Post')
|
||||||
]);
|
]);
|
||||||
this.remove_social_comment_reply_form();
|
this.remove_social_comment_reply_form();
|
||||||
|
|
||||||
if (options.is_editing && options.$reply) {
|
if (options.is_editing && options.$reply) {
|
||||||
var original_message = $('.NB-story-comment-reply-content', options.$reply).text();
|
$form.data('reply_id', options.reply.get("reply_id"));
|
||||||
$('input', $form).val(original_message);
|
|
||||||
$form.data('original_message', original_message);
|
|
||||||
options.$reply.hide().addClass('NB-story-comment-reply-hidden');
|
options.$reply.hide().addClass('NB-story-comment-reply-hidden');
|
||||||
options.$reply.after($form);
|
options.$reply.after($form);
|
||||||
} else {
|
} else {
|
||||||
|
@ -172,7 +170,7 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
|
||||||
var $submit = $(".NB-modal-submit-button", $form);
|
var $submit = $(".NB-modal-submit-button", $form);
|
||||||
var comment_user_id = this.model.get('user_id');
|
var comment_user_id = this.model.get('user_id');
|
||||||
var comment_reply = $('.NB-story-comment-reply-comments', $form).val();
|
var comment_reply = $('.NB-story-comment-reply-comments', $form).val();
|
||||||
var original_message = $form.data('original_message');
|
var reply_id = $form.data('reply_id');
|
||||||
|
|
||||||
if (!comment_reply || comment_reply.length <= 1) {
|
if (!comment_reply || comment_reply.length <= 1) {
|
||||||
this.remove_social_comment_reply_form();
|
this.remove_social_comment_reply_form();
|
||||||
|
@ -189,7 +187,7 @@ NEWSBLUR.Views.StoryComment = Backbone.View.extend({
|
||||||
$submit.addClass('NB-disabled').text('Posting...');
|
$submit.addClass('NB-disabled').text('Posting...');
|
||||||
NEWSBLUR.assets.save_comment_reply(this.options.story.id, this.options.story.get('story_feed_id'),
|
NEWSBLUR.assets.save_comment_reply(this.options.story.id, this.options.story.get('story_feed_id'),
|
||||||
comment_user_id, comment_reply,
|
comment_user_id, comment_reply,
|
||||||
original_message,
|
reply_id,
|
||||||
_.bind(function(data) {
|
_.bind(function(data) {
|
||||||
if (this.options.on_social_page) {
|
if (this.options.on_social_page) {
|
||||||
this.options.story_comments_view.replace_comment(this.model.get('user_id'), data);
|
this.options.story_comments_view.replace_comment(this.model.get('user_id'), data);
|
||||||
|
|
|
@ -9,6 +9,7 @@ from django.http import HttpResponse, HttpResponseForbidden, Http404
|
||||||
from django.core.mail import mail_admins
|
from django.core.mail import mail_admins
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
from mongoengine.queryset import QuerySet as MongoQuerySet
|
from mongoengine.queryset import QuerySet as MongoQuerySet
|
||||||
|
from bson.objectid import ObjectId
|
||||||
import sys
|
import sys
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ def json_encode(data, *args, **kwargs):
|
||||||
# Same as for lists above.
|
# Same as for lists above.
|
||||||
elif isinstance(data, dict):
|
elif isinstance(data, dict):
|
||||||
ret = _dict(data)
|
ret = _dict(data)
|
||||||
elif isinstance(data, Decimal):
|
elif isinstance(data, (Decimal, ObjectId)):
|
||||||
# json.dumps() cant handle Decimal
|
# json.dumps() cant handle Decimal
|
||||||
ret = str(data)
|
ret = str(data)
|
||||||
elif isinstance(data, models.query.QuerySet):
|
elif isinstance(data, models.query.QuerySet):
|
||||||
|
|
|
@ -78,7 +78,7 @@ def _mongodb_decode_wire_protocol(message):
|
||||||
try:
|
try:
|
||||||
if message[zidx:]:
|
if message[zidx:]:
|
||||||
msg = bson.decode_all(message[zidx:])
|
msg = bson.decode_all(message[zidx:])
|
||||||
except Exception, e:
|
except InvalidBSON:
|
||||||
msg = 'invalid bson'
|
msg = 'invalid bson'
|
||||||
return { 'op': op, 'collection': collection_name,
|
return { 'op': op, 'collection': collection_name,
|
||||||
'msg_id': msg_id, 'skip': skip, 'limit': limit,
|
'msg_id': msg_id, 'skip': skip, 'limit': limit,
|
||||||
|
|
Loading…
Add table
Reference in a new issue