##// END OF EJS Templates
Parse tripcode for post sync
neko259 -
r1557:68d42ef1 default
parent child Browse files
Show More
@@ -1,160 +1,161 b''
1 1 import logging
2 2
3 3 from datetime import datetime, timedelta, date
4 4 from datetime import time as dtime
5 5
6 6 from django.db import models, transaction
7 7 from django.utils import timezone
8 8
9 9 import boards
10 10
11 11 from boards.models.user import Ban
12 12 from boards.mdx_neboard import Parser
13 13 from boards.models import PostImage, Attachment
14 14 from boards import utils
15 15
16 16 __author__ = 'neko259'
17 17
18 18 IMAGE_TYPES = (
19 19 'jpeg',
20 20 'jpg',
21 21 'png',
22 22 'bmp',
23 23 'gif',
24 24 )
25 25
26 26 POSTS_PER_DAY_RANGE = 7
27 27 NO_IP = '0.0.0.0'
28 28
29 29
30 30 class PostManager(models.Manager):
31 31 @transaction.atomic
32 32 def create_post(self, title: str, text: str, file=None, thread=None,
33 33 ip=NO_IP, tags: list=None, opening_posts: list=None,
34 34 tripcode='', monochrome=False, images=[]):
35 35 """
36 36 Creates new post
37 37 """
38 38
39 39 if thread is not None and thread.is_archived():
40 40 raise Exception('Cannot post into an archived thread')
41 41
42 42 if not utils.is_anonymous_mode():
43 43 is_banned = Ban.objects.filter(ip=ip).exists()
44 44 else:
45 45 is_banned = False
46 46
47 47 # TODO Raise specific exception and catch it in the views
48 48 if is_banned:
49 49 raise Exception("This user is banned")
50 50
51 51 if not tags:
52 52 tags = []
53 53 if not opening_posts:
54 54 opening_posts = []
55 55
56 56 posting_time = timezone.now()
57 57 new_thread = False
58 58 if not thread:
59 59 thread = boards.models.thread.Thread.objects.create(
60 60 bump_time=posting_time, last_edit_time=posting_time,
61 61 monochrome=monochrome)
62 62 list(map(thread.tags.add, tags))
63 63 boards.models.thread.Thread.objects.process_oldest_threads()
64 64 new_thread = True
65 65
66 66 pre_text = Parser().preparse(text)
67 67
68 68 post = self.create(title=title,
69 69 text=pre_text,
70 70 pub_time=posting_time,
71 71 poster_ip=ip,
72 72 thread=thread,
73 73 last_edit_time=posting_time,
74 74 tripcode=tripcode,
75 75 opening=new_thread)
76 76 post.threads.add(thread)
77 77
78 78 logger = logging.getLogger('boards.post.create')
79 79
80 80 logger.info('Created post [{}] with text [{}] by {}'.format(post,
81 81 post.get_text(),post.poster_ip))
82 82
83 83 if file:
84 84 self._add_file_to_post(file, post)
85 85 for image in images:
86 86 post.images.add(image)
87 87
88 88 post.connect_threads(opening_posts)
89 89 post.set_global_id()
90 90
91 91 # Thread needs to be bumped only when the post is already created
92 92 if not new_thread:
93 93 thread.last_edit_time = posting_time
94 94 thread.bump()
95 95 thread.save()
96 96
97 97 return post
98 98
99 99 def delete_posts_by_ip(self, ip):
100 100 """
101 101 Deletes all posts of the author with same IP
102 102 """
103 103
104 104 posts = self.filter(poster_ip=ip)
105 105 for post in posts:
106 106 post.delete()
107 107
108 108 @utils.cached_result()
109 109 def get_posts_per_day(self) -> float:
110 110 """
111 111 Gets average count of posts per day for the last 7 days
112 112 """
113 113
114 114 day_end = date.today()
115 115 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
116 116
117 117 day_time_start = timezone.make_aware(datetime.combine(
118 118 day_start, dtime()), timezone.get_current_timezone())
119 119 day_time_end = timezone.make_aware(datetime.combine(
120 120 day_end, dtime()), timezone.get_current_timezone())
121 121
122 122 posts_per_period = float(self.filter(
123 123 pub_time__lte=day_time_end,
124 124 pub_time__gte=day_time_start).count())
125 125
126 126 ppd = posts_per_period / POSTS_PER_DAY_RANGE
127 127
128 128 return ppd
129 129
130 130 @transaction.atomic
131 131 def import_post(self, title: str, text: str, pub_time: str, global_id,
132 opening_post=None, tags=list(), files=list()):
132 opening_post=None, tags=list(), files=list(),
133 tripcode=None):
133 134 is_opening = opening_post is None
134 135 if is_opening:
135 136 thread = boards.models.thread.Thread.objects.create(
136 137 bump_time=pub_time, last_edit_time=pub_time)
137 138 list(map(thread.tags.add, tags))
138 139 else:
139 140 thread = opening_post.get_thread()
140 141
141 142 post = self.create(title=title, text=text,
142 143 pub_time=pub_time,
143 144 poster_ip=NO_IP,
144 145 last_edit_time=pub_time,
145 146 global_id=global_id,
146 147 opening=is_opening,
147 thread=thread)
148 thread=thread, tripcode=tripcode)
148 149
149 150 # TODO Add files
150 151 for file in files:
151 152 self._add_file_to_post(file, post)
152 153
153 154 post.threads.add(thread)
154 155
155 156 def _add_file_to_post(self, file, post):
156 157 file_type = file.name.split('.')[-1].lower()
157 158 if file_type in IMAGE_TYPES:
158 159 post.images.add(PostImage.objects.create_with_hash(file))
159 160 else:
160 161 post.attachments.add(Attachment.objects.create_with_hash(file))
@@ -1,275 +1,276 b''
1 1 import xml.etree.ElementTree as et
2 2
3 3 from boards.models.attachment.downloaders import download
4 4 from boards.utils import get_file_mimetype, get_file_hash
5 5 from django.db import transaction
6 6 from boards.models import KeyPair, GlobalId, Signature, Post, Tag
7 7
8 8 EXCEPTION_NODE = 'Sync node returned an error: {}'
9 9 EXCEPTION_OP = 'Load the OP first'
10 10 EXCEPTION_DOWNLOAD = 'File was not downloaded'
11 11 EXCEPTION_HASH = 'File hash does not match attachment hash'
12 12 EXCEPTION_SIGNATURE = 'Invalid model signature for {}'
13 13 ENCODING_UNICODE = 'unicode'
14 14
15 15 TAG_MODEL = 'model'
16 16 TAG_REQUEST = 'request'
17 17 TAG_RESPONSE = 'response'
18 18 TAG_ID = 'id'
19 19 TAG_STATUS = 'status'
20 20 TAG_MODELS = 'models'
21 21 TAG_TITLE = 'title'
22 22 TAG_TEXT = 'text'
23 23 TAG_THREAD = 'thread'
24 24 TAG_PUB_TIME = 'pub-time'
25 25 TAG_SIGNATURES = 'signatures'
26 26 TAG_SIGNATURE = 'signature'
27 27 TAG_CONTENT = 'content'
28 28 TAG_ATTACHMENTS = 'attachments'
29 29 TAG_ATTACHMENT = 'attachment'
30 30 TAG_TAGS = 'tags'
31 31 TAG_TAG = 'tag'
32 32 TAG_ATTACHMENT_REFS = 'attachment-refs'
33 33 TAG_ATTACHMENT_REF = 'attachment-ref'
34 34 TAG_TRIPCODE = 'tripcode'
35 35
36 36 TYPE_GET = 'get'
37 37
38 38 ATTR_VERSION = 'version'
39 39 ATTR_TYPE = 'type'
40 40 ATTR_NAME = 'name'
41 41 ATTR_VALUE = 'value'
42 42 ATTR_MIMETYPE = 'mimetype'
43 43 ATTR_KEY = 'key'
44 44 ATTR_REF = 'ref'
45 45 ATTR_URL = 'url'
46 46
47 47 STATUS_SUCCESS = 'success'
48 48
49 49
50 50 class SyncException(Exception):
51 51 pass
52 52
53 53
54 54 class SyncManager:
55 55 @staticmethod
56 56 def generate_response_get(model_list: list):
57 57 response = et.Element(TAG_RESPONSE)
58 58
59 59 status = et.SubElement(response, TAG_STATUS)
60 60 status.text = STATUS_SUCCESS
61 61
62 62 models = et.SubElement(response, TAG_MODELS)
63 63
64 64 for post in model_list:
65 65 model = et.SubElement(models, TAG_MODEL)
66 66 model.set(ATTR_NAME, 'post')
67 67
68 68 global_id = post.global_id
69 69
70 70 images = post.images.all()
71 71 attachments = post.attachments.all()
72 72 if global_id.content:
73 73 model.append(et.fromstring(global_id.content))
74 74 if len(images) > 0 or len(attachments) > 0:
75 75 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
76 76 for image in images:
77 77 SyncManager._attachment_to_xml(
78 78 None, attachment_refs, image.image.file,
79 79 image.hash, image.image.url)
80 80 for file in attachments:
81 81 SyncManager._attachment_to_xml(
82 82 None, attachment_refs, file.file.file,
83 83 file.hash, file.file.url)
84 84 else:
85 85 content_tag = et.SubElement(model, TAG_CONTENT)
86 86
87 87 tag_id = et.SubElement(content_tag, TAG_ID)
88 88 global_id.to_xml_element(tag_id)
89 89
90 90 title = et.SubElement(content_tag, TAG_TITLE)
91 91 title.text = post.title
92 92
93 93 text = et.SubElement(content_tag, TAG_TEXT)
94 94 text.text = post.get_sync_text()
95 95
96 96 thread = post.get_thread()
97 97 if post.is_opening():
98 98 tag_tags = et.SubElement(content_tag, TAG_TAGS)
99 99 for tag in thread.get_tags():
100 100 tag_tag = et.SubElement(tag_tags, TAG_TAG)
101 101 tag_tag.text = tag.name
102 102 else:
103 103 tag_thread = et.SubElement(content_tag, TAG_THREAD)
104 104 thread_id = et.SubElement(tag_thread, TAG_ID)
105 105 thread.get_opening_post().global_id.to_xml_element(thread_id)
106 106
107 107 pub_time = et.SubElement(content_tag, TAG_PUB_TIME)
108 108 pub_time.text = str(post.get_pub_time_str())
109 109
110 110 if post.tripcode:
111 111 tripcode = et.SubElement(content_tag, TAG_TRIPCODE)
112 112 tripcode.text = post.tripcode
113 113
114 114 if len(images) > 0 or len(attachments) > 0:
115 115 attachments_tag = et.SubElement(content_tag, TAG_ATTACHMENTS)
116 116 attachment_refs = et.SubElement(model, TAG_ATTACHMENT_REFS)
117 117
118 118 for image in images:
119 119 SyncManager._attachment_to_xml(
120 120 attachments_tag, attachment_refs, image.image.file,
121 121 image.hash, image.image.url)
122 122 for file in attachments:
123 123 SyncManager._attachment_to_xml(
124 124 attachments_tag, attachment_refs, file.file.file,
125 125 file.hash, file.file.url)
126 126
127 127 global_id.content = et.tostring(content_tag, ENCODING_UNICODE)
128 128 global_id.save()
129 129
130 130 signatures_tag = et.SubElement(model, TAG_SIGNATURES)
131 131 post_signatures = global_id.signature_set.all()
132 132 if post_signatures:
133 133 signatures = post_signatures
134 134 else:
135 135 key = KeyPair.objects.get(public_key=global_id.key)
136 136 signature = Signature(
137 137 key_type=key.key_type,
138 138 key=key.public_key,
139 139 signature=key.sign(global_id.content),
140 140 global_id=global_id,
141 141 )
142 142 signature.save()
143 143 signatures = [signature]
144 144 for signature in signatures:
145 145 signature_tag = et.SubElement(signatures_tag, TAG_SIGNATURE)
146 146 signature_tag.set(ATTR_TYPE, signature.key_type)
147 147 signature_tag.set(ATTR_VALUE, signature.signature)
148 148 signature_tag.set(ATTR_KEY, signature.key)
149 149
150 150 return et.tostring(response, ENCODING_UNICODE)
151 151
152 152 @staticmethod
153 153 @transaction.atomic
154 154 def parse_response_get(response_xml, hostname):
155 155 tag_root = et.fromstring(response_xml)
156 156 tag_status = tag_root.find(TAG_STATUS)
157 157 if STATUS_SUCCESS == tag_status.text:
158 158 tag_models = tag_root.find(TAG_MODELS)
159 159 for tag_model in tag_models:
160 160 tag_content = tag_model.find(TAG_CONTENT)
161 161
162 162 content_str = et.tostring(tag_content, ENCODING_UNICODE)
163 163 signatures = SyncManager._verify_model(content_str, tag_model)
164 164
165 165 tag_id = tag_content.find(TAG_ID)
166 166 global_id, exists = GlobalId.from_xml_element(tag_id)
167 167
168 168 if exists:
169 169 print('Post with same ID already exists')
170 170 else:
171 171 global_id.content = content_str
172 172 global_id.save()
173 173 for signature in signatures:
174 174 signature.global_id = global_id
175 175 signature.save()
176 176
177 177 title = tag_content.find(TAG_TITLE).text or ''
178 178 text = tag_content.find(TAG_TEXT).text or ''
179 179 pub_time = tag_content.find(TAG_PUB_TIME).text
180 tripcode = tag_content.find(TAG_TRIPCODE).text
180 181
181 182 thread = tag_content.find(TAG_THREAD)
182 183 tags = []
183 184 if thread:
184 185 thread_id = thread.find(TAG_ID)
185 186 op_global_id, exists = GlobalId.from_xml_element(thread_id)
186 187 if exists:
187 188 opening_post = Post.objects.get(global_id=op_global_id)
188 189 else:
189 190 raise SyncException(EXCEPTION_OP)
190 191 else:
191 192 opening_post = None
192 193 tag_tags = tag_content.find(TAG_TAGS)
193 194 for tag_tag in tag_tags:
194 195 tag, created = Tag.objects.get_or_create(
195 196 name=tag_tag.text)
196 197 tags.append(tag)
197 198
198 199 # TODO Check that the replied posts are already present
199 200 # before adding new ones
200 201
201 202 files = []
202 203 tag_attachments = tag_content.find(TAG_ATTACHMENTS) or list()
203 204 tag_refs = tag_model.find(TAG_ATTACHMENT_REFS)
204 205 for attachment in tag_attachments:
205 206 tag_ref = tag_refs.find("{}[@ref='{}']".format(
206 207 TAG_ATTACHMENT_REF, attachment.text))
207 208 url = tag_ref.get(ATTR_URL)
208 209 attached_file = download(hostname + url)
209 210 if attached_file is None:
210 211 raise SyncException(EXCEPTION_DOWNLOAD)
211 212
212 213 hash = get_file_hash(attached_file)
213 214 if hash != attachment.text:
214 215 raise SyncException(EXCEPTION_HASH)
215 216
216 217 files.append(attached_file)
217 218
218 219 Post.objects.import_post(
219 220 title=title, text=text, pub_time=pub_time,
220 221 opening_post=opening_post, tags=tags,
221 global_id=global_id, files=files)
222 global_id=global_id, files=files, tripcode=tripcode)
222 223 else:
223 224 raise SyncException(EXCEPTION_NODE.format(tag_status.text))
224 225
225 226 @staticmethod
226 227 def generate_response_pull():
227 228 response = et.Element(TAG_RESPONSE)
228 229
229 230 status = et.SubElement(response, TAG_STATUS)
230 231 status.text = STATUS_SUCCESS
231 232
232 233 models = et.SubElement(response, TAG_MODELS)
233 234
234 235 for post in Post.objects.all():
235 236 tag_id = et.SubElement(models, TAG_ID)
236 237 post.global_id.to_xml_element(tag_id)
237 238
238 239 return et.tostring(response, ENCODING_UNICODE)
239 240
240 241 @staticmethod
241 242 def _verify_model(content_str, tag_model):
242 243 """
243 244 Verifies all signatures for a single model.
244 245 """
245 246
246 247 signatures = []
247 248
248 249 tag_signatures = tag_model.find(TAG_SIGNATURES)
249 250 for tag_signature in tag_signatures:
250 251 signature_type = tag_signature.get(ATTR_TYPE)
251 252 signature_value = tag_signature.get(ATTR_VALUE)
252 253 signature_key = tag_signature.get(ATTR_KEY)
253 254
254 255 signature = Signature(key_type=signature_type,
255 256 key=signature_key,
256 257 signature=signature_value)
257 258
258 259 if not KeyPair.objects.verify(signature, content_str):
259 260 raise SyncException(EXCEPTION_SIGNATURE.format(content_str))
260 261
261 262 signatures.append(signature)
262 263
263 264 return signatures
264 265
265 266 @staticmethod
266 267 def _attachment_to_xml(tag_attachments, tag_refs, file, hash, url):
267 268 if tag_attachments is not None:
268 269 mimetype = get_file_mimetype(file)
269 270 attachment = et.SubElement(tag_attachments, TAG_ATTACHMENT)
270 271 attachment.set(ATTR_MIMETYPE, mimetype)
271 272 attachment.text = hash
272 273
273 274 attachment_ref = et.SubElement(tag_refs, TAG_ATTACHMENT_REF)
274 275 attachment_ref.set(ATTR_REF, hash)
275 276 attachment_ref.set(ATTR_URL, url)
General Comments 0
You need to be logged in to leave comments. Login now