/
character.py
442 lines (361 loc) · 15.3 KB
/
character.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
from __future__ import absolute_import
import arrow
import re
from libweasyl import ratings
from libweasyl import staff
from libweasyl import text
from weasyl import api
from weasyl import blocktag
from weasyl import comment
from weasyl import define
from weasyl import favorite
from weasyl import files
from weasyl import frienduser
from weasyl import ignoreuser
from weasyl import image
from weasyl import macro
from weasyl import media
from weasyl import profile
from weasyl import report
from weasyl import searchtag
from weasyl import thumbnail
from weasyl import welcome
from weasyl.error import PostgresError, WeasylError
_MEGABYTE = 1048576
def create(userid, character, friends, tags, thumbfile, submitfile):
# Make temporary filenames
tempthumb = files.get_temporary(userid, "thumb")
tempsubmit = files.get_temporary(userid, "char")
# Determine filesizes
thumbsize = len(thumbfile)
submitsize = len(submitfile)
# Check invalid title or rating
if not character.char_name:
raise WeasylError("characterNameInvalid")
elif not character.rating:
raise WeasylError("ratingInvalid")
profile.check_user_rating_allowed(userid, character.rating)
# Write temporary thumbnail file
if thumbsize:
files.write(tempthumb, thumbfile)
thumbextension = files.get_extension_for_category(
thumbfile, macro.ART_SUBMISSION_CATEGORY)
else:
thumbextension = None
# Write temporary submission file
if submitsize:
files.write(tempsubmit, submitfile)
submitextension = files.get_extension_for_category(
submitfile, macro.ART_SUBMISSION_CATEGORY)
else:
submitextension = None
# Check invalid file data
if not submitsize:
files.clear_temporary(userid)
raise WeasylError("submitSizeZero")
elif submitsize > 10 * _MEGABYTE:
files.clear_temporary(userid)
raise WeasylError("submitSizeExceedsLimit")
elif thumbsize > 10 * _MEGABYTE:
files.clear_temporary(userid)
raise WeasylError("thumbSizeExceedsLimit")
elif submitextension not in [".jpg", ".png", ".gif"]:
files.clear_temporary(userid)
raise WeasylError("submitType")
elif thumbsize and thumbextension not in [".jpg", ".png", ".gif"]:
files.clear_temporary(userid)
raise WeasylError("thumbType")
# Assign settings
settings = []
settings.append("f" if friends else "")
settings.append(files.typeflag("submit", submitextension))
settings.append(files.typeflag("cover", submitextension))
settings = "".join(settings)
# Insert submission data
ch = define.meta.tables["character"]
try:
charid = define.engine.scalar(ch.insert().returning(ch.c.charid), {
"userid": userid,
"unixtime": arrow.now(),
"char_name": character.char_name,
"age": character.age,
"gender": character.gender,
"height": character.height,
"weight": character.weight,
"species": character.species,
"content": character.content,
"rating": character.rating.code,
"settings": settings,
})
except PostgresError:
files.clear_temporary(userid)
raise
# Assign search tags
searchtag.associate(userid, tags, charid=charid)
# Make submission file
files.make_character_directory(charid)
files.copy(tempsubmit, files.make_resource(userid, charid, "char/submit", submitextension))
# Make cover file
image.make_cover(tempsubmit, files.make_resource(userid, charid, "char/cover", submitextension))
# Make thumbnail selection file
if thumbsize:
image.make_cover(
tempthumb, files.make_resource(userid, charid, "char/.thumb"))
thumbnail.create(userid, 0, 0, 0, 0, charid=charid, remove=False)
# Create notifications
welcome.character_insert(userid, charid, rating=character.rating.code,
settings=settings)
# Clear temporary files
files.clear_temporary(userid)
define.metric('increment', 'characters')
return charid
def reupload(userid, charid, submitdata):
submitsize = len(submitdata)
if not submitsize:
raise WeasylError("submitSizeZero")
elif submitsize > 10 * _MEGABYTE:
raise WeasylError("submitSizeExceedsLimit")
# Select character data
query, = define.engine.execute("""
SELECT userid, settings FROM character WHERE charid = %(character)s AND settings !~ 'h'
""", character=charid)
if userid != query.userid:
raise WeasylError("Unexpected")
im = image.from_string(submitdata)
submitextension = image.image_extension(im)
# Check invalid file data
if not submitextension:
raise WeasylError("submitType")
# Make submission file
submitfile = files.make_resource(userid, charid, "char/submit", submitextension)
files.ensure_file_directory(submitfile)
im.write(submitfile)
# Make cover file
image.make_cover(
submitfile, files.make_resource(userid, charid, "char/cover", submitextension))
# Update settings
settings = re.sub(r'[~=].', '', query.settings)
settings += files.typeflag("submit", submitextension)
settings += files.typeflag("cover", submitextension)
define.engine.execute("""
UPDATE character
SET settings = %(settings)s
WHERE charid = %(character)s
""", settings=settings, character=charid)
def _select_character_and_check(userid, charid, rating=None, ignore=True, anyway=False, increment_views=True):
"""Selects a character, after checking if the user is authorized, etc.
Args:
userid (int): Currently authenticating user ID.
charid (int): Character ID to fetch.
rating (int): Maximum rating to display. Defaults to None.
ignore (bool): Whether to respect ignored or blocked tags. Defaults to True.
anyway (bool): Whether to ignore checks and display anyway. Defaults to False.
increment_views (bool): Whether to increment the number of views on the submission. Defaults to True.
Returns:
A character and all needed data as a dict.
"""
query = define.engine.execute("""
SELECT
ch.userid, pr.username, ch.unixtime, ch.char_name, ch.age, ch.gender, ch.height, ch.weight, ch.species,
ch.content, ch.rating, ch.settings, ch.page_views, pr.config
FROM character ch
INNER JOIN profile pr USING (userid)
WHERE ch.charid = %(charid)s
""", charid=charid).first()
if query and userid in staff.MODS and anyway:
pass
elif not query or 'h' in query.settings:
raise WeasylError('characterRecordMissing')
elif query.rating > rating and ((userid != query.userid and userid not in staff.MODS) or define.is_sfw_mode()):
raise WeasylError('RatingExceeded')
elif 'f' in query.settings and not frienduser.check(userid, query.userid):
raise WeasylError('FriendsOnly')
elif ignore and ignoreuser.check(userid, query.userid):
raise WeasylError('UserIgnored')
elif ignore and blocktag.check(userid, charid=charid):
raise WeasylError('TagBlocked')
query = dict(query)
if increment_views and define.common_view_content(userid, charid, 'char'):
query['page_views'] += 1
return query
def select_view(userid, charid, rating, ignore=True, anyway=None):
query = _select_character_and_check(
userid, charid, rating=rating, ignore=ignore, anyway=anyway == "true")
login = define.get_sysname(query['username'])
return {
'charid': charid,
'userid': query['userid'],
'username': query['username'],
'user_media': media.get_user_media(query['userid']),
'mine': userid == query['userid'],
'unixtime': query['unixtime'],
'title': query['char_name'],
'age': query['age'],
'gender': query['gender'],
'height': query['height'],
'weight': query['weight'],
'species': query['species'],
'content': query['content'],
'rating': query['rating'],
'settings': query['settings'],
'reported': report.check(charid=charid),
'favorited': favorite.check(userid, charid=charid),
'page_views': query['page_views'],
'friends_only': 'f' in query['settings'],
'hidden_submission': 'h' in query['settings'],
'fave_count': favorite.count(charid, 'character'),
'comments': comment.select(userid, charid=charid),
'sub_media': fake_media_items(
charid, query['userid'], login, query['settings']),
'tags': searchtag.select(charid=charid),
}
def select_view_api(userid, charid, anyway=False, increment_views=False):
rating = define.get_rating(userid)
query = _select_character_and_check(
userid, charid, rating=rating, ignore=anyway,
anyway=anyway, increment_views=increment_views)
login = define.get_sysname(query['username'])
return {
'charid': charid,
'owner': query['username'],
'owner_login': login,
'owner_media': api.tidy_all_media(
media.get_user_media(query['userid'])),
'posted_at': define.iso8601(query['unixtime']),
'title': query['char_name'],
'age': query['age'],
'gender': query['gender'],
'height': query['height'],
'weight': query['weight'],
'species': query['species'],
'content': text.markdown(query['content']),
'rating': ratings.CODE_TO_NAME[query['rating']],
'favorited': favorite.check(userid, charid=charid),
'views': query['page_views'],
'friends_only': 'f' in query['settings'],
'favorites': favorite.count(charid, 'character'),
'comments': comment.count(charid, 'character'),
'media': api.tidy_all_media(fake_media_items(
charid, query['userid'], login, query['settings'])),
'tags': searchtag.select(charid=charid),
'type': 'character',
'link': define.absolutify_url(
'/character/%d/%s' % (charid, text.slug_for(query['char_name']))),
}
def select_query(userid, rating, otherid=None, backid=None, nextid=None, options=[], config=None):
statement = [" FROM character ch INNER JOIN profile pr ON ch.userid = pr.userid WHERE ch.settings !~ '[h]'"]
# Ignored users and blocked tags
if userid:
# filter own content in SFW mode
if define.is_sfw_mode():
statement.append(" AND (ch.rating <= %i)" % (rating,))
else:
statement.append(" AND (ch.rating <= %i OR ch.userid = %i)" % (rating, userid))
if not otherid:
statement.append(macro.MACRO_IGNOREUSER % (userid, "ch"))
statement.append(macro.MACRO_BLOCKTAG_CHAR % (userid, userid))
statement.append(macro.MACRO_FRIENDUSER_CHARACTER % (userid, userid, userid))
else:
statement.append(" AND ch.rating <= %i AND ch.settings !~ 'f'" % (rating,))
# Content owner
if otherid:
statement.append(" AND ch.userid = %i" % (otherid,))
# Browse selection
if backid:
statement.append(" AND ch.charid > %i" % (backid,))
elif nextid:
statement.append(" AND ch.charid < %i" % (nextid,))
return statement
def select_count(userid, rating, otherid=None, backid=None, nextid=None, options=[], config=None):
statement = ["SELECT count(ch.charid)"]
statement.extend(select_query(userid, rating, otherid, backid, nextid,
options, config))
return define.execute("".join(statement))[0][0]
def select_list(userid, rating, limit, otherid=None, backid=None, nextid=None, options=[], config=None):
statement = ["SELECT ch.charid, ch.char_name, ch.rating, ch.unixtime, ch.userid, pr.username, ch.settings "]
statement.extend(select_query(userid, rating, otherid, backid, nextid,
options, config))
statement.append(" ORDER BY ch.charid%s LIMIT %i" % ("" if backid else " DESC", limit))
query = []
for i in define.execute("".join(statement)):
query.append({
"contype": 20,
"charid": i[0],
"title": i[1],
"rating": i[2],
"unixtime": i[3],
"userid": i[4],
"username": i[5],
"sub_media": fake_media_items(i[0], i[4], define.get_sysname(i[5]), i[6]),
})
return query[::-1] if backid else query
def edit(userid, character, friends_only):
query = define.execute("SELECT userid, settings FROM character WHERE charid = %i",
[character.charid], options="single")
if not query or "h" in query[1]:
raise WeasylError("Unexpected")
elif userid != query[0] and userid not in staff.MODS:
raise WeasylError("InsufficientPermissions")
elif not character.char_name:
raise WeasylError("characterNameInvalid")
elif not character.rating:
raise WeasylError("Unexpected")
profile.check_user_rating_allowed(userid, character.rating)
# Assign settings
settings = [query[1].replace("f", "")]
settings.append("f" if friends_only else "")
settings = "".join(settings)
if "f" in settings:
welcome.character_remove(character.charid)
define.execute(
"""
UPDATE character
SET (char_name, age, gender, height, weight, species, content, rating, settings) =
('%s', '%s', '%s', '%s', '%s', '%s', '%s', %i, '%s')
WHERE charid = %i
""",
[character.char_name, character.age, character.gender, character.height, character.weight, character.species,
character.content, character.rating.code, settings, character.charid])
if userid != query[0]:
from weasyl import moderation
moderation.note_about(
userid, query[0], 'The following character was edited:',
'- ' + text.markdown_link(character.char_name, '/character/%s?anyway=true' % (character.charid,)))
def remove(userid, charid):
ownerid = define.get_ownerid(charid=charid)
if userid not in staff.MODS and userid != ownerid:
raise WeasylError("InsufficientPermissions")
query = define.execute("UPDATE character SET settings = settings || 'h'"
" WHERE charid = %i AND settings !~ 'h'"
" RETURNING charid", [charid])
if query:
welcome.character_remove(charid)
return ownerid
def fake_media_items(charid, userid, login, settings):
submission_url = define.cdnify_url(define.url_make(
charid, "char/submit", query=[userid, settings], file_prefix=login))
cover_url = define.cdnify_url(define.url_make(
charid, "char/cover", query=[settings], file_prefix=login))
thumbnail_url = define.cdnify_url(define.url_make(
charid, "char/thumb", query=[settings]))
return {
"submission": [{
"display_url": submission_url,
"described": {
"cover": [{
"display_url": cover_url,
}],
},
}],
"thumbnail-generated": [{
"display_url": thumbnail_url,
}],
"cover": [{
"display_url": cover_url,
"described": {
"submission": [{
"display_url": submission_url,
}],
},
}],
}