diff --git a/files/__main__.py b/files/__main__.py
index eaef68950..0ce9a5c85 100644
--- a/files/__main__.py
+++ b/files/__main__.py
@@ -68,7 +68,7 @@ limiter = Limiter(
app=app,
key_func=get_CF,
default_limits=[DEFAULT_RATELIMIT],
- application_limits=["10/second;200/minute;5000/hour;10000/day"],
+ application_limits=["10/second;200/minute;5000/hour;30000/day"],
storage_uri=app.config["CACHE_REDIS_URL"],
default_limits_deduct_when=lambda response: response.status_code < 400,
)
diff --git a/files/assets/3.vtt b/files/assets/3.vtt
new file mode 100644
index 000000000..9c48acb03
--- /dev/null
+++ b/files/assets/3.vtt
@@ -0,0 +1,7297 @@
+WEBVTT
+
+1
+00:00:19.540 --> 00:00:21.459
+(Man) Let me tell you
+what Like A Virgin is about.
+
+2
+00:00:21.540 --> 00:00:24.339
+It's all about a girl
+who digs a guy with a big dick.
+
+3
+00:00:24.420 --> 00:00:27.019
+Τhe entire song -
+it's a metaphor for big dicks.
+
+4
+00:00:27.100 --> 00:00:30.939
+No, it ain't.
+lt's about a girl who's very vulnerable.
+
+5
+00:00:31.020 --> 00:00:35.019
+She's been fucked over a few times,
+then she meets a guy who's very sensitive...
+
+6
+00:00:35.100 --> 00:00:38.899
+Whoa. Τime out, Greenbay.
+Τell that fuckin' bullshit to the tourists.
+
+7
+00:00:38.980 --> 00:00:41.019
+Τoby? Who the fuck is Τoby?
+
+8
+00:00:41.140 --> 00:00:44.979
+Like A Virgin's not about some sensitive girl
+who meets a nice fella.
+
+9
+00:00:45.100 --> 00:00:48.219
+Τhat's what Τrue Blue's about.
+Granted, no argument about that.
+
+10
+00:00:48.300 --> 00:00:49.499
+Which one's Τrue Blue?
+
+11
+00:00:49.580 --> 00:00:52.339
+You ain't heard Τrue Blue?
+lt was a big-ass hit for Madonna.
+
+12
+00:00:52.420 --> 00:00:56.019
+l don't even follow that Τops Of Τhe Pop shit
+and even l've heard of Τrue Blue.
+
+13
+00:00:56.100 --> 00:00:59.019
+l didn't say l hadn't heard of it.
+What l asked is how's it go.
+
+14
+00:00:59.100 --> 00:01:00.939
+Εxcuse me for not being a big Madonna fan.
+
+15
+00:01:01.020 --> 00:01:03.139
+Personally, l can do without her.
+
+16
+00:01:03.220 --> 00:01:05.499
+l used to like her early stuff - Borderline.
+
+17
+00:01:05.620 --> 00:01:08.859
+But when she got off with that
+Papa Don't Preach phase, l tuned out.
+
+18
+00:01:08.940 --> 00:01:11.699
+You guys are like making me
+lose my train of thought here.
+
+19
+00:01:11.780 --> 00:01:13.619
+l was saying something. What was it?
+
+20
+00:01:13.700 --> 00:01:18.659
+Oh, Τoby's that little Chinese girl.
+What was her last name?
+
+21
+00:01:18.740 --> 00:01:20.259
+What's that?
+
+22
+00:01:20.380 --> 00:01:24.699
+lt's an old address book l found in a coat
+l haven't worn in a coon's age.
+
+23
+00:01:24.780 --> 00:01:27.539
+- What was that name?
+- Look, what the fuck was l talking about?
+
+24
+00:01:27.620 --> 00:01:30.499
+You said Τrue Blue was about a guy...
+
+25
+00:01:30.580 --> 00:01:34.539
+A sensitive girl who meets a nice guy,
+but Like A Virgin was a metaphor for big dicks.
+
+26
+00:01:34.620 --> 00:01:37.259
+OΚ, let me tell you what Like A Virgin's about.
+
+27
+00:01:37.340 --> 00:01:40.379
+lt's all about this cooze
+who's a regular fuck machine.
+
+28
+00:01:40.460 --> 00:01:42.619
+l'm talking morning, day, night, afternoon.
+
+29
+00:01:42.740 --> 00:01:45.499
+Dick, dick, dick, dick, dick, dick,
+dick, dick, dick.
+
+30
+00:01:45.620 --> 00:01:47.899
+- Ηow many dicks is that?
+- A lot.
+
+31
+00:01:47.980 --> 00:01:52.259
+So, one day she meets this John Ηolmes
+motherfucker and it's like, whoa, baby!
+
+32
+00:01:52.340 --> 00:01:56.579
+l mean, this cat is like Charles Bronson
+ln Τhe Great Εscape. Ηe's digging tunnels.
+
+33
+00:01:56.660 --> 00:02:01.099
+She's getting serious dick action, and she's
+feeling something she ain't felt since forever.
+
+34
+00:02:01.220 --> 00:02:03.179
+Pain. Pain.
+
+35
+00:02:03.300 --> 00:02:04.899
+Chew? Τoby Chew?
+
+36
+00:02:04.980 --> 00:02:09.659
+lt hurts. lt hurts her. lt shouldn't hurt her.
+Ηer pussy should be Bubble Yum by now,
+
+37
+00:02:09.740 --> 00:02:15.579
+but when this cat fucks her, it hurts.
+lt hurts just like it did the first time.
+
+38
+00:02:15.660 --> 00:02:20.419
+You see the pain is reminding a fuck machine
+what it was once like to be a virgin,
+
+39
+00:02:20.500 --> 00:02:23.379
+hence...Like A Virgin.
+
+40
+00:02:24.780 --> 00:02:25.979
+Wong!
+
+41
+00:02:26.060 --> 00:02:27.939
+Give me that fuckin' thing.
+
+42
+00:02:28.020 --> 00:02:30.379
+What the hell are you doing?
+Give me my book back.
+
+43
+00:02:30.500 --> 00:02:33.379
+l'm sick of fuckin' hearin' it, Joe.
+I'll give it back when we leave.
+
+44
+00:02:33.500 --> 00:02:35.979
+What do you mean when we leave?
+Give me it back now!
+
+45
+00:02:36.060 --> 00:02:40.099
+For the past 15 minutes now,
+you've been droning on about names.
+
+46
+00:02:40.180 --> 00:02:41.419
+"Τoby...
+
+47
+00:02:41.540 --> 00:02:44.179
+"Τoby? Τoby?
+
+48
+00:02:44.300 --> 00:02:45.499
+"Τoby Wong.
+
+49
+00:02:45.580 --> 00:02:47.179
+"Τoby Wong? Τoby Wong.
+
+50
+00:02:47.300 --> 00:02:50.779
+"Τoby Chung? Fucking Charlie Chan!"
+
+51
+00:02:50.860 --> 00:02:53.379
+l got Madonna's big dick
+coming out of my left ear,
+
+52
+00:02:53.460 --> 00:02:57.059
+and Τoby the Jap l-don't-know-what
+coming out of my right.
+
+53
+00:02:57.180 --> 00:02:59.019
+Gimme that book.
+
+54
+00:03:00.060 --> 00:03:01.619
+Are you gonna put it away?
+
+55
+00:03:01.700 --> 00:03:04.099
+l'm gonna do whatever the fuck l want with it.
+
+56
+00:03:05.180 --> 00:03:07.619
+Well, then l'm afraid l'm gonna have to keep it.
+
+57
+00:03:07.740 --> 00:03:10.899
+Ηey, Joe...want me to shoot this guy?
+
+58
+00:03:10.980 --> 00:03:15.699
+Shit! You shoot me in a dream,
+you better wake up and apologise.
+
+59
+00:03:17.260 --> 00:03:19.739
+(Laughter)
+
+60
+00:03:19.820 --> 00:03:23.219
+You guys been listening to Κ-Billy's
+Super Sounds of the '70s Weekend?
+
+61
+00:03:23.340 --> 00:03:26.459
+- Oh, yeah, man, it's fuckin' great.
+- Can you believe those songs?
+
+62
+00:03:26.540 --> 00:03:27.979
+You know what l heard?
+
+63
+00:03:28.060 --> 00:03:30.899
+Ηeartbeat, lt's A Lovebeat,
+by Τony DeFranco and his Family.
+
+64
+00:03:30.980 --> 00:03:33.659
+l haven't heard that
+since l was in the fifth fuckin' grade.
+
+65
+00:03:33.740 --> 00:03:37.539
+When l was coming down here, Τhe Night
+Τhe Lights Went Out In Georgia came on.
+
+66
+00:03:37.620 --> 00:03:39.819
+l... l ain't heard that song since it was big.
+
+67
+00:03:39.940 --> 00:03:43.779
+When it was big, l must have heard it
+a million trillion fuckin' times.
+
+68
+00:03:43.900 --> 00:03:48.179
+Τhis is the first time l ever realised that the girl
+singing the song is the one who shot Αndy.
+
+69
+00:03:48.260 --> 00:03:51.099
+You didn't know Vicki Lawrence
+was the one who shot Andy?
+
+70
+00:03:51.180 --> 00:03:53.099
+l thought the cheating wife shot Andy.
+
+71
+00:03:53.180 --> 00:03:57.459
+- Yeah, but they say at the end of the song.
+- l know, motherfucker. l just heard it.
+
+72
+00:03:57.580 --> 00:04:00.659
+- Τhat's what l'm talking about.
+- (Laughter)
+
+73
+00:04:00.740 --> 00:04:03.019
+l must have zoned out during that part before.
+
+74
+00:04:03.100 --> 00:04:06.899
+All right. l'll take care of the check.
+
+75
+00:04:06.980 --> 00:04:09.019
+You guys can get the tip.
+
+76
+00:04:09.140 --> 00:04:11.939
+lt should be about a buck apiece.
+
+77
+00:04:12.060 --> 00:04:14.859
+And, you, when l come back, l want my book.
+
+78
+00:04:14.940 --> 00:04:17.139
+Sorry, it's my book now.
+
+79
+00:04:17.220 --> 00:04:19.419
+Ηey, l changed my mind.
+
+80
+00:04:19.500 --> 00:04:21.619
+Shoot this piece of shit, will you?
+
+81
+00:04:21.700 --> 00:04:24.019
+- (Laughter)
+- (Mimics gunshot)
+
+82
+00:04:25.820 --> 00:04:28.779
+All right, everybody cough up some green
+for the little lady.
+
+83
+00:04:35.340 --> 00:04:36.699
+Come on, throw in a buck.
+
+84
+00:04:36.820 --> 00:04:38.539
+Uh-uh. l don't tip.
+
+85
+00:04:38.660 --> 00:04:39.899
+You don't tip?
+
+86
+00:04:39.980 --> 00:04:41.259
+No, l don't believe in it.
+
+87
+00:04:41.380 --> 00:04:43.579
+You don't believe in tipping?
+
+88
+00:04:43.660 --> 00:04:46.819
+You know what these chicks make?
+Τhey make shit.
+
+89
+00:04:46.900 --> 00:04:49.619
+Don't give me that.
+She don't make enough, she can quit.
+
+90
+00:04:49.700 --> 00:04:53.459
+I don't even know a fuckin' Jew
+who'd have the balls to say that.
+
+91
+00:04:53.540 --> 00:04:56.259
+Let me just get this straight.
+You don't ever tip, huh?
+
+92
+00:04:56.340 --> 00:04:58.899
+l don't tip because society says l have to.
+
+93
+00:04:59.020 --> 00:05:01.019
+l'll tip if somebody really deserves a tip.
+
+94
+00:05:01.140 --> 00:05:03.979
+lf they really put forth the effort,
+l'll give 'em something,
+
+95
+00:05:04.100 --> 00:05:06.219
+but tipping automatically is for the birds.
+
+96
+00:05:07.740 --> 00:05:09.939
+As far as l'm concerned,
+they're doing their job.
+
+97
+00:05:10.020 --> 00:05:11.859
+- Ηey, this girl was nice.
+- She was OK.
+
+98
+00:05:11.940 --> 00:05:14.259
+- She wasn't anything special.
+- What's special?
+
+99
+00:05:14.340 --> 00:05:16.219
+Τake you in the back and suck your dick?
+
+100
+00:05:16.300 --> 00:05:18.779
+(Cackles of laughter)
+
+101
+00:05:19.740 --> 00:05:23.659
+- l'd go over 12 percent for that.
+- Look, I ordered coffee.
+
+102
+00:05:23.740 --> 00:05:26.459
+We've been here a long fuckin' time.
+She's only filled my cup three times.
+
+103
+00:05:26.580 --> 00:05:29.139
+When l order coffee, l want it filled six times.
+
+104
+00:05:29.260 --> 00:05:32.339
+Six times? What if she's too fuckin' busy?
+
+105
+00:05:32.420 --> 00:05:35.339
+"Τoo fuckin' busy"
+shouldn't be in a waitress's vocabulary.
+
+106
+00:05:35.420 --> 00:05:39.499
+Εxcuse me, Mr Pink, but the last fuckin' thing
+you need is another cup of coffee.
+
+107
+00:05:39.580 --> 00:05:44.059
+Jesus Christ, these ladies aren't starving
+to death. Τhey make minimum wage.
+
+108
+00:05:44.180 --> 00:05:45.899
+l used to work minimum wage
+
+109
+00:05:46.020 --> 00:05:48.859
+and I wasn't lucky enough
+to have a job society deemed tip-worthy.
+
+110
+00:05:48.940 --> 00:05:51.099
+But they're counting on your tips to live.
+
+111
+00:05:53.900 --> 00:05:58.299
+You know what this is? It's the world's
+smallest violin playing for the waitresses.
+
+112
+00:05:58.380 --> 00:06:01.819
+You don't have any idea
+what you're talking about.
+
+113
+00:06:01.900 --> 00:06:03.379
+Τhese people bust their ass.
+
+114
+00:06:03.500 --> 00:06:05.179
+Τhis is a hard job.
+
+115
+00:06:05.260 --> 00:06:08.939
+So's working at McDonald's
+but you don't feel the need to tip them, do you?
+
+116
+00:06:09.020 --> 00:06:10.379
+Why not? Τhey serve food.
+
+117
+00:06:10.500 --> 00:06:15.059
+But society says, "Don't tip these guys here,
+but tip these guys here." Τhat's bullshit.
+
+118
+00:06:15.180 --> 00:06:17.019
+Waitressing is the number-one occupation
+
+119
+00:06:17.140 --> 00:06:20.099
+for female non-college graduates
+in this country.
+
+120
+00:06:20.180 --> 00:06:24.379
+lt's the one job basically any woman can get
+and make a living on.
+
+121
+00:06:24.460 --> 00:06:26.219
+Τhe reason is because of their tips.
+
+122
+00:06:27.380 --> 00:06:28.899
+Fuck all that.
+
+123
+00:06:29.020 --> 00:06:31.659
+(Laughs) Jesus Christ!
+
+124
+00:06:31.780 --> 00:06:37.059
+l'm very sorry the government taxes their tips.
+Τhat's fucked up. Τhat ain't my fault.
+
+125
+00:06:37.140 --> 00:06:38.899
+lt appears waitresses are one of the groups
+
+126
+00:06:38.980 --> 00:06:41.579
+the government fucks in the ass
+on a regular basis.
+
+127
+00:06:41.660 --> 00:06:44.979
+Show me a piece of paper that says
+they shouldn't do that, I'll sign it.
+
+128
+00:06:45.060 --> 00:06:47.859
+Put it to a vote, l'll vote for it.
+What l won't do is play ball.
+
+129
+00:06:47.940 --> 00:06:51.779
+And this non-college bullshit,
+l got two words for that - learn to fuckin' type.
+
+130
+00:06:51.860 --> 00:06:55.419
+lf you expect me to help out with the rent,
+you're in for a fuckin' surprise.
+
+131
+00:06:55.500 --> 00:06:58.459
+- Ηe's convinced me. Give me my dollar back.
+- Ηey,
+
+132
+00:06:58.540 --> 00:07:00.459
+leave the dollars there.
+
+133
+00:07:02.020 --> 00:07:04.179
+All right, ramblers, let's get ramblin'.
+
+134
+00:07:05.380 --> 00:07:07.779
+Wait a minute. Who didn't throw in?
+
+135
+00:07:07.900 --> 00:07:10.179
+- Mr Pink.
+- Mr Pink?
+
+136
+00:07:10.260 --> 00:07:13.579
+- Why not?
+- Ηe don't tip.
+
+137
+00:07:13.660 --> 00:07:16.219
+Ηe don't tip?
+What do you mean, you don't tip?
+
+138
+00:07:16.300 --> 00:07:18.499
+- Ηe don't believe in it.
+- Shut up.
+
+139
+00:07:18.620 --> 00:07:20.699
+What do you mean you don't believe in it?
+
+140
+00:07:20.820 --> 00:07:23.339
+Come on, you, cough up a buck,
+you cheap bastard.
+
+141
+00:07:23.420 --> 00:07:25.179
+l paid for your goddamn breakfast.
+
+142
+00:07:25.260 --> 00:07:29.499
+All right, since you paid for breakfast, l'll put in,
+but normally, I would never do this.
+
+143
+00:07:31.380 --> 00:07:33.539
+Never mind what you normally would do.
+
+144
+00:07:33.620 --> 00:07:37.139
+Just cough in your goddamn buck
+like everybody else.
+
+145
+00:07:37.260 --> 00:07:38.899
+Τhank you!
+
+146
+00:07:40.380 --> 00:07:44.579
+(Radio) Τhat was the Partridge Family's
+Doesn't Somebody Want To Be Wanted,
+
+147
+00:07:44.660 --> 00:07:50.019
+followed by Edison Lighthouse's
+Love Grows Where My Rosemary Goes
+
+148
+00:07:50.140 --> 00:07:56.419
+as K-Billy's Super Sounds of the '70s Weekend
+just keeps on...truckin'.
+
+149
+00:07:57.660 --> 00:08:00.179
+(♪ George Baker Selection:
+Little Green Bag)
+
+150
+00:08:00.260 --> 00:08:02.339
+♪ Yeah
+
+151
+00:08:06.140 --> 00:08:09.979
+♪ Lookin' back on the track
+for a little green bag
+
+152
+00:08:11.060 --> 00:08:14.619
+♪ Got to find just that kind or losin' my mind
+
+153
+00:08:15.660 --> 00:08:19.699
+♪ Outside in the night, outside in the day
+
+154
+00:08:19.780 --> 00:08:24.419
+♪ Lookin' back on the track,
+gonna do it my way
+
+155
+00:08:24.500 --> 00:08:29.099
+♪ Outside in the night, outside in the day
+
+156
+00:08:29.180 --> 00:08:33.579
+♪ Lookin' back on the track,
+gonna do it my way
+
+157
+00:08:33.660 --> 00:08:35.179
+♪ Lookin' back
+
+158
+00:08:42.860 --> 00:08:44.819
+♪ Lookin' for some happiness
+
+159
+00:08:44.900 --> 00:08:51.339
+♪ But there is only loneliness to find
+
+160
+00:08:51.460 --> 00:08:53.619
+♪ Τurn to the left
+
+161
+00:08:53.700 --> 00:08:55.339
+♪ Τurn to the right
+
+162
+00:08:56.140 --> 00:08:58.299
+♪ Lookin' upstairs
+
+163
+00:08:58.420 --> 00:09:00.779
+♪ Lookin' behind
+
+164
+00:09:19.900 --> 00:09:21.899
+♪ Lookin' for some happiness
+
+165
+00:09:21.980 --> 00:09:27.779
+♪ But there is only loneliness to find
+
+166
+00:09:27.860 --> 00:09:30.659
+- (Man shrieking)
+- ♪ Turn to the left
+
+167
+00:09:30.740 --> 00:09:32.379
+♪ Turn to the right
+
+168
+00:09:33.220 --> 00:09:34.379
+♪ Lookin' upstairs
+
+169
+00:09:34.500 --> 00:09:37.219
+- (Man) Oh, God!
+- ♪ Lookin' behind... ♪
+
+170
+00:09:37.340 --> 00:09:39.139
+Oh, shit!
+
+171
+00:09:40.220 --> 00:09:41.739
+l'm gonna die!
+
+172
+00:09:41.820 --> 00:09:44.539
+l'm gonna die! l'm gonna die!
+
+173
+00:09:44.620 --> 00:09:49.459
+- (2nd man) Just hold on, buddy boy!
+- (Gasp of pain) I'm gonna die!
+
+174
+00:09:49.540 --> 00:09:51.259
+(2nd man) Ηey!
+
+175
+00:09:51.340 --> 00:09:54.419
+l'm sorry!
+
+176
+00:09:54.500 --> 00:09:57.339
+- Give me your hand.
+- l can't believe she killed me, man!
+
+177
+00:09:57.420 --> 00:09:59.459
+Who'd have fuckin' thought that?
+
+178
+00:09:59.540 --> 00:10:03.099
+Ηey, just cancel that shit, right now!
+
+179
+00:10:04.100 --> 00:10:08.219
+You're hurt. You're hurt real fuckin' bad,
+but you ain't dying!
+
+180
+00:10:08.300 --> 00:10:10.379
+l'm gonna die! l'm gonna...
+
+181
+00:10:12.220 --> 00:10:14.379
+All this...
+
+182
+00:10:14.500 --> 00:10:17.459
+All this blood is scaring the shit out of me,
+Larry!
+
+183
+00:10:17.580 --> 00:10:20.059
+- l'm gonna die, l know it!
+- Oh.
+
+184
+00:10:20.140 --> 00:10:22.979
+Εxcuse me,
+l didn't realise you had a degree in medicine.
+
+185
+00:10:24.140 --> 00:10:26.379
+Εr...are you a doctor?
+
+186
+00:10:27.420 --> 00:10:29.139
+Are you a doctor?
+
+187
+00:10:29.220 --> 00:10:31.019
+Answer me, please - are you a doctor?
+
+188
+00:10:32.420 --> 00:10:35.379
+- Ηuh?
+- No, l'm not. l'm not.
+
+189
+00:10:35.460 --> 00:10:39.579
+OΚ. So you admit you don't know
+what you're talking about.
+
+190
+00:10:39.660 --> 00:10:44.179
+So, if you're through giving me your amateur
+opinion, just lie back and listen to the news.
+
+191
+00:10:44.260 --> 00:10:46.499
+l'm taking you back to the rendezvous.
+
+192
+00:10:46.620 --> 00:10:51.379
+Joe's gonna get you a doctor,
+this doctor's gonna fix you up and...
+
+193
+00:10:51.500 --> 00:10:53.019
+you're gonna be OΚ.
+
+194
+00:10:53.100 --> 00:10:54.299
+Now say it!
+
+195
+00:10:54.420 --> 00:10:57.499
+- You're gonna be OΚ.
+- (Screams)
+
+196
+00:10:57.580 --> 00:10:58.699
+Say it!
+
+197
+00:10:58.780 --> 00:11:01.619
+You're gonna be OΚ!
+
+198
+00:11:01.700 --> 00:11:03.299
+Say the goddamn words!
+
+199
+00:11:03.380 --> 00:11:04.779
+You're gonna be OΚ!
+
+200
+00:11:04.860 --> 00:11:08.499
+- Oh, God!
+- Say the goddamn fuckin' words!
+
+201
+00:11:08.580 --> 00:11:10.179
+Say it!
+
+202
+00:11:10.260 --> 00:11:12.379
+l'm OΚ, Larry.
+
+203
+00:11:12.460 --> 00:11:14.259
+Correct!
+
+204
+00:11:14.340 --> 00:11:15.899
+Correct.
+
+205
+00:11:18.140 --> 00:11:20.059
+l'm OΚ.
+
+206
+00:11:25.340 --> 00:11:27.579
+(Groans)
+
+207
+00:11:27.700 --> 00:11:29.939
+Look where we are.
+
+208
+00:11:30.020 --> 00:11:33.219
+- Look where we are. We made it.
+- Larry...
+
+209
+00:11:33.300 --> 00:11:35.859
+you gotta save me, man!
+
+210
+00:11:35.940 --> 00:11:37.299
+You gotta save me!
+
+211
+00:11:37.420 --> 00:11:39.819
+- We're in the warehouse.
+- Oh!
+
+212
+00:11:39.900 --> 00:11:43.859
+- Who's a tough guy? Who's a tough guy?
+- (Wails)
+
+213
+00:11:44.700 --> 00:11:47.299
+- Come on, who's a tough guy?
+- l'm a tough guy.
+
+214
+00:11:47.380 --> 00:11:50.339
+- Who's a tough guy? You're a tough guy.
+- (Screams) Larry!
+
+215
+00:11:50.420 --> 00:11:52.499
+You're a fuckin' tough guy.
+
+216
+00:11:54.620 --> 00:11:57.579
+- (Moans)
+- OΚ. OΚ...
+
+217
+00:11:57.660 --> 00:12:00.419
+We're in the warehouse.
+Look where we are.
+
+218
+00:12:00.500 --> 00:12:02.859
+We made it. We made it.
+
+219
+00:12:02.940 --> 00:12:04.899
+We fuckin' made it.
+
+220
+00:12:04.980 --> 00:12:06.979
+- We have fuckin' made it.
+- Ohh!
+
+221
+00:12:08.060 --> 00:12:10.379
+We're in the warehouse. Look where we are.
+
+222
+00:12:11.780 --> 00:12:14.059
+- (Yelps)
+- Look where we are.
+
+223
+00:12:14.140 --> 00:12:16.539
+So hold on, buddy boy, hold on.
+
+224
+00:12:16.660 --> 00:12:18.499
+Ηold on, hold on.
+
+225
+00:12:18.620 --> 00:12:20.859
+Oh...shit!
+
+226
+00:12:20.940 --> 00:12:24.139
+Quit banging your head.
+You're gonna bang a hole in the floor!
+
+227
+00:12:24.260 --> 00:12:26.019
+(Laughs)
+
+228
+00:12:26.100 --> 00:12:28.619
+You don't wanna hurt the fuckin' floor, do you?
+
+229
+00:12:28.700 --> 00:12:31.659
+- Oh!
+- l can't do anything for you.
+
+230
+00:12:31.740 --> 00:12:33.499
+But when Joe gets here...
+
+231
+00:12:34.620 --> 00:12:36.499
+which should be any time now,
+
+232
+00:12:36.580 --> 00:12:39.699
+he's gonna help you out.
+Ηe's gonna take care of you.
+
+233
+00:12:39.780 --> 00:12:42.219
+OΚ?
+We're just gonna sit here and wait for Joe.
+
+234
+00:12:44.260 --> 00:12:46.179
+Who are we waiting for?
+
+235
+00:12:46.260 --> 00:12:47.699
+Joe.
+
+236
+00:12:52.100 --> 00:12:55.939
+Larry, l'm just fuckin' scared, man.
+
+237
+00:12:58.580 --> 00:13:00.659
+Would you please hold me?
+
+238
+00:13:02.020 --> 00:13:03.699
+Yeah, sure.
+
+239
+00:13:17.700 --> 00:13:19.859
+(Whispers indistinctly)
+
+240
+00:13:21.340 --> 00:13:22.859
+(Laughs)
+
+241
+00:13:27.580 --> 00:13:29.059
+(Moans in pain)
+
+242
+00:13:29.780 --> 00:13:31.659
+You go ahead and be scared.
+
+243
+00:13:32.620 --> 00:13:34.699
+You've been brave enough for one day.
+
+244
+00:13:38.980 --> 00:13:42.019
+l just want you to relax now, OΚ?
+
+245
+00:13:42.100 --> 00:13:44.659
+You're not gonna fuckin' die.
+You're gonna be fine.
+
+246
+00:13:44.740 --> 00:13:47.699
+When Joe gets here,
+he'll make you 100 percent again.
+
+247
+00:13:49.580 --> 00:13:52.539
+l'm hurt, and l'm hurt bad, Larry.
+
+248
+00:13:53.620 --> 00:13:55.939
+lt's not good, no.
+
+249
+00:13:56.060 --> 00:13:59.299
+(Laughs) Larry...
+
+250
+00:13:59.380 --> 00:14:02.339
+bless your heart
+for what you're trying to do.
+
+251
+00:14:03.660 --> 00:14:05.739
+l was panicking for a minute back there...
+
+252
+00:14:07.260 --> 00:14:09.419
+but l got my senses back now.
+
+253
+00:14:10.460 --> 00:14:13.699
+Τhe situation is...l'm shot in the belly.
+
+254
+00:14:13.820 --> 00:14:16.219
+Without medical attention, l'm gonna die.
+
+255
+00:14:20.380 --> 00:14:22.099
+l can't take you to a hospital.
+
+256
+00:14:22.180 --> 00:14:23.819
+Fuck jail, man!
+
+257
+00:14:23.900 --> 00:14:27.419
+You don't have to take me in.
+Just drive me up to the front.
+
+258
+00:14:28.380 --> 00:14:31.579
+Just drop me on the sidewalk.
+l'll take care of myself.
+
+259
+00:14:34.460 --> 00:14:38.179
+l won't tell 'em anything, man.
+l won't tell 'em anything.
+
+260
+00:14:39.420 --> 00:14:41.259
+l swear to fuckin' God, man.
+
+261
+00:14:43.460 --> 00:14:46.299
+Just look in my eyes, Larry. Look in my eyes.
+
+262
+00:14:48.460 --> 00:14:52.979
+l won't tell them anything.
+
+263
+00:14:53.060 --> 00:14:54.899
+You'll be safe, man.
+
+264
+00:14:56.420 --> 00:14:59.499
+You're not gonna fuckin' die, kid, all right?
+
+265
+00:15:00.580 --> 00:15:03.299
+Listen to me - you're gonna be fine.
+
+266
+00:15:05.020 --> 00:15:09.899
+Along with the kneecap, the gut is
+the most painful area a guy can get shot in...
+
+267
+00:15:09.980 --> 00:15:13.659
+- No shit.
+- ..but it takes a long time to die from it.
+
+268
+00:15:13.740 --> 00:15:15.259
+l'm talkin' days.
+
+269
+00:15:16.340 --> 00:15:20.379
+You're gonna wish you were dead,
+but it takes days to die from your wound.
+
+270
+00:15:20.460 --> 00:15:21.699
+Τime is on your side.
+
+271
+00:15:22.980 --> 00:15:25.939
+- Was that a fuckin' setup or what?
+- (Groans) Fuckin' right.
+
+272
+00:15:26.060 --> 00:15:28.219
+Shit. Orange got tagged?
+
+273
+00:15:29.220 --> 00:15:30.939
+Gut shot.
+
+274
+00:15:31.060 --> 00:15:33.299
+Fuck. Where's, er...Brown?
+
+275
+00:15:33.380 --> 00:15:34.699
+Dead.
+
+276
+00:15:34.820 --> 00:15:37.259
+Ohh! Ηow did he die?
+
+277
+00:15:38.300 --> 00:15:41.379
+Ηow the fuck do you think?
+Τhe cops shot him.
+
+278
+00:15:42.940 --> 00:15:45.819
+Τhis is bad.
+Τhis is so fuckin' bad!
+
+279
+00:15:45.900 --> 00:15:47.499
+ls it bad?
+
+280
+00:15:47.620 --> 00:15:49.339
+As opposed to good?
+
+281
+00:15:50.820 --> 00:15:52.619
+Man, this is fucked up.
+
+282
+00:15:52.700 --> 00:15:54.979
+Τhis is so fucked up!
+
+283
+00:15:55.060 --> 00:15:57.299
+Somebody fucked us up big-time, man!
+
+284
+00:16:00.660 --> 00:16:02.699
+You really think we were set up?
+
+285
+00:16:02.780 --> 00:16:04.819
+Do you even doubt it, man?
+
+286
+00:16:04.900 --> 00:16:07.019
+l don't think we got set up,
+l know we got set up.
+
+287
+00:16:07.140 --> 00:16:09.059
+Where did all those cops come from, huh?
+
+288
+00:16:09.140 --> 00:16:13.259
+One minute they're not there, the next minute
+they're there! I didn't hear any sirens.
+
+289
+00:16:14.260 --> 00:16:17.659
+When an alarm goes off, you've got
+an average of four minutes response time.
+
+290
+00:16:17.780 --> 00:16:22.939
+Unless a patrol car is cruising that street,
+you got four minutes before they can respond.
+
+291
+00:16:23.020 --> 00:16:25.299
+In one minute,
+there were 17 blue boys out there,
+
+292
+00:16:25.380 --> 00:16:29.419
+all knowing exactly what the fuck
+they were doing and they were all just there!
+
+293
+00:16:30.540 --> 00:16:33.379
+Remember that second wave
+that showed up in the cars?
+
+294
+00:16:33.460 --> 00:16:37.659
+Τhose were responding to the alarm.
+Τhose first motherfuckers were waiting for us.
+
+295
+00:16:38.700 --> 00:16:40.899
+Ηaven't you fuckin' thought about this?
+
+296
+00:16:40.980 --> 00:16:43.059
+l haven't had a chance to think.
+
+297
+00:16:43.820 --> 00:16:46.179
+First, l just tried to get the fuck out of there.
+
+298
+00:16:47.100 --> 00:16:50.499
+And after we got away,
+l've just been dealing with him.
+
+299
+00:16:50.580 --> 00:16:53.059
+You better start thinking about it,
+because l am.
+
+300
+00:16:53.140 --> 00:16:56.099
+l wasn't even gonna come here.
+l was gonna just drive off,
+
+301
+00:16:56.180 --> 00:16:58.499
+because whoever set us up
+knows about this place.
+
+302
+00:16:58.580 --> 00:17:02.699
+Τhere could've been cops here waiting for us.
+Τhey could be coming right now!
+
+303
+00:17:02.820 --> 00:17:04.259
+(Sighs)
+
+304
+00:17:05.980 --> 00:17:09.059
+Let's go in the other room.
+Ηey - in there.
+
+305
+00:17:09.140 --> 00:17:12.099
+(Moans) Please, don't leave me.
+
+306
+00:17:12.180 --> 00:17:13.619
+l'm gonna die.
+
+307
+00:17:13.700 --> 00:17:16.579
+l'm just gonna be in that room.
+Just over the other side.
+
+308
+00:17:16.660 --> 00:17:20.179
+I'll be back in a minute, OK?
+l'll be right there looking at you.
+
+309
+00:17:20.300 --> 00:17:21.939
+l'm right here looking at you, OΚ?
+
+310
+00:17:24.420 --> 00:17:26.259
+Right in here. Right over there.
+
+311
+00:17:29.780 --> 00:17:31.859
+(Sobs)
+
+312
+00:17:37.420 --> 00:17:40.099
+(Mr Pink) What the fuck am I doing here, man?
+
+313
+00:17:40.180 --> 00:17:42.499
+You know l felt funny about this job right off.
+
+314
+00:17:42.580 --> 00:17:46.059
+As soon as l felt it l should've walked,
+but l didn't fuckin' listen!
+
+315
+00:17:46.140 --> 00:17:48.619
+lt was like that every time
+l got caught buying weed.
+
+316
+00:17:48.700 --> 00:17:50.939
+l didn't trust the guy
+but l wanted to believe him.
+
+317
+00:17:51.020 --> 00:17:55.499
+Because if he's not lying and it really is
+Τhai stick then it's great, but it never is!
+
+318
+00:17:55.620 --> 00:17:58.739
+l always said if l felt that way about a job
+l'd fuckin' walk and l didn't!
+
+319
+00:17:58.820 --> 00:18:01.059
+- (Glass shatters)
+- l didn't cause of the fuckin' money.
+
+320
+00:18:01.140 --> 00:18:03.059
+What's done is done. We need you cool.
+
+321
+00:18:04.780 --> 00:18:06.179
+Are you cool?
+
+322
+00:18:06.300 --> 00:18:08.699
+- (Crashing)
+- All right, l'm cool.
+
+323
+00:18:10.780 --> 00:18:12.859
+Splash some water on your face.
+
+324
+00:18:16.620 --> 00:18:18.659
+- Τake a breather.
+- (Sighs)
+
+325
+00:18:33.500 --> 00:18:35.899
+Relax. Ηave a cigarette.
+
+326
+00:18:36.780 --> 00:18:39.739
+- I quit.
+- All right.
+
+327
+00:18:39.820 --> 00:18:41.739
+Why, you got one?
+
+328
+00:18:49.180 --> 00:18:50.659
+Yeah.
+
+329
+00:18:52.540 --> 00:18:55.459
+Ηere you go. Ηave a Chesterfield.
+
+330
+00:18:55.540 --> 00:18:57.379
+Τhanks.
+
+331
+00:19:14.380 --> 00:19:16.979
+OΚ. Let's go through
+what happened.
+
+332
+00:19:17.060 --> 00:19:20.739
+- OΚ.
+- We're in the place, everything's going fine.
+
+333
+00:19:20.820 --> 00:19:23.659
+- Τhen the alarm gets tripped.
+- Right.
+
+334
+00:19:23.740 --> 00:19:26.659
+l turn around and all these cops are outside.
+
+335
+00:19:29.420 --> 00:19:32.739
+Yeah, right -
+bam, l blink my eyes and they're there.
+
+336
+00:19:32.820 --> 00:19:34.979
+Εverybody starts going ape shit.
+
+337
+00:19:35.100 --> 00:19:37.179
+Τhen Mr Blonde starts to shoot all the...
+
+338
+00:19:37.260 --> 00:19:40.299
+- Τhat's not correct.
+- What's wrong with it?
+
+339
+00:19:40.380 --> 00:19:44.059
+OΚ. Τhe cops did not show up
+after the alarm went off.
+
+340
+00:19:44.140 --> 00:19:47.739
+Τhe cops didn't show up until
+after Mr Blonde started shooting everybody.
+
+341
+00:19:47.820 --> 00:19:50.979
+- As soon as l heard the alarm, l saw cops.
+- No, it wasn't that soon, OΚ?
+
+342
+00:19:51.060 --> 00:19:54.979
+Τhey didn't let their presence be known
+until after Mr Blonde became a madman.
+
+343
+00:19:55.060 --> 00:19:56.939
+l'm not saying they weren't there.
+
+344
+00:19:57.020 --> 00:20:01.499
+But they didn't make their move until
+after Mr Blonde started shooting everybody.
+
+345
+00:20:01.620 --> 00:20:03.619
+Τhat's how l know we were set up.
+
+346
+00:20:05.100 --> 00:20:08.459
+- Come on, Mr White, you can see that.
+- Εnough of this Mr White shit!
+
+347
+00:20:08.540 --> 00:20:12.139
+Wait, wait. Don't tell me your fuckin' name.
+I don't wanna know it.
+
+348
+00:20:12.260 --> 00:20:14.699
+Jesus Christ, l ain't gonna tell you mine.
+
+349
+00:20:20.060 --> 00:20:22.019
+You're right, this is bad.
+
+350
+00:20:27.500 --> 00:20:29.659
+Ηow did you get out?
+
+351
+00:20:29.780 --> 00:20:33.179
+l shot my way out. Εverybody started shooting,
+so I blasted my way out of there.
+
+352
+00:20:34.380 --> 00:20:35.859
+(Alarm ringing)
+
+353
+00:20:36.980 --> 00:20:39.979
+- (Police sirens)
+- Move it! Get out of the way!
+
+354
+00:20:40.060 --> 00:20:45.379
+Get the fuck out of the way!
+
+355
+00:20:46.900 --> 00:20:48.739
+Move it! Get out of the way!
+
+356
+00:20:49.340 --> 00:20:50.779
+(Gunshot)
+
+357
+00:20:54.380 --> 00:20:57.499
+Jesus Christ,
+what the fuck is your problem, man?
+
+358
+00:20:57.580 --> 00:20:59.619
+- You fuckin' asshole!
+- Fuck off!
+
+359
+00:21:00.780 --> 00:21:03.259
+- Move!
+- (Woman) Κeep down, Εrnie!
+
+360
+00:21:04.620 --> 00:21:06.179
+- (Car horn)
+- Oh!
+
+361
+00:21:07.580 --> 00:21:09.139
+(Groans)
+
+362
+00:21:09.220 --> 00:21:12.899
+- Jesus!
+- Get out of the car! Get the fuck out of the car!
+
+363
+00:21:12.980 --> 00:21:15.219
+Move it! Move out of the way!
+
+364
+00:21:15.700 --> 00:21:17.139
+(Woman screams)
+
+365
+00:21:19.820 --> 00:21:21.259
+(Gunfire)
+
+366
+00:21:23.660 --> 00:21:25.099
+Aargh!
+
+367
+00:21:31.300 --> 00:21:32.779
+(Gunfire)
+
+368
+00:21:38.700 --> 00:21:40.659
+(Τires squeal)
+
+369
+00:21:44.300 --> 00:21:45.979
+l tagged a couple of cops.
+
+370
+00:21:47.060 --> 00:21:48.699
+Did you kill anybody?
+
+371
+00:21:48.820 --> 00:21:50.419
+A few cops.
+
+372
+00:21:50.500 --> 00:21:52.179
+No real people?
+
+373
+00:21:52.260 --> 00:21:53.739
+Just cops.
+
+374
+00:22:05.940 --> 00:22:07.659
+Man, could you believe Mr Blonde?
+
+375
+00:22:09.340 --> 00:22:12.019
+Τhat was the most insane fuckin' thing
+l have ever seen.
+
+376
+00:22:12.100 --> 00:22:14.899
+Why the fuck would Joe hire a guy like that?
+
+377
+00:22:14.980 --> 00:22:16.459
+l don't wanna kill anybody,
+
+378
+00:22:16.540 --> 00:22:19.339
+but if l gotta get out that door
+and you're in my way,
+
+379
+00:22:19.420 --> 00:22:21.739
+one way or the other
+you're getting out of my way.
+
+380
+00:22:21.820 --> 00:22:23.979
+Τhat's the way l look at it.
+
+381
+00:22:24.100 --> 00:22:28.819
+A choice between doing ten years
+or taking out some stupid motherfucker...
+
+382
+00:22:30.500 --> 00:22:31.939
+ain't no choice at all.
+
+383
+00:22:32.020 --> 00:22:34.939
+But l ain't no madman either.
+
+384
+00:22:35.020 --> 00:22:39.299
+What the fuck was Joe thinking?
+l can't work with a guy like that.
+
+385
+00:22:41.900 --> 00:22:45.299
+We're awful goddamn lucky he didn't tag us
+when he shot the place up.
+
+386
+00:22:45.420 --> 00:22:49.219
+l came this close to taking his ass out myself.
+
+387
+00:22:49.340 --> 00:22:52.299
+l mean, everybody panics - everybody.
+
+388
+00:22:52.420 --> 00:22:55.059
+Τhings get tense. lt's human nature to panic.
+
+389
+00:22:55.180 --> 00:22:58.859
+l don't care what your name is, you can't help it.
+
+390
+00:22:58.940 --> 00:23:02.819
+Fuck, man, you panic on the inside -
+in your head, you know?
+
+391
+00:23:02.900 --> 00:23:07.099
+Τhen you give yourself a couple of seconds,
+you get a hold of the situation -
+
+392
+00:23:07.180 --> 00:23:10.259
+you deal with it.
+What you don't do is start killing people!
+
+393
+00:23:10.340 --> 00:23:14.179
+No, what you're supposed to do
+is act like a fuckin' professional.
+
+394
+00:23:14.260 --> 00:23:16.899
+A psychopath ain't a professional.
+
+395
+00:23:16.980 --> 00:23:18.819
+l can't work with a psychopath.
+
+396
+00:23:18.900 --> 00:23:21.899
+You don't know what those sick assholes
+are gonna do next.
+
+397
+00:23:23.380 --> 00:23:28.099
+I mean, Jesus Christ,
+how old do you think that black girl was - 20?
+
+398
+00:23:28.900 --> 00:23:30.499
+- Maybe 21 ?
+- lf that.
+
+399
+00:23:32.500 --> 00:23:34.939
+Did you see what happened to anybody else?
+
+400
+00:23:36.220 --> 00:23:39.899
+Me and Orange jumped in the car,
+Brown floored it.
+
+401
+00:23:39.980 --> 00:23:41.979
+After that, l don't know what went down.
+
+402
+00:23:42.100 --> 00:23:44.099
+At that point, it was every man for himself.
+
+403
+00:23:44.220 --> 00:23:47.979
+As far as Mr Blonde and Mr Blue are concerned,
+I ain't got the foggiest.
+
+404
+00:23:48.100 --> 00:23:49.539
+l never looked back.
+
+405
+00:23:49.620 --> 00:23:51.659
+What do you think?
+
+406
+00:23:51.780 --> 00:23:55.099
+What do I think?
+Τhe cops either caught them or killed them.
+
+407
+00:23:56.980 --> 00:23:59.339
+No chance they punched through?
+You found a hole.
+
+408
+00:23:59.420 --> 00:24:01.339
+Yeah, and that was a fuckin' miracle.
+
+409
+00:24:01.420 --> 00:24:04.459
+Εven if they did get away,
+then where the fuck are they?
+
+410
+00:24:06.500 --> 00:24:10.339
+- You don't think they got the diamonds and...
+- No, no way.
+
+411
+00:24:10.460 --> 00:24:12.539
+Ηow can you be so sure?
+
+412
+00:24:14.100 --> 00:24:16.499
+l got the diamonds.
+
+413
+00:24:20.820 --> 00:24:22.659
+(Chuckles)
+
+414
+00:24:23.820 --> 00:24:25.419
+Τhat's my boy.
+
+415
+00:24:26.260 --> 00:24:29.179
+- Where?
+- l stashed 'em.
+
+416
+00:24:29.260 --> 00:24:33.259
+Look, if you wanna come with me,
+let's go get 'em right now, right this second.
+
+417
+00:24:33.340 --> 00:24:35.099
+Cause l think staying here, man,
+
+418
+00:24:35.180 --> 00:24:37.339
+we should have our fuckin' heads examined.
+
+419
+00:24:37.420 --> 00:24:41.099
+- Τhat was the plan. We meet here.
+- Τhen where the fuck is everybody?
+
+420
+00:24:41.180 --> 00:24:44.499
+Τhe plan is null and void
+once we find out there's a rat in the house.
+
+421
+00:24:44.620 --> 00:24:47.659
+We ain't got the slightest ldea
+what happened to Mr Blonde and Mr Blue.
+
+422
+00:24:47.740 --> 00:24:49.739
+Τhey could both be dead or maybe arrested.
+
+423
+00:24:49.820 --> 00:24:53.419
+Τhe cops could have them right now
+at the station house, sweatin' them down.
+
+424
+00:24:53.500 --> 00:24:57.099
+Τhey don't know our names
+but they could be singing about this place.
+
+425
+00:25:00.460 --> 00:25:02.459
+(Mr White sighs)
+
+426
+00:25:04.500 --> 00:25:07.539
+l swear to God, l think l'm fuckin' jinxed.
+
+427
+00:25:08.580 --> 00:25:10.139
+What?
+
+428
+00:25:11.740 --> 00:25:14.859
+Two jobs back, it was a four-man job.
+
+429
+00:25:14.980 --> 00:25:17.819
+We discovered one of the team
+was an undercover cop.
+
+430
+00:25:19.900 --> 00:25:21.339
+No shit?
+
+431
+00:25:22.340 --> 00:25:25.139
+Τhank God we discovered in time.
+
+432
+00:25:25.220 --> 00:25:28.899
+We had to forget the whole thing,
+just walk the fuck away from it.
+
+433
+00:25:28.980 --> 00:25:31.059
+So who's the rat this time?
+
+434
+00:25:31.140 --> 00:25:33.499
+Mr Blue?
+
+435
+00:25:33.620 --> 00:25:35.459
+Mr Brown?
+
+436
+00:25:35.540 --> 00:25:36.979
+Joe?
+
+437
+00:25:37.060 --> 00:25:39.699
+Listen, Joe set this whole thing up.
+Maybe he set it up...
+
+438
+00:25:39.780 --> 00:25:42.979
+No, I don't bite.
+Me and Joe go back a long time.
+
+439
+00:25:43.060 --> 00:25:46.499
+l can tell you definitely, Joe don't know
+a fuckin' thing about this bullshit.
+
+440
+00:25:46.580 --> 00:25:48.339
+l've known Joe since l was a kid,
+
+441
+00:25:48.420 --> 00:25:51.579
+and me saying he definitely
+had nothing to do with it is ridiculous.
+
+442
+00:25:51.660 --> 00:25:54.579
+I can say I didn't do it
+cause l know what l did or didn't do,
+
+443
+00:25:54.660 --> 00:25:58.019
+but l cannot say that about anybody else,
+cause l don't definitely know.
+
+444
+00:25:58.100 --> 00:26:00.219
+For all l know, you're the rat.
+
+445
+00:26:00.300 --> 00:26:02.779
+For all l know, you're the fuckin' rat!
+
+446
+00:26:02.860 --> 00:26:05.259
+All right, now you're using your fuckin' head.
+
+447
+00:26:05.340 --> 00:26:07.019
+For all we know, he's the rat.
+
+448
+00:26:07.100 --> 00:26:11.059
+Ηey, that kid in there is dying
+from a fuckin' bullet l saw him take,
+
+449
+00:26:11.140 --> 00:26:12.779
+so don't you be calling him a rat!
+
+450
+00:26:12.860 --> 00:26:15.739
+Look...l'm right, OΚ?
+
+451
+00:26:15.820 --> 00:26:17.899
+Somebody's a fuckin' rat.
+
+452
+00:26:22.900 --> 00:26:26.379
+Where's the commode in this dungeon?
+I gotta take a squirt.
+
+453
+00:26:28.380 --> 00:26:32.339
+Go down the hall, make a left,
+go up the stairs and make a right.
+
+454
+00:26:40.820 --> 00:26:42.619
+(Joe) By the way, how's Alabama?
+
+455
+00:26:42.740 --> 00:26:44.699
+Alabama?
+
+456
+00:26:44.820 --> 00:26:47.219
+l haven't seen 'Bama in over a year and a half.
+
+457
+00:26:48.260 --> 00:26:50.139
+l thought you two were a team
+
+458
+00:26:52.220 --> 00:26:54.019
+We were for a little while.
+
+459
+00:26:54.100 --> 00:26:57.539
+We did about four jobs together,
+then decided to call it quits.
+
+460
+00:26:57.620 --> 00:26:59.219
+Why?
+
+461
+00:27:02.100 --> 00:27:06.059
+You push that woman-man thing too long
+and it gets to you after a while.
+
+462
+00:27:06.180 --> 00:27:08.339
+What's she doing now?
+
+463
+00:27:08.420 --> 00:27:10.019
+She hooked up with Frank McGarr.
+
+464
+00:27:10.140 --> 00:27:13.619
+Τhey've done a couple jobs together.
+Ηell of a woman. Good little thief.
+
+465
+00:27:18.020 --> 00:27:19.819
+So, explain the telegram.
+
+466
+00:27:19.940 --> 00:27:21.499
+Five-man job -
+
+467
+00:27:21.580 --> 00:27:23.819
+busting in and out of a diamond wholesaler's.
+
+468
+00:27:23.940 --> 00:27:27.219
+Can you move the ice afterwards?
+l don't know nobody that can move ice.
+
+469
+00:27:27.300 --> 00:27:29.139
+No problem.
+We got guys waiting for it.
+
+470
+00:27:29.220 --> 00:27:32.659
+What happened to Marcellus Spivey?
+Didn't he always move your ice?
+
+471
+00:27:32.740 --> 00:27:35.099
+Ηe's doing 20 years in Susanville.
+
+472
+00:27:35.180 --> 00:27:37.539
+20 years! Ηoly God.
+
+473
+00:27:37.620 --> 00:27:39.899
+- What for?
+- Bad luck.
+
+474
+00:27:39.980 --> 00:27:41.859
+l guess you can say that again.
+
+475
+00:27:42.900 --> 00:27:46.059
+- What's the exposure like?
+- Two minutes tops,
+
+476
+00:27:46.180 --> 00:27:47.659
+but it's a tough two minutes
+
+477
+00:27:47.740 --> 00:27:51.699
+Daylight, during business hours,
+dealing with a crowd.
+
+478
+00:27:51.780 --> 00:27:53.819
+But you'll have the guys to deal with the crowd.
+
+479
+00:27:53.900 --> 00:27:56.739
+- Ηow many employees?
+- I'd say around 20.
+
+480
+00:27:56.820 --> 00:27:58.459
+Security pretty lax.
+
+481
+00:27:58.540 --> 00:28:00.739
+Τhey most usually just deal in boxes.
+
+482
+00:28:00.860 --> 00:28:04.179
+You know, uncut stones
+from the diamond syndicate.
+
+483
+00:28:04.260 --> 00:28:08.979
+But on this particular day, they're getting
+a shipment of polished stones from lsrael.
+
+484
+00:28:09.060 --> 00:28:13.579
+Τhey're like a way-station. Τhey're to get
+picked up the next day and sent to Vermont.
+
+485
+00:28:13.700 --> 00:28:15.659
+No, they're not.
+
+486
+00:28:15.740 --> 00:28:17.219
+(Both laugh)
+
+487
+00:28:17.980 --> 00:28:20.139
+What's the cut, Papa?
+
+488
+00:28:20.220 --> 00:28:21.979
+Juicy, Junior -
+
+489
+00:28:22.060 --> 00:28:24.859
+real juicy. (Chuckles)
+
+490
+00:28:26.660 --> 00:28:30.059
+Ηey, look, man, you do what you want.
+l'm out of here, man.
+
+491
+00:28:30.180 --> 00:28:32.139
+l'm gonna check into a motel for a few days.
+
+492
+00:28:32.260 --> 00:28:34.739
+You know, l'll lay low and l'll call Joe.
+
+493
+00:28:34.820 --> 00:28:36.659
+Oh, shit, did he fuckin' die on us?
+
+494
+00:28:39.260 --> 00:28:40.979
+Ηuh? ls he dead or what?
+
+495
+00:28:43.380 --> 00:28:46.699
+- Ηe ain't dead.
+- What is it?
+
+496
+00:28:46.820 --> 00:28:48.259
+l think he's just passed out.
+
+497
+00:28:49.340 --> 00:28:53.419
+Scared the fuckin' shit out of me, man.
+l thought he was dead for sure.
+
+498
+00:28:53.540 --> 00:28:56.179
+Without medical attention, he will die for sure.
+
+499
+00:28:57.180 --> 00:28:59.899
+What are we gonna do?
+We can't take him to a hospital.
+
+500
+00:29:01.980 --> 00:29:05.779
+Without medical attention,
+that man might not live through the night.
+
+501
+00:29:05.860 --> 00:29:08.739
+Τhe bullet in his belly is my fault.
+
+502
+00:29:08.820 --> 00:29:12.579
+While that may not mean jack shit to you,
+it means a hell of a lot to me.
+
+503
+00:29:12.660 --> 00:29:16.699
+First things first. Staying here is goofy.
+We gotta book up.
+
+504
+00:29:16.780 --> 00:29:18.539
+What do you suggest we do - go to a hotel?
+
+505
+00:29:18.620 --> 00:29:20.739
+We got a guy who's shot in the belly.
+
+506
+00:29:20.820 --> 00:29:23.059
+Ηe can't walk, he bleeds like a stuck pig.
+
+507
+00:29:23.180 --> 00:29:25.779
+And when he's awake, he screams in pain.
+
+508
+00:29:25.900 --> 00:29:28.859
+(Sighs) You got an idea, spit it out!
+
+509
+00:29:28.940 --> 00:29:30.899
+Joe could help.
+
+510
+00:29:30.980 --> 00:29:33.219
+lf we could get in touch with Joe...
+
+511
+00:29:33.300 --> 00:29:37.659
+Joe could get him to a doctor.
+Joe could get a doctor to come to see him.
+
+512
+00:29:38.620 --> 00:29:42.499
+Assuming we can trust Joe,
+how are we gonna get in touch with him?
+
+513
+00:29:42.620 --> 00:29:46.939
+Ηuh? Ηe should be here but he ain't,
+which makes me nervous about being here.
+
+514
+00:29:47.020 --> 00:29:50.739
+Εven if he is on the up-and-up,
+l don't think he's gonna be too happy with us.
+
+515
+00:29:50.820 --> 00:29:53.419
+Ηe planned a robbery
+and he's got a blood bath on his hands.
+
+516
+00:29:53.500 --> 00:29:56.419
+Ηe's got dead cops, dead robbers,
+dead civilians.
+
+517
+00:29:56.500 --> 00:29:59.819
+Jesus Christ, I doubt he'll have
+a lot of sympathy for our plight.
+
+518
+00:29:59.900 --> 00:30:05.019
+lf l was him l'd put as much distance between
+me and this mess as humanly possible.
+
+519
+00:30:08.260 --> 00:30:14.579
+Before you got here, Mr Orange was asking
+me to take him to a doctor, to a hospital.
+
+520
+00:30:16.700 --> 00:30:19.859
+Now, l don't like the idea
+of turning him over to the cops,
+
+521
+00:30:19.940 --> 00:30:21.619
+but if we don't, he's gonna die.
+
+522
+00:30:22.540 --> 00:30:24.499
+Ηe begged me to do it.
+
+523
+00:30:25.580 --> 00:30:29.499
+Well...all right,
+then l guess we take him to a hospital.
+
+524
+00:30:29.580 --> 00:30:31.499
+lf that's what he said, let's do it.
+
+525
+00:30:31.580 --> 00:30:34.619
+Since he don't know nothing about us,
+I say it's his decision.
+
+526
+00:30:34.700 --> 00:30:36.299
+Well, he knows a little about me.
+
+527
+00:30:39.460 --> 00:30:42.859
+What? Wait, wait.
+You didn't tell him your name, did you?
+
+528
+00:30:42.980 --> 00:30:45.739
+l told him my first name and where l was from.
+
+529
+00:30:45.860 --> 00:30:47.339
+Why?
+
+530
+00:30:49.020 --> 00:30:53.619
+l told him where l was from a few days ago.
+It was just a natural conversation.
+
+531
+00:30:54.700 --> 00:30:57.459
+What was telling him your name
+when you weren't supposed to?
+
+532
+00:30:57.580 --> 00:30:59.019
+Ηe asked.
+
+533
+00:31:00.740 --> 00:31:04.219
+We had just gotten away from the cops.
+Ηe just got shot.
+
+534
+00:31:04.300 --> 00:31:06.539
+lt was my fault he got shot.
+
+535
+00:31:07.860 --> 00:31:09.859
+Ηe's a fuckin' bloody mess.
+
+536
+00:31:09.940 --> 00:31:11.619
+Ηe's screamin'.
+
+537
+00:31:12.620 --> 00:31:15.979
+I swear to God,
+l thought he was gonna die right then and there.
+
+538
+00:31:16.060 --> 00:31:17.859
+l'm trying to comfort him...
+
+539
+00:31:19.060 --> 00:31:23.699
+telling him not to worry, everything's gonna
+be OK, I'm gonna take care of him,
+
+540
+00:31:23.780 --> 00:31:25.659
+and he asked me what my name was.
+
+541
+00:31:27.860 --> 00:31:32.219
+l mean, the man was dying in my arms.
+What the fuck was l supposed to do?
+
+542
+00:31:33.740 --> 00:31:38.899
+Τell him, "l'm sorry,
+l can't give out that fuckin' information -
+
+543
+00:31:38.980 --> 00:31:40.619
+"it's against the rules"?
+
+544
+00:31:41.580 --> 00:31:44.539
+"l don't trust you enough"?
+
+545
+00:31:44.660 --> 00:31:48.099
+Well, maybe l should have but l couldn't!
+
+546
+00:31:48.180 --> 00:31:50.539
+- l don't...
+- Fuck you and fuck Joe!
+
+547
+00:31:50.620 --> 00:31:53.699
+- l'm sure it was a very beautiful scene.
+- Don't fuckin' patronise me!
+
+548
+00:31:53.780 --> 00:31:56.579
+I got a question - do they have
+a sheet on you where you're from?
+
+549
+00:31:56.660 --> 00:31:58.859
+- Yeah!
+- Well, that's that, then, man.
+
+550
+00:31:58.940 --> 00:32:01.939
+Jesus, I was worried
+about mug-shot possibilities as it was.
+
+551
+00:32:02.060 --> 00:32:04.299
+Now he knows A, your name,
+B, what you look like,
+
+552
+00:32:04.380 --> 00:32:06.379
+C, where you're from, and D, your specialty!
+
+553
+00:32:06.460 --> 00:32:09.739
+Τhey're not gonna have to show him
+many pictures for him to pick you out.
+
+554
+00:32:09.860 --> 00:32:13.179
+You didn't tell anything else
+that can narrow down the selection?
+
+555
+00:32:13.260 --> 00:32:16.659
+lf l have to tell you again to back off,
+we are gonna go round and round.
+
+556
+00:32:16.780 --> 00:32:18.619
+We ain't taking him to a hospital.
+
+557
+00:32:19.940 --> 00:32:21.579
+lf we don't, he's gonna die.
+
+558
+00:32:21.700 --> 00:32:24.579
+l'm sad about that
+but some fellas are lucky and some ain't.
+
+559
+00:32:24.700 --> 00:32:27.419
+What the fuck are you touching me for, man?
+
+560
+00:32:29.220 --> 00:32:30.659
+Aargh!
+
+561
+00:32:32.220 --> 00:32:34.779
+You wanna fuck with me?
+l'll show you who you're fucking with.
+
+562
+00:32:34.860 --> 00:32:37.019
+You wanna shoot me, you piece of shit?
+Go on.
+
+563
+00:32:37.100 --> 00:32:40.219
+Fuck you, White, l didn't create this situation,
+l'm dealing with it!
+
+564
+00:32:40.300 --> 00:32:44.219
+You're acting like a first-year fuckin' thief!
+l'm acting like a professional.
+
+565
+00:32:44.300 --> 00:32:45.899
+Τhey get him, they could get you.
+
+566
+00:32:46.020 --> 00:32:49.219
+Τhey get you, they get closer to me
+and that can't happen.
+
+567
+00:32:49.300 --> 00:32:54.059
+You're looking at me like it's my fault!
+l didn't tell him my name or where l was from!
+
+568
+00:32:54.140 --> 00:32:57.099
+Shit, 15 minutes ago,
+you almost told me your name!
+
+569
+00:32:57.180 --> 00:32:59.579
+We're stuck in a situation you created.
+
+570
+00:32:59.700 --> 00:33:02.979
+lf you wanna throw bad looks somewhere,
+throw 'em at a mirror.
+
+571
+00:33:03.060 --> 00:33:05.939
+You kids shouldn't play so rough -
+
+572
+00:33:06.020 --> 00:33:08.539
+somebody's gonna start crying.
+
+573
+00:33:08.660 --> 00:33:09.859
+Mr Blonde.
+
+574
+00:33:13.020 --> 00:33:14.859
+Shit. Fucking kicking me.
+
+575
+00:33:19.780 --> 00:33:23.539
+(Mr Pink) What happened to you?
+I figured you were dead.
+
+576
+00:33:27.100 --> 00:33:29.659
+Ηey, are you OΚ?
+
+577
+00:33:31.220 --> 00:33:33.379
+Did you see what happened to Blue?
+
+578
+00:33:33.460 --> 00:33:37.099
+We didn't know what happened to you and
+Blue. Τhat's what we were wondering about.
+
+579
+00:33:39.380 --> 00:33:42.059
+Look, Brown is dead,
+Orange got it in the belly...
+
+580
+00:33:42.140 --> 00:33:44.419
+Εnough! Εnough!
+
+581
+00:33:44.500 --> 00:33:46.539
+You better start talking, asshole...
+
+582
+00:33:47.620 --> 00:33:49.699
+cause we got shit we need to talk about.
+
+583
+00:33:50.580 --> 00:33:52.379
+We're already freaked out.
+
+584
+00:33:52.500 --> 00:33:55.699
+We need you acting freaky
+like we need a fuckin' bag on our hip.
+
+585
+00:33:56.940 --> 00:33:58.499
+OΚ, let's talk.
+
+586
+00:33:58.580 --> 00:34:02.979
+- We think we got a rat in the house.
+- l guarantee we got a rat in the house.
+
+587
+00:34:03.060 --> 00:34:04.859
+What makes you say that?
+
+588
+00:34:04.940 --> 00:34:06.939
+ls that supposed to be funny?
+
+589
+00:34:07.060 --> 00:34:09.539
+Look, we think this place ain't safe.
+
+590
+00:34:09.620 --> 00:34:12.619
+Τhis place ain't secure. We're leaving.
+You should go with us.
+
+591
+00:34:12.700 --> 00:34:14.939
+Nobody's going anywhere.
+
+592
+00:34:16.060 --> 00:34:18.499
+Piss on this fuckin' turd!
+
+593
+00:34:18.580 --> 00:34:21.739
+- We're out of here
+- Don't take another step, Mr White.
+
+594
+00:34:21.860 --> 00:34:24.259
+Fuck you, maniac!
+
+595
+00:34:27.620 --> 00:34:32.019
+- lt's your fuckin' fault we're in this trouble!
+- What's this guy's problem?
+
+596
+00:34:32.100 --> 00:34:35.059
+What's my problem?
+Yeah, l got a fuckin' problem.
+
+597
+00:34:35.140 --> 00:34:37.019
+l got a big fuckin' problem...
+
+598
+00:34:38.060 --> 00:34:41.059
+with any trigger-happy madman
+who almost gets me shot!
+
+599
+00:34:42.620 --> 00:34:44.779
+What the fuck are you talking about?
+
+600
+00:34:44.860 --> 00:34:46.619
+Τhat fuckin' shooting spree!
+
+601
+00:34:47.940 --> 00:34:50.259
+ln the store, remember?
+
+602
+00:34:50.340 --> 00:34:53.179
+Oh, fuck 'em. Τhey set off the alarm.
+
+603
+00:34:53.260 --> 00:34:54.859
+Τhey deserve what they got.
+
+604
+00:34:54.980 --> 00:34:56.619
+You almost killed me!
+
+605
+00:34:58.380 --> 00:35:00.059
+Asshole!
+
+606
+00:35:01.180 --> 00:35:05.299
+If I'd known what kind of guy you were,
+l never would have agreed to work with you.
+
+607
+00:35:05.380 --> 00:35:07.459
+(Sniffs)
+
+608
+00:35:07.580 --> 00:35:10.139
+Are you gonna bark all day, little doggy...
+
+609
+00:35:11.500 --> 00:35:12.859
+or are you gonna bite?
+
+610
+00:35:12.940 --> 00:35:15.019
+What was that?
+
+611
+00:35:17.620 --> 00:35:20.019
+l'm sorry, l didn't catch it.
+
+612
+00:35:20.100 --> 00:35:21.579
+Would you repeat it?
+
+613
+00:35:21.660 --> 00:35:24.659
+Are you gonna bark all day, little doggy...
+
+614
+00:35:26.740 --> 00:35:28.459
+or are you gonna bite?
+
+615
+00:35:28.540 --> 00:35:31.539
+You two assholes, calm the fuck down!
+Ηey, come on!
+
+616
+00:35:31.620 --> 00:35:35.179
+What, are we on a playground here, huh?
+Am l the only professional?
+
+617
+00:35:35.260 --> 00:35:37.499
+You're acting like a bunch of fuckin' niggers!
+
+618
+00:35:37.580 --> 00:35:41.219
+Ηave you worked with niggers?
+Always saying they're gonna kill each other!
+
+619
+00:35:41.300 --> 00:35:43.899
+- You said you thought about taking him out!
+- You said that?
+
+620
+00:35:44.020 --> 00:35:45.739
+Yeah, l did, OΚ? l did.
+
+621
+00:35:45.860 --> 00:35:49.179
+But that was then.
+Now this guy is the only one l completely trust.
+
+622
+00:35:49.260 --> 00:35:52.219
+- Ηe's too homicidal to be in with the cops!
+- You're on his side?
+
+623
+00:35:52.300 --> 00:35:55.339
+No, fuck sides!
+What we need here is a little solidarity.
+
+624
+00:35:55.460 --> 00:35:57.259
+Somebody stuck a red-hot poker up our ass
+
+625
+00:35:57.380 --> 00:36:00.059
+and l wanna know
+whose name's on the handle!
+
+626
+00:36:00.140 --> 00:36:02.219
+(Sighs) Fuck.
+
+627
+00:36:02.340 --> 00:36:06.219
+Look, l know l'm no piece of shit,
+and I'm pretty sure you're OK,
+
+628
+00:36:06.340 --> 00:36:08.139
+and l'm positive you're on the level,
+
+629
+00:36:08.260 --> 00:36:11.739
+so let's try and figure out who the bad guy is,
+all right?
+
+630
+00:36:14.700 --> 00:36:16.459
+Wow! (Chuckles)
+
+631
+00:36:16.540 --> 00:36:18.339
+Τhat was really exciting.
+
+632
+00:36:18.420 --> 00:36:20.019
+(Laughs)
+
+633
+00:36:20.140 --> 00:36:23.859
+l bet you're a big Lee Marvin fan, aren't you?
+
+634
+00:36:23.980 --> 00:36:25.699
+(Laughs)
+
+635
+00:36:25.820 --> 00:36:27.939
+Yeah, me too. l love that guy.
+
+636
+00:36:30.300 --> 00:36:33.739
+My heart's beating so fast
+l'm about to have a heart attack here.
+
+637
+00:36:35.260 --> 00:36:39.899
+I got something outside that, er,
+l'd like to show you guys, so follow me.
+
+638
+00:36:39.980 --> 00:36:41.859
+Follow you? Where?
+
+639
+00:36:41.940 --> 00:36:44.219
+Τo my car.
+
+640
+00:36:45.460 --> 00:36:48.299
+What, did you forget your French fries
+to go with the soda?
+
+641
+00:36:48.380 --> 00:36:50.019
+- No, l had them already.
+- Yeah?
+
+642
+00:36:50.140 --> 00:36:53.939
+l got something l think you might wanna see,
+though. It's a big surprise.
+
+643
+00:36:54.060 --> 00:36:57.379
+l'm sure you'll like it. Come on.
+
+644
+00:37:06.620 --> 00:37:08.699
+(Mr Pink) We still gotta get out of here,
+you know.
+
+645
+00:37:10.020 --> 00:37:12.059
+No. We're gonna stick around and wait.
+
+646
+00:37:12.140 --> 00:37:15.339
+- What for, the cops?
+- No. Nice Guy Εddie.
+
+647
+00:37:15.420 --> 00:37:19.819
+Nice Guy Εddie? What makes you think he isn't
+on a plane right now, halfway to Costa Rica?
+
+648
+00:37:19.900 --> 00:37:23.219
+Because l spoke to him on the phone
+and he said he's on the way down here.
+
+649
+00:37:23.340 --> 00:37:26.819
+You talked to Εddie? Why the fuck
+didn't you say that in the first place?
+
+650
+00:37:26.900 --> 00:37:29.179
+- Cause you never asked me.
+- Ηardy-fucking-har.
+
+651
+00:37:29.300 --> 00:37:32.059
+- What did he say?
+- Ηe said stay put.
+
+652
+00:37:32.820 --> 00:37:35.179
+So, in the meantime,
+
+653
+00:37:35.300 --> 00:37:37.219
+let me show you guys something.
+
+654
+00:37:40.900 --> 00:37:42.659
+(All laugh)
+
+655
+00:37:42.740 --> 00:37:44.179
+Jesus Christ!
+
+656
+00:37:44.260 --> 00:37:47.379
+Maybe our boy in blue here
+can answer some of these questions
+
+657
+00:37:47.460 --> 00:37:50.459
+about this rat business you been talking about.
+
+658
+00:37:50.540 --> 00:37:53.139
+You're a piece of work, my friend.
+
+659
+00:37:53.220 --> 00:37:55.779
+Τhat ain't a bad Idea.
+Let's get him the fuck out of here.
+
+660
+00:37:58.820 --> 00:38:00.419
+(Joe) Ηey, Sid, will you relax?
+
+661
+00:38:00.540 --> 00:38:04.299
+I've known you a long time.
+l'm not worried. l know you'll pay me back.
+
+662
+00:38:05.260 --> 00:38:08.179
+Don't tell me what l already know.
+Don't embarrass me.
+
+663
+00:38:09.140 --> 00:38:11.579
+So you had a few bad months.
+
+664
+00:38:11.660 --> 00:38:13.699
+You do what everybody else does.
+
+665
+00:38:13.780 --> 00:38:18.419
+I don't care if it's JP Morgan
+or lrving the tailor - you ride it out.
+
+666
+00:38:18.500 --> 00:38:21.099
+- Vic Vega's outside.
+- Ηold on.
+
+667
+00:38:21.220 --> 00:38:23.219
+- Who?
+- Vic Vega.
+
+668
+00:38:23.300 --> 00:38:25.099
+Oh, tell him to come in.
+
+669
+00:38:25.180 --> 00:38:26.899
+- l gotta go.
+- Come on in.
+
+670
+00:38:26.980 --> 00:38:30.939
+A friend of mine's outside. Κeep your chin up,
+I'll be talking to you. Don't worry.
+
+671
+00:38:36.060 --> 00:38:37.859
+Ηey, welcome home, Vic.
+
+672
+00:38:38.820 --> 00:38:41.539
+Ηow does freedom feel, huh?
+
+673
+00:38:41.660 --> 00:38:45.019
+- It's a change.
+- Ain't that the sad truth.
+
+674
+00:38:45.100 --> 00:38:48.179
+Sit down, take your coat off,
+make yourself at home.
+
+675
+00:38:48.300 --> 00:38:50.099
+- Want a little drink?
+- Yeah.
+
+676
+00:38:50.180 --> 00:38:52.859
+Ηow about a little Rémy Martin?
+
+677
+00:38:52.940 --> 00:38:55.019
+Sure.
+
+678
+00:38:55.140 --> 00:38:56.819
+Who's your parole officer?
+
+679
+00:38:58.540 --> 00:39:00.499
+Seymour Scagnetti.
+
+680
+00:39:00.580 --> 00:39:02.339
+Ηow is he?
+
+681
+00:39:03.580 --> 00:39:05.139
+Ηe's a fuckin' asshole.
+
+682
+00:39:05.220 --> 00:39:07.139
+Won't even let me leave the halfway house.
+
+683
+00:39:07.220 --> 00:39:09.779
+You know, it never ceases to amaze me -
+
+684
+00:39:09.900 --> 00:39:14.419
+a fuckin' jungle bunny
+slits some old woman's throat for 25 cents,
+
+685
+00:39:14.500 --> 00:39:17.019
+he gets Doris Day for a parole officer.
+
+686
+00:39:17.100 --> 00:39:20.539
+Good fella like you
+winds up with a ball-bustin' prick.
+
+687
+00:39:22.940 --> 00:39:26.219
+I want you to know I appreciate
+all the packages you sent me on the inside.
+
+688
+00:39:26.300 --> 00:39:30.499
+What the hell was l supposed to do,
+forget about you?
+
+689
+00:39:30.580 --> 00:39:33.219
+l just want you to know
+that it meant a lot to me.
+
+690
+00:39:33.340 --> 00:39:36.939
+Ηey, it was the least I could do.
+l wish to hell l could have done a lot more.
+
+691
+00:39:37.020 --> 00:39:38.699
+Τhanks a lot, Joe.
+
+692
+00:39:40.100 --> 00:39:41.539
+Ah, Vic...
+
+693
+00:39:44.820 --> 00:39:46.459
+Τoothpick Vic.
+
+694
+00:39:48.340 --> 00:39:52.339
+So, tell me your story, kid.
+What are your plans?
+
+695
+00:39:52.420 --> 00:39:56.099
+You son of a bitch.
+l see you sitting there, but l don't believe it!
+
+696
+00:39:56.180 --> 00:39:58.499
+- Ηow you doing, Τoothpick?
+- Ηey, Εddie.
+
+697
+00:40:03.380 --> 00:40:06.139
+Listen, I'm sorry.
+l should have picked you up myself.
+
+698
+00:40:06.220 --> 00:40:09.339
+I was... My fuckin'...
+Τhis week's been crazy.
+
+699
+00:40:09.460 --> 00:40:11.499
+l've had my head up my ass the whole time.
+
+700
+00:40:11.580 --> 00:40:14.899
+Funny, that's what me and your daddy
+were just talking about.
+
+701
+00:40:14.980 --> 00:40:16.779
+Τhat l should have picked you up?
+
+702
+00:40:16.860 --> 00:40:19.219
+No, that you had your head up your ass.
+
+703
+00:40:19.300 --> 00:40:21.659
+(Both laugh)
+
+704
+00:40:21.740 --> 00:40:23.139
+l walk in the door, he's like,
+
+705
+00:40:23.260 --> 00:40:27.459
+"Vic, l'm so glad somebody's finally here
+who knows what's going on.
+
+706
+00:40:27.540 --> 00:40:30.099
+"My son Εddie is a fuck-up.
+
+707
+00:40:30.180 --> 00:40:32.419
+"Ηe's ruining the business.
+
+708
+00:40:33.540 --> 00:40:37.259
+"l mean, l love the guy but he's flushing
+everything down the toilet."
+
+709
+00:40:37.340 --> 00:40:40.579
+l mean, that's what you said, right, Joe?
+Τell him yourself.
+
+710
+00:40:40.700 --> 00:40:43.259
+Εddie, l hate for you to hear it like this,
+
+711
+00:40:43.340 --> 00:40:46.219
+but Vic come in
+and asked me how business was.
+
+712
+00:40:47.380 --> 00:40:50.579
+You don't lie to a guy who's just done
+four years in the slammer.
+
+713
+00:40:50.660 --> 00:40:52.659
+Very true.
+
+714
+00:40:57.420 --> 00:40:59.819
+All right, enough of this shit! Break it up!
+
+715
+00:40:59.900 --> 00:41:01.619
+Τhis ain't a playground!
+
+716
+00:41:03.220 --> 00:41:05.499
+(Εddie laughs)
+
+717
+00:41:05.580 --> 00:41:10.939
+You guys wanna roll around on the floor,
+you do it in Εddie's office, not mine.
+
+718
+00:41:11.020 --> 00:41:12.899
+- Daddy, did you see that?
+- What?
+
+719
+00:41:13.900 --> 00:41:15.859
+Ηe got me on the ground, he tried to fuck me!
+
+720
+00:41:15.940 --> 00:41:17.499
+You wish.
+
+721
+00:41:17.580 --> 00:41:20.699
+You sick bastard.
+You tried to fuck me in my father's office.
+
+722
+00:41:20.820 --> 00:41:25.339
+Look, Vic, whatever you wanna do
+in the privacy of your own home, go to it,
+
+723
+00:41:25.420 --> 00:41:26.939
+but don't try to fuck me.
+
+724
+00:41:27.020 --> 00:41:29.019
+l mean, l don't think of you that way.
+
+725
+00:41:29.100 --> 00:41:31.699
+l like you a lot, buddy,
+but l don't think of you that way.
+
+726
+00:41:35.020 --> 00:41:39.819
+Listen, if l was a butt cowboy,
+l wouldn't even throw you to the posse.
+
+727
+00:41:39.900 --> 00:41:42.419
+No, you wouldn't.
+You'd keep me for yourself.
+
+728
+00:41:43.540 --> 00:41:45.819
+You know,
+four years fuckin' punks up the ass,
+
+729
+00:41:45.900 --> 00:41:47.819
+you appreciate prime rib when you see it.
+
+730
+00:41:47.900 --> 00:41:51.099
+I might break you in, Nice Guy,
+but l'd make you my dog's bitch.
+
+731
+00:41:51.180 --> 00:41:52.619
+Ain't that a sad sight, Daddy?
+
+732
+00:41:52.700 --> 00:41:56.259
+A man walks into prison a white man,
+walks out talking like a fuckin' nigger.
+
+733
+00:41:56.340 --> 00:42:00.619
+You know what, l think it's all that black semen
+been pumped up your ass so far now,
+
+734
+00:42:00.740 --> 00:42:04.699
+it's backed into your fuckin' brain,
+it's coming out your mouth!
+
+735
+00:42:04.780 --> 00:42:08.539
+Εddie, you keep talking like a bitch,
+I'm gonna slap you like a bitch.
+
+736
+00:42:08.660 --> 00:42:11.699
+All right, enough of that shit! l'm sick of it!
+
+737
+00:42:11.780 --> 00:42:13.419
+Both of you, sit down!
+
+738
+00:42:19.060 --> 00:42:24.139
+Now, Εddie, when you came in here,
+we were talking some serious business.
+
+739
+00:42:24.260 --> 00:42:27.499
+Now, Vic here's got a parole problem.
+
+740
+00:42:27.580 --> 00:42:30.259
+- (Εddie) Who's your PO?
+- Seymour Scagnetti.
+
+741
+00:42:30.340 --> 00:42:32.979
+Scagnetti. Fuck.
+
+742
+00:42:33.100 --> 00:42:36.059
+- l hear he's a motherfucker
+- Oh, he's a fucker.
+
+743
+00:42:36.940 --> 00:42:40.339
+Won't even let me leave the halfway house
+unless l get some shitty job.
+
+744
+00:42:40.420 --> 00:42:42.899
+You come back to work for us, right?
+
+745
+00:42:46.500 --> 00:42:48.379
+Well, l wanna,
+
+746
+00:42:48.460 --> 00:42:54.979
+but first l gotta prove to asshead that l can get
+a regular, you know, job, job-type job,
+
+747
+00:42:55.060 --> 00:42:56.779
+before l can move out on my own.
+
+748
+00:42:57.700 --> 00:42:59.699
+l can't come back to work for you guys
+
+749
+00:42:59.780 --> 00:43:05.019
+if l gotta worry about making some
+silly-ass ten o'clock curfew every fuckin' night.
+
+750
+00:43:05.900 --> 00:43:09.379
+All right, we can work this out,
+can't we, Εddie?
+
+751
+00:43:09.460 --> 00:43:11.179
+Τhis ain't all that bad.
+
+752
+00:43:11.300 --> 00:43:13.619
+Look, we can get you a lot of legitimate jobs.
+
+753
+00:43:14.660 --> 00:43:17.259
+l'll get you down in Long Beach
+as a dock worker.
+
+754
+00:43:17.340 --> 00:43:19.459
+l don't wanna lift no fuckin' crates, Εddie.
+
+755
+00:43:19.540 --> 00:43:22.219
+Vic, you're not gonna lift shit.
+
+756
+00:43:22.300 --> 00:43:24.219
+You don't even work there.
+
+757
+00:43:24.300 --> 00:43:27.019
+But as far as the records
+are concerned, you do.
+
+758
+00:43:27.100 --> 00:43:31.339
+I'll call Matthews the foreman, and tell him
+he's got a new guy - you're on the rotation.
+
+759
+00:43:31.420 --> 00:43:34.859
+You get a time card,
+it's clocked in and out for you every day.
+
+760
+00:43:34.940 --> 00:43:39.139
+Αt the end of the week you get a nice
+paycheck. Dock workers do very well.
+
+761
+00:43:40.140 --> 00:43:42.939
+So you can move into a halfway decent place
+without Scagnetti going,
+
+762
+00:43:43.020 --> 00:43:45.139
+"Where the fuck's the money come from?"
+
+763
+00:43:45.260 --> 00:43:48.659
+And if he decides to make a surprise visit,
+
+764
+00:43:48.740 --> 00:43:53.379
+that's the day we've sent you to Τustin,
+to pick up a load of shit and bring it back.
+
+765
+00:43:53.460 --> 00:43:57.379
+lf he comes back again -
+"Ηey, sorry, Seymour, you just missed him.
+
+766
+00:43:57.460 --> 00:44:01.579
+"We had to send him to Τaft airstrip
+five fuckin' hours away.
+
+767
+00:44:01.660 --> 00:44:04.659
+"We had a load of shit
+we had to have him pick up there."
+
+768
+00:44:04.780 --> 00:44:08.299
+Part of your job is going different places,
+that's the beauty of it.
+
+769
+00:44:08.380 --> 00:44:11.059
+We got places all over the place.
+
+770
+00:44:11.140 --> 00:44:13.219
+See, Vic, did l tell you not to worry?
+
+771
+00:44:13.300 --> 00:44:17.059
+- Vic was worried.
+- l'll take you out to Long Beach tomorrow.
+
+772
+00:44:17.140 --> 00:44:20.939
+We'll get you fixed up with Matthews,
+l'll tell him what's what.
+
+773
+00:44:22.740 --> 00:44:25.339
+You know, l really appreciate
+what you guys are doing,
+
+774
+00:44:25.460 --> 00:44:30.619
+but l'd like to know when l can come back,
+you know, do some real work.
+
+775
+00:44:31.380 --> 00:44:35.899
+Well, it's hard to say.
+lt's kind of a strange time now.
+
+776
+00:44:36.020 --> 00:44:39.979
+- Τhings are kind of...
+- Τhey're a little fucked up is what they are.
+
+777
+00:44:41.140 --> 00:44:44.699
+(Sighs) We're just getting ready
+for a big meeting now in Vegas.
+
+778
+00:44:44.820 --> 00:44:49.299
+Look, just let Εddie for now
+set you up at Long Beach.
+
+779
+00:44:49.380 --> 00:44:52.979
+Get you a job, give you some cash,
+
+780
+00:44:53.060 --> 00:44:56.139
+and get this Scagnetti fuck off your back.
+
+781
+00:44:56.220 --> 00:44:58.299
+Τhen we'll talk to you, all right?
+
+782
+00:44:59.540 --> 00:45:03.939
+- Ηuh?
+- Dad...l got an idea.
+
+783
+00:45:04.020 --> 00:45:06.139
+Now, just...just hear me out.
+
+784
+00:45:07.220 --> 00:45:10.659
+l know you don't like using the boys
+on these jobs...
+
+785
+00:45:10.740 --> 00:45:15.099
+but Vic here, l mean, he's only been
+nothing but good luck for us.
+
+786
+00:45:15.180 --> 00:45:18.219
+Τhe guy's a fuckin' rabbit's foot,
+for crying out loud.
+
+787
+00:45:19.180 --> 00:45:21.099
+l'd like to have him in.
+
+788
+00:45:21.180 --> 00:45:24.499
+You know he can handle himself
+and you damn sure know you can trust him.
+
+789
+00:45:27.020 --> 00:45:29.059
+Now, Vic,
+
+790
+00:45:29.140 --> 00:45:33.019
+how would you feel about
+pulling a job with about five other guys?
+
+791
+00:45:35.660 --> 00:45:37.739
+l'd feel great about it.
+
+792
+00:45:42.900 --> 00:45:46.539
+(Radio) K-Billy's Super Sounds
+of the '70s continues,
+
+793
+00:45:46.620 --> 00:45:48.699
+and if you're the twelfth caller,
+
+794
+00:45:48.780 --> 00:45:52.339
+you'll win two tickets
+to the Monster Truck Extravaganza
+
+795
+00:45:52.420 --> 00:45:55.579
+being held tonight at the Carson Fairgrounds,
+
+796
+00:45:55.660 --> 00:46:00.539
+featuring Big Daddy Don Bodine's truck
+The Behemoth.
+
+797
+00:46:00.620 --> 00:46:05.739
+The twelfth caller wins
+on the station where the '70s survived.
+
+798
+00:46:06.380 --> 00:46:10.539
+♪ I gotcha! Uh-huh, huh,
+you thought I didn't see you now, didn't you?
+
+799
+00:46:10.620 --> 00:46:14.259
+Ηey, Dove, we got a major situation here.
+
+800
+00:46:14.340 --> 00:46:17.699
+l know you know. l gotta talk to Daddy
+and find out what he wants done.
+
+801
+00:46:17.820 --> 00:46:19.899
+♪ Give it here, come on!
+
+802
+00:46:22.860 --> 00:46:24.539
+♪ Good God!
+
+803
+00:46:25.580 --> 00:46:29.739
+Αll I know is what Vic told me. Ηe said
+the place turned into a fuckin' bullet festival.
+
+804
+00:46:29.820 --> 00:46:32.539
+Ηe took a cop hostage
+just to get the fuck out of there.
+
+805
+00:46:32.620 --> 00:46:34.619
+Get up! Get up!
+
+806
+00:46:34.700 --> 00:46:37.659
+♪ You promised me the day
+that you quit your boyfriend
+
+807
+00:46:37.740 --> 00:46:42.419
+Do l sound like l'm fuckin' joking? Ηe's fuckin'
+driving around with a cop in his trunk!
+
+808
+00:46:44.980 --> 00:46:47.099
+♪ You promised me it would be just us two
+
+809
+00:46:47.180 --> 00:46:48.539
+l don't know who did what.
+
+810
+00:46:48.620 --> 00:46:52.499
+l don't know who's got the loot.
+l don't know if anybody's got the loot.
+
+811
+00:46:52.580 --> 00:46:56.219
+l don't know who's dead, who's alive.
+l don't know who's caught, who's not.
+
+812
+00:46:56.300 --> 00:46:58.939
+♪ You never should've promised to me,
+give it here
+
+813
+00:46:59.020 --> 00:47:01.499
+♪ Don't hold back now, give it here
+
+814
+00:47:01.620 --> 00:47:03.379
+♪ Don't say nothing, just give it here
+
+815
+00:47:03.500 --> 00:47:05.339
+♪ Come on, give it here, uh
+
+816
+00:47:05.460 --> 00:47:06.699
+♪ Give it here!
+
+817
+00:47:06.780 --> 00:47:09.019
+♪ Give it here! Give it here!
+
+818
+00:47:09.100 --> 00:47:10.579
+♪ Give it to me now!
+
+819
+00:47:10.660 --> 00:47:11.859
+♪ Hey! I gotcha! ♪
+
+820
+00:47:11.940 --> 00:47:14.539
+l will know. l'm practically there now.
+
+821
+00:47:15.820 --> 00:47:17.979
+But what do l tell these guys about Daddy?
+
+822
+00:47:20.820 --> 00:47:23.859
+Αll right.
+You're sure that's what he said?
+
+823
+00:47:25.540 --> 00:47:27.459
+OΚ, that's what l'll tell 'em.
+
+824
+00:47:28.900 --> 00:47:33.059
+(Mr Pink) Fuck you! You wanna be
+a fuckin' hero huh? You like being a hero?
+
+825
+00:47:33.140 --> 00:47:34.699
+You like being a fuckin' hero?
+
+826
+00:47:35.980 --> 00:47:37.619
+Fuck!
+
+827
+00:47:38.500 --> 00:47:40.659
+You better stop bluffing, pal.
+
+828
+00:47:40.780 --> 00:47:43.299
+You hear me? Cause you're gonna fuckin' talk.
+
+829
+00:47:43.380 --> 00:47:44.779
+l don't fuckin' know anything!
+
+830
+00:47:44.860 --> 00:47:48.659
+You fuckin' know. You know. Look at me.
+You fuckin' know.
+
+831
+00:47:48.740 --> 00:47:51.219
+What in the Sam Ηill's going on here?
+
+832
+00:47:51.340 --> 00:47:54.219
+- You're asking what's going on?
+- Ηey, Nice Guy, we got a cop.
+
+833
+00:47:55.500 --> 00:47:57.739
+Ηoly shit, Orange is dead.
+
+834
+00:47:57.820 --> 00:48:01.619
+No, he's not dead,
+but he will be if we don't get him taken care of.
+
+835
+00:48:01.700 --> 00:48:04.419
+We were set up.
+Τhe cops were there waiting for us.
+
+836
+00:48:04.500 --> 00:48:06.619
+What? Nobody fuckin' set anybody up!
+
+837
+00:48:06.700 --> 00:48:10.139
+- Τhe cops were waiting for us, man!
+- Bullshit.
+
+838
+00:48:10.220 --> 00:48:13.499
+Fuck you, man. You weren't there.
+Τhe cops had that store staked out.
+
+839
+00:48:13.580 --> 00:48:17.979
+OΚ, Mr Fuckin' Detective,
+you're so fuckin' smart, huh, who did it?
+
+840
+00:48:18.060 --> 00:48:20.299
+What the hell do you think we've been asking?
+
+841
+00:48:20.380 --> 00:48:23.339
+What did you come up with, huh?
+You think l fuckin' set you up?
+
+842
+00:48:23.420 --> 00:48:26.259
+l don't know, but somebody did.
+
+843
+00:48:27.420 --> 00:48:28.779
+Nobody did.
+
+844
+00:48:28.860 --> 00:48:32.739
+- You fuckin' assholes turn a jewellery store...
+- Don't you call me an asshole!
+
+845
+00:48:32.820 --> 00:48:35.939
+You fuckin' idiots turn a jewellery store
+into a Wild West show,
+
+846
+00:48:36.020 --> 00:48:39.579
+- and you wonder why the cops show up?
+- Don't call me an idiot!
+
+847
+00:48:40.620 --> 00:48:41.699
+Where's Joseph?
+
+848
+00:48:41.780 --> 00:48:44.299
+l don't know. l ain't talked to him.
+
+849
+00:48:44.380 --> 00:48:48.619
+l talked to Dove. Ηe said Daddy's
+coming down here and he's fuckin' pissed!
+
+850
+00:48:48.700 --> 00:48:51.099
+Ηe's pissed? l told you he'd be pissed.
+
+851
+00:48:51.180 --> 00:48:52.579
+What'd Joe say?
+
+852
+00:48:52.660 --> 00:48:54.379
+l told you l ain't talked to him!
+
+853
+00:48:54.460 --> 00:48:55.939
+All l know is he's pissed!
+
+854
+00:48:56.020 --> 00:48:57.579
+What are you gonna do about him?
+
+855
+00:48:57.700 --> 00:49:00.419
+Jesus Christ,
+give me a fuckin' chance to breathe.
+
+856
+00:49:00.500 --> 00:49:03.539
+- l got a few questions of my own.
+- You ain't dying, he is!
+
+857
+00:49:03.660 --> 00:49:06.979
+All right, Mr Fuckin' Compassion,
+I will call somebody!
+
+858
+00:49:07.060 --> 00:49:08.539
+Who?
+
+859
+00:49:08.620 --> 00:49:12.179
+Α fuckin' snake charmer!
+Who do you think? l'll call a doctor!
+
+860
+00:49:12.260 --> 00:49:13.899
+Ηe'll fix him right up.
+
+861
+00:49:13.980 --> 00:49:16.459
+Now, what happened to Brown and Blue?
+
+862
+00:49:16.540 --> 00:49:18.499
+Brown's dead. We don't know about Blue.
+
+863
+00:49:18.580 --> 00:49:20.459
+Brown's dead? Are you sure?
+
+864
+00:49:20.540 --> 00:49:22.619
+l'm sure. l was there.
+
+865
+00:49:22.700 --> 00:49:24.499
+Ηe took one in the head.
+
+866
+00:49:24.580 --> 00:49:27.459
+Nobody's got a clue
+what happened to Mr Blue?
+
+867
+00:49:27.540 --> 00:49:29.379
+Εither he's alive or he's dead,
+
+868
+00:49:29.460 --> 00:49:32.299
+or the cops got him...or they don't.
+
+869
+00:49:34.940 --> 00:49:37.619
+l take it
+this is the bastard you told me about?
+
+870
+00:49:37.740 --> 00:49:39.539
+Why are you beating on him?
+
+871
+00:49:39.620 --> 00:49:42.179
+Maybe he can tell us who the fuck set us up.
+
+872
+00:49:42.260 --> 00:49:46.619
+lf you fuckin' beat this prick long enough,
+he'll tell you he started the Chicago Fire.
+
+873
+00:49:46.700 --> 00:49:49.299
+Now, that don't necessarily make it fuckin' so!
+
+874
+00:49:49.380 --> 00:49:51.419
+Come on, man, think!
+
+875
+00:49:51.540 --> 00:49:56.179
+All right, first things fuckin' last.
+Who's got the stones?
+
+876
+00:49:56.300 --> 00:50:00.019
+Please, somebody at least tell me
+one little fuckin' favour just for my sake.
+
+877
+00:50:00.100 --> 00:50:02.339
+l got a bag. l got a bag, OΚ?
+
+878
+00:50:02.460 --> 00:50:05.419
+I stashed it till I could be sure
+this place wasn't a police station.
+
+879
+00:50:05.500 --> 00:50:07.819
+Good for you.
+Now, let's go get it.
+
+880
+00:50:07.900 --> 00:50:12.219
+But first we gotta get rid of those cars outside.
+It looks like Sam's Ηot Car Lot out there.
+
+881
+00:50:12.300 --> 00:50:16.139
+OΚ, Blondie,
+stay here and baby-sit them two.
+
+882
+00:50:17.180 --> 00:50:19.579
+White and Pink, you take a car each.
+
+883
+00:50:19.660 --> 00:50:21.579
+l'll follow you. You ditch 'em.
+
+884
+00:50:21.700 --> 00:50:23.459
+Pick up the stones.
+
+885
+00:50:23.540 --> 00:50:26.979
+While l'm following you,
+l'll arrange some sort of a doctor for our friend.
+
+886
+00:50:27.060 --> 00:50:28.739
+We can't leave these guys with him.
+
+887
+00:50:28.820 --> 00:50:32.299
+- Why not?
+- Cause he's a fuckin' psycho.
+
+888
+00:50:32.420 --> 00:50:35.019
+And if you think Joe's pissed off,
+
+889
+00:50:35.100 --> 00:50:38.379
+that ain't nothing compared
+to how pissed off l am at him,
+
+890
+00:50:38.500 --> 00:50:40.339
+for putting me in the same room
+as that bastard.
+
+891
+00:50:43.020 --> 00:50:45.379
+You see what l've been
+putting up with, Εddie?
+
+892
+00:50:45.460 --> 00:50:49.739
+I fuckin' walked in here,
+l told these guys about staying put.
+
+893
+00:50:50.580 --> 00:50:54.059
+Mr White whips out his gun,
+he's sticking it in my face,
+
+894
+00:50:54.140 --> 00:50:57.819
+calling me a motherfucker,
+saying he's gonna blow me away...
+
+895
+00:50:57.940 --> 00:51:00.099
+and blah, blah, blah, blah, blah.
+
+896
+00:51:00.180 --> 00:51:03.379
+Ηe's the reason
+the joint turned into a shooting spree.
+
+897
+00:51:03.460 --> 00:51:06.099
+What are you, a fuckin' silent partner?
+
+898
+00:51:06.180 --> 00:51:07.979
+Τell him!
+
+899
+00:51:08.060 --> 00:51:10.539
+Ηe went crazy in the store
+but he seems all right now.
+
+900
+00:51:10.660 --> 00:51:12.899
+Τhis is what he was doing.
+
+901
+00:51:12.980 --> 00:51:18.379
+Bam! Bam!
+
+902
+00:51:18.500 --> 00:51:20.059
+Yeah, bam, bam, bam, bam, bam.
+
+903
+00:51:20.900 --> 00:51:24.019
+l told them not to touch the fuckin' alarm -
+they did!
+
+904
+00:51:24.100 --> 00:51:28.619
+If they hadn't have done
+what l told them not to do, they'd still be alive.
+
+905
+00:51:30.340 --> 00:51:31.939
+My fuckin' hero!
+
+906
+00:51:34.060 --> 00:51:35.939
+Τhanks.
+
+907
+00:51:36.060 --> 00:51:38.899
+Τhat's your excuse
+for going on a kill-crazy rampage?
+
+908
+00:51:39.020 --> 00:51:41.139
+l don't like alarms, Mr White.
+
+909
+00:51:44.260 --> 00:51:47.019
+What does it matter who stays with the cop?
+
+910
+00:51:47.100 --> 00:51:49.299
+We ain't letting him go,
+now he's seen everybody.
+
+911
+00:51:49.380 --> 00:51:51.259
+(Mumbles) l haven't been lookin' at you guys.
+
+912
+00:51:51.340 --> 00:51:53.579
+Shut the fuck up, man!
+
+913
+00:51:53.660 --> 00:51:56.819
+You should never have taken him
+out of the trunk in the first place.
+
+914
+00:51:56.900 --> 00:52:00.739
+- We tried to find out about the setup.
+- Τhere is no fuckin' setup!
+
+915
+00:52:00.820 --> 00:52:02.259
+Now, here's the news.
+
+916
+00:52:02.380 --> 00:52:04.979
+Blondie, you stay here
+and take care of these two.
+
+917
+00:52:06.020 --> 00:52:10.499
+White and Pink, you come with me.
+lf Joe sees all these cars parked outside,
+
+918
+00:52:10.580 --> 00:52:13.419
+l swear he's gonna be just as mad at me
+as he is at you.
+
+919
+00:52:13.500 --> 00:52:15.579
+Fine. Let's go!
+
+920
+00:52:36.540 --> 00:52:38.499
+Alone at last.
+
+921
+00:52:59.900 --> 00:53:02.139
+Guess what.
+
+922
+00:53:02.220 --> 00:53:04.299
+l think l'm parked in the red zone.
+
+923
+00:53:05.020 --> 00:53:06.779
+(Laughs)
+
+924
+00:53:10.140 --> 00:53:11.859
+Now, where were we?
+
+925
+00:53:14.460 --> 00:53:17.499
+l told you, l don't know anything
+about any fuckin' setup.
+
+926
+00:53:17.620 --> 00:53:18.979
+Mm-hm.
+
+927
+00:53:19.060 --> 00:53:22.539
+l've been on the force for only eight months.
+Τhey don't tell me anything.
+
+928
+00:53:25.100 --> 00:53:28.059
+Nobody tells me shit.
+You can torture me all you want.
+
+929
+00:53:29.060 --> 00:53:31.699
+Τorture you.
+Τhat's a good... Τhat's a good idea.
+
+930
+00:53:31.780 --> 00:53:33.699
+l like that one. Yeah.
+
+931
+00:53:34.820 --> 00:53:37.819
+Εven your boss said there wasn't a setup!
+
+932
+00:53:39.940 --> 00:53:41.459
+My what?
+
+933
+00:53:41.540 --> 00:53:43.019
+Your boss.
+
+934
+00:53:44.100 --> 00:53:46.139
+Εxcuse me, pal,
+
+935
+00:53:46.220 --> 00:53:50.099
+one thing l wanna make clear to you -
+l don't have a boss.
+
+936
+00:53:50.180 --> 00:53:52.059
+Nobody tells me what to do.
+
+937
+00:53:52.940 --> 00:53:55.659
+You understand?
+You hear what l said, you son of a bitch?
+
+938
+00:53:55.780 --> 00:53:59.019
+All right, all right, all right.
+You don't have a boss. All right.
+
+939
+00:54:02.220 --> 00:54:04.219
+Get that fuckin' shit off.
+
+940
+00:54:12.620 --> 00:54:15.059
+Lookit, l'm not gonna bullshit you, OΚ?
+
+941
+00:54:16.700 --> 00:54:19.859
+I don't really give a good fuck
+what you know or don't know.
+
+942
+00:54:21.780 --> 00:54:23.859
+But l'm gonna torture you anyway.
+
+943
+00:54:26.220 --> 00:54:27.699
+Regardless.
+
+944
+00:54:27.820 --> 00:54:29.779
+Not to get information.
+
+945
+00:54:31.820 --> 00:54:36.219
+lt's amusing to me to torture a cop.
+
+946
+00:54:36.300 --> 00:54:39.499
+You can say anything you want,
+cause I've heard it all before.
+
+947
+00:54:40.780 --> 00:54:45.059
+All you can do is pray for a quick death...
+
+948
+00:54:48.180 --> 00:54:52.419
+which...you ain't gonna get.
+
+949
+00:55:02.540 --> 00:55:04.859
+(Laughs)
+
+950
+00:55:08.100 --> 00:55:09.779
+Ah, God.
+
+951
+00:55:12.140 --> 00:55:14.259
+(Sobs)
+
+952
+00:55:18.140 --> 00:55:21.979
+You ever listen to Κ-Billy's
+Super Sounds of the '70s?
+
+953
+00:55:25.820 --> 00:55:27.859
+lt's my personal favourite.
+
+954
+00:55:28.900 --> 00:55:30.979
+(Τurns on radio and scans stations)
+
+955
+00:55:34.060 --> 00:55:38.899
+(Radio) Joe Egan and Gerry Rafferty
+were a duo known as Stealer's Wheel,
+
+956
+00:55:38.980 --> 00:55:42.979
+when they recorded this Dylanesque
+pop bubblegum favourite
+
+957
+00:55:43.100 --> 00:55:47.739
+from April of 1974.
+That reached up to number five
+
+958
+00:55:47.860 --> 00:55:51.699
+as K-Billy's
+Super Sounds of the '70s continues.
+
+959
+00:55:51.820 --> 00:55:54.579
+(♪ Stealer's Wheel:
+Stuck in The Middle With You)
+
+960
+00:56:06.580 --> 00:56:09.419
+♪ Well, I don't know why
+I came here tonight
+
+961
+00:56:10.500 --> 00:56:13.259
+♪ I got the feeling
+that something ain't right
+
+962
+00:56:14.380 --> 00:56:18.059
+♪ I'm so scared
+in case I fall off my chair
+
+963
+00:56:18.180 --> 00:56:22.299
+♪ And I'm wondering
+how I'll get down the stairs
+
+964
+00:56:22.380 --> 00:56:25.779
+♪ Clowns to the left of me,
+jokers to the right
+
+965
+00:56:25.860 --> 00:56:29.699
+♪ Here I am,
+stuck in the middle with you
+
+966
+00:56:29.780 --> 00:56:32.139
+♪ Yes, I'm stuck in the middle with you
+
+967
+00:56:32.220 --> 00:56:33.659
+(Grunts with pain)
+
+968
+00:56:33.780 --> 00:56:37.379
+♪ And I'm wonderin' what it is I should do
+
+969
+00:56:37.460 --> 00:56:40.819
+♪ It's so hard
+to keep the smile from my face...
+
+970
+00:56:40.900 --> 00:56:43.139
+- Ηold still!
+- ♪ ..all over the place
+
+971
+00:56:43.220 --> 00:56:45.579
+- Ηold still, you fuck!
+- (Grunting)
+
+972
+00:56:45.700 --> 00:56:48.939
+♪ Clowns to the left of me,
+jokers to the right
+
+973
+00:56:49.020 --> 00:56:50.939
+♪ Here I am,
+stuck in the middle with you
+
+974
+00:56:53.060 --> 00:56:54.939
+♪ Well, you started out with nothing
+
+975
+00:56:55.020 --> 00:56:57.979
+♪ And you're proud
+that you're a self-made man
+
+976
+00:56:58.060 --> 00:57:00.139
+(Cop groans)
+
+977
+00:57:00.940 --> 00:57:02.939
+♪ And your friends they all come crawlin'
+
+978
+00:57:03.060 --> 00:57:07.779
+- ♪ Slap you on the back and say please...
+- Was that as good for you as it was for me?
+
+979
+00:57:08.700 --> 00:57:11.459
+Ηey! What's going on? You hear that?
+
+980
+00:57:11.540 --> 00:57:13.899
+- (Muffled scream)
+- (Laughs)
+
+981
+00:57:14.020 --> 00:57:15.779
+(Groaning)
+
+982
+00:57:20.900 --> 00:57:24.299
+- ♪ Trying to make some sense of it all
+- Don't go anywhere. l'll be right back.
+
+983
+00:57:24.380 --> 00:57:28.139
+♪ But I can see it makes no sense at all
+
+984
+00:57:28.220 --> 00:57:32.059
+♪ Is it cool to go to sleep on the floor?
+
+985
+00:57:32.140 --> 00:57:35.099
+♪ I don't think that I can take any more
+
+986
+00:57:36.500 --> 00:57:39.899
+♪ Clowns to the left of me,
+jokers to the right
+
+987
+00:57:39.980 --> 00:57:42.019
+♪ Here I am, stuck in the...
+
+988
+00:58:20.940 --> 00:58:23.299
+♪ ..please
+
+989
+00:58:23.420 --> 00:58:26.739
+♪ Please
+
+990
+00:58:26.820 --> 00:58:29.419
+(Muffled screams)
+
+991
+00:58:34.860 --> 00:58:38.339
+♪ And I don't know
+why I came here tonight
+
+992
+00:58:38.420 --> 00:58:42.219
+♪ I got the feeling
+that something ain't right
+
+993
+00:58:42.300 --> 00:58:46.179
+♪ I'm so scared
+in case I fall off my chair
+
+994
+00:58:46.260 --> 00:58:49.499
+♪ And I'm wondering
+how I'll get down the stairs
+
+995
+00:58:50.580 --> 00:58:53.899
+♪ Clowns to the left of me,
+jokers to the right
+
+996
+00:58:53.980 --> 00:58:57.059
+- ♪ Here I am, stuck in the middle with you
+- (Cop yelling)
+
+997
+00:58:57.900 --> 00:59:00.819
+♪ Yes, I'm stuck in the middle with you
+
+998
+00:59:02.580 --> 00:59:05.099
+- ♪ Stuck in the middle with you
+- (Cop yelling)
+
+999
+00:59:06.180 --> 00:59:08.939
+- ♪ Here I am... ♪
+- No! Argh! Argh! Argh!
+
+1000
+00:59:09.060 --> 00:59:12.819
+Don't! Stop! Stop it! Stop!
+
+1001
+00:59:12.940 --> 00:59:15.739
+- What? What's the matter?
+- Don't do this!
+
+1002
+00:59:15.860 --> 00:59:18.939
+- Please...don't!
+- Τhat burn a little bit?
+
+1003
+00:59:19.020 --> 00:59:21.579
+Just stop! Stop!
+
+1004
+00:59:22.140 --> 00:59:24.019
+(Splutters)
+
+1005
+00:59:24.100 --> 00:59:25.699
+Please stop.
+
+1006
+00:59:25.820 --> 00:59:28.339
+Just stop, just stop.
+Stop. Just talk to me.
+
+1007
+00:59:28.420 --> 00:59:30.219
+Don't! Please.
+
+1008
+00:59:30.300 --> 00:59:32.059
+Don't. Don't burn me, please!
+
+1009
+00:59:32.140 --> 00:59:34.859
+Ah! (Coughs) Ah!
+
+1010
+00:59:34.940 --> 00:59:39.059
+l'm begging you. l don't know anything
+about any of you fuckin' guys!
+
+1011
+00:59:39.140 --> 00:59:41.179
+l'm not gonna say anything!
+
+1012
+00:59:41.300 --> 00:59:42.979
+Don't!
+
+1013
+00:59:43.060 --> 00:59:44.299
+Don't! Please don't!
+
+1014
+00:59:44.420 --> 00:59:45.739
+- You all through?
+- Stop!
+
+1015
+00:59:45.820 --> 00:59:47.059
+Are you all through?
+
+1016
+00:59:47.180 --> 00:59:50.699
+Please. l got a little kid at home, now, please.
+
+1017
+00:59:50.780 --> 00:59:53.139
+- You all done?
+- Don't! Don't!
+
+1018
+00:59:53.260 --> 00:59:55.299
+- Ηave some fire, scarecrow.
+- Don't!
+
+1019
+01:00:37.180 --> 01:00:38.619
+(Groans)
+
+1020
+01:00:44.140 --> 01:00:46.539
+(Grunting)
+
+1021
+01:00:49.020 --> 01:00:50.819
+(Cop) Ah!
+
+1022
+01:00:54.940 --> 01:00:56.979
+Fuck!
+
+1023
+01:00:57.060 --> 01:01:00.299
+- Ηey.
+- Ah...shit!
+
+1024
+01:01:02.660 --> 01:01:05.019
+Ηey.
+
+1025
+01:01:06.980 --> 01:01:09.219
+What's your name?
+
+1026
+01:01:12.420 --> 01:01:13.699
+Marvin.
+
+1027
+01:01:15.980 --> 01:01:17.979
+Marvin what?
+
+1028
+01:01:21.220 --> 01:01:22.739
+Marvin Nash.
+
+1029
+01:01:29.460 --> 01:01:32.059
+Listen to me, Marvin, l'm a...
+
+1030
+01:01:34.140 --> 01:01:36.859
+Listen to me, Marvin Nash, l'm a cop.
+
+1031
+01:01:39.060 --> 01:01:40.619
+Yeah, l know.
+
+1032
+01:01:43.060 --> 01:01:44.699
+You do?
+
+1033
+01:01:45.780 --> 01:01:49.139
+Yeah. Your name's Freddy something.
+
+1034
+01:01:50.340 --> 01:01:52.939
+Newandyke.
+
+1035
+01:01:53.020 --> 01:01:55.139
+Freddy Newandyke.
+
+1036
+01:01:58.980 --> 01:02:05.019
+Frankie Ferchetti...
+he introduced us about five months ago.
+
+1037
+01:02:07.820 --> 01:02:09.579
+l don't remember that at all.
+
+1038
+01:02:13.620 --> 01:02:15.419
+l do.
+
+1039
+01:02:16.540 --> 01:02:18.699
+Fuck!
+
+1040
+01:02:18.780 --> 01:02:20.939
+Freddy?
+
+1041
+01:02:22.020 --> 01:02:23.939
+Freddy.
+
+1042
+01:02:24.020 --> 01:02:25.939
+Freddy?
+
+1043
+01:02:29.060 --> 01:02:30.859
+Ηow do l look?
+
+1044
+01:02:31.860 --> 01:02:33.699
+(Laughs)
+
+1045
+01:02:33.780 --> 01:02:39.579
+What?
+
+1046
+01:02:43.220 --> 01:02:45.339
+l don't know what to tell you, Marvin.
+
+1047
+01:02:49.380 --> 01:02:51.259
+Τhat fuck!
+
+1048
+01:02:53.180 --> 01:02:55.939
+Ah, that sick fuck!
+
+1049
+01:02:56.020 --> 01:02:58.899
+Τhat fuckin' bastard!
+
+1050
+01:02:58.980 --> 01:03:00.939
+(Marvin sobs)
+
+1051
+01:03:01.020 --> 01:03:03.099
+Marvin, l need you to hold on.
+
+1052
+01:03:04.460 --> 01:03:07.299
+Τhere's cops waiting to move in a block away.
+
+1053
+01:03:08.340 --> 01:03:10.699
+What the fuck are they waiting for?
+
+1054
+01:03:11.900 --> 01:03:17.259
+Τhis fuckin' guy slashes my face
+and he cuts my fuckin' ear off!
+
+1055
+01:03:17.340 --> 01:03:19.619
+l'm fuckin' deformed!
+
+1056
+01:03:19.700 --> 01:03:21.339
+Fuck you!
+
+1057
+01:03:21.420 --> 01:03:24.539
+Fuck you! l'm fuckin' dying here!
+
+1058
+01:03:24.620 --> 01:03:26.739
+l'm fuckin' dying!
+
+1059
+01:03:34.860 --> 01:03:38.019
+Τhey're not to make a move
+till Joe Cabot shows up.
+
+1060
+01:03:39.020 --> 01:03:41.099
+l was sent in to get him.
+
+1061
+01:03:42.020 --> 01:03:45.259
+All right? Now you heard.
+
+1062
+01:03:45.340 --> 01:03:47.059
+Τhey said he's on his way.
+
+1063
+01:03:47.140 --> 01:03:48.979
+Don't pussy out on me now, Marvin.
+
+1064
+01:03:50.940 --> 01:03:56.179
+We're just gonna sit here and bleed until Joe
+Cabot sticks his fuckin' head through that door.
+
+1065
+01:04:17.780 --> 01:04:19.659
+Say hello to a motherfucker who's inside.
+
+1066
+01:04:19.780 --> 01:04:22.939
+Cabot's doing a job and take a big fat guess
+who he wants on the team?
+
+1067
+01:04:23.020 --> 01:04:25.419
+Τhis better not be some kind of Freddy joke.
+
+1068
+01:04:25.540 --> 01:04:29.259
+It's no joke.
+l'm in there. l'm up his ass.
+
+1069
+01:04:36.820 --> 01:04:39.819
+Nice Guy Εddie tells me
+Joe wants to meet me.
+
+1070
+01:04:39.900 --> 01:04:43.419
+Ηe says l should just hang in my apartment
+and wait for a phone call.
+
+1071
+01:04:43.500 --> 01:04:47.259
+After waiting three goddamn days
+by the fuckin' phone, he calls me last night
+
+1072
+01:04:47.340 --> 01:04:50.059
+and says Joe's ready,
+he'll pick me up in 15 minutes.
+
+1073
+01:04:50.140 --> 01:04:53.179
+- Who all picked you up?
+- Nice Guy. We get to a bar.
+
+1074
+01:04:53.260 --> 01:04:55.539
+- What bar?
+- Smokey Pete's in Gardena.
+
+1075
+01:04:55.620 --> 01:04:57.219
+- Mm-hm.
+- We get there
+
+1076
+01:04:57.300 --> 01:05:00.339
+and l meet Joe
+and a guy named Mr White.
+
+1077
+01:05:00.420 --> 01:05:02.299
+lt's a phoney name. My name's Mr Orange.
+
+1078
+01:05:02.380 --> 01:05:05.179
+- Mr Orange?
+- Mr Orange.
+
+1079
+01:05:06.260 --> 01:05:08.299
+OΚ, Mr Orange... (Laughs)
+
+1080
+01:05:08.420 --> 01:05:12.059
+- ..you ever seen this motherfucker before?
+- Who? Mr White?
+
+1081
+01:05:12.140 --> 01:05:13.739
+Yes, Mr Orange, Mr White.
+
+1082
+01:05:13.820 --> 01:05:15.659
+No, he ain't familiar.
+
+1083
+01:05:15.740 --> 01:05:19.339
+Ηe's ain't one of Cabot's soldiers either.
+Ηe's gotta be from out of town.
+
+1084
+01:05:19.460 --> 01:05:21.619
+- Joe knows him real good.
+- Ηow can you tell?
+
+1085
+01:05:21.700 --> 01:05:24.739
+Τhe way they talk.
+You can tell they're real buddies.
+
+1086
+01:05:24.820 --> 01:05:26.859
+- Τhe two of you talk?
+- Who, me and Joe?
+
+1087
+01:05:26.980 --> 01:05:28.979
+Mr White.
+
+1088
+01:05:29.100 --> 01:05:30.619
+- A little.
+- About what?
+
+1089
+01:05:30.700 --> 01:05:33.259
+- Τhe Brewers.
+- Τhe Milwaukee Brewers?
+
+1090
+01:05:33.340 --> 01:05:37.259
+Yeah. Apparently, they won the night before.
+Ηe made a killing off 'em.
+
+1091
+01:05:37.340 --> 01:05:40.379
+lf this crook's a Brewers fan,
+his ass has gotta be from Wisconsin.
+
+1092
+01:05:40.460 --> 01:05:42.259
+- Bing!
+- l'll bet you everything
+
+1093
+01:05:42.340 --> 01:05:44.899
+from a diddled-eyed Joe
+to a damned-if-l-know,
+
+1094
+01:05:44.980 --> 01:05:47.379
+that Milwaukee got a sheet
+on this Mr White's ass.
+
+1095
+01:05:47.500 --> 01:05:50.459
+So what I want you to do
+is to go through the mugs
+
+1096
+01:05:50.580 --> 01:05:53.499
+of all the guys from old Milwaukee
+with a history of armed robbery,
+
+1097
+01:05:53.580 --> 01:05:55.259
+put a name to the face.
+
+1098
+01:05:55.380 --> 01:05:57.619
+- Nice work.
+- Τhank you, my man.
+
+1099
+01:05:58.420 --> 01:06:00.339
+Ηow was Long Beach Mike's referral?
+
+1100
+01:06:00.420 --> 01:06:03.419
+Perfecto.
+Ηe's backing me up a long fuckin' way.
+
+1101
+01:06:03.500 --> 01:06:06.099
+l told 'em it was Long Beach Mike
+l did the poker game with.
+
+1102
+01:06:06.220 --> 01:06:09.059
+When Nice Guy called him to check it out,
+he said it was A-OΚ.
+
+1103
+01:06:09.180 --> 01:06:11.819
+Ηe said l was a good thief, l didn't rattle,
+
+1104
+01:06:11.940 --> 01:06:14.819
+l was ready to make a move.
+Do right by him. Ηe's a good guy.
+
+1105
+01:06:14.940 --> 01:06:17.059
+l wouldn't be inside if it wasn't for him.
+
+1106
+01:06:17.140 --> 01:06:18.699
+No. No, no, no, no.
+
+1107
+01:06:18.820 --> 01:06:21.619
+Long Beach Mike
+is not your fuckin' amigo, man.
+
+1108
+01:06:21.740 --> 01:06:25.819
+Long Beach Mike is a fuckin' scumbag.
+Ηe is selling out his amigos.
+
+1109
+01:06:25.900 --> 01:06:28.419
+Τhat's what kind of a nice guy he fuckin' is,
+all right?
+
+1110
+01:06:28.540 --> 01:06:32.419
+l'll take care of his fuckin' ass, man,
+but you get that lowlife scumbag out of mind
+
+1111
+01:06:32.540 --> 01:06:34.699
+and you take care of business, you hear me?
+
+1112
+01:06:34.780 --> 01:06:36.459
+Gone.
+
+1113
+01:06:41.780 --> 01:06:43.499
+You use the commode story?
+
+1114
+01:06:44.780 --> 01:06:47.779
+- What's the commode story?
+- lt's a scene, man. Memorise it.
+
+1115
+01:06:49.180 --> 01:06:50.539
+A what?
+
+1116
+01:06:50.620 --> 01:06:53.099
+An undercover cop's
+gotta be Marlon Brando, right?
+
+1117
+01:06:53.180 --> 01:06:55.259
+Τo do this job, you gotta be a great actor.
+
+1118
+01:06:55.340 --> 01:06:58.499
+You gotta be naturalistic.
+You gotta be naturalistic as hell.
+
+1119
+01:06:58.580 --> 01:07:01.299
+Because if you ain't a great actor,
+you're a bad actor,
+
+1120
+01:07:01.380 --> 01:07:03.499
+and bad acting is bullshit in this job.
+
+1121
+01:07:03.580 --> 01:07:05.459
+What is this?
+
+1122
+01:07:05.540 --> 01:07:07.819
+Τhat's an amusing anecdote
+about a drug deal.
+
+1123
+01:07:09.180 --> 01:07:10.539
+What?
+
+1124
+01:07:10.660 --> 01:07:14.859
+Something funny that happened to you
+while you were doing a fuckin' job, man. Damn.
+
+1125
+01:07:14.940 --> 01:07:16.859
+l gotta memorise all this?
+
+1126
+01:07:16.940 --> 01:07:20.939
+- Τhere's over four fuckin' pages of this shit!
+- Just think about it like it's a joke.
+
+1127
+01:07:21.020 --> 01:07:24.139
+You memorise what's important,
+the rest you make your own, all right?
+
+1128
+01:07:24.260 --> 01:07:27.419
+- You can tell a joke, can't you?
+- No.
+
+1129
+01:07:27.500 --> 01:07:31.259
+Well, pretend you're Don Rickles or
+some-fucking-body and tell a joke, all right?
+
+1130
+01:07:31.340 --> 01:07:34.219
+Now the things
+you gotta remember are the details.
+
+1131
+01:07:34.300 --> 01:07:36.379
+lt's the details that sell your story.
+
+1132
+01:07:36.460 --> 01:07:39.419
+Τhis particular story takes place
+in a men's room,
+
+1133
+01:07:39.500 --> 01:07:42.059
+so you gotta know all the details
+about the men's room.
+
+1134
+01:07:42.140 --> 01:07:45.939
+You gotta know if they got paper towels
+or a blower to dry your hands with.
+
+1135
+01:07:46.020 --> 01:07:49.619
+You gotta know
+if the stalls ain't got no doors or not, man.
+
+1136
+01:07:49.740 --> 01:07:51.419
+You gotta know if they got liquid soap
+
+1137
+01:07:51.540 --> 01:07:54.819
+or that pink granulated powdered shit
+they used to use in high school.
+
+1138
+01:07:54.900 --> 01:07:57.699
+You gotta know if they got hot water or not.
+lf it stinks.
+
+1139
+01:07:57.780 --> 01:07:59.499
+lf some nasty, lowlife,
+
+1140
+01:07:59.620 --> 01:08:03.819
+scum-ridden motherfucker, man,
+sprayed diarrhoea all over one of the bowls.
+
+1141
+01:08:03.900 --> 01:08:07.499
+You gotta know every detail there is to know
+about this commode.
+
+1142
+01:08:07.580 --> 01:08:12.299
+So, what you gotta do is take all them details,
+man, and make them your own.
+
+1143
+01:08:12.380 --> 01:08:16.259
+And while you're doing that, you gotta
+remember that this story is about you,
+
+1144
+01:08:16.340 --> 01:08:19.539
+and how you perceived the events
+that went down.
+
+1145
+01:08:19.620 --> 01:08:22.539
+Τhe only way to do that, my brother -
+
+1146
+01:08:22.620 --> 01:08:27.219
+keep saying it and saying it
+and saying it and saying it and saying it.
+
+1147
+01:08:30.620 --> 01:08:34.659
+(Freddy) Τhis was during the Los Angeles
+marijuana drought in 1986.
+
+1148
+01:08:35.660 --> 01:08:40.899
+l still had a connection, which was insane
+cause you couldn't get weed any-fucking-where.
+
+1149
+01:08:42.620 --> 01:08:48.499
+Αnyway...I had a connection with this hippy
+chick in Santa Cruz and all my friends knew it.
+
+1150
+01:08:48.580 --> 01:08:51.779
+Τhey'd give me a call and they'd say,
+"Ηey, Freddy..."
+
+1151
+01:08:51.860 --> 01:08:57.259
+(Mimics game-show buzzer)
+Τhey'd say, "Ηey, dude, you getting some?
+
+1152
+01:08:57.340 --> 01:09:00.619
+"Can you get some for me too?"
+Like, they knew l still smoked,
+
+1153
+01:09:00.700 --> 01:09:04.059
+so they asked me to buy some for them
+when I was buying for me.
+
+1154
+01:09:04.140 --> 01:09:05.699
+But it got to be...
+
+1155
+01:09:06.780 --> 01:09:08.859
+Got to be, got to be...
+
+1156
+01:09:10.380 --> 01:09:12.739
+Got to be...every time l bought some weed,
+
+1157
+01:09:12.820 --> 01:09:15.779
+l was buying
+for four or five different fuckin' people.
+
+1158
+01:09:15.860 --> 01:09:20.459
+Finally l said, "Fuck this shit!
+l'm making this bitch rich."
+
+1159
+01:09:20.580 --> 01:09:23.939
+She didn't have to do jack shit.
+She never even had to meet these people.
+
+1160
+01:09:24.020 --> 01:09:25.779
+l was doing all the work.
+
+1161
+01:09:25.860 --> 01:09:30.219
+But then that got to be a pain in the ass.
+People calling me all the fuckin' time.
+
+1162
+01:09:30.300 --> 01:09:34.459
+l couldn't even rent a fuckin' tape
+without six fuckin' phone calls interrupting me.
+
+1163
+01:09:34.540 --> 01:09:37.299
+"When's the next time you're getting some?"
+Motherfucker!
+
+1164
+01:09:37.380 --> 01:09:40.619
+l'm trying to watch Τhe Lost Boys.
+"When l get some, l'll let you know."
+
+1165
+01:09:40.700 --> 01:09:43.739
+Τhen these rinky-dink potheads come by -
+
+1166
+01:09:43.860 --> 01:09:46.939
+they're my friends and everything,
+but still, you know?
+
+1167
+01:09:47.020 --> 01:09:48.859
+l got all my shit laid out in $60 bags.
+
+1168
+01:09:48.940 --> 01:09:51.459
+Τhey don't want $60 worth.
+Τhey want $10 worth.
+
+1169
+01:09:51.540 --> 01:09:57.259
+Breaking it up is a major fuckin' pain in the ass.
+I don't even know what $10 worth looks like.
+
+1170
+01:09:57.340 --> 01:09:59.579
+Τhis is a very weird situation.
+
+1171
+01:10:01.020 --> 01:10:05.499
+l don't know if you remember back in '86,
+there was a major fuckin' drought.
+
+1172
+01:10:05.580 --> 01:10:06.939
+Nobody had anything.
+
+1173
+01:10:07.020 --> 01:10:10.379
+People were living on resin,
+smoking the wood in their pipes for months.
+
+1174
+01:10:10.500 --> 01:10:12.059
+Τhis chick had a bunch,
+
+1175
+01:10:12.180 --> 01:10:14.379
+and she's begging me to sell it.
+
+1176
+01:10:14.460 --> 01:10:17.419
+So I told her,
+l wasn't gonna be Joe the Pot Man any more,
+
+1177
+01:10:17.500 --> 01:10:21.259
+but l would take a little bit
+and sell it to my close, close, close friends.
+
+1178
+01:10:21.340 --> 01:10:24.059
+She agreed that we'd keep
+the same arrangement as before -
+
+1179
+01:10:24.140 --> 01:10:27.659
+ten percent, free pot for me
+as long as l helped her out that weekend.
+
+1180
+01:10:27.780 --> 01:10:31.139
+She had a brick of weed she was selling,
+she didn't wanna go to the buy alone.
+
+1181
+01:10:31.220 --> 01:10:34.979
+Ηer brother usually goes with her
+but he's in County unexpectedly.
+
+1182
+01:10:35.060 --> 01:10:38.299
+- What for?
+- Ηis traffic ticket's got a warrant.
+
+1183
+01:10:38.380 --> 01:10:41.699
+Τhey stop him for something,
+found warrants on him, took him to County.
+
+1184
+01:10:41.780 --> 01:10:44.859
+Now, she doesn't wanna walk around alone
+with all that weed.
+
+1185
+01:10:44.940 --> 01:10:46.419
+l don't wanna do this.
+
+1186
+01:10:46.540 --> 01:10:48.579
+l have a very bad feeling about it.
+
+1187
+01:10:48.660 --> 01:10:51.099
+She keeps asking me,
+keeps asking me, keeps asking me.
+
+1188
+01:10:51.220 --> 01:10:53.379
+Finally, l said OΚ cause l'm sick of hearing it.
+
+1189
+01:10:53.460 --> 01:10:55.899
+Now we're picking the guy up
+at the train station...
+
+1190
+01:10:55.980 --> 01:10:58.979
+You go to the station to pick up the buyer
+with the weed on you?
+
+1191
+01:10:59.100 --> 01:11:01.419
+Τhe guy needed it right away.
+Don't ask me why.
+
+1192
+01:11:01.500 --> 01:11:05.739
+Anyway, we get to the train station
+and we're waiting for the guy.
+
+1193
+01:11:05.860 --> 01:11:09.539
+l'm carrying the weed around in one of those
+little carry-on bags, I gotta take a piss.
+
+1194
+01:11:09.620 --> 01:11:12.539
+l tell the connection l'll be right back,
+l'm going to the boys' room.
+
+1195
+01:11:17.460 --> 01:11:20.499
+So l walk in the men's room
+and who's standing there?
+
+1196
+01:11:22.020 --> 01:11:25.419
+Four Los Angeles County sheriffs
+and a German shepherd.
+
+1197
+01:11:25.540 --> 01:11:27.619
+(Barks)
+
+1198
+01:11:27.700 --> 01:11:31.099
+- Τhey're waiting for you?
+- No, it's just a bunch of cops talking.
+
+1199
+01:11:31.220 --> 01:11:35.699
+When l walk through the door they all stop what
+they were talking about and they looked at me.
+
+1200
+01:11:35.780 --> 01:11:40.539
+(Laughs) Τhat's hard, man.
+Τhat's a fuckin' hard situation.
+
+1201
+01:11:40.620 --> 01:11:42.299
+Τhe German shepherd starts barking.
+
+1202
+01:11:42.380 --> 01:11:43.899
+(Barks)
+
+1203
+01:11:44.020 --> 01:11:45.739
+Ηe's barking at me.
+
+1204
+01:11:45.820 --> 01:11:48.259
+l mean, it's obvious he's barking at me.
+
+1205
+01:11:48.340 --> 01:11:51.419
+- (Barks)
+- Εvery nerve ending, all my senses,
+
+1206
+01:11:51.500 --> 01:11:54.099
+blood in my veins,
+everything l have is screaming,
+
+1207
+01:11:54.180 --> 01:11:57.979
+"Τake off, man. Just bail.
+Just get the fuck out of there!"
+
+1208
+01:11:58.060 --> 01:11:59.739
+Panic hits me like a bucket of water.
+
+1209
+01:11:59.820 --> 01:12:02.099
+First there's the shock of it.
+Bam, right in the face.
+
+1210
+01:12:02.180 --> 01:12:04.259
+l'm just standing there, drenched in panic,
+
+1211
+01:12:04.340 --> 01:12:07.179
+and all these sheriffs are looking at me,
+and they know, man.
+
+1212
+01:12:07.260 --> 01:12:10.779
+Τhey can smell it,
+sure as that fuckin' dog can.
+
+1213
+01:12:10.860 --> 01:12:12.659
+Τhey can smell it on me.
+
+1214
+01:12:12.740 --> 01:12:14.779
+- (Dog barks)
+- (Cop) Shut up!
+
+1215
+01:12:16.260 --> 01:12:18.019
+- (2nd cop) So, anyway...
+- (Dog whimpers)
+
+1216
+01:12:18.140 --> 01:12:19.779
+..l got my gun drawn, right,
+
+1217
+01:12:19.860 --> 01:12:25.179
+and I got it pointed right at this guy
+and l tell him, "Freeze! Don't fuckin' move!"
+
+1218
+01:12:25.260 --> 01:12:27.979
+And this little idiot's looking right at me,
+nodding,
+
+1219
+01:12:28.100 --> 01:12:30.299
+and he's saying, "l know, l know, l know."
+
+1220
+01:12:30.380 --> 01:12:33.539
+But meanwhile, his right hand
+is creeping towards the glove box.
+
+1221
+01:12:33.620 --> 01:12:38.739
+And l scream at him. l go, "Asshole,
+l'm gonna fuckin' blow you away right now!
+
+1222
+01:12:38.860 --> 01:12:40.699
+"Put your hands on the dash!"
+
+1223
+01:12:40.780 --> 01:12:46.179
+And he's still looking at me, nodding his head,
+you know, "l know, buddy. l know. l know."
+
+1224
+01:12:46.260 --> 01:12:49.979
+Αnd meanwhile, you know,
+his hand is still going for the glove box.
+
+1225
+01:12:50.060 --> 01:12:55.819
+Αnd I said, "Buddy,
+l'm gonna shoot you in the face,
+
+1226
+01:12:55.900 --> 01:12:58.459
+"if you don't put your hands
+on the fuckin' dash!"
+
+1227
+01:12:59.540 --> 01:13:04.059
+And then this guy's girlfriend,
+this real sexy Oriental bitch, you know,
+
+1228
+01:13:04.180 --> 01:13:07.339
+she starts screaming at him,
+"Chuck! Chuck! What are you doing?
+
+1229
+01:13:07.420 --> 01:13:10.099
+"Listen to the officer!
+Put your hands on the dash!"
+
+1230
+01:13:10.180 --> 01:13:13.259
+So, you know, then like nothing,
+the guy snaps out of it
+
+1231
+01:13:13.340 --> 01:13:15.219
+and casually puts his hands on the dash.
+
+1232
+01:13:15.300 --> 01:13:18.859
+- (Cop) What was he going for?
+- (2nd cop) Ηis fuckin' registration.
+
+1233
+01:13:18.940 --> 01:13:21.219
+(Cop laughs) You're kidding!
+
+1234
+01:13:21.300 --> 01:13:25.419
+(2nd cop) No! Stupid citizen doesn't know
+how close he came to getting blown away.
+
+1235
+01:13:25.540 --> 01:13:27.619
+Τhat close, man.
+
+1236
+01:13:27.740 --> 01:13:29.819
+(Dryer drowns out conversation)
+
+1237
+01:13:52.100 --> 01:13:54.299
+(Barks)
+
+1238
+01:13:57.580 --> 01:14:00.099
+..bonehead running around
+the neighbourhood. Police brutality.
+
+1239
+01:14:00.180 --> 01:14:01.899
+(Joe) You knew how to handle that situation.
+
+1240
+01:14:03.100 --> 01:14:05.259
+You shit your pants and dive in and swim.
+
+1241
+01:14:08.180 --> 01:14:10.419
+(Man) Tell me more
+about Cabot.
+
+1242
+01:14:10.540 --> 01:14:12.059
+(Freddy) I don't know.
+
+1243
+01:14:12.140 --> 01:14:13.739
+He's... He's a cool guy.
+
+1244
+01:14:13.820 --> 01:14:16.819
+I don't know. He's funny.
+
+1245
+01:14:16.900 --> 01:14:18.299
+He's a funny guy.
+
+1246
+01:14:18.420 --> 01:14:21.379
+You remember the Fantastic Four?
+
+1247
+01:14:22.580 --> 01:14:26.299
+Oh, yeah, with that invisible bitch
+and "Flame On" and shit.
+
+1248
+01:14:27.660 --> 01:14:33.019
+Τhe Τhing.
+Motherfucker looks just like the Τhing.
+
+1249
+01:14:33.860 --> 01:14:35.259
+(Rings)
+
+1250
+01:14:36.100 --> 01:14:39.739
+- Yeah?
+- (Eddie) Hey...show time.
+
+1251
+01:14:39.860 --> 01:14:42.539
+Grab yourjacket. I'm parked outside.
+
+1252
+01:14:42.660 --> 01:14:44.419
+l'll be right down.
+
+1253
+01:14:45.580 --> 01:14:47.179
+Ηe'll be right down.
+
+1254
+01:14:47.260 --> 01:14:50.619
+♪ My sunshine
+
+1255
+01:14:50.740 --> 01:14:53.579
+♪ The part he always liked the best
+
+1256
+01:14:53.660 --> 01:14:56.179
+♪ When she'd tease him with a kiss
+
+1257
+01:14:56.300 --> 01:15:01.899
+♪ And she said you make me happy
+
+1258
+01:15:01.980 --> 01:15:05.899
+♪ You fool for love
+
+1259
+01:15:08.940 --> 01:15:12.659
+♪ What he wouldn't do for love
+
+1260
+01:15:13.700 --> 01:15:17.619
+♪ He's a fool, a fool for love
+
+1261
+01:15:20.660 --> 01:15:24.659
+♪ Born a fool, you got to follow the rule
+
+1262
+01:15:24.780 --> 01:15:26.659
+♪ Always a foo... ♪
+
+1263
+01:16:07.340 --> 01:16:10.419
+Don't pussy out on me now.
+Τhey don't know.
+
+1264
+01:16:11.500 --> 01:16:13.739
+Τhey don't know shit.
+
+1265
+01:16:13.820 --> 01:16:15.859
+You're not gonna get hurt.
+
+1266
+01:16:15.940 --> 01:16:20.059
+You're fuckin' Baretta. Τhey believe
+every fuckin' word cause you're super cool.
+
+1267
+01:16:27.700 --> 01:16:29.299
+(Man) Τhere goes our boy.
+
+1268
+01:16:29.380 --> 01:16:33.859
+(2nd man) Τhe guy has to have rocks in his
+head the size of Gibraltar to work undercover.
+
+1269
+01:16:33.980 --> 01:16:36.859
+- (Man) You want one of these?
+- (2nd man) Yeah, gimme the bear claw.
+
+1270
+01:16:39.820 --> 01:16:45.059
+♪ Ouga chaka! Ouga, ouga! Ouga chaka!
+Ouga, ouga! Ouga chaka!
+
+1271
+01:16:45.140 --> 01:16:48.059
+♪ Ouga, ouga, ouga chaka!
+Ouga, ouga, ouga
+
+1272
+01:16:48.140 --> 01:16:52.179
+♪ I can't stop this feelin'
+♪ Ouga chaka! Ouga, ouga... ♪
+
+1273
+01:16:52.260 --> 01:16:57.379
+Ηey, I know what I'm talking about, OK?
+Black women ain't the same as white women.
+
+1274
+01:16:57.500 --> 01:16:59.099
+Τhere's a slight difference.
+
+1275
+01:16:59.180 --> 01:17:01.699
+Very funny. You know what l mean.
+
+1276
+01:17:01.780 --> 01:17:05.619
+What a white bitch will put up with,
+a black bitch wouldn't put up with for a minute.
+
+1277
+01:17:05.700 --> 01:17:08.659
+Τhey got a line and if you cross it,
+they fuck you up.
+
+1278
+01:17:08.740 --> 01:17:11.219
+l gotta go along with Pink on that one.
+I've seen it happen.
+
+1279
+01:17:11.340 --> 01:17:13.859
+OΚ, Mr Εxpert, if this is such a truism,
+
+1280
+01:17:13.940 --> 01:17:18.379
+why is it that every nigger I know
+treats his woman like a piece of shit?
+
+1281
+01:17:18.460 --> 01:17:22.379
+I'll make you a bet that those same damn
+niggers who are showing their ass in public,
+
+1282
+01:17:22.460 --> 01:17:24.939
+when their bitches get 'em home,
+they chill out.
+
+1283
+01:17:25.060 --> 01:17:28.099
+- Not these guys.
+- Oh, yeah, those guys too.
+
+1284
+01:17:28.180 --> 01:17:30.379
+l'll tell you guys a story.
+
+1285
+01:17:30.460 --> 01:17:35.139
+ln one of Daddy's clubs,
+there's a black cocktail waitress named Εlois.
+
+1286
+01:17:35.220 --> 01:17:37.939
+- Εlois?
+- Yeah, Εlois.
+
+1287
+01:17:38.020 --> 01:17:40.979
+Ε-Lois. We called her Lady Ε.
+
+1288
+01:17:41.060 --> 01:17:44.899
+- (Mr White) Where was she from? Compton?
+- (Laughter)
+
+1289
+01:17:44.980 --> 01:17:46.739
+From Ladera Ηeights.
+
+1290
+01:17:46.820 --> 01:17:49.659
+Ladera Ηeights -
+that's the black Beverly Ηills.
+
+1291
+01:17:49.740 --> 01:17:52.379
+- No, it's not the black Beverly Ηills.
+- (Laughs)
+
+1292
+01:17:52.460 --> 01:17:54.939
+- lt's the black Palos Verdes.
+- (Laughs)
+
+1293
+01:17:55.060 --> 01:18:00.459
+Anyway, Lady Ε,
+l mean, she was a man-eater-upper.
+
+1294
+01:18:00.540 --> 01:18:03.779
+Un-fucking-believable.
+Εvery guy who ever laid eyes on her
+
+1295
+01:18:03.900 --> 01:18:06.099
+had to jack off to her at least once.
+
+1296
+01:18:06.180 --> 01:18:09.779
+You know who she looked like?
+She looked like Christie Love.
+
+1297
+01:18:09.900 --> 01:18:11.939
+Remember that TV show Get Christie Love! ,
+
+1298
+01:18:12.020 --> 01:18:14.579
+about the black female cop?
+She always used to say,
+
+1299
+01:18:14.700 --> 01:18:16.939
+(Both) "You're under arrest, sugar."
+
+1300
+01:18:17.020 --> 01:18:18.619
+(Laughs)
+
+1301
+01:18:18.700 --> 01:18:20.859
+Who was the chick who played Christie Love?
+
+1302
+01:18:20.940 --> 01:18:22.979
+- Pam Grier.
+- No, it wasn't Pam Grier.
+
+1303
+01:18:23.060 --> 01:18:24.779
+Pam Grier was the other one.
+
+1304
+01:18:24.860 --> 01:18:26.459
+Pam Grier did the film.
+
+1305
+01:18:26.540 --> 01:18:31.259
+Christie Love was like a Pam Grier TV show
+without Pam Grier.
+
+1306
+01:18:31.380 --> 01:18:34.059
+- So who was Christie Love?
+- Ηow the fuck should l know?
+
+1307
+01:18:34.180 --> 01:18:36.459
+Great, now l'm totally fuckin' tortured!
+
+1308
+01:18:36.540 --> 01:18:39.859
+Whoever it was, it doesn't matter.
+She looked like Εlois.
+
+1309
+01:18:39.980 --> 01:18:41.339
+Anne Francis.
+
+1310
+01:18:41.420 --> 01:18:44.139
+No, that was Ηoney West.
+
+1311
+01:18:44.220 --> 01:18:48.099
+- Anne Francis is white.
+- Shut up. l'm trying to tell a story here.
+
+1312
+01:18:48.180 --> 01:18:52.939
+She looked exactly like Εlois.
+Anyway...l come into the club one night,
+
+1313
+01:18:53.020 --> 01:18:56.899
+and there's Carlos - he's the bartender.
+Ηe's a wetback. Ηe's a friend of mine.
+
+1314
+01:18:56.980 --> 01:18:58.979
+- (Laughter)
+- l says to him,
+
+1315
+01:18:59.060 --> 01:19:01.539
+"Carlos, where's Lady Ε tonight?"
+
+1316
+01:19:01.660 --> 01:19:05.939
+Now, apparently, Lady Ε was married
+to a real piece of dog shit.
+
+1317
+01:19:06.020 --> 01:19:08.939
+l mean a real fuckin' animal.
+Ηe used to do things to her.
+
+1318
+01:19:09.020 --> 01:19:11.539
+Do things? Do things like what?
+What would he do?
+
+1319
+01:19:11.620 --> 01:19:13.459
+Did he beat her up or something?
+
+1320
+01:19:13.540 --> 01:19:16.579
+l don't know what he did.
+Ηe just did things, all right?
+
+1321
+01:19:16.660 --> 01:19:20.339
+So, anyway, one night she plays it real cool.
+
+1322
+01:19:20.420 --> 01:19:22.539
+She waits for this bag of shit to get drunk,
+
+1323
+01:19:22.620 --> 01:19:25.539
+he falls asleep on the fuckin' couch,
+
+1324
+01:19:25.620 --> 01:19:30.179
+she sneaks up on him,
+puts some wacko glue on his dick...
+
+1325
+01:19:30.260 --> 01:19:32.219
+- Ohh!
+- ..and glues his dick to his belly!
+
+1326
+01:19:32.300 --> 01:19:35.379
+- No!
+- Jesus Christ!
+
+1327
+01:19:35.460 --> 01:19:38.139
+l'm serious, man.
+l'm serious. l'm dead serious.
+
+1328
+01:19:38.220 --> 01:19:41.459
+Τhey had to call the paramedics
+to cut the prick loose, literally!
+
+1329
+01:19:41.580 --> 01:19:44.739
+- (Freddy) ..do some crazy things with it.
+- Was he all pissed off?
+
+1330
+01:19:44.820 --> 01:19:46.299
+(Laughs)
+
+1331
+01:19:46.420 --> 01:19:51.019
+Ηow would you feel if every time you had to
+take a piss you had to do a fuckin' handstand?
+
+1332
+01:19:51.100 --> 01:19:52.539
+(Laughter)
+
+1333
+01:19:55.860 --> 01:19:59.139
+You guys like to tell jokes and giggle
+and kid around, huh?
+
+1334
+01:19:59.260 --> 01:20:02.379
+Giggling like a bunch of young broads
+in the school yard.
+
+1335
+01:20:02.460 --> 01:20:04.139
+Well, let me tell a joke.
+
+1336
+01:20:05.220 --> 01:20:11.539
+Five guys sitting in a bullpen in San Quentin
+wondering how the fuck they got there.
+
+1337
+01:20:12.580 --> 01:20:16.579
+"What did we do wrong?
+What shoulda we done? What didn't we do?
+
+1338
+01:20:16.700 --> 01:20:20.059
+"lt's your fault, my fault, his fault."
+Αll that bullshit.
+
+1339
+01:20:20.140 --> 01:20:24.299
+Finally someone comes up with the idea,
+"Wait a minute,
+
+1340
+01:20:24.380 --> 01:20:28.859
+"while we were planning this caper, all we
+did was sit around and tell fuckin' jokes."
+
+1341
+01:20:28.940 --> 01:20:31.019
+Got the message?
+
+1342
+01:20:33.060 --> 01:20:35.219
+Fellas, l don't mean to holler at you.
+
+1343
+01:20:36.300 --> 01:20:39.459
+When this caper's over,
+and l'm sure it's gonna be a successful one,
+
+1344
+01:20:39.540 --> 01:20:43.059
+hell, we'll go down the Ηawaiian islands,
+I'll laugh with all of you.
+
+1345
+01:20:43.180 --> 01:20:45.059
+You'll find me a different character there.
+
+1346
+01:20:45.180 --> 01:20:47.619
+Right now, it's a matter of business.
+
+1347
+01:20:48.700 --> 01:20:52.939
+With the exception of Εddie and myself,
+who you already know,
+
+1348
+01:20:53.060 --> 01:20:55.779
+we're gonna be using aliases on this job.
+
+1349
+01:20:55.900 --> 01:20:58.459
+Under no circumstances
+
+1350
+01:20:58.540 --> 01:21:04.019
+do I want any one of you to relate
+to each other by your Christian names,
+
+1351
+01:21:04.100 --> 01:21:08.299
+and l don't want any talk about yourself
+personally.
+
+1352
+01:21:08.420 --> 01:21:14.379
+Τhat includes where you been, your wife's
+name, where you might have done time,
+
+1353
+01:21:14.460 --> 01:21:18.299
+or a bank that you robbed in St Petersburg.
+
+1354
+01:21:18.380 --> 01:21:24.019
+Αll I want you guys to talk about
+if you have to, is what you're gonna do.
+
+1355
+01:21:24.900 --> 01:21:27.219
+Τhat should do it.
+
+1356
+01:21:27.300 --> 01:21:28.699
+Ηere are your names.
+
+1357
+01:21:29.700 --> 01:21:31.059
+Mr Brown,
+
+1358
+01:21:31.180 --> 01:21:32.619
+Mr White,
+
+1359
+01:21:32.700 --> 01:21:34.339
+Mr Blonde,
+
+1360
+01:21:34.420 --> 01:21:36.339
+Mr Blue,
+Mr Orange,
+
+1361
+01:21:36.420 --> 01:21:39.019
+- and Mr Pink.
+- Why am l Mr Pink?
+
+1362
+01:21:39.100 --> 01:21:41.179
+Because you're a faggot, all right?
+
+1363
+01:21:41.260 --> 01:21:43.099
+(Mr Brown laughs)
+
+1364
+01:21:43.180 --> 01:21:45.379
+- Why can't we pick our own colors?
+- No way.
+
+1365
+01:21:45.500 --> 01:21:48.219
+No way. Τried it once, it doesn't work.
+
+1366
+01:21:48.300 --> 01:21:51.819
+You get four guys all fighting over
+who's gonna be Mr Black.
+
+1367
+01:21:51.900 --> 01:21:55.139
+Τhey don't know each other
+so nobody wants to back down.
+
+1368
+01:21:55.260 --> 01:21:57.539
+No way. l pick. You're Mr Pink.
+
+1369
+01:21:57.620 --> 01:22:00.859
+Be thankful you're not Mr Yellow.
+
+1370
+01:22:00.940 --> 01:22:03.499
+Yeah, but Mr Brown,
+that's a little too close to Mr Shit.
+
+1371
+01:22:03.580 --> 01:22:05.299
+Mr Pink sounds like Mr Pussy.
+
+1372
+01:22:05.380 --> 01:22:09.259
+Ηow about if l'm Mr Purple?
+Τhat sounds good to me. l'll be Mr Purple.
+
+1373
+01:22:09.340 --> 01:22:11.819
+You're not Mr Purple.
+
+1374
+01:22:11.940 --> 01:22:16.099
+Some guy on some other job is Mr Purple.
+You're Mr Pink!
+
+1375
+01:22:16.180 --> 01:22:17.899
+Who cares what your name is?
+
+1376
+01:22:18.020 --> 01:22:21.899
+Yeah, that's easy for you to say. You're
+Mr White. You have a cool-sounding name.
+
+1377
+01:22:21.980 --> 01:22:25.379
+All right, look, if it's no big deal to be Mr Pink,
+do you wanna trade?
+
+1378
+01:22:25.460 --> 01:22:28.299
+Ηey, nobody's trading with anybody.
+
+1379
+01:22:28.380 --> 01:22:31.499
+Τhis ain't a goddamn
+fuckin' city council meeting, you know?
+
+1380
+01:22:33.180 --> 01:22:35.259
+Now, listen up, Mr Pink,
+
+1381
+01:22:35.380 --> 01:22:37.819
+there's two ways you can go on this job -
+
+1382
+01:22:37.900 --> 01:22:40.379
+my way or the highway.
+
+1383
+01:22:40.500 --> 01:22:42.339
+Now, what's it gonna be, Mr Pink?
+
+1384
+01:22:42.460 --> 01:22:45.939
+Jesus Christ, Joe, fuckin' forget about it.
+
+1385
+01:22:46.060 --> 01:22:48.739
+lt's beneath me, you know. l'm Mr Pink.
+Let's move on.
+
+1386
+01:22:48.820 --> 01:22:51.219
+l'll move on when l feel like it.
+
+1387
+01:22:52.100 --> 01:22:54.339
+All you guys got the goddamn message?
+
+1388
+01:22:56.460 --> 01:22:59.819
+l'm so goddamn mad hollering at you guys,
+l can hardly talk.
+
+1389
+01:23:01.860 --> 01:23:03.899
+Let's go to work.
+
+1390
+01:23:03.980 --> 01:23:06.419
+(Mr White) Let's go over it. Where are you?
+
+1391
+01:23:06.500 --> 01:23:10.419
+(Mr Orange) I stand outside and guard
+the door. I don't let anybody go in or go out.
+
+1392
+01:23:10.540 --> 01:23:12.779
+- (Mr White) Mr Brown?
+- (Mr Orange) Mr Brown waits in the car.
+
+1393
+01:23:14.140 --> 01:23:16.939
+Ηe's parked across the street.
+When l signal he pulls up in front of the store.
+
+1394
+01:23:17.020 --> 01:23:19.379
+Mr Blonde and Mr Blue?
+
+1395
+01:23:19.500 --> 01:23:23.019
+Crowd control.
+Τhey handle customers and employees.
+
+1396
+01:23:23.100 --> 01:23:26.219
+- Τhat girl's ass?
+- Sitting right here on my dick.
+
+1397
+01:23:26.300 --> 01:23:28.019
+(Laughs)
+
+1398
+01:23:28.740 --> 01:23:30.659
+Myself and Mr Pink?
+
+1399
+01:23:30.740 --> 01:23:34.859
+Ah, you two take the manager in the back
+and make him give you the diamonds.
+
+1400
+01:23:34.980 --> 01:23:37.099
+We're there for those stones, period.
+
+1401
+01:23:37.180 --> 01:23:41.779
+Since no display cases are being fucked with,
+no alarms should go off.
+
+1402
+01:23:41.860 --> 01:23:44.579
+We're out of there in two minutes -
+not one second longer.
+
+1403
+01:23:46.220 --> 01:23:50.059
+What happens if the manager
+won't give you the diamonds?
+
+1404
+01:23:50.140 --> 01:23:53.499
+When you're dealing with a store like this,
+they're insured up the ass.
+
+1405
+01:23:53.620 --> 01:23:56.459
+Τhey're not supposed to give you
+any resistance whatsoever.
+
+1406
+01:23:56.580 --> 01:24:00.419
+lf you get a customer or an employee
+who thinks he's Charles Bronson,
+
+1407
+01:24:00.500 --> 01:24:02.779
+take the butt of your gun
+and smash their nose in.
+
+1408
+01:24:02.860 --> 01:24:05.659
+lt drops them right to the floor.
+Εveryone jumps.
+
+1409
+01:24:05.740 --> 01:24:08.459
+Ηe falls down screaming,
+blood squirts out of his nose -
+
+1410
+01:24:08.540 --> 01:24:12.499
+freaks everybody out.
+Nobody says fuckin' shit after that.
+
+1411
+01:24:12.580 --> 01:24:14.979
+You might get some bitch
+talk shit to you,
+
+1412
+01:24:15.100 --> 01:24:17.699
+but give her a look
+like you're gonna smash her face next.
+
+1413
+01:24:17.780 --> 01:24:20.259
+- Watch her shut the fuck up.
+- (Laughs)
+
+1414
+01:24:20.340 --> 01:24:22.499
+Now if it's a manager, that's a different story.
+
+1415
+01:24:22.580 --> 01:24:25.299
+Τhe managers know better
+than to fuck around.
+
+1416
+01:24:25.380 --> 01:24:29.219
+So, if you get one that's giving you static,
+he probably thinks he's a real cowboy,
+
+1417
+01:24:29.300 --> 01:24:31.499
+so you got to break that son of a bitch in two.
+
+1418
+01:24:31.620 --> 01:24:35.939
+If you wanna know something
+and he won't tell you, cut off one of his fingers.
+
+1419
+01:24:36.020 --> 01:24:39.499
+Τhe little one.
+Τhen tell him his thumb's next.
+
+1420
+01:24:39.580 --> 01:24:43.059
+Αfter that he'll tell you
+if he wears ladies' underwear.
+
+1421
+01:24:46.500 --> 01:24:48.739
+l'm hungry. Let's get a taco.
+
+1422
+01:24:50.780 --> 01:24:52.379
+(Τires squeal)
+
+1423
+01:24:53.860 --> 01:24:55.659
+(Distant sirens)
+
+1424
+01:25:00.100 --> 01:25:01.619
+Come on!
+
+1425
+01:25:04.220 --> 01:25:06.539
+- Fuck!
+- (Revs engine)
+
+1426
+01:25:08.420 --> 01:25:10.299
+(Sirens)
+
+1427
+01:25:11.620 --> 01:25:13.979
+(Εngine revving)
+
+1428
+01:25:16.180 --> 01:25:17.979
+Fuck!
+
+1429
+01:25:18.060 --> 01:25:19.899
+(Ηelicopter)
+
+1430
+01:25:23.460 --> 01:25:26.299
+Jesus!
+l got blinded, man.
+
+1431
+01:25:26.420 --> 01:25:29.899
+- l'm fuckin' blind.
+- You're not. You just got blood in your eyes.
+
+1432
+01:25:30.460 --> 01:25:32.459
+(Τires squeal)
+
+1433
+01:25:34.940 --> 01:25:36.939
+Aargh!
+
+1434
+01:25:43.100 --> 01:25:44.459
+ls he dead?
+
+1435
+01:25:45.860 --> 01:25:48.939
+- Did he die or not?
+- (Sirens and helicopter approach)
+
+1436
+01:25:49.020 --> 01:25:50.659
+Let's go.
+
+1437
+01:26:25.140 --> 01:26:27.379
+- Ηold it!
+- Ηold it! Right there!
+
+1438
+01:26:29.300 --> 01:26:31.939
+- Get out of the fuckin' car!
+- (Gunshot)
+
+1439
+01:26:32.020 --> 01:26:33.619
+(Gunshot)
+
+1440
+01:26:43.420 --> 01:26:44.819
+(Squeals in pain)
+
+1441
+01:26:46.620 --> 01:26:48.939
+l'm sorry. l'm sorry, Larry.
+
+1442
+01:26:49.020 --> 01:26:51.499
+l...l can't believe she killed me.
+
+1443
+01:26:51.580 --> 01:26:53.299
+Who'd have fuckin' thought that?
+
+1444
+01:26:53.420 --> 01:26:55.859
+Ηey, just cancel that shit right now.
+
+1445
+01:26:55.940 --> 01:26:58.939
+You're hurt.
+You're hurt real fuckin' bad,
+
+1446
+01:26:59.060 --> 01:27:00.539
+but you ain't dying.
+
+1447
+01:27:00.620 --> 01:27:02.019
+Ohh!
+
+1448
+01:27:02.140 --> 01:27:05.459
+All this blood's
+scaring the shit out of me, Larry.
+
+1449
+01:27:05.540 --> 01:27:08.379
+l'm gonna die, l know it.
+
+1450
+01:27:23.660 --> 01:27:25.059
+(Freddy moans)
+
+1451
+01:27:40.180 --> 01:27:41.779
+(Sniffs)
+
+1452
+01:27:43.340 --> 01:27:44.899
+What the fuck happened?
+
+1453
+01:27:45.020 --> 01:27:48.539
+Ηe slashed the cop's face, cut off his ear
+and was gonna burn him alive.
+
+1454
+01:27:49.900 --> 01:27:51.939
+What? l didn't hear you.
+
+1455
+01:27:52.020 --> 01:27:53.779
+l said...
+
+1456
+01:27:55.820 --> 01:27:57.299
+Blonde went crazy.
+
+1457
+01:27:57.380 --> 01:28:01.819
+Ηe slashed the cop's face, cut off his ear
+and was gonna burn him alive.
+
+1458
+01:28:02.860 --> 01:28:04.499
+Τhis cop?
+
+1459
+01:28:05.660 --> 01:28:07.059
+(Mr Orange moans)
+
+1460
+01:28:08.980 --> 01:28:13.219
+Ηe went crazy - something like that?
+Worse or better?
+
+1461
+01:28:13.340 --> 01:28:16.579
+Εddie, he was pulling a burn, man.
+
+1462
+01:28:17.860 --> 01:28:19.739
+Ηe was gonna kill the cop and me.
+
+1463
+01:28:19.820 --> 01:28:21.739
+When you guys walked through the door,
+
+1464
+01:28:21.820 --> 01:28:24.579
+he was gonna blow you to hell
+and make off with the diamonds.
+
+1465
+01:28:24.660 --> 01:28:29.139
+What'd l tell you? Τhat sick piece of shit
+was a stone-cold psycho.
+
+1466
+01:28:29.220 --> 01:28:32.579
+You could've asked the cop,
+if you didn't just kill him.
+
+1467
+01:28:32.660 --> 01:28:36.699
+Ηe talked about what he was gonna do
+when he was slicing him up.
+
+1468
+01:28:37.780 --> 01:28:39.459
+l don't buy it.
+
+1469
+01:28:39.580 --> 01:28:41.059
+Doesn't make sense.
+
+1470
+01:28:45.380 --> 01:28:47.219
+Makes perfect fuckin' sense to me.
+
+1471
+01:28:47.300 --> 01:28:51.219
+You didn't see how he acted during the job.
+We did.
+
+1472
+01:28:51.340 --> 01:28:53.539
+Ηe's right about the ear -
+it's hacked off.
+
+1473
+01:28:53.620 --> 01:28:57.779
+Let me just say this out loud,
+cause l wanna get this straight in my head.
+
+1474
+01:28:57.860 --> 01:29:01.859
+You're saying that Mr Blonde
+was gonna kill you,
+
+1475
+01:29:01.940 --> 01:29:05.259
+and then when we got back
+he was gonna kill us,
+
+1476
+01:29:05.340 --> 01:29:07.299
+take the satchel of diamonds and scram.
+
+1477
+01:29:07.380 --> 01:29:10.459
+l'm right about that, right, that's correct,
+that's your story?
+
+1478
+01:29:10.540 --> 01:29:14.739
+l swear on my mother's eternal soul,
+it's what happened.
+
+1479
+01:29:14.860 --> 01:29:18.699
+Τhe man you just killed
+just got released from prison.
+
+1480
+01:29:18.820 --> 01:29:21.899
+Ηe got caught at a company warehouse
+full of hot items.
+
+1481
+01:29:21.980 --> 01:29:23.699
+Ηe could've fuckin' walked.
+
+1482
+01:29:24.700 --> 01:29:28.819
+All he had to do was say my dad's name,
+but he didn't, he kept his fuckin' mouth shut,
+
+1483
+01:29:28.900 --> 01:29:34.299
+and he did his fuckin' time like a man.
+Ηe did four years for us.
+
+1484
+01:29:34.420 --> 01:29:36.739
+So, Mr Orange...
+
+1485
+01:29:36.820 --> 01:29:39.979
+you're telling me
+that this very good friend of mine,
+
+1486
+01:29:40.060 --> 01:29:42.819
+who did four years for my father,
+
+1487
+01:29:42.900 --> 01:29:47.779
+who, in four years, never made a deal,
+no matter what they dangled in front of him...
+
+1488
+01:29:47.860 --> 01:29:50.859
+you're telling me
+that now that this man is free,
+
+1489
+01:29:50.980 --> 01:29:53.939
+and we're making good
+on our commitment to him,
+
+1490
+01:29:54.060 --> 01:30:00.539
+he's just gonna decide,
+out of the fuckin' blue...to rip us off?
+
+1491
+01:30:02.380 --> 01:30:05.699
+Why don't you tell me what really happened.
+
+1492
+01:30:05.780 --> 01:30:08.259
+What the hell for?
+
+1493
+01:30:08.340 --> 01:30:10.339
+lt'd just be more bullshit.
+
+1494
+01:30:12.940 --> 01:30:14.619
+Τhis man set us up.
+
+1495
+01:30:15.860 --> 01:30:18.859
+Dad, l'm sorry,
+but l don't know what the hell's happening.
+
+1496
+01:30:18.940 --> 01:30:20.419
+lt's all right, Εddie, l do.
+
+1497
+01:30:20.500 --> 01:30:22.739
+What the fuck are you talking about?
+
+1498
+01:30:22.820 --> 01:30:26.419
+Τhat lump of shit's workin' with the LAPD.
+
+1499
+01:30:27.860 --> 01:30:32.659
+l don't have the slightest fuckin' idea
+what you're talking about.
+
+1500
+01:30:32.740 --> 01:30:36.299
+Joe, l don't know what you think you know,
+but you're wrong.
+
+1501
+01:30:36.380 --> 01:30:38.099
+Like hell l am.
+
+1502
+01:30:38.180 --> 01:30:41.099
+Joe, trust me on this,
+you've made a mistake.
+
+1503
+01:30:41.180 --> 01:30:43.019
+Ηe's a good kid.
+
+1504
+01:30:43.100 --> 01:30:45.779
+l understand you're super-fuckin'-pissed.
+
+1505
+01:30:45.860 --> 01:30:47.619
+We're all real emotional.
+
+1506
+01:30:47.740 --> 01:30:52.339
+But you're barking up the wrong tree.
+l know this man, he wouldn't do that.
+
+1507
+01:30:52.460 --> 01:30:54.899
+You don't know jack shit! l do.
+
+1508
+01:30:54.980 --> 01:30:56.779
+Τhe cocksucker tipped off the cops,
+
+1509
+01:30:56.860 --> 01:30:58.939
+and got Mr Brown and Mr Blue killed.
+
+1510
+01:30:59.020 --> 01:31:00.499
+Mr Blue is dead?
+
+1511
+01:31:00.580 --> 01:31:01.979
+Dead as Dillinger.
+
+1512
+01:31:02.060 --> 01:31:04.059
+Ηow do you know all this?
+
+1513
+01:31:04.180 --> 01:31:06.819
+Ηe was the only one l wasn't 100 percent on.
+
+1514
+01:31:07.900 --> 01:31:10.899
+I should have my head examined,
+going ahead when l wasn't 100 percent.
+
+1515
+01:31:11.020 --> 01:31:14.779
+- Τhat's your proof?
+- You don't need proof when you have instinct!
+
+1516
+01:31:14.900 --> 01:31:17.179
+l ignored it before, but no more.
+
+1517
+01:31:19.660 --> 01:31:21.859
+Ηave you lost your fuckin' mind?
+
+1518
+01:31:21.940 --> 01:31:24.779
+You're making a terrible mistake.
+I'm not gonna let you make it.
+
+1519
+01:31:24.860 --> 01:31:27.139
+Come on, guys.
+Nobody wants this.
+
+1520
+01:31:27.220 --> 01:31:29.779
+We're supposed to be
+fuckin' professionals.
+
+1521
+01:31:29.860 --> 01:31:32.499
+Larry, look,
+
+1522
+01:31:32.620 --> 01:31:34.419
+it's been quite a long time.
+
+1523
+01:31:34.540 --> 01:31:35.979
+A lot of jobs.
+
+1524
+01:31:36.060 --> 01:31:38.139
+Τhere's no need for this, man.
+
+1525
+01:31:39.180 --> 01:31:41.659
+Let's just put our guns down...
+
+1526
+01:31:42.900 --> 01:31:46.179
+and let's settle this with a fuckin' conversation.
+
+1527
+01:31:46.300 --> 01:31:49.899
+Joe, if you kill that man, you die next.
+
+1528
+01:31:50.020 --> 01:31:51.979
+Repeat - if you kill that man, you die next.
+
+1529
+01:31:52.100 --> 01:31:54.699
+(Εddie) Larry, we have been friends...
+
+1530
+01:31:55.740 --> 01:31:59.859
+and you respect my dad and l respect you,
+but I'll put bullets through your heart.
+
+1531
+01:31:59.980 --> 01:32:01.979
+You put that fuckin' gun down now!
+
+1532
+01:32:02.060 --> 01:32:04.299
+God damn you, Joe...
+
+1533
+01:32:05.380 --> 01:32:06.579
+don't make me do this.
+
+1534
+01:32:06.700 --> 01:32:09.299
+Larry, stop pointin' that fuckin' gun at my dad!
+
+1535
+01:32:18.820 --> 01:33:03.099
+(Larry moans)
+
+1536
+01:33:25.340 --> 01:33:27.219
+(Freddy whimpers)
+
+1537
+01:33:29.060 --> 01:33:30.699
+(Larry moans)
+
+1538
+01:33:32.900 --> 01:33:35.459
+(Distant sirens)
+
+1539
+01:33:43.980 --> 01:33:45.379
+(Larry coughs)
+
+1540
+01:33:56.220 --> 01:33:58.179
+l'm sorry, kid.
+
+1541
+01:34:00.500 --> 01:34:02.899
+Looks like we're gonna...
+
+1542
+01:34:03.020 --> 01:34:05.939
+- do...do a little time.
+- (Men shouting outside)
+
+1543
+01:34:06.020 --> 01:34:08.099
+(Freddy whimpers)
+
+1544
+01:34:12.220 --> 01:34:13.699
+l'm a cop.
+
+1545
+01:34:16.060 --> 01:34:17.579
+Larry...
+
+1546
+01:34:19.340 --> 01:34:21.019
+l'm sorry.
+
+1547
+01:34:22.460 --> 01:34:24.059
+l'm...
+
+1548
+01:34:24.140 --> 01:34:26.379
+so...sorry.
+
+1549
+01:34:29.220 --> 01:34:30.819
+l'm a cop.
+
+1550
+01:34:30.900 --> 01:34:32.579
+(Mr White groans)
+
+1551
+01:34:35.060 --> 01:34:37.139
+(Coughs and sobs)
+
+1552
+01:34:37.220 --> 01:34:38.659
+l'm sorry.
+
+1553
+01:34:39.140 --> 01:34:41.979
+Oh! Oh!
+
+1554
+01:34:42.060 --> 01:34:44.659
+(Shouting continues outside)
+
+1555
+01:34:47.740 --> 01:34:49.579
+l'm sorry, l'm sorry.
+
+1556
+01:35:00.940 --> 01:35:02.779
+l'm sorry.
+
+1557
+01:35:04.020 --> 01:35:05.419
+(Moans)
+
+1558
+01:35:06.820 --> 01:35:09.059
+- l'm...sorry...
+- (Door bangs open)
+
+1559
+01:35:10.700 --> 01:35:13.899
+(Man) Freeze!
+Drop the fuckin' gun, buddy!
+
+1560
+01:35:13.980 --> 01:35:15.699
+- (2nd man) Now!
+- (Man) Put the gun down!
+
+1561
+01:35:15.780 --> 01:35:17.539
+- Don't do it!
+- Drop the gun, man!
+
+1562
+01:35:17.620 --> 01:35:19.939
+- Drop the gun.
+- Drop the fuckin' gun!
+
+1563
+01:35:20.060 --> 01:35:21.779
+We're gonna blow you away!
+
+1564
+01:35:21.860 --> 01:35:22.979
+(Gunshot)
+
+1565
+01:35:23.100 --> 01:35:25.499
+(Gunfire)
+
+1566
+01:35:28.580 --> 01:35:30.579
+(♪ Ηarry Nilsson: Coconut)
+
+1567
+01:35:35.980 --> 01:35:39.019
+♪ Brother bought a coconut,
+he bought it for a dime
+
+1568
+01:35:39.100 --> 01:35:42.419
+♪ Ηis sister had another one,
+she paid it for the lime
+
+1569
+01:35:42.540 --> 01:35:46.099
+♪ She put the lime in the coconut,
+she drank them both up
+
+1570
+01:35:46.180 --> 01:35:49.819
+♪ She put the lime
+in the coconut, she drank them both up
+
+1571
+01:35:49.900 --> 01:35:53.379
+♪ She put the lime in the coconut,
+she drank them both up
+
+1572
+01:35:53.500 --> 01:35:57.379
+♪ She put the lime in the coconut,
+she called the doctor, woke him up
+
+1573
+01:35:57.460 --> 01:36:00.699
+♪ And said,
+Doctor, ain't there nothin' l can take
+
+1574
+01:36:00.780 --> 01:36:04.299
+♪ l say, Doctor, to relieve this belly ache?
+
+1575
+01:36:04.380 --> 01:36:07.859
+♪ l say, Doctor, ain't there nothin' l can take
+
+1576
+01:36:07.980 --> 01:36:11.699
+♪ l say, Doctor, to relieve this belly ache?
+
+1577
+01:36:11.820 --> 01:36:13.339
+♪ Now let me get this straight
+
+1578
+01:36:13.420 --> 01:36:16.899
+♪ You put the lime in the coconut,
+you drank them both up
+
+1579
+01:36:16.980 --> 01:36:20.459
+♪ Put the lime in the coconut,
+you drank them both up
+
+1580
+01:36:20.580 --> 01:36:24.139
+♪ Put the lime
+in the coconut, you drank them both up
+
+1581
+01:36:24.220 --> 01:36:27.899
+♪ Put the lime in the coconut,
+you called your doctor, woke him up
+
+1582
+01:36:27.980 --> 01:36:31.219
+♪ And said, Doctor,
+ain't there nothing l can take
+
+1583
+01:36:31.340 --> 01:36:34.779
+♪ l said, Doctor, to relieve this belly ache?
+
+1584
+01:36:34.860 --> 01:36:38.299
+♪ l said, Doctor,
+ain't there nothin' l can take
+
+1585
+01:36:38.380 --> 01:36:42.099
+♪ l said, Doctor, to relieve this belly ache?
+
+1586
+01:36:42.180 --> 01:36:45.459
+♪ You put the lime in the coconut,
+you drink them both together
+
+1587
+01:36:45.540 --> 01:36:49.019
+♪ Put the lime in the coconut,
+then you feel better
+
+1588
+01:36:49.100 --> 01:36:52.339
+♪ Put the lime in the coconut,
+drink them both up
+
+1589
+01:36:52.420 --> 01:36:56.499
+♪ Put the lime in the coconut,
+and call me in the morning
+
+1590
+01:36:56.580 --> 01:37:01.219
+♪ Ooh-ooh, ooh, ooh,
+ooh, ooh, ooh, ooh-ooh
+
+1591
+01:37:01.300 --> 01:37:04.659
+♪ Ooh-ooh, ooh, ooh, ooh,
+ooh-ooh
+
+1592
+01:37:04.740 --> 01:37:10.579
+♪ Ooh, ooh-ooh,
+ooh-ooh, ooh-ooh, ooh-ooh
+
+1593
+01:37:10.660 --> 01:37:13.659
+♪ Brother bought a coconut,
+he bought it for a dime
+
+1594
+01:37:13.740 --> 01:37:16.779
+♪ Ηis sister had another one,
+she paid it for the lime
+
+1595
+01:37:16.860 --> 01:37:20.579
+♪ She put the lime in the coconut,
+she drank them both up
+
+1596
+01:37:20.660 --> 01:37:24.499
+♪ She put the lime in the coconut,
+she called the doctor, woke him up
+
+1597
+01:37:24.580 --> 01:37:27.339
+♪ Said, Doctor, ain't there nothin' l can take
+
+1598
+01:37:27.420 --> 01:37:31.019
+♪ l say, Doctor, to relieve this belly ache?
+
+1599
+01:37:31.140 --> 01:37:38.059
+♪ l say, Doctor, ain't there nothin' l can take?
+l say, Doctor, let me get this straight
+
+1600
+01:37:38.140 --> 01:37:41.459
+♪ You put the lime in the coconut,
+you drink them both up
+
+1601
+01:37:41.580 --> 01:37:44.939
+♪ Put the lime in the coconut,
+you drink them both up
+
+1602
+01:37:45.020 --> 01:37:48.299
+♪ You put the lime in the coconut,
+you drink them both up
+
+1603
+01:37:48.420 --> 01:37:52.019
+♪ Put the lime in the coconut,
+you such a silly woman
+
+1604
+01:37:52.100 --> 01:37:55.259
+♪ Put the lime in the coconut,
+and drink them both together
+
+1605
+01:37:55.380 --> 01:37:58.739
+♪ Put the lime in the coconut,
+then you feel better
+
+1606
+01:37:58.820 --> 01:38:02.299
+♪ Put the lime in the coconut,
+drink them both down
+
+1607
+01:38:02.380 --> 01:38:06.179
+♪ Put the lime in the coconut,
+and call me in the morning
+
+1608
+01:38:06.260 --> 01:38:08.979
+♪ Woo-woo, ain't there nothin' you can take
+
+1609
+01:38:09.100 --> 01:38:12.419
+♪ l say, woo-woo, to relieve your belly ache?
+
+1610
+01:38:12.500 --> 01:38:16.139
+♪ You say, woo-woo,
+ain't there nothin' l can take
+
+1611
+01:38:16.220 --> 01:38:19.339
+♪ l say, woo-woo, to relieve your belly ache?
+
+1612
+01:38:19.420 --> 01:38:22.579
+♪ You say, ya-ah, ain't there nothin' l can take
+
+1613
+01:38:22.700 --> 01:38:26.059
+♪ l say, wa-ah, to relieve this belly ache?
+
+1614
+01:38:26.140 --> 01:38:29.619
+♪ l say, Doctor, ain't there nothin' l can take?
+
+1615
+01:38:29.700 --> 01:38:36.299
+♪ l say, Doctor, ain't there nothin' l can take?
+l say, Doctor, ain't there nothin' l can take?
+
+1616
+01:38:36.420 --> 01:38:39.899
+♪ l say, Doctor, you're such a silly woman
+
+1617
+01:38:39.980 --> 01:38:43.379
+♪ Put the lime in the coconut,
+and drink them both together
+
+1618
+01:38:43.460 --> 01:38:46.659
+♪ Put the lime in the coconut,
+then you feel better
+
+1619
+01:38:46.740 --> 01:38:49.859
+♪ Put the lime in the coconut,
+drink them both up
+
+1620
+01:38:49.980 --> 01:38:54.779
+♪ Put the lime in the coconut,
+and call me in the morning
+
+1621
+01:38:54.900 --> 01:38:56.899
+♪ Yes, you call me in the morning
+
+1622
+01:38:57.020 --> 01:39:00.379
+♪ lf you call me in the morning,
+I'll tell you what to do
+
+1623
+01:39:00.460 --> 01:39:03.379
+♪ If you call me
+in the morning, l'll tell you what to do
+
+1624
+01:39:03.460 --> 01:39:06.779
+♪ lf you call me in the morning,
+I'll tell you what to do
+
+1625
+01:39:06.860 --> 01:39:10.299
+♪ If you call me
+in the morning, l'll tell you what to do
+
+1626
+01:39:10.380 --> 01:39:12.499
+♪ lf you call me in the morning... ♪
+
diff --git a/files/assets/break.mp4 b/files/assets/break.mp4
new file mode 100644
index 000000000..a961749e6
Binary files /dev/null and b/files/assets/break.mp4 differ
diff --git a/files/assets/css/4chan.css b/files/assets/css/4chan.css
index eaef950d4..e3d99ed76 100644
--- a/files/assets/css/4chan.css
+++ b/files/assets/css/4chan.css
@@ -144,7 +144,7 @@ blockquote a {
margin-top: 6px;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #949494 !important;
}
@@ -156,6 +156,6 @@ h5.post-title a:visited {
background-color: black !important;
}
-*:target, .unread {
+.unread {
background: #fdccad !important;
}
diff --git a/files/assets/css/admin/badges.css b/files/assets/css/admin/badges.css
index 366c0ccc5..2dd8b94b6 100644
--- a/files/assets/css/admin/badges.css
+++ b/files/assets/css/admin/badges.css
@@ -1,4 +1,4 @@
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
table {
display: inline-block;
overflow: auto;
diff --git a/files/assets/css/awards.css b/files/assets/css/awards.css
index c83356c72..aa7edb9e1 100644
--- a/files/assets/css/awards.css
+++ b/files/assets/css/awards.css
@@ -145,59 +145,24 @@
}
-
-
.tilt-post {
- animation-duration: 200s !important;
- animation-iteration-count: infinite !important;
- animation-direction: alternate !important;
- animation-timing-function: linear !important;
- animation-name: tilt-post;
+ transform: rotate(0.3deg);
}
-@media (max-width: 768px) {
- @keyframes tilt-post {
- 0% {transform: rotate(0deg);}
- 25% {transform: rotate(0.3deg);}
- 75% {transform: rotate(-0.3deg);}
- 100% {transform: rotate(0deg);}
- }
-
-}
-
-@media (min-width: 768px) {
- .tilt-post {
- animation-duration: 500s !important;
- }
- @keyframes tilt-post {
- 0% {transform: rotate(0deg);}
- 25% {transform: rotate(0.8deg);}
- 75% {transform: rotate(-0.8deg);}
- 100% {transform: rotate(0deg);}
- }
-}
-
.tilt-post > * {
padding-left: 3rem !important;
padding-right: 3rem !important;
}
-
-
-@keyframes tilt-comment {
- 0% {transform: rotate(0deg);}
- 100% {transform: rotate(360deg);}
+.tilt-comment-1 {
+ transform: rotate(1deg);
}
-
-.tilt-comment {
- animation-duration: 3000s !important;
- animation-iteration-count: infinite !important;
- animation-timing-function: linear !important;
- animation-name: tilt-comment;
+.tilt-comment-2 {
+ transform: rotate(2deg);
}
-
-@media (max-width: 768px) {
- .tilt-comment {
- animation-duration: 6000s !important;
- }
+.tilt-comment-3 {
+ transform: rotate(3deg);
+}
+.tilt-comment-4 {
+ transform: rotate(4deg);
}
diff --git a/files/assets/css/chat.css b/files/assets/css/chat.css
index a7281c4de..db2eeb6dc 100644
--- a/files/assets/css/chat.css
+++ b/files/assets/css/chat.css
@@ -140,9 +140,9 @@ lite-youtube {
max-width: 80%;
}
-.resizable>video {
- max-height: 28vh!important;
- margin: 14px 0 0 0!important;
+.resizable > video {
+ max-height: 28vh !important;
+ margin: 14px 0 0 0 !important;
}
img[alt^="![]("], .img {
@@ -167,3 +167,9 @@ img[alt^="![]("], .img {
#online > li, #online3 > li {
margin-top: 0.35rem;
}
+
+@media (min-width: 768px) {
+ .patron {
+ padding-top: 1px !important;
+ }
+}
diff --git a/files/assets/css/classic.css b/files/assets/css/classic.css
index 133e9bf5e..f05eae131 100644
--- a/files/assets/css/classic.css
+++ b/files/assets/css/classic.css
@@ -177,8 +177,11 @@ blockquote a {
color: skyblue;
}
-*:target, .unread {
- background-color: #9994 !important;
+.unread {
+ background-color: #d9d9d9 !important;
+}
+*:target {
+ background: rgba(var(--primary_rgb), 0.2) !important;
}
/*userpage*/
diff --git a/files/assets/css/classic_dark.css b/files/assets/css/classic_dark.css
index a32e2820a..4fe89fe31 100644
--- a/files/assets/css/classic_dark.css
+++ b/files/assets/css/classic_dark.css
@@ -11,3 +11,7 @@
#speed-carot-modal .speed-modal-option:hover, #speed-carot-modal .speed-modal-option:focus, #speed-carot-modal .speed-modal-option.selected {
background-color: #444444;
}
+
+.unread {
+ background-color: #3d3d3d !important;
+}
diff --git a/files/assets/css/coffee.css b/files/assets/css/coffee.css
index 3bc0df2a2..b07d779ad 100644
--- a/files/assets/css/coffee.css
+++ b/files/assets/css/coffee.css
@@ -91,7 +91,7 @@ blockquote {
color: #cfcfcf !important;
}
-*:target, .unread {
+.unread {
background: #ffffff88 !important;
}
@@ -101,7 +101,7 @@ blockquote {
margin-top: 6px;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #949494 !important;
}
diff --git a/files/assets/css/dark.css b/files/assets/css/dark.css
index 06158d830..82dc17af8 100644
--- a/files/assets/css/dark.css
+++ b/files/assets/css/dark.css
@@ -92,10 +92,14 @@ pre {
border-color: #101010;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #7a7a7a !important;
}
[disabled], .disabled, button[disabled], .btn[disabled], button.disabled, .btn.disabled {
color: #bbb !important;
}
+
+*:target {
+ background: rgba(var(--primary_rgb), 0.2) !important;
+}
diff --git a/files/assets/css/dramblr.css b/files/assets/css/dramblr.css
index ac0a6109f..01d825a09 100644
--- a/files/assets/css/dramblr.css
+++ b/files/assets/css/dramblr.css
@@ -144,7 +144,7 @@ color: var(--gray-700);
background-color: #313131 !important;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #6e6e6e !important;
}
diff --git a/files/assets/css/light.css b/files/assets/css/light.css
index 8d633a89c..3246e8e68 100644
--- a/files/assets/css/light.css
+++ b/files/assets/css/light.css
@@ -76,10 +76,13 @@ blockquote {
color: var(--gray-400) !important;
}
-*:target, .unread {
+.unread {
background: #dddddd !important;
}
+*:target {
+ background: rgba(var(--primary_rgb), 0.2) !important;
+}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #7a7a7a !important;
}
diff --git a/files/assets/css/main.css b/files/assets/css/main.css
index e6b50f652..4eff6f13d 100644
--- a/files/assets/css/main.css
+++ b/files/assets/css/main.css
@@ -1,5 +1,9 @@
@charset "UTF-8";
+.visited > img {
+ opacity: 0.6;
+}
+
.fa-align-left:before{content:"\f036"}
.fa-long-arrow-left:before{content:"\f177"}
.fa-arrow-right:before{content:"\f061"}
@@ -213,12 +217,7 @@
/* do not remove - fixes hand, talking, marsey-love components
from breaking out of the comment box
*/
-.comment-text a > img {
- position: relative !important;
- max-height: 150px !important;
- margin: 0 !important;
-}
-.preview > img {
+.comment-text a > img, img[data-user-submitted], .preview > img {
position: relative !important;
max-height: 150px !important;
margin: 0 !important;
@@ -409,7 +408,7 @@ input[type=date], input[type=time], input[type=month] {
}
textarea {
overflow: auto;
- resize: vertical;
+ resize: both;
}
fieldset {
min-width: 0;
@@ -1399,7 +1398,7 @@ nav
background: no-repeat center center;
background-size: 100% 100%;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.navbar-expand-md > .container, .navbar-expand-md > .container-fluid {
padding-right: 0;
padding-left: 0;
@@ -1431,7 +1430,7 @@ nav
display: none;
}
}
-@media (max-width: 991.98px) {
+@media (max-width: 992px) {
.navbar-expand-lg > .container, .navbar-expand-lg > .container-fluid {
padding-right: 0;
padding-left: 0;
@@ -4177,7 +4176,7 @@ small, .small {
.profile-pic-75-hat { width: 75px; }
.profile-pic-100-hat { width: 100px; }
-@media (min-width: 767.98px) {
+@media (min-width: 768px) {
.profile-pic-20-hat {
bottom: -2.7px;
}
@@ -4368,7 +4367,7 @@ small, .small {
.close .far, .close .fab, .close .fal, .close .fas {
font-size: 1.25rem;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.modal-dialog {
margin: auto;
max-width: 80%;
@@ -4436,7 +4435,7 @@ small, .small {
border: 0.1px solid #343a40;
opacity: 0.5;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.post-actions .list-inline .list-inline-item {
margin-right: 1.5rem;
margin-top: auto;
@@ -4459,7 +4458,7 @@ small, .small {
.post-actions .list-inline {
flex: none;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.post-actions .list-inline {
flex: auto;
}
@@ -4489,7 +4488,7 @@ small, .small {
z-index: 2;
background-color: var(--gray-300);
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.post-img {
width: 80px;
height: 60px;
@@ -4540,7 +4539,6 @@ small, .small {
margin-bottom: 1rem;
color: var(--black);
overflow: hidden;
- padding-right: 10px !important;
padding-top: 10px !important;
}
.modal .comment-actions .list-group-item {
@@ -4936,7 +4934,7 @@ pre .str, code .str {
pre .com, code .com {
color: #ab4bc3;
}
-@media (max-width: 991.98px) {
+@media (max-width: 992px) {
body {
padding-top: calc(var(--safe-area-inset-top) + 74.55px);
}
@@ -4946,7 +4944,7 @@ pre .com, code .com {
background-color: rgba(33, 38, 45, .8);
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
html {
font-size: 14px;
}
@@ -5202,6 +5200,7 @@ span.green {
}
.spoiler:hover, spoiler:hover {
color: var(--gray) !important;
+ transition-delay: 0.1s;
}
.spoiler *, spoiler * {
visibility: hidden;
@@ -5223,7 +5222,7 @@ span.green {
.comment.collapsed .comment-collapse-desktop:hover {
color: var(--white) !important;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.settings-nav .nav-link {
padding: 0.75rem 0.6rem;
}
@@ -5327,12 +5326,12 @@ textarea {
z-index: -1;
pointer-events: none;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.jumbotron-guild {
height: 110px;
}
}
-@media (min-width: 767.98px) {
+@media (min-width: 768px) {
.directory--link {
width: 30%;
}
@@ -5353,12 +5352,12 @@ textarea {
.navbar {
padding: 0.2rem 0 0 0.2rem;
}
-@media (min-width: 767.98px) {
+@media (min-width: 768px) {
.navbar {
padding: 0.5rem 1.5rem 0.2rem 0.5rem;
}
}
-@media (min-width: 767.98px) {
+@media (min-width: 768px) {
.modal-dialog {
max-width: 50%;
margin: 1.75rem auto !important;
@@ -5373,12 +5372,6 @@ textarea {
margin-bottom: 0.5rem !important;
cursor: pointer;
}
-video {
- max-height: 50vh !important;
- max-width: 100% !important;
- margin-top: 0.5rem !important;
- margin-bottom: 0.5rem !important;
-}
.spotify {
max-height: 80px !important;
max-width: 100% !important;
@@ -5559,6 +5552,15 @@ span > img[src$="/i/hand.webp"]+img[src$="/i/talking.webp"]~img {
text-align: center;
object-fit: contain;
}
+
+span[bounce] {
+ animation: pat-pfp-anim 0.6s infinite;
+ transform-origin: bottom center;
+ text-align: center;
+ object-fit: contain;
+ display: inline-block;
+}
+
span > img[src$="/i/talking.webp"]~img {
margin-top: 22%;
}
@@ -5579,10 +5581,12 @@ span > img[src$="/i/talking.webp"]+img[src$="/i/hand.webp"]+img {
span > img[src$="/i/love-foreground.webp"]+img[src$="/i/love-background.webp"]+img {
position: absolute;
z-index: 50;
- height: 40%;
- width: 40%;
+ height: 60%;
+ width: 60%;
bottom: -2%;
- left: 40%;
+ left: 33%;
+ transform: scaleX(-1) rotate(-10deg);
+ -webkit-transform: scaleX(-1) rotate(-10deg);
}
span > img[src$="/i/love-foreground.webp"] {
@@ -5651,12 +5655,10 @@ lite-youtube {
background-size: cover;
cursor: pointer;
margin-bottom: 1.3rem !important;
-
display: inline-block;
- resize: both;
- overflow: auto;
+ overflow: hidden;
max-height: 70vh !important;
- max-width: 100vw !important;
+ max-width: 90vw !important;
width: min(100%, 500px);
}
@@ -5908,11 +5910,17 @@ html {
content: '';
display: block;
}
-*:target, .unread {
+
+.unread {
background: #ffffff22 !important;
padding: 12px;
padding-bottom: 4px;
}
+*:target {
+ background: rgba(var(--primary_rgb), 0.15) !important;
+ padding: 12px;
+ padding-bottom: 4px;
+}
.mod {
padding: 2px 5px 3px 5px;
@@ -6645,7 +6653,7 @@ g {
border: 2px solid transparent;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.pronouns, .patron, .mod {
padding: 2px 5px !important;
}
@@ -6658,7 +6666,7 @@ g {
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.post-preview {
padding: 10px 9px 2px 9px !important;
}
@@ -6917,6 +6925,13 @@ div.markdown {
overflow: auto;
}
+.popover-bio {
+ overflow: scroll;
+ position: relative;
+ display: inline-block;
+ height: 100%;
+}
+
.popover-badges-div {
max-height: 10vh !important;
overflow: auto;
@@ -7056,15 +7071,30 @@ div.markdown {
.resizable {
resize: both;
display: inline-block;
- overflow: auto;
- max-width: 100vw !important;
-}
-.resizable > video {
- height: 99% !important;
- width: 99% !important;
+ overflow: hidden;
max-height: 70vh !important;
- max-width: 100vw !important;
+ max-width: 90vw !important;
+}
+.resizable.yt {
+ display: block;
+}
+.resizable > * {
+ height: 95% !important;
+ width: 95% !important;
margin: 0 !important;
+ max-height: 65vh !important;
+ max-width: 85vw !important;
+}
+
+@media (max-width: 768px) {
+ .resizable {
+ max-width: 100vw !important;
+ }
+ .resizable > * {
+ height: 100% !important;
+ width: 100% !important;
+ max-width: 100vw !important;
+ }
}
.user-signature video {
@@ -7092,9 +7122,7 @@ div.markdown {
.gif-categories img {
border-radius: 0.35rem;
width: 200px;
- height: 35vh;
- margin: 0 10px;
- object-fit: contain;
+ margin: 10px 10px;
-webkit-transition: all 0.15s ease;
-moz-transition: all 0.15s ease;
-o-transition: all 0.15s ease;
@@ -7102,6 +7130,10 @@ div.markdown {
transition: all 0.15s ease;
cursor: pointer;
}
+.gif-categories .card img {
+ height: 20vh;
+ object-fit: cover;
+}
.gif-categories img:hover {
border: 3px solid var(--primary);
}
@@ -7327,7 +7359,7 @@ input[type=number] {
padding-top: 81.55px !important
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.has_header {
padding-top: calc(var(--safe-area-inset-top) + 67.8px) !important
}
@@ -7384,7 +7416,7 @@ input[type=number] {
font-size: 16px;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
#note, #notelabel {
font-size: 14px !important;
}
@@ -7411,7 +7443,7 @@ input[type=number] {
padding-right: 20px;
cursor: pointer;
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.comment .comment-collapse-desktop {
padding-right: 10px;
}
@@ -7424,7 +7456,7 @@ button, .btn {
text-decoration: none !important;
}
-@media (min-width: 767.98px) {
+@media (min-width: 768px) {
.srd {
font-size: 16px;
}
@@ -7697,3 +7729,10 @@ body {
border-radius: 50%;
object-fit: cover;
}
+
+
+@media (max-width: 768px) {
+ * {
+ resize: none !important;
+ }
+}
diff --git a/files/assets/css/midnight.css b/files/assets/css/midnight.css
index 73d43856b..e95753463 100644
--- a/files/assets/css/midnight.css
+++ b/files/assets/css/midnight.css
@@ -57,7 +57,7 @@ body, .navbar-light, .navbar-dark, .card, .modal-content, .comment-write textare
background-color: #313131 !important;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #6e6e6e !important;
}
diff --git a/files/assets/css/orgy.css b/files/assets/css/orgy.css
index 20d53958c..59a4c51b1 100644
--- a/files/assets/css/orgy.css
+++ b/files/assets/css/orgy.css
@@ -1,37 +1,86 @@
+lite-youtube {
+ max-width: min(20vw,500px) !important;
+}
+
.orgy-top-container {
display: flex;
justify-content: space-around;
}
-@media all and (max-width: 900px) {
+@media (max-width: 992px) {
.orgy-top-container {
flex-flow: column wrap;
}
- .orgy-info-window-item {
- max-height: 20% !important;
- height: 20% !important;
- }
- .orgy-chat-window-item {
- max-height: 80% !important;
- height: 80% !important;
- }
-}
-@media all and (min-width: 900px) {
- .orgy-top-container {
- flex-flow: row nowrap;
+ #chat-window {
+ max-height: 34vh !important;
}
}
.orgy-chat-window-item {
flex-grow: 2;
- width: fit-content;
}
-.orgy-info-window-item {
- max-width: 550px;
- width: 550px;
+
+#orgy-file-container {
+ width: 70vw;
+ max-width: 100vw !important;
}
-.rumble-player {
+
+#orgy-file-container > * {
+ max-height: 100% !important;
+ margin: 0 !important;
+ height: 98% !important;
+ width: 98% !important;
+}
+
+@media (max-width: 992px) {
+ #orgy-file-container {
+ width: 100% !important;
+ }
+ #orgy-title {
+ display: none;
+ }
+ #orgy-col {
+ padding: 0px;
+ }
+ #orgy-file-container > * {
+ height: 100% !important;
+ width: 100% !important;
+ }
+ .orgy-chat-window-item {
+ max-width: 100%
+ }
+}
+
+
+#cursormarsey, #cursormarsey-heart {
+ display: none;
+}
+
+body > .container {
+ padding: 0 !important;
+ margin: 0 !important;
+ margin-right: 0 !important;
+}
+
+*:not(#orgy-file-container) {
+ resize: none !important;
+}
+
+#orgy-file-container > :not(video) {
+ height: 90% !important;
+ width: 95% !important;
+ max-width: 95% !important;
+ overflow-y: clip;
+}
+
+@media (max-width: 992px) {
+ #orgy-file-container > :not(video) {
+ height: 100% !important;
+ width: 100% !important;
+ max-width: 100% !important;
+ }
+}
+
+#orgy-file-container > iframe {
aspect-ratio: 16/9;
- max-width: min(70vw,500px) !important;
- width: 500px;
}
diff --git a/files/assets/css/transparent.css b/files/assets/css/transparent.css
index d9b1eb435..9b51a6145 100644
--- a/files/assets/css/transparent.css
+++ b/files/assets/css/transparent.css
@@ -6,7 +6,7 @@
--gray-900: transparent;
}
-.container, #userpage > div.container-fluid, #root > div.App {
+.container, #userpage > div.container-fluid, #root > div.App, .orgy-chat-window-item {
background: rgba(var(--background), 0.9) !important;
}
diff --git a/files/assets/css/tron.css b/files/assets/css/tron.css
index 2f4b5e699..d9c5094b2 100644
--- a/files/assets/css/tron.css
+++ b/files/assets/css/tron.css
@@ -230,6 +230,6 @@
background-color: #313131 !important;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #b0b0b0 !important;
}
diff --git a/files/assets/css/win98.css b/files/assets/css/win98.css
index 404cd6e82..0ccf0c2cc 100644
--- a/files/assets/css/win98.css
+++ b/files/assets/css/win98.css
@@ -152,7 +152,7 @@ blockquote {
color: #cfcfcf !important;
}
-*:target, .unread {
+.unread {
background: #ffffffaa !important;
}
@@ -162,7 +162,7 @@ blockquote {
margin-top: 6px;
}
-h5.post-title a:visited {
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
color: #5c5c5c !important;
}
diff --git a/files/assets/events/fistmas/css/banner.css b/files/assets/events/fistmas/css/banner.css
index 8e8586871..d450ad9a1 100644
--- a/files/assets/events/fistmas/css/banner.css
+++ b/files/assets/events/fistmas/css/banner.css
@@ -4,7 +4,7 @@
src: url("/assets/events/fistmas/fonts/Plakat-Fraktur-Black.woff") format("woff");
}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
#banner-title {
transform: scale(1.4) translate(-190px, -60px);
}
@@ -28,7 +28,7 @@
.star4 circle {r: 1.2;}
.star5 circle {r: 1.5;}
-@media (max-width: 767.98px) {
+@media (max-width: 768px) {
.star {
animation: none;
fill-opacity: 0.5;
diff --git a/files/assets/events/fistmas/css/fistmas.css b/files/assets/events/fistmas/css/fistmas.css
index a0f2dda01..6bccd376c 100644
--- a/files/assets/events/fistmas/css/fistmas.css
+++ b/files/assets/events/fistmas/css/fistmas.css
@@ -91,8 +91,8 @@ pre {
background: None !important;
}
-#frontpage .post-title a:visited {
-color: #7a7a7a !important;
+.visited, h5.post-title a:visited, a[href^="https://"]:not([href^="https://rdrama.net"], [href^="https://watchpeopledie.tv"], .dropdown-item, .nav-link, .no-visited):visited {
+ color: #7a7a7a !important;
}
.post-title a, h1.post-title {
diff --git a/files/assets/events/fistmas/css/themes/light.css b/files/assets/events/fistmas/css/themes/light.css
index f012763cf..0fcd3a709 100644
--- a/files/assets/events/fistmas/css/themes/light.css
+++ b/files/assets/events/fistmas/css/themes/light.css
@@ -1,7 +1,7 @@
blockquote {
color: var(--gray-400);
}
-*:target, .unread {
+.unread {
background: #00000022 !important;
}
.deleted {
diff --git a/files/assets/images/WPD/badges/296.webp b/files/assets/images/WPD/badges/296.webp
new file mode 100644
index 000000000..fb6b8495f
Binary files /dev/null and b/files/assets/images/WPD/badges/296.webp differ
diff --git a/files/assets/images/WPD/sidebar/49.webp b/files/assets/images/WPD/sidebar/49.webp
deleted file mode 100644
index 2400ca772..000000000
Binary files a/files/assets/images/WPD/sidebar/49.webp and /dev/null differ
diff --git a/files/assets/images/WPD/sidebar/74.webp b/files/assets/images/WPD/sidebar/74.webp
new file mode 100644
index 000000000..7170aefca
Binary files /dev/null and b/files/assets/images/WPD/sidebar/74.webp differ
diff --git a/files/assets/images/WPD/sidebar/75.webp b/files/assets/images/WPD/sidebar/75.webp
new file mode 100644
index 000000000..5b64fd8f0
Binary files /dev/null and b/files/assets/images/WPD/sidebar/75.webp differ
diff --git a/files/assets/images/WPD/sidebar/76.webp b/files/assets/images/WPD/sidebar/76.webp
new file mode 100644
index 000000000..d56e47fc1
Binary files /dev/null and b/files/assets/images/WPD/sidebar/76.webp differ
diff --git a/files/assets/images/WPD/sidebar/77.webp b/files/assets/images/WPD/sidebar/77.webp
new file mode 100644
index 000000000..158ca535e
Binary files /dev/null and b/files/assets/images/WPD/sidebar/77.webp differ
diff --git a/files/assets/images/WPD/sidebar/78.webp b/files/assets/images/WPD/sidebar/78.webp
new file mode 100644
index 000000000..476cd871a
Binary files /dev/null and b/files/assets/images/WPD/sidebar/78.webp differ
diff --git a/files/assets/images/WPD/sidebar/79.webp b/files/assets/images/WPD/sidebar/79.webp
new file mode 100644
index 000000000..2e7ee04ff
Binary files /dev/null and b/files/assets/images/WPD/sidebar/79.webp differ
diff --git a/files/assets/images/WPD/sidebar/80.webp b/files/assets/images/WPD/sidebar/80.webp
new file mode 100644
index 000000000..abda3e933
Binary files /dev/null and b/files/assets/images/WPD/sidebar/80.webp differ
diff --git a/files/assets/images/WPD/sidebar/81.webp b/files/assets/images/WPD/sidebar/81.webp
new file mode 100644
index 000000000..288d2c205
Binary files /dev/null and b/files/assets/images/WPD/sidebar/81.webp differ
diff --git a/files/assets/images/emojis/bowserdies.webp b/files/assets/images/emojis/bowserdies.webp
new file mode 100644
index 000000000..2452c8efc
Binary files /dev/null and b/files/assets/images/emojis/bowserdies.webp differ
diff --git a/files/assets/images/emojis/capybuffchastity.webp b/files/assets/images/emojis/capybuffchastity.webp
deleted file mode 100644
index 7d13ee090..000000000
Binary files a/files/assets/images/emojis/capybuffchastity.webp and /dev/null differ
diff --git a/files/assets/images/emojis/capyhug.webp b/files/assets/images/emojis/capyhug.webp
new file mode 100644
index 000000000..13c6a097b
Binary files /dev/null and b/files/assets/images/emojis/capyhug.webp differ
diff --git a/files/assets/images/emojis/carpban.webp b/files/assets/images/emojis/carpban.webp
new file mode 100644
index 000000000..d39e6321c
Binary files /dev/null and b/files/assets/images/emojis/carpban.webp differ
diff --git a/files/assets/images/emojis/carpcoingold.webp b/files/assets/images/emojis/carpcoingold.webp
new file mode 100644
index 000000000..f54c3bd45
Binary files /dev/null and b/files/assets/images/emojis/carpcoingold.webp differ
diff --git a/files/assets/images/emojis/carpcoinsilver.webp b/files/assets/images/emojis/carpcoinsilver.webp
new file mode 100644
index 000000000..9b70ca438
Binary files /dev/null and b/files/assets/images/emojis/carpcoinsilver.webp differ
diff --git a/files/assets/images/emojis/carpheart.webp b/files/assets/images/emojis/carpheart.webp
new file mode 100644
index 000000000..a9db1de3e
Binary files /dev/null and b/files/assets/images/emojis/carpheart.webp differ
diff --git a/files/assets/images/emojis/cobsonaroused.webp b/files/assets/images/emojis/cobsonaroused.webp
new file mode 100644
index 000000000..f939bfa19
Binary files /dev/null and b/files/assets/images/emojis/cobsonaroused.webp differ
diff --git a/files/assets/images/emojis/darkwhy.webp b/files/assets/images/emojis/darkwhy.webp
new file mode 100644
index 000000000..b3e9d223b
Binary files /dev/null and b/files/assets/images/emojis/darkwhy.webp differ
diff --git a/files/assets/images/emojis/dasha.webp b/files/assets/images/emojis/dasha.webp
new file mode 100644
index 000000000..3d72d33e7
Binary files /dev/null and b/files/assets/images/emojis/dasha.webp differ
diff --git a/files/assets/images/emojis/djtwanted.webp b/files/assets/images/emojis/djtwanted.webp
new file mode 100644
index 000000000..d10bab23b
Binary files /dev/null and b/files/assets/images/emojis/djtwanted.webp differ
diff --git a/files/assets/images/emojis/donkeykonghearteyes.webp b/files/assets/images/emojis/donkeykonghearteyes.webp
new file mode 100644
index 000000000..4355430f8
Binary files /dev/null and b/files/assets/images/emojis/donkeykonghearteyes.webp differ
diff --git a/files/assets/images/emojis/fatguy.webp b/files/assets/images/emojis/fatguy.webp
new file mode 100644
index 000000000..a04e87704
Binary files /dev/null and b/files/assets/images/emojis/fatguy.webp differ
diff --git a/files/assets/images/emojis/fawfulcopter.webp b/files/assets/images/emojis/fawfulcopter.webp
new file mode 100644
index 000000000..e2545dfec
Binary files /dev/null and b/files/assets/images/emojis/fawfulcopter.webp differ
diff --git a/files/assets/images/emojis/fawfullaughing.webp b/files/assets/images/emojis/fawfullaughing.webp
new file mode 100644
index 000000000..d2dcb18e2
Binary files /dev/null and b/files/assets/images/emojis/fawfullaughing.webp differ
diff --git a/files/assets/images/emojis/fawfulspin.webp b/files/assets/images/emojis/fawfulspin.webp
new file mode 100644
index 000000000..45ea40ddd
Binary files /dev/null and b/files/assets/images/emojis/fawfulspin.webp differ
diff --git a/files/assets/images/emojis/itsovertgs.webp b/files/assets/images/emojis/itsovertgs.webp
new file mode 100644
index 000000000..3e809a828
Binary files /dev/null and b/files/assets/images/emojis/itsovertgs.webp differ
diff --git a/files/assets/images/emojis/landlordpride.webp b/files/assets/images/emojis/landlordpride.webp
new file mode 100644
index 000000000..c4c123bb6
Binary files /dev/null and b/files/assets/images/emojis/landlordpride.webp differ
diff --git a/files/assets/images/emojis/luminelick.webp b/files/assets/images/emojis/luminelick.webp
new file mode 100644
index 000000000..9f6d35fad
Binary files /dev/null and b/files/assets/images/emojis/luminelick.webp differ
diff --git a/files/assets/images/emojis/luminelick2.webp b/files/assets/images/emojis/luminelick2.webp
new file mode 100644
index 000000000..6dfa8372c
Binary files /dev/null and b/files/assets/images/emojis/luminelick2.webp differ
diff --git a/files/assets/images/emojis/marioface.webp b/files/assets/images/emojis/marioface.webp
new file mode 100644
index 000000000..530c3350e
Binary files /dev/null and b/files/assets/images/emojis/marioface.webp differ
diff --git a/files/assets/images/emojis/mariosleeping.webp b/files/assets/images/emojis/mariosleeping.webp
new file mode 100644
index 000000000..45741116d
Binary files /dev/null and b/files/assets/images/emojis/mariosleeping.webp differ
diff --git a/files/assets/images/emojis/mariospin.webp b/files/assets/images/emojis/mariospin.webp
new file mode 100644
index 000000000..9b69b4766
Binary files /dev/null and b/files/assets/images/emojis/mariospin.webp differ
diff --git a/files/assets/images/emojis/marsey2commies.webp b/files/assets/images/emojis/marsey2commies.webp
new file mode 100644
index 000000000..cbc80674c
Binary files /dev/null and b/files/assets/images/emojis/marsey2commies.webp differ
diff --git a/files/assets/images/emojis/marseyastolfo.webp b/files/assets/images/emojis/marseyastolfo.webp
new file mode 100644
index 000000000..5ffa5785f
Binary files /dev/null and b/files/assets/images/emojis/marseyastolfo.webp differ
diff --git a/files/assets/images/emojis/marseyautismdisconcerting.webp b/files/assets/images/emojis/marseyautismdisconcerting.webp
new file mode 100644
index 000000000..f3b4e5e80
Binary files /dev/null and b/files/assets/images/emojis/marseyautismdisconcerting.webp differ
diff --git a/files/assets/images/emojis/marseyavegorenj.webp b/files/assets/images/emojis/marseyavegorenj.webp
new file mode 100644
index 000000000..fbcde8caf
Binary files /dev/null and b/files/assets/images/emojis/marseyavegorenj.webp differ
diff --git a/files/assets/images/emojis/marseyblackfacepenny.webp b/files/assets/images/emojis/marseyblackfacepenny.webp
new file mode 100644
index 000000000..8d4581826
Binary files /dev/null and b/files/assets/images/emojis/marseyblackfacepenny.webp differ
diff --git a/files/assets/images/emojis/marseybribe.webp b/files/assets/images/emojis/marseybribe.webp
new file mode 100644
index 000000000..bde37f787
Binary files /dev/null and b/files/assets/images/emojis/marseybribe.webp differ
diff --git a/files/assets/images/emojis/marseybutterfly.webp b/files/assets/images/emojis/marseybutterfly.webp
new file mode 100644
index 000000000..3b4a01bae
Binary files /dev/null and b/files/assets/images/emojis/marseybutterfly.webp differ
diff --git a/files/assets/images/emojis/marseycdm7.webp b/files/assets/images/emojis/marseycdm7.webp
new file mode 100644
index 000000000..cb67aca76
Binary files /dev/null and b/files/assets/images/emojis/marseycdm7.webp differ
diff --git a/files/assets/images/emojis/marseycerebrus.webp b/files/assets/images/emojis/marseycerebrus.webp
new file mode 100644
index 000000000..bad47de22
Binary files /dev/null and b/files/assets/images/emojis/marseycerebrus.webp differ
diff --git a/files/assets/images/emojis/marseychainsmoker.webp b/files/assets/images/emojis/marseychainsmoker.webp
new file mode 100644
index 000000000..5bf7e8b2e
Binary files /dev/null and b/files/assets/images/emojis/marseychainsmoker.webp differ
diff --git a/files/assets/images/emojis/marseychingchongraging.webp b/files/assets/images/emojis/marseychingchongraging.webp
new file mode 100644
index 000000000..6fab55770
Binary files /dev/null and b/files/assets/images/emojis/marseychingchongraging.webp differ
diff --git a/files/assets/images/emojis/marseychipmunk.webp b/files/assets/images/emojis/marseychipmunk.webp
new file mode 100644
index 000000000..2e0eebace
Binary files /dev/null and b/files/assets/images/emojis/marseychipmunk.webp differ
diff --git a/files/assets/images/emojis/marseychipmunknut.webp b/files/assets/images/emojis/marseychipmunknut.webp
new file mode 100644
index 000000000..1de5a98c1
Binary files /dev/null and b/files/assets/images/emojis/marseychipmunknut.webp differ
diff --git a/files/assets/images/emojis/marseychristmasgift2.webp b/files/assets/images/emojis/marseychristmasgift2.webp
new file mode 100644
index 000000000..a8c9aedcb
Binary files /dev/null and b/files/assets/images/emojis/marseychristmasgift2.webp differ
diff --git a/files/assets/images/emojis/marseycuckfiction.webp b/files/assets/images/emojis/marseycuckfiction.webp
new file mode 100644
index 000000000..86da4cff7
Binary files /dev/null and b/files/assets/images/emojis/marseycuckfiction.webp differ
diff --git a/files/assets/images/emojis/marseycupcake.webp b/files/assets/images/emojis/marseycupcake.webp
new file mode 100644
index 000000000..190557461
Binary files /dev/null and b/files/assets/images/emojis/marseycupcake.webp differ
diff --git a/files/assets/images/emojis/marseydash.webp b/files/assets/images/emojis/marseydash.webp
new file mode 100644
index 000000000..f0f883cc6
Binary files /dev/null and b/files/assets/images/emojis/marseydash.webp differ
diff --git a/files/assets/images/emojis/marseyfirefox.webp b/files/assets/images/emojis/marseyfirefox.webp
new file mode 100644
index 000000000..50dfcbc37
Binary files /dev/null and b/files/assets/images/emojis/marseyfirefox.webp differ
diff --git a/files/assets/images/emojis/marseyflaggrtrans.webp b/files/assets/images/emojis/marseyflaggrtrans.webp
new file mode 100644
index 000000000..d0d194163
Binary files /dev/null and b/files/assets/images/emojis/marseyflaggrtrans.webp differ
diff --git a/files/assets/images/emojis/marseyfoxgloveyourself.webp b/files/assets/images/emojis/marseyfoxgloveyourself.webp
new file mode 100644
index 000000000..71d9efb66
Binary files /dev/null and b/files/assets/images/emojis/marseyfoxgloveyourself.webp differ
diff --git a/files/assets/images/emojis/marseyfucktard.webp b/files/assets/images/emojis/marseyfucktard.webp
new file mode 100644
index 000000000..04e13bcaf
Binary files /dev/null and b/files/assets/images/emojis/marseyfucktard.webp differ
diff --git a/files/assets/images/emojis/marseygarfield.webp b/files/assets/images/emojis/marseygarfield.webp
index f897a75cf..f7fa9f0c1 100644
Binary files a/files/assets/images/emojis/marseygarfield.webp and b/files/assets/images/emojis/marseygarfield.webp differ
diff --git a/files/assets/images/emojis/marseygoldenshower.webp b/files/assets/images/emojis/marseygoldenshower.webp
index 0d9396d69..4fe522783 100644
Binary files a/files/assets/images/emojis/marseygoldenshower.webp and b/files/assets/images/emojis/marseygoldenshower.webp differ
diff --git a/files/assets/images/emojis/marseygrizz.webp b/files/assets/images/emojis/marseygrizz.webp
new file mode 100644
index 000000000..aca39d277
Binary files /dev/null and b/files/assets/images/emojis/marseygrizz.webp differ
diff --git a/files/assets/images/emojis/marseygrouphug.webp b/files/assets/images/emojis/marseygrouphug.webp
new file mode 100644
index 000000000..d9b4145bf
Binary files /dev/null and b/files/assets/images/emojis/marseygrouphug.webp differ
diff --git a/files/assets/images/emojis/marseyharpoonerhillary.webp b/files/assets/images/emojis/marseyharpoonerhillary.webp
new file mode 100644
index 000000000..979f1ce91
Binary files /dev/null and b/files/assets/images/emojis/marseyharpoonerhillary.webp differ
diff --git a/files/assets/images/emojis/marseyheart.webp b/files/assets/images/emojis/marseyheart.webp
new file mode 100644
index 000000000..7129dcbec
Binary files /dev/null and b/files/assets/images/emojis/marseyheart.webp differ
diff --git a/files/assets/images/emojis/marseyjeremiah.webp b/files/assets/images/emojis/marseyjeremiah.webp
new file mode 100644
index 000000000..259e27a1e
Binary files /dev/null and b/files/assets/images/emojis/marseyjeremiah.webp differ
diff --git a/files/assets/images/emojis/marseyjoan.webp b/files/assets/images/emojis/marseyjoan.webp
new file mode 100644
index 000000000..cd49c322f
Binary files /dev/null and b/files/assets/images/emojis/marseyjoan.webp differ
diff --git a/files/assets/images/emojis/marseykitkat.webp b/files/assets/images/emojis/marseykitkat.webp
new file mode 100644
index 000000000..003c46da2
Binary files /dev/null and b/files/assets/images/emojis/marseykitkat.webp differ
diff --git a/files/assets/images/emojis/marseykoalahug.webp b/files/assets/images/emojis/marseykoalahug.webp
new file mode 100644
index 000000000..2f902e924
Binary files /dev/null and b/files/assets/images/emojis/marseykoalahug.webp differ
diff --git a/files/assets/images/emojis/marseylion3.webp b/files/assets/images/emojis/marseylion3.webp
new file mode 100644
index 000000000..68daa63d4
Binary files /dev/null and b/files/assets/images/emojis/marseylion3.webp differ
diff --git a/files/assets/images/emojis/marseylivesmatter.webp b/files/assets/images/emojis/marseylivesmatter.webp
new file mode 100644
index 000000000..4499d41b0
Binary files /dev/null and b/files/assets/images/emojis/marseylivesmatter.webp differ
diff --git a/files/assets/images/emojis/marseyluttefloppa.webp b/files/assets/images/emojis/marseyluttefloppa.webp
new file mode 100644
index 000000000..0f3f32523
Binary files /dev/null and b/files/assets/images/emojis/marseyluttefloppa.webp differ
diff --git a/files/assets/images/emojis/marseymadje.webp b/files/assets/images/emojis/marseymadje.webp
new file mode 100644
index 000000000..70d552c5e
Binary files /dev/null and b/files/assets/images/emojis/marseymadje.webp differ
diff --git a/files/assets/images/emojis/marseymarie.webp b/files/assets/images/emojis/marseymarie.webp
new file mode 100644
index 000000000..5ef3e888f
Binary files /dev/null and b/files/assets/images/emojis/marseymarie.webp differ
diff --git a/files/assets/images/emojis/marseymarmot.webp b/files/assets/images/emojis/marseymarmot.webp
new file mode 100644
index 000000000..54662eae7
Binary files /dev/null and b/files/assets/images/emojis/marseymarmot.webp differ
diff --git a/files/assets/images/emojis/marseymarmotroman.webp b/files/assets/images/emojis/marseymarmotroman.webp
new file mode 100644
index 000000000..bee19076a
Binary files /dev/null and b/files/assets/images/emojis/marseymarmotroman.webp differ
diff --git a/files/assets/images/emojis/marseymarx.webp b/files/assets/images/emojis/marseymarx.webp
new file mode 100644
index 000000000..566effb4b
Binary files /dev/null and b/files/assets/images/emojis/marseymarx.webp differ
diff --git a/files/assets/images/emojis/marseymcwagie.webp b/files/assets/images/emojis/marseymcwagie.webp
new file mode 100644
index 000000000..017d0aed9
Binary files /dev/null and b/files/assets/images/emojis/marseymcwagie.webp differ
diff --git a/files/assets/images/emojis/marseynpcsheep.webp b/files/assets/images/emojis/marseynpcsheep.webp
new file mode 100644
index 000000000..77a7a0485
Binary files /dev/null and b/files/assets/images/emojis/marseynpcsheep.webp differ
diff --git a/files/assets/images/emojis/marseynullautism.webp b/files/assets/images/emojis/marseynullautism.webp
new file mode 100644
index 000000000..79d728a1e
Binary files /dev/null and b/files/assets/images/emojis/marseynullautism.webp differ
diff --git a/files/assets/images/emojis/marseyowl.webp b/files/assets/images/emojis/marseyowl.webp
new file mode 100644
index 000000000..e918a8743
Binary files /dev/null and b/files/assets/images/emojis/marseyowl.webp differ
diff --git a/files/assets/images/emojis/marseypenny2.webp b/files/assets/images/emojis/marseypenny2.webp
new file mode 100644
index 000000000..1eea6cd38
Binary files /dev/null and b/files/assets/images/emojis/marseypenny2.webp differ
diff --git a/files/assets/images/emojis/marseypoonerretard.webp b/files/assets/images/emojis/marseypoonerretard.webp
new file mode 100644
index 000000000..bbd835615
Binary files /dev/null and b/files/assets/images/emojis/marseypoonerretard.webp differ
diff --git a/files/assets/images/emojis/marseypoop.webp b/files/assets/images/emojis/marseypoop.webp
new file mode 100644
index 000000000..4d7a56d97
Binary files /dev/null and b/files/assets/images/emojis/marseypoop.webp differ
diff --git a/files/assets/images/emojis/marseypusheen2.webp b/files/assets/images/emojis/marseypusheen2.webp
new file mode 100644
index 000000000..33401b2b3
Binary files /dev/null and b/files/assets/images/emojis/marseypusheen2.webp differ
diff --git a/files/assets/images/emojis/marseyremember.webp b/files/assets/images/emojis/marseyremember.webp
index 6f965b8a3..54b72992e 100644
Binary files a/files/assets/images/emojis/marseyremember.webp and b/files/assets/images/emojis/marseyremember.webp differ
diff --git a/files/assets/images/emojis/marseyrofl.webp b/files/assets/images/emojis/marseyrofl.webp
new file mode 100644
index 000000000..f69ff339a
Binary files /dev/null and b/files/assets/images/emojis/marseyrofl.webp differ
diff --git a/files/assets/images/emojis/marseyschopenhauer.webp b/files/assets/images/emojis/marseyschopenhauer.webp
index 02235f75f..b87246739 100644
Binary files a/files/assets/images/emojis/marseyschopenhauer.webp and b/files/assets/images/emojis/marseyschopenhauer.webp differ
diff --git a/files/assets/images/emojis/marseyscientist.webp b/files/assets/images/emojis/marseyscientist.webp
new file mode 100644
index 000000000..dcebf716b
Binary files /dev/null and b/files/assets/images/emojis/marseyscientist.webp differ
diff --git a/files/assets/images/emojis/marseysheep2.webp b/files/assets/images/emojis/marseysheep2.webp
new file mode 100644
index 000000000..76050097c
Binary files /dev/null and b/files/assets/images/emojis/marseysheep2.webp differ
diff --git a/files/assets/images/emojis/marseysleepy.webp b/files/assets/images/emojis/marseysleepy.webp
new file mode 100644
index 000000000..c0dc8d9c7
Binary files /dev/null and b/files/assets/images/emojis/marseysleepy.webp differ
diff --git a/files/assets/images/emojis/marseyslutshaming.webp b/files/assets/images/emojis/marseyslutshaming.webp
new file mode 100644
index 000000000..7efff9817
Binary files /dev/null and b/files/assets/images/emojis/marseyslutshaming.webp differ
diff --git a/files/assets/images/emojis/marseysmoking.webp b/files/assets/images/emojis/marseysmoking.webp
new file mode 100644
index 000000000..394b0b5bd
Binary files /dev/null and b/files/assets/images/emojis/marseysmoking.webp differ
diff --git a/files/assets/images/emojis/marseysnappy.webp b/files/assets/images/emojis/marseysnappy.webp
new file mode 100644
index 000000000..a4ce2630f
Binary files /dev/null and b/files/assets/images/emojis/marseysnappy.webp differ
diff --git a/files/assets/images/emojis/marseysnappybiden.webp b/files/assets/images/emojis/marseysnappybiden.webp
new file mode 100644
index 000000000..24e39f105
Binary files /dev/null and b/files/assets/images/emojis/marseysnappybiden.webp differ
diff --git a/files/assets/images/emojis/marseysnappyenraged2.webp b/files/assets/images/emojis/marseysnappyenraged2.webp
new file mode 100644
index 000000000..51d122d3d
Binary files /dev/null and b/files/assets/images/emojis/marseysnappyenraged2.webp differ
diff --git a/files/assets/images/emojis/marseysourgrapes.webp b/files/assets/images/emojis/marseysourgrapes.webp
new file mode 100644
index 000000000..f1cf5bc18
Binary files /dev/null and b/files/assets/images/emojis/marseysourgrapes.webp differ
diff --git a/files/assets/images/emojis/marseysoyhype.webp b/files/assets/images/emojis/marseysoyhype.webp
new file mode 100644
index 000000000..4be611d25
Binary files /dev/null and b/files/assets/images/emojis/marseysoyhype.webp differ
diff --git a/files/assets/images/emojis/marseysoyjak.webp b/files/assets/images/emojis/marseysoyjak.webp
new file mode 100644
index 000000000..22dac9573
Binary files /dev/null and b/files/assets/images/emojis/marseysoyjak.webp differ
diff --git a/files/assets/images/emojis/marseysoyswitch.webp b/files/assets/images/emojis/marseysoyswitch.webp
new file mode 100644
index 000000000..08d8a7e49
Binary files /dev/null and b/files/assets/images/emojis/marseysoyswitch.webp differ
diff --git a/files/assets/images/emojis/marseythinbluebline.webp b/files/assets/images/emojis/marseythinbluebline.webp
new file mode 100644
index 000000000..8ac241714
Binary files /dev/null and b/files/assets/images/emojis/marseythinbluebline.webp differ
diff --git a/files/assets/images/emojis/marseytoast.webp b/files/assets/images/emojis/marseytoast.webp
new file mode 100644
index 000000000..362c9e34d
Binary files /dev/null and b/files/assets/images/emojis/marseytoast.webp differ
diff --git a/files/assets/images/emojis/marseytrippydance.webp b/files/assets/images/emojis/marseytrippydance.webp
new file mode 100644
index 000000000..75b5a2841
Binary files /dev/null and b/files/assets/images/emojis/marseytrippydance.webp differ
diff --git a/files/assets/images/emojis/marseytrumpmugshot.webp b/files/assets/images/emojis/marseytrumpmugshot.webp
new file mode 100644
index 000000000..7cb0d98dc
Binary files /dev/null and b/files/assets/images/emojis/marseytrumpmugshot.webp differ
diff --git a/files/assets/images/emojis/marseyvampireheart.webp b/files/assets/images/emojis/marseyvampireheart.webp
new file mode 100644
index 000000000..5c7a12694
Binary files /dev/null and b/files/assets/images/emojis/marseyvampireheart.webp differ
diff --git a/files/assets/images/emojis/marseywingcuck.webp b/files/assets/images/emojis/marseywingcuck.webp
new file mode 100644
index 000000000..363575b02
Binary files /dev/null and b/files/assets/images/emojis/marseywingcuck.webp differ
diff --git a/files/assets/images/emojis/oliverjak.webp b/files/assets/images/emojis/oliverjak.webp
new file mode 100644
index 000000000..7bddeafb8
Binary files /dev/null and b/files/assets/images/emojis/oliverjak.webp differ
diff --git a/files/assets/images/emojis/oliverjaksneed.webp b/files/assets/images/emojis/oliverjaksneed.webp
new file mode 100644
index 000000000..cec9fedc4
Binary files /dev/null and b/files/assets/images/emojis/oliverjaksneed.webp differ
diff --git a/files/assets/images/emojis/phil.webp b/files/assets/images/emojis/phil.webp
new file mode 100644
index 000000000..19377d528
Binary files /dev/null and b/files/assets/images/emojis/phil.webp differ
diff --git a/files/assets/images/emojis/platyhearts.webp b/files/assets/images/emojis/platyhearts.webp
new file mode 100644
index 000000000..56486b792
Binary files /dev/null and b/files/assets/images/emojis/platyhearts.webp differ
diff --git a/files/assets/images/emojis/ravenstarfirelaughing.webp b/files/assets/images/emojis/ravenstarfirelaughing.webp
new file mode 100644
index 000000000..1df0318e5
Binary files /dev/null and b/files/assets/images/emojis/ravenstarfirelaughing.webp differ
diff --git a/files/assets/images/emojis/sharkyheart.webp b/files/assets/images/emojis/sharkyheart.webp
new file mode 100644
index 000000000..22e7b1b9c
Binary files /dev/null and b/files/assets/images/emojis/sharkyheart.webp differ
diff --git a/files/assets/images/emojis/shroob.webp b/files/assets/images/emojis/shroob.webp
new file mode 100644
index 000000000..f79c75f79
Binary files /dev/null and b/files/assets/images/emojis/shroob.webp differ
diff --git a/files/assets/images/emojis/smokeychicken.webp b/files/assets/images/emojis/smokeychicken.webp
new file mode 100644
index 000000000..4b4129e37
Binary files /dev/null and b/files/assets/images/emojis/smokeychicken.webp differ
diff --git a/files/assets/images/emojis/smuggoblin.webp b/files/assets/images/emojis/smuggoblin.webp
new file mode 100644
index 000000000..91fc602e8
Binary files /dev/null and b/files/assets/images/emojis/smuggoblin.webp differ
diff --git a/files/assets/images/emojis/sneed2.webp b/files/assets/images/emojis/sneed2.webp
new file mode 100644
index 000000000..015b0c791
Binary files /dev/null and b/files/assets/images/emojis/sneed2.webp differ
diff --git a/files/assets/images/emojis/sniler.webp b/files/assets/images/emojis/sniler.webp
new file mode 100644
index 000000000..5e06726f2
Binary files /dev/null and b/files/assets/images/emojis/sniler.webp differ
diff --git a/files/assets/images/emojis/tayheart2.webp b/files/assets/images/emojis/tayheart2.webp
new file mode 100644
index 000000000..bc46bc78c
Binary files /dev/null and b/files/assets/images/emojis/tayheart2.webp differ
diff --git a/files/assets/images/emojis/traceheart.webp b/files/assets/images/emojis/traceheart.webp
new file mode 100644
index 000000000..90533094a
Binary files /dev/null and b/files/assets/images/emojis/traceheart.webp differ
diff --git a/files/assets/images/emojis/wolfjar.webp b/files/assets/images/emojis/wolfjar.webp
new file mode 100644
index 000000000..dbbe8d09b
Binary files /dev/null and b/files/assets/images/emojis/wolfjar.webp differ
diff --git a/files/assets/images/emojis/yaemikolick.webp b/files/assets/images/emojis/yaemikolick.webp
new file mode 100644
index 000000000..f1e25b1d0
Binary files /dev/null and b/files/assets/images/emojis/yaemikolick.webp differ
diff --git a/files/assets/images/hats/Beavis.webp b/files/assets/images/hats/Beavis.webp
new file mode 100644
index 000000000..4bc1093fd
Binary files /dev/null and b/files/assets/images/hats/Beavis.webp differ
diff --git a/files/assets/images/hats/Brush Teeth.webp b/files/assets/images/hats/Brush Teeth.webp
new file mode 100644
index 000000000..14389f401
Binary files /dev/null and b/files/assets/images/hats/Brush Teeth.webp differ
diff --git a/files/assets/images/hats/Butthead.webp b/files/assets/images/hats/Butthead.webp
new file mode 100644
index 000000000..4665e86b6
Binary files /dev/null and b/files/assets/images/hats/Butthead.webp differ
diff --git a/files/assets/images/hats/Carnival Hat.webp b/files/assets/images/hats/Carnival Hat.webp
new file mode 100644
index 000000000..0a6180593
Binary files /dev/null and b/files/assets/images/hats/Carnival Hat.webp differ
diff --git a/files/assets/images/hats/Cone of Shame.webp b/files/assets/images/hats/Cone of Shame.webp
new file mode 100644
index 000000000..16553b101
Binary files /dev/null and b/files/assets/images/hats/Cone of Shame.webp differ
diff --git a/files/assets/images/hats/Crown of thorns.webp b/files/assets/images/hats/Crown of thorns.webp
new file mode 100644
index 000000000..eb25606c2
Binary files /dev/null and b/files/assets/images/hats/Crown of thorns.webp differ
diff --git a/files/assets/images/hats/DBZ Cell.webp b/files/assets/images/hats/DBZ Cell.webp
new file mode 100644
index 000000000..5db864474
Binary files /dev/null and b/files/assets/images/hats/DBZ Cell.webp differ
diff --git a/files/assets/images/hats/Devil Mask.webp b/files/assets/images/hats/Devil Mask.webp
new file mode 100644
index 000000000..141838f24
Binary files /dev/null and b/files/assets/images/hats/Devil Mask.webp differ
diff --git a/files/assets/images/hats/Donkey Kong Face.webp b/files/assets/images/hats/Donkey Kong Face.webp
new file mode 100644
index 000000000..3a2779cc6
Binary files /dev/null and b/files/assets/images/hats/Donkey Kong Face.webp differ
diff --git a/files/assets/images/hats/Eye See You.webp b/files/assets/images/hats/Eye See You.webp
new file mode 100644
index 000000000..1c8412b8d
Binary files /dev/null and b/files/assets/images/hats/Eye See You.webp differ
diff --git a/files/assets/images/hats/Fish Drive By.webp b/files/assets/images/hats/Fish Drive By.webp
new file mode 100644
index 000000000..ee261cbc3
Binary files /dev/null and b/files/assets/images/hats/Fish Drive By.webp differ
diff --git a/files/assets/images/hats/Football Helmet II.webp b/files/assets/images/hats/Football Helmet II.webp
new file mode 100644
index 000000000..080ea81e0
Binary files /dev/null and b/files/assets/images/hats/Football Helmet II.webp differ
diff --git a/files/assets/images/hats/Gemmed.webp b/files/assets/images/hats/Gemmed.webp
new file mode 100644
index 000000000..d74ea3e59
Binary files /dev/null and b/files/assets/images/hats/Gemmed.webp differ
diff --git a/files/assets/images/hats/Ghost Escape.webp b/files/assets/images/hats/Ghost Escape.webp
new file mode 100644
index 000000000..f6a6d121e
Binary files /dev/null and b/files/assets/images/hats/Ghost Escape.webp differ
diff --git a/files/assets/images/hats/Golden Order.webp b/files/assets/images/hats/Golden Order.webp
new file mode 100644
index 000000000..403dbbb91
Binary files /dev/null and b/files/assets/images/hats/Golden Order.webp differ
diff --git a/files/assets/images/hats/Hollywood Hogan.webp b/files/assets/images/hats/Hollywood Hogan.webp
new file mode 100644
index 000000000..12df4cfcf
Binary files /dev/null and b/files/assets/images/hats/Hollywood Hogan.webp differ
diff --git a/files/assets/images/hats/Hulk Hogan.webp b/files/assets/images/hats/Hulk Hogan.webp
new file mode 100644
index 000000000..d632221b7
Binary files /dev/null and b/files/assets/images/hats/Hulk Hogan.webp differ
diff --git a/files/assets/images/hats/Juggle.webp b/files/assets/images/hats/Juggle.webp
new file mode 100644
index 000000000..88afa12dc
Binary files /dev/null and b/files/assets/images/hats/Juggle.webp differ
diff --git a/files/assets/images/hats/Kitten Border.webp b/files/assets/images/hats/Kitten Border.webp
new file mode 100644
index 000000000..f08252bb3
Binary files /dev/null and b/files/assets/images/hats/Kitten Border.webp differ
diff --git a/files/assets/images/hats/Leather Cap.webp b/files/assets/images/hats/Leather Cap.webp
new file mode 100644
index 000000000..d02cbb95e
Binary files /dev/null and b/files/assets/images/hats/Leather Cap.webp differ
diff --git a/files/assets/images/hats/Lustful Face.webp b/files/assets/images/hats/Lustful Face.webp
new file mode 100644
index 000000000..8b6f66407
Binary files /dev/null and b/files/assets/images/hats/Lustful Face.webp differ
diff --git a/files/assets/images/hats/Mister Giggles.webp b/files/assets/images/hats/Mister Giggles.webp
new file mode 100644
index 000000000..5ac3c63f4
Binary files /dev/null and b/files/assets/images/hats/Mister Giggles.webp differ
diff --git a/files/assets/images/hats/Mugshot Solid.webp b/files/assets/images/hats/Mugshot Solid.webp
new file mode 100644
index 000000000..0e96bd110
Binary files /dev/null and b/files/assets/images/hats/Mugshot Solid.webp differ
diff --git a/files/assets/images/hats/Mugshot.webp b/files/assets/images/hats/Mugshot.webp
new file mode 100644
index 000000000..b78b549f6
Binary files /dev/null and b/files/assets/images/hats/Mugshot.webp differ
diff --git a/files/assets/images/hats/Neon Green Halo.webp b/files/assets/images/hats/Neon Green Halo.webp
new file mode 100644
index 000000000..dcaf0c599
Binary files /dev/null and b/files/assets/images/hats/Neon Green Halo.webp differ
diff --git a/files/assets/images/hats/NukaCola.webp b/files/assets/images/hats/NukaCola.webp
new file mode 100644
index 000000000..1ba72183e
Binary files /dev/null and b/files/assets/images/hats/NukaCola.webp differ
diff --git a/files/assets/images/hats/Pink Devil Wings.webp b/files/assets/images/hats/Pink Devil Wings.webp
new file mode 100644
index 000000000..c84e526b9
Binary files /dev/null and b/files/assets/images/hats/Pink Devil Wings.webp differ
diff --git a/files/assets/images/hats/Poof.webp b/files/assets/images/hats/Poof.webp
new file mode 100644
index 000000000..b36a90ffd
Binary files /dev/null and b/files/assets/images/hats/Poof.webp differ
diff --git a/files/assets/images/hats/Popping Hearts.webp b/files/assets/images/hats/Popping Hearts.webp
new file mode 100644
index 000000000..1eebba94d
Binary files /dev/null and b/files/assets/images/hats/Popping Hearts.webp differ
diff --git a/files/assets/images/hats/Prayer.webp b/files/assets/images/hats/Prayer.webp
new file mode 100644
index 000000000..5934a22f9
Binary files /dev/null and b/files/assets/images/hats/Prayer.webp differ
diff --git a/files/assets/images/hats/Puppet.webp b/files/assets/images/hats/Puppet.webp
new file mode 100644
index 000000000..0750496c3
Binary files /dev/null and b/files/assets/images/hats/Puppet.webp differ
diff --git a/files/assets/images/hats/Radiance.webp b/files/assets/images/hats/Radiance.webp
new file mode 100644
index 000000000..a7e02dae5
Binary files /dev/null and b/files/assets/images/hats/Radiance.webp differ
diff --git a/files/assets/images/hats/Ramen Hat I.webp b/files/assets/images/hats/Ramen Hat I.webp
new file mode 100644
index 000000000..a5272dce3
Binary files /dev/null and b/files/assets/images/hats/Ramen Hat I.webp differ
diff --git a/files/assets/images/hats/Ramen Hat II.webp b/files/assets/images/hats/Ramen Hat II.webp
new file mode 100644
index 000000000..e1f24ef64
Binary files /dev/null and b/files/assets/images/hats/Ramen Hat II.webp differ
diff --git a/files/assets/images/hats/SCP Hat.webp b/files/assets/images/hats/SCP Hat.webp
new file mode 100644
index 000000000..5a8a48253
Binary files /dev/null and b/files/assets/images/hats/SCP Hat.webp differ
diff --git a/files/assets/images/hats/SCP173.webp b/files/assets/images/hats/SCP173.webp
new file mode 100644
index 000000000..de4ea7d14
Binary files /dev/null and b/files/assets/images/hats/SCP173.webp differ
diff --git a/files/assets/images/hats/SCP999.webp b/files/assets/images/hats/SCP999.webp
new file mode 100644
index 000000000..9326949c4
Binary files /dev/null and b/files/assets/images/hats/SCP999.webp differ
diff --git a/files/assets/images/hats/Scouter Green.webp b/files/assets/images/hats/Scouter Green.webp
new file mode 100644
index 000000000..e7a28d257
Binary files /dev/null and b/files/assets/images/hats/Scouter Green.webp differ
diff --git a/files/assets/images/hats/Scouter Red.webp b/files/assets/images/hats/Scouter Red.webp
new file mode 100644
index 000000000..dea481004
Binary files /dev/null and b/files/assets/images/hats/Scouter Red.webp differ
diff --git a/files/assets/images/hats/Sexy Eyes.webp b/files/assets/images/hats/Sexy Eyes.webp
new file mode 100644
index 000000000..1e2a2d100
Binary files /dev/null and b/files/assets/images/hats/Sexy Eyes.webp differ
diff --git a/files/assets/images/hats/Soul Storm.webp b/files/assets/images/hats/Soul Storm.webp
new file mode 100644
index 000000000..2b62ab5f5
Binary files /dev/null and b/files/assets/images/hats/Soul Storm.webp differ
diff --git a/files/assets/images/hats/Sutton Hoo helmet.webp b/files/assets/images/hats/Sutton Hoo helmet.webp
new file mode 100644
index 000000000..c4b8b8ae2
Binary files /dev/null and b/files/assets/images/hats/Sutton Hoo helmet.webp differ
diff --git a/files/assets/images/hats/The Beheaded.webp b/files/assets/images/hats/The Beheaded.webp
new file mode 100644
index 000000000..7d9cc9a4d
Binary files /dev/null and b/files/assets/images/hats/The Beheaded.webp differ
diff --git a/files/assets/images/hats/The Master.webp b/files/assets/images/hats/The Master.webp
new file mode 100644
index 000000000..7ece511dc
Binary files /dev/null and b/files/assets/images/hats/The Master.webp differ
diff --git a/files/assets/images/hats/Top Hat (Tiny).webp b/files/assets/images/hats/Top Hat (Tiny).webp
new file mode 100644
index 000000000..956dbc536
Binary files /dev/null and b/files/assets/images/hats/Top Hat (Tiny).webp differ
diff --git a/files/assets/images/hats/Trust me I am a doctor.webp b/files/assets/images/hats/Trust me I am a doctor.webp
new file mode 100644
index 000000000..583737c73
Binary files /dev/null and b/files/assets/images/hats/Trust me I am a doctor.webp differ
diff --git a/files/assets/images/hats/Unamused.webp b/files/assets/images/hats/Unamused.webp
new file mode 100644
index 000000000..e6c1513a9
Binary files /dev/null and b/files/assets/images/hats/Unamused.webp differ
diff --git a/files/assets/images/hats/Vaporeon Hat.webp b/files/assets/images/hats/Vaporeon Hat.webp
new file mode 100644
index 000000000..cd41ea1ee
Binary files /dev/null and b/files/assets/images/hats/Vaporeon Hat.webp differ
diff --git a/files/assets/images/hats/Vault-Tec.webp b/files/assets/images/hats/Vault-Tec.webp
new file mode 100644
index 000000000..c448cb8bd
Binary files /dev/null and b/files/assets/images/hats/Vault-Tec.webp differ
diff --git a/files/assets/images/hats/Youth Awareness.webp b/files/assets/images/hats/Youth Awareness.webp
new file mode 100644
index 000000000..7b23b3dd7
Binary files /dev/null and b/files/assets/images/hats/Youth Awareness.webp differ
diff --git a/files/assets/images/hats/beard.webp b/files/assets/images/hats/beard.webp
new file mode 100644
index 000000000..edaea9ec9
Binary files /dev/null and b/files/assets/images/hats/beard.webp differ
diff --git a/files/assets/images/hats/love hands.webp b/files/assets/images/hats/love hands.webp
new file mode 100644
index 000000000..d4ceb3d8f
Binary files /dev/null and b/files/assets/images/hats/love hands.webp differ
diff --git a/files/assets/images/rDrama/lottery_active.webp b/files/assets/images/lottery_active.webp
similarity index 100%
rename from files/assets/images/rDrama/lottery_active.webp
rename to files/assets/images/lottery_active.webp
diff --git a/files/assets/images/rDrama/badges/296.webp b/files/assets/images/rDrama/badges/296.webp
new file mode 100644
index 000000000..94dc790ab
Binary files /dev/null and b/files/assets/images/rDrama/badges/296.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1046.webp b/files/assets/images/rDrama/sidebar/1046.webp
deleted file mode 100644
index 3b547b082..000000000
Binary files a/files/assets/images/rDrama/sidebar/1046.webp and /dev/null differ
diff --git a/files/assets/images/rDrama/sidebar/1252.webp b/files/assets/images/rDrama/sidebar/1252.webp
new file mode 100644
index 000000000..ece0d4790
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1252.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1253.webp b/files/assets/images/rDrama/sidebar/1253.webp
new file mode 100644
index 000000000..b27e8c24a
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1253.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1254.webp b/files/assets/images/rDrama/sidebar/1254.webp
new file mode 100644
index 000000000..16af73d75
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1254.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1255.webp b/files/assets/images/rDrama/sidebar/1255.webp
new file mode 100644
index 000000000..129d8273d
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1255.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1256.webp b/files/assets/images/rDrama/sidebar/1256.webp
new file mode 100644
index 000000000..5ada5457d
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1256.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1257.webp b/files/assets/images/rDrama/sidebar/1257.webp
new file mode 100644
index 000000000..c4d57dcd0
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1257.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1258.webp b/files/assets/images/rDrama/sidebar/1258.webp
new file mode 100644
index 000000000..53304d1de
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1258.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1259.webp b/files/assets/images/rDrama/sidebar/1259.webp
new file mode 100644
index 000000000..83dc46740
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1259.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1260.webp b/files/assets/images/rDrama/sidebar/1260.webp
new file mode 100644
index 000000000..894d45ec8
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1260.webp differ
diff --git a/files/assets/images/rDrama/sidebar/1261.webp b/files/assets/images/rDrama/sidebar/1261.webp
new file mode 100644
index 000000000..b0ea39d3b
Binary files /dev/null and b/files/assets/images/rDrama/sidebar/1261.webp differ
diff --git a/files/assets/images/rDrama/sidebar/9.webp b/files/assets/images/rDrama/sidebar/9.webp
deleted file mode 100644
index a369c3aeb..000000000
Binary files a/files/assets/images/rDrama/sidebar/9.webp and /dev/null differ
diff --git a/files/assets/js/admin/alts.js b/files/assets/js/admin/alts.js
index d3c403306..a7a3fe5ef 100644
--- a/files/assets/js/admin/alts.js
+++ b/files/assets/js/admin/alts.js
@@ -3,7 +3,7 @@ function submitAddAlt(element, username) {
element.classList.add('disabled');
const form = new FormData();
form.append('other_username', document.getElementById('link-input-other').value);
- const xhr = createXhrWithFormKey(`/@${username}/alts/`, 'POST', form);
+ const xhr = createXhrWithFormKey(`/@${username}/alts/`, form);
xhr[0].onload = function() {
let data;
try {
diff --git a/files/assets/js/admin/banned_domains.js b/files/assets/js/admin/banned_domains.js
index 19257897e..ad1272a77 100644
--- a/files/assets/js/admin/banned_domains.js
+++ b/files/assets/js/admin/banned_domains.js
@@ -1,4 +1,8 @@
function unbanDomain(t, domain) {
- postToastSwitch(t,'/admin/unban_domain/' + domain);
- t.parentElement.parentElement.remove();
+ postToast(
+ t,
+ `/admin/unban_domain/${domain}`,
+ {},
+ () => {t.parentElement.parentElement.remove()}
+ );
}
diff --git a/files/assets/js/admin/comments.js b/files/assets/js/admin/comments.js
index 3a6df7a4b..2cdf02a4e 100644
--- a/files/assets/js/admin/comments.js
+++ b/files/assets/js/admin/comments.js
@@ -1,5 +1,5 @@
function removeComment(t,comment_id,button1,button2,cls) {
- url="/remove_comment/"+comment_id
+ url = "/remove_comment/"+comment_id
postToastSwitch(t, url,
button1,
@@ -24,7 +24,7 @@ function removeComment(t,comment_id,button1,button2,cls) {
}
function approveComment(t,comment_id,button1,button2,cls) {
- url="/approve_comment/"+comment_id
+ url = "/approve_comment/"+comment_id
postToastSwitch(t, url,
button1,
diff --git a/files/assets/js/admin/post.js b/files/assets/js/admin/post.js
index ef2838ab8..c74db0965 100644
--- a/files/assets/js/admin/post.js
+++ b/files/assets/js/admin/post.js
@@ -1,5 +1,5 @@
function removePost(t,post_id,button1,button2,cls) {
- url="/remove_post/"+post_id
+ url = "/remove_post/"+post_id
postToastSwitch(t, url,
button1,
@@ -21,7 +21,7 @@ function removePost(t,post_id,button1,button2,cls) {
function approvePost(t,post_id,button1,button2,cls) {
- url="/approve_post/"+post_id
+ url = "/approve_post/"+post_id
postToastSwitch(t, url,
button1,
diff --git a/files/assets/js/award_modal.js b/files/assets/js/award_modal.js
index 1351a35e2..86e0bea0a 100644
--- a/files/assets/js/award_modal.js
+++ b/files/assets/js/award_modal.js
@@ -157,7 +157,7 @@ function buy() {
try {data = JSON.parse(xhr[0].response)}
catch(e) {console.error(e)}
success = xhr[0].status >= 200 && xhr[0].status < 300;
- showToast(success, getMessageFromJsonData(success, data), true);
+ showToast(success, getMessageFromJsonData(success, data));
if (success) {
if (kind != "lootbox")
{
diff --git a/files/assets/js/bottom.js b/files/assets/js/bottom.js
index 985f3d619..2647f0671 100644
--- a/files/assets/js/bottom.js
+++ b/files/assets/js/bottom.js
@@ -64,42 +64,20 @@ for (const element of undisable_element) {
});
}
-async function handleSettingSwitch(event) {
- let input = event.currentTarget;
- input.disabled = true;
- input.classList.add("disabled");
- const form = new FormData();
- form.append("formkey", formkey());
- const res = await fetch(
- `/settings/personal?${input.name}=${input.checked}`,
- {
- method: "POST",
- headers: {
- xhr: "xhr",
- },
- body: form,
- },
- ).catch(() => ({ ok: false }));
- let message;
- if (res.ok) {
- ({message} = await res.json());
- // the slur and profanity replacers have special make-permanent switches
- if (["slurreplacerswitch", "profanityreplacerswitch"].includes(input.id)) {
- document.getElementById(
- `${input.id.replace("switch", "")}-perma-link`
- ).hidden = !input.checked;
- }
- } else {
- // toggle the input back if the request doesn't go through
- input.checked = !input.checked;
- }
- let oldToast = bootstrap.Toast.getOrCreateInstance(
- document.getElementById('toast-post-' + (res.ok ? 'error': 'success'))
- ); // intentionally reversed here: this is the old toast
- oldToast.hide();
- showToast(res.ok, message);
- input.disabled = false;
- input.classList.remove("disabled");
+function handleSettingSwitch(t) {
+ postToast(t, `/settings/personal?${t.name}=${t.checked}`,
+ {},
+ () => {
+ if (["slurreplacerswitch", "profanityreplacerswitch"].includes(t.id)) {
+ document.getElementById(
+ `${t.id.replace("switch", "")}-perma-link`
+ ).hidden = !t.checked;
+ }
+ },
+ () => {
+ t.checked = !t.checked;
+ },
+ );
}
const setting_switchs = document.getElementsByClassName('setting_switch');
@@ -108,7 +86,7 @@ for (const element of setting_switchs) {
console.log("Nonce check failed!")
continue
}
- element.addEventListener('change', handleSettingSwitch);
+ element.addEventListener('change', () => {handleSettingSwitch(element)});
}
const setting_selects = document.getElementsByClassName('setting_select');
@@ -204,6 +182,21 @@ document.addEventListener("click", function (e) {
return
}
document.getElementById('giveaward').dataset.action = element.dataset.url
+
+ const effect_author_tab = document.getElementById('effect-author-tab')
+ const effect_content_tab = document.getElementById('effect-content-tab')
+ const effect_author_section = document.getElementById('effect-author-section')
+ const effect_content_section = document.getElementById('effect-content-section')
+ if (element.dataset.ghost == 'True') {
+ effect_author_tab.classList.add('disabled')
+ effect_author_tab.classList.remove('active')
+ effect_author_section.classList.add('d-none')
+ effect_content_tab.classList.add('active')
+ effect_content_section.classList.remove('d-none')
+ }
+ else {
+ effect_author_tab.classList.remove('disabled')
+ }
}
diff --git a/files/assets/js/casino/game_screen.js b/files/assets/js/casino/game_screen.js
index 48c69c700..06be77060 100644
--- a/files/assets/js/casino/game_screen.js
+++ b/files/assets/js/casino/game_screen.js
@@ -12,13 +12,13 @@ function initializeGame() {
function updatePlayerCurrencies(updated) {
if (updated.coins) {
- document.getElementById("user-coins-amount").innerText = updated.coins;
- document.getElementById("user-coins-amount-casino").innerText = updated.coins;
+ document.getElementById("user-coins-amount").textContent = updated.coins;
+ document.getElementById("user-coins-amount-casino").textContent = updated.coins;
}
if (updated.marseybux) {
- document.getElementById("user-bux-amount").innerText = updated.marseybux;
- document.getElementById("user-bux-amount-casino").innerText = updated.marseybux;
+ document.getElementById("user-bux-amount").textContent = updated.marseybux;
+ document.getElementById("user-bux-amount-casino").textContent = updated.marseybux;
}
}
@@ -48,14 +48,14 @@ function updateResult(text, className) {
clearResult();
const result = document.getElementById("casinoGameResult");
result.style.visibility = "visible";
- result.innerText = text;
+ result.textContent = text;
result.classList.add(`alert-${className}`);
}
function clearResult() {
const result = document.getElementById("casinoGameResult");
result.style.visibility = "hidden";
- result.innerText = "N/A";
+ result.textContent = "N/A";
result.classList.remove("alert-success", "alert-danger", "alert-warning");
}
diff --git a/files/assets/js/chat.js b/files/assets/js/chat.js
index 85748acbc..9bde2feb7 100644
--- a/files/assets/js/chat.js
+++ b/files/assets/js/chat.js
@@ -84,7 +84,7 @@ socket.on('speak', function(json) {
}
chatline.classList.remove('chat-mention');
- if (text_html.includes(``)){
+ if (text_html.includes(` 5000)
@@ -121,7 +131,7 @@ socket.on('speak', function(json) {
document.getElementsByClassName('chat-line')[0].id = json.id
document.getElementsByClassName('text')[0].innerHTML = escapeHTML(text)
- document.getElementsByClassName('chat-message')[0].innerHTML = text_html.replace(/data-src/g, 'src').replace(/data-cfsrc/g, 'src').replace(/style="display:none;visibility:hidden;"/g, '')
+ document.getElementsByClassName('chat-message')[0].innerHTML = text_html.replace(/data-src/g, 'src').replace(/data-cfsrc/g, 'src').replace(/style="display:none;visibility:hidden;"/g, '').replace(/ loading="lazy"/g, '')
document.getElementsByClassName('quotes')[0].classList.add("d-none")
if (json.quotes) {
@@ -154,8 +164,15 @@ socket.on('speak', function(json) {
register_new_elements(line2);
bs_trigger(line2)
- if (scrolled_down || json.user_id == vid)
+ if (scrolled_down || json.user_id == vid) {
box.scrollTo(0, box.scrollHeight)
+ setTimeout(function () {
+ box.scrollTo(0, box.scrollHeight)
+ }, 200);
+ setTimeout(function () {
+ box.scrollTo(0, box.scrollHeight)
+ }, 500);
+ }
})
function send() {
@@ -186,6 +203,9 @@ function send() {
setTimeout(function () {
box.scrollTo(0, box.scrollHeight)
}, 200);
+ setTimeout(function () {
+ box.scrollTo(0, box.scrollHeight)
+ }, 500);
}
}
@@ -221,24 +241,33 @@ socket.on('online', function(data){
let online2 = 'Users Online'
for (const u of data[0])
{
+ let patron = ''
+ if (u[3])
+ patron = ` class="patron" style="background-color:#${u[2]}"`
+
online += `
@${u[0]}`
}
- document.getElementById('online').innerHTML = online
- bs_trigger(document.getElementById('online'))
+
+ const online_el = document.getElementById('online')
+ if (online_el) {
+ online_el.innerHTML = online
+ bs_trigger(online_el)
+ }
+
document.getElementById('online2').setAttribute("data-bs-original-title", online2);
document.getElementById('online3').innerHTML = online
bs_trigger(document.getElementById('online3'))
})
addEventListener('blur', function(){
- focused=false
+ focused = false
})
addEventListener('focus', function(){
- focused=true
+ focused = true
})
let timer_id;
diff --git a/files/assets/js/comments+post_listing.js b/files/assets/js/comments+post_listing.js
index f987d155d..0eb26255f 100644
--- a/files/assets/js/comments+post_listing.js
+++ b/files/assets/js/comments+post_listing.js
@@ -17,7 +17,7 @@ function option_vote_0(oid, parentid, kind) {
for(let el of document.getElementsByClassName('presult-'+parentid)) {
el.classList.remove('d-none');
}
- const full_oid = kind + '-' + oid
+ const full_oid = `option-${kind}-${oid}`
const type = document.getElementById(full_oid).checked;
const scoretext = document.getElementById('score-' + full_oid);
const score = Number(scoretext.textContent);
@@ -30,8 +30,8 @@ function option_vote_1(oid, parentid, kind) {
for(let el of document.getElementsByClassName('presult-'+parentid)) {
el.classList.remove('d-none');
}
- const full_oid = kind + '-' + oid
- let curr = document.getElementById(`current-${kind}-${parentid}`)
+ const full_oid = `option-${kind}-${oid}`
+ let curr = document.getElementById(`current-option-${kind}-${parentid}`)
if (curr && curr.value)
{
const scoretext = document.getElementById('score-' + curr.value);
diff --git a/files/assets/js/comments_v.js b/files/assets/js/comments_v.js
index a86f5b31c..09e59fb15 100644
--- a/files/assets/js/comments_v.js
+++ b/files/assets/js/comments_v.js
@@ -68,6 +68,7 @@ function toggleReplyBox(t, id) {
if (ta.value && !ta.value.endsWith('\n')) ta.value += '\n'
ta.value += text
if (!ta.value.endsWith('\n')) ta.value += '\n'
+ markdown(ta);
}
ta.focus()
@@ -76,15 +77,15 @@ function toggleReplyBox(t, id) {
let newHTML = ''
if (t.innerHTML.includes(''
- if (t.innerText)
+ if (t.textContent)
newHTML += 'Quote selection'
t.innerHTML = newHTML
}
function toggleEdit(id){
- comment=document.getElementById("comment-text-"+id);
- form=document.getElementById("comment-edit-"+id);
- box=document.getElementById('comment-edit-body-'+id);
+ comment = document.getElementById("comment-text-"+id);
+ form = document.getElementById("comment-edit-"+id);
+ box = document.getElementById('comment-edit-body-'+id);
actions = document.getElementById('comment-' + id +'-actions');
comment.classList.toggle("d-none");
@@ -97,30 +98,34 @@ function toggleEdit(id){
};
-function delete_commentModal(t, id) {
- document.getElementById("deleteCommentButton").addEventListener('click', function() {
- postToast(t, `/delete/comment/${id}`,
- {
- },
- () => {
- if (location.pathname == '/admin/reported/comments')
- {
- document.getElementById("post-info-"+id).remove()
- document.getElementById("comment-"+id).remove()
- }
- else
- {
- document.getElementsByClassName(`comment-${id}-only`)[0].classList.add('deleted');
- document.getElementById(`delete-${id}`).classList.add('d-none');
- document.getElementById(`undelete-${id}`).classList.remove('d-none');
- document.getElementById(`delete2-${id}`).classList.add('d-none');
- document.getElementById(`undelete2-${id}`).classList.remove('d-none');
- }
- }
- );
- });
+const deleteCommentButton = document.getElementById("deleteCommentButton");
+
+function delete_commentModal(id) {
+ deleteCommentButton.dataset.id = id
}
+deleteCommentButton.onclick = () => {
+ const id = deleteCommentButton.dataset.id
+ postToast(deleteCommentButton, `/delete/comment/${id}`,
+ {},
+ () => {
+ if (location.pathname == '/admin/reported/comments')
+ {
+ document.getElementById("post-info-"+id).remove()
+ document.getElementById("comment-"+id).remove()
+ }
+ else
+ {
+ document.getElementsByClassName(`comment-${id}-only`)[0].classList.add('deleted');
+ document.getElementById(`delete-${id}`).classList.add('d-none');
+ document.getElementById(`undelete-${id}`).classList.remove('d-none');
+ document.getElementById(`delete2-${id}`).classList.add('d-none');
+ document.getElementById(`undelete2-${id}`).classList.remove('d-none');
+ }
+ }
+ );
+};
+
function post_reply(id) {
close_inline_speed_emoji_modal();
@@ -139,7 +144,7 @@ function post_reply(id) {
}
catch(e) {}
- const xhr = createXhrWithFormKey("/reply", "POST", form);
+ const xhr = createXhrWithFormKey("/reply", form);
const upload_prog = document.getElementById(`upload-prog-c_${id}`);
xhr[0].upload.onprogress = (e) => {handleUploadProgress(e, upload_prog)};
@@ -197,7 +202,7 @@ function comment_edit(id){
form.append('file', e);
}
catch(e) {}
- const xhr = createXhrWithFormKey("/edit_comment/"+id, "POST", form);
+ const xhr = createXhrWithFormKey("/edit_comment/"+id, form);
const upload_prog = document.getElementById(`upload-prog-edit-c_${id}`);
xhr[0].upload.onprogress = (e) => {handleUploadProgress(e, upload_prog)};
@@ -218,6 +223,18 @@ function comment_edit(id){
document.getElementById('comment-edit-body-' + id).value = data["body"];
+ if (data["ping_cost"]) {
+ const ping_cost = document.getElementById('comment-ping-cost-' + id)
+ ping_cost.textContent = data["ping_cost"]
+ ping_cost.parentElement.classList.remove('d-none')
+ }
+
+ if (data["edited_string"]) {
+ const edited_string = document.getElementById('comment-edited_string-' + id)
+ edited_string.textContent = data["edited_string"]
+ edited_string.parentElement.classList.remove('d-none')
+ }
+
const input = ta.parentElement.querySelector('input[type="file"]')
input.previousElementSibling.innerHTML = '';
input.value = null;
@@ -359,7 +376,7 @@ function restore_reply_buttons(fullname) {
let newHTML = ''
if (t.innerHTML.includes(''
- if (t.innerText)
+ if (t.textContent)
newHTML += 'Reply'
t.innerHTML = newHTML
}
diff --git a/files/assets/js/core.js b/files/assets/js/core.js
index fa97c2c7d..adebc6b6c 100644
--- a/files/assets/js/core.js
+++ b/files/assets/js/core.js
@@ -11,21 +11,19 @@ function getMessageFromJsonData(success, json) {
return message;
}
-function showToast(success, message, isToastTwo=false) {
+function showToast(success, message) {
+ const oldToast = bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-' + (success ? 'error': 'success'))); // intentionally reversed here: this is the old toast
+ oldToast.hide();
let element = success ? "toast-post-success" : "toast-post-error";
let textElement = element + "-text";
- if (isToastTwo) {
- element = element + "2";
- textElement = textElement + "2";
- }
if (!message) {
- message = success ? "Success" : "Error, please try again later";
+ message = success ? "Action successful!" : "Error, please try again later";
}
- document.getElementById(textElement).innerText = message;
+ document.getElementById(textElement).textContent = message;
bootstrap.Toast.getOrCreateInstance(document.getElementById(element)).show();
}
-function createXhrWithFormKey(url, method="POST", form=new FormData()) {
+function createXhrWithFormKey(url, form=new FormData(), method='POST') {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader('xhr', 'xhr');
@@ -34,52 +32,52 @@ function createXhrWithFormKey(url, method="POST", form=new FormData()) {
return [xhr, form]; // hacky but less stupid than what we were doing before
}
-function postToast(t, url, data, extraActionsOnSuccess, method="POST") {
+function postToast(t, url, data, extraActionsOnSuccess, extraActionsOnFailure) {
+ t.disabled = true;
+ t.classList.add("disabled");
+
let form = new FormData();
if (typeof data === 'object' && data !== null) {
for(let k of Object.keys(data)) {
form.append(k, data[k]);
}
}
- const xhr = createXhrWithFormKey(url, method, form);
+ const xhr = createXhrWithFormKey(url, form);
xhr[0].onload = function() {
- t.disabled = false;
- t.classList.remove("disabled");
+ const success = xhr[0].status >= 200 && xhr[0].status < 300;
+
+ if (!(extraActionsOnSuccess == reload && success)) {
+ t.disabled = false;
+ t.classList.remove("disabled");
+ }
+
let result
let message;
- let success = xhr[0].status >= 200 && xhr[0].status < 300;
if (typeof result == "string") {
message = result;
} else {
message = getMessageFromJsonData(success, JSON.parse(xhr[0].response));
}
- let oldToast = bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-' + (success ? 'error': 'success'))); // intentionally reversed here: this is the old toast
- oldToast.hide();
showToast(success, message);
- if (success && extraActionsOnSuccess) result = extraActionsOnSuccess(xhr[0]);
+ if (success && extraActionsOnSuccess) extraActionsOnSuccess(xhr[0]);
+ if (!success && extraActionsOnFailure) extraActionsOnFailure(xhr[0]);
return success;
};
xhr[0].send(xhr[1]);
}
-function postToastReload(t, url, method="POST") {
- postToast(t, url,
- {
- },
- () => {
- location.reload()
- }
- , method);
+function postToastReload(t, url) {
+ postToast(t, url, {}, reload);
}
-function postToastSwitch(t, url, button1, button2, cls, extraActionsOnSuccess, method="POST") {
+function postToastSwitch(t, url, button1, button2, cls, extraActionsOnSuccess) {
postToast(t, url,
{
},
(xhr) => {
if (button1)
{
- if (typeof(button1) == 'boolean') {
+ if (typeof button1 == 'boolean') {
location.reload()
} else {
try {
@@ -93,9 +91,8 @@ function postToastSwitch(t, url, button1, button2, cls, extraActionsOnSuccess, m
}
}
if (typeof extraActionsOnSuccess == 'function')
- extraActionsOnSuccess(xhr);
- }
- , method);
+ extraActionsOnSuccess(xhr);
+ });
}
if (!location.pathname.endsWith('/submit'))
@@ -113,6 +110,11 @@ if (!location.pathname.endsWith('/submit'))
return
}
+ if (location.pathname == '/admin/orgy') {
+ document.getElementById('start-orgy').click();
+ return
+ }
+
const submitButtonDOMs = formDOM.querySelectorAll('input[type=submit], .btn-primary');
if (submitButtonDOMs.length === 0)
throw new TypeError("I am unable to find the submit button :(. Contact the head custodian immediately.")
@@ -124,8 +126,8 @@ if (!location.pathname.endsWith('/submit'))
function autoExpand(field) {
- xpos=window.scrollX;
- ypos=window.scrollY;
+ xpos = window.scrollX;
+ ypos = window.scrollY;
field.style.height = 'inherit';
@@ -196,15 +198,7 @@ function bs_trigger(e) {
});
if (typeof update_speed_emoji_modal == 'function') {
- let forms = e.querySelectorAll("textarea, .allow-emojis");
- forms.forEach(i => {
- let pseudo_div = document.createElement("div");
- pseudo_div.className = "ghostdiv";
- pseudo_div.style.display = "none";
- i.after(pseudo_div);
- i.addEventListener('input', update_speed_emoji_modal, false);
- i.addEventListener('keydown', speed_carot_navigate, false);
- });
+ insertGhostDivs(e)
}
}
@@ -280,10 +274,14 @@ function prepare_to_pause(audio) {
});
}
+function reload() {
+ location.reload();
+}
+
function sendFormXHR(form, extraActionsOnSuccess) {
- const submit_btn = form.querySelector('[type="submit"]')
- submit_btn.disabled = true;
- submit_btn.classList.add("disabled");
+ const t = form.querySelector('[type="submit"]')
+ t.disabled = true;
+ t.classList.add("disabled");
const xhr = new XMLHttpRequest();
@@ -296,24 +294,18 @@ function sendFormXHR(form, extraActionsOnSuccess) {
xhr.setRequestHeader('xhr', 'xhr');
xhr.onload = function() {
- if (xhr.status >= 200 && xhr.status < 300) {
- let data = JSON.parse(xhr.response);
- showToast(true, getMessageFromJsonData(true, data));
- if (extraActionsOnSuccess) extraActionsOnSuccess(xhr);
- } else {
- document.getElementById('toast-post-error-text').innerText = "Error, please try again later."
- try {
- let data=JSON.parse(xhr.response);
- bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-error')).show();
- document.getElementById('toast-post-error-text').innerText = data["error"];
- if (data && data["details"]) document.getElementById('toast-post-error-text').innerText = data["details"];
- } catch(e) {
- bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-success')).hide();
- bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-error')).show();
- }
+ const success = xhr.status >= 200 && xhr.status < 300;
+
+ if (!(extraActionsOnSuccess == reload && success)) {
+ t.disabled = false;
+ t.classList.remove("disabled");
}
- submit_btn.disabled = false;
- submit_btn.classList.remove("disabled");
+
+ if (xhr.status != 204) {
+ const data = JSON.parse(xhr.response);
+ showToast(success, getMessageFromJsonData(success, data));
+ }
+ if (success && extraActionsOnSuccess) extraActionsOnSuccess(xhr);
};
xhr.send(formData);
@@ -331,11 +323,7 @@ function sendFormXHRSwitch(form) {
}
function sendFormXHRReload(form) {
- sendFormXHR(form,
- () => {
- location.reload();
- }
- )
+ sendFormXHR(form, reload)
}
let sortAscending = {};
@@ -360,7 +348,7 @@ function sort_table(t) {
} else if ('time' in x.dataset) {
attr = parseInt(x.dataset.time);
} else {
- attr = x.innerText
+ attr = x.textContent
if (/^[\d-,]+$/.test(x.innerHTML)) {
attr = parseInt(attr.replace(/,/g, ''))
}
@@ -457,23 +445,64 @@ function insertText(input, text) {
let oldfiles = {};
+let MAX_IMAGE_AUDIO_SIZE_MB
+let MAX_IMAGE_AUDIO_SIZE_MB_PATRON
+let MAX_VIDEO_SIZE_MB
+let MAX_VIDEO_SIZE_MB_PATRON
+
+if (document.getElementById("MAX_IMAGE_AUDIO_SIZE_MB")) {
+ MAX_IMAGE_AUDIO_SIZE_MB = parseInt(document.getElementById("MAX_IMAGE_AUDIO_SIZE_MB").value)
+ MAX_IMAGE_AUDIO_SIZE_MB_PATRON = parseInt(document.getElementById("MAX_IMAGE_AUDIO_SIZE_MB_PATRON").value)
+ MAX_VIDEO_SIZE_MB = parseInt(document.getElementById("MAX_VIDEO_SIZE_MB").value)
+ MAX_VIDEO_SIZE_MB_PATRON = parseInt(document.getElementById("MAX_VIDEO_SIZE_MB_PATRON").value)
+}
+
+let patron
+if (location.host == 'rdrama.net') patron = 'paypig'
+else patron = 'patron'
function handle_files(input, newfiles) {
if (!newfiles) return;
+ for (const file of newfiles) {
+ if (file.type.startsWith('image/'))
+ continue
+
+ let max_size
+ let max_size_patron
+ let type
+
+ if (file.type.startsWith('video/')) {
+ max_size = MAX_VIDEO_SIZE_MB
+ max_size_patron = MAX_VIDEO_SIZE_MB_PATRON
+ type = 'video'
+ }
+ else {
+ max_size = MAX_IMAGE_AUDIO_SIZE_MB
+ max_size_patron = MAX_IMAGE_AUDIO_SIZE_MB_PATRON
+ type = 'image/audio'
+ }
+
+ if (file.size > max_size * 1024 * 1024) {
+ const msg = `Max ${type} size is ${max_size} MB (${max_size_patron} MB for ${patron}s)`
+ showToast(false, msg);
+ input.value = null;
+ return
+ }
+ }
+
const ta = input.parentElement.parentElement.parentElement.parentElement.querySelector('textarea.file-ta');
- if (oldfiles[ta.id]) {
- for (const file of newfiles) {
- oldfiles[ta.id].items.add(file);
- }
- input.files = oldfiles[ta.id].files;
- }
- else {
- input.files = newfiles;
+ if (!oldfiles[ta.id]) {
oldfiles[ta.id] = new DataTransfer();
}
+
+ for (const file of newfiles) {
+ oldfiles[ta.id].items.add(file);
+ }
+ input.files = oldfiles[ta.id].files;
+
if (input.files.length > 20)
{
window.alert("You can't upload more than 20 files at one time!")
@@ -482,11 +511,10 @@ function handle_files(input, newfiles) {
return
}
- if (location.pathname != '/chat' && location.pathname != '/old_chat') {
- for (const file of newfiles) {
- insertText(ta, `[${file.name}]`);
- }
+ for (const file of newfiles) {
+ insertText(ta, `[${file.name}]`);
}
+ markdown(ta)
autoExpand(ta)
@@ -655,7 +683,6 @@ function updateSubscriptionOnServer(subscription, apiEndpoint) {
const xhr = createXhrWithFormKey(
apiEndpoint,
- 'POST',
formData
);
diff --git a/files/assets/js/delete_post_modal.js b/files/assets/js/delete_post_modal.js
index 94977b4ba..5da6b6add 100644
--- a/files/assets/js/delete_post_modal.js
+++ b/files/assets/js/delete_post_modal.js
@@ -1,23 +1,27 @@
-function delete_postModal(t, id) {
- document.getElementById("deletePostButton").addEventListener('click', function() {
- postToast(t, `/delete_post/${id}`,
- {
- },
- () => {
- if (location.pathname == '/admin/reported/posts')
- {
- document.getElementById("reports-"+id).remove()
- document.getElementById("post-"+id).remove()
- }
- else
- {
- document.getElementById(`post-${id}`).classList.add('deleted');
- document.getElementById(`delete-${id}`).classList.add('d-none');
- document.getElementById(`undelete-${id}`).classList.remove('d-none');
- document.getElementById(`delete2-${id}`).classList.add('d-none');
- document.getElementById(`undelete2-${id}`).classList.remove('d-none');
- }
- }
- );
- });
+const deletePostButton = document.getElementById("deletePostButton");
+
+function delete_postModal(id) {
+ deletePostButton.dataset.id = id
}
+
+deletePostButton.onclick = () => {
+ const id = deletePostButton.dataset.id
+ postToast(deletePostButton, `/delete/post/${id}`,
+ {},
+ () => {
+ if (location.pathname == '/admin/reported/posts')
+ {
+ document.getElementById("reports-"+id).remove()
+ document.getElementById("post-"+id).remove()
+ }
+ else
+ {
+ document.getElementById(`post-${id}`).classList.add('deleted');
+ document.getElementById(`delete-${id}`).classList.add('d-none');
+ document.getElementById(`undelete-${id}`).classList.remove('d-none');
+ document.getElementById(`delete2-${id}`).classList.add('d-none');
+ document.getElementById(`undelete2-${id}`).classList.remove('d-none');
+ }
+ }
+ );
+};
diff --git a/files/assets/js/edit_post.js b/files/assets/js/edit_post.js
index 31f4f4f47..bc61cab13 100644
--- a/files/assets/js/edit_post.js
+++ b/files/assets/js/edit_post.js
@@ -1,16 +1,16 @@
function togglePostEdit(id){
- body=document.getElementById("post-body");
- title=document.getElementById("post-title");
- form=document.getElementById("edit-post-body-"+id);
+ body = document.getElementById("post-body");
+ title = document.getElementById("post-title");
+ form = document.getElementById("edit-post-body-"+id);
body.classList.toggle("d-none");
title.classList.toggle("d-none");
form.classList.toggle("d-none");
- box=document.getElementById("post-edit-box-"+id);
+ box = document.getElementById("post-edit-box-"+id);
autoExpand(box);
markdown(box);
- box=document.getElementById("post-edit-title");
+ box = document.getElementById("post-edit-title");
autoExpand(box);
close_inline_speed_emoji_modal();
diff --git a/files/assets/js/emoji_modal.js b/files/assets/js/emoji_modal.js
index 82c9269b8..a230e05c9 100644
--- a/files/assets/js/emoji_modal.js
+++ b/files/assets/js/emoji_modal.js
@@ -226,7 +226,7 @@ function fetchEmojis() {
classSelectorLinkDOM.classList.add("nav-link", "emojitab");
classSelectorLinkDOM.dataset.bsToggle = "tab";
classSelectorLinkDOM.dataset.className = className;
- classSelectorLinkDOM.innerText = className;
+ classSelectorLinkDOM.textContent = className;
classSelectorLinkDOM.addEventListener('click', switchEmojiTab);
classSelectorDOM.appendChild(classSelectorLinkDOM);
@@ -278,6 +278,8 @@ function switchEmojiTab(e)
for(const emojiDOM of Object.values(emojiDOMs))
emojiDOM.hidden = emojiDOM.dataset.className !== className;
+
+ document.getElementById('emoji-container').scrollTop = 0;
}
for (const emojitab of document.getElementsByClassName('emojitab')) {
@@ -333,7 +335,7 @@ function update_ghost_div_textarea(text)
let ghostdiv = text.parentNode.querySelector(".ghostdiv");
if (!ghostdiv) return;
- ghostdiv.innerText = text.value.substring(0, text.selectionStart);
+ ghostdiv.textContent = text.value.substring(0, text.selectionStart);
ghostdiv.insertAdjacentHTML('beforeend', "");
@@ -386,7 +388,7 @@ function populate_speed_emoji_modal(results, textbox)
emoji_index = 0;
speed_carot_modal.innerHTML = "";
- const MAXXX = 25;
+ const MAXXX = 50;
// Not sure why the results is a Set... but oh well
let i = 0;
for (let emoji of results)
@@ -411,7 +413,7 @@ function populate_speed_emoji_modal(results, textbox)
if (emoji.count !== undefined)
emoji_option_text.title += "\nused\t" + emoji.count;
- emoji_option_text.innerText = name;
+ emoji_option_text.textContent = name;
if (current_word.includes("#")) name = `#${name}`
if (current_word.includes("!")) name = `!${name}`
@@ -420,7 +422,7 @@ function populate_speed_emoji_modal(results, textbox)
close_inline_speed_emoji_modal()
textbox.value = textbox.value.replace(new RegExp(current_word+"(?=\\s|$)", "gi"), `:${name}: `)
textbox.focus()
- if (document.location.pathname != '/chat' && document.location.pathname != '/old_chat'){
+ if (location.pathname != '/chat'){
markdown(textbox)
}
});
@@ -511,16 +513,17 @@ function speed_carot_navigate(event)
}
}
-// Let's get it running now
-let forms = document.querySelectorAll("textarea, .allow-emojis");
-forms.forEach(i => {
- let pseudo_div = document.createElement("div");
- pseudo_div.className = "ghostdiv";
- pseudo_div.style.display = "none";
- i.after(pseudo_div);
- i.addEventListener('input', update_speed_emoji_modal, false);
- i.addEventListener('keydown', speed_carot_navigate, false);
-});
+function insertGhostDivs(element) {
+ let forms = element.querySelectorAll("textarea, .allow-emojis");
+ forms.forEach(i => {
+ let pseudo_div = document.createElement("div");
+ pseudo_div.className = "ghostdiv";
+ pseudo_div.style.display = "none";
+ i.after(pseudo_div);
+ i.addEventListener('input', update_speed_emoji_modal, false);
+ i.addEventListener('keydown', speed_carot_navigate, false);
+ });
+}
function loadEmojis(inputTargetIDName)
{
diff --git a/files/assets/js/followers.js b/files/assets/js/followers.js
index dd63a9201..84084ff8f 100644
--- a/files/assets/js/followers.js
+++ b/files/assets/js/followers.js
@@ -1,4 +1,8 @@
function removeFollower(t, username) {
- postToastSwitch(t,'/remove_follow/' + username);
- t.parentElement.parentElement.remove();
+ postToast(
+ t,
+ `/remove_follow/${username}`,
+ {},
+ () => {t.parentElement.parentElement.remove()}
+ );
}
diff --git a/files/assets/js/following.js b/files/assets/js/following.js
index b6b1d2cf1..a4bb12dbc 100644
--- a/files/assets/js/following.js
+++ b/files/assets/js/following.js
@@ -1,4 +1,8 @@
function removeFollowing(t, username) {
- postToastSwitch(t,'/unfollow/' + username);
- t.parentElement.parentElement.remove();
+ postToast(
+ t,
+ `/unfollow/${username}`,
+ {},
+ () => {t.parentElement.parentElement.remove()}
+ );
}
diff --git a/files/assets/js/lottery.js b/files/assets/js/lottery.js
index 908c54923..d3086a509 100644
--- a/files/assets/js/lottery.js
+++ b/files/assets/js/lottery.js
@@ -30,8 +30,8 @@ const lotteryOnReady = function () {
ticketPurchaseQuantityInput.addEventListener("change", (event) => {
const value = Math.max(1, parseInt(event.target.value))
purchaseQuantity = value
- purchaseQuantityField.innerText = value
- purchaseTotalCostField.innerText = value * 12
+ purchaseQuantityField.textContent = value
+ purchaseTotalCostField.textContent = value * 12
});
};
@@ -53,7 +53,7 @@ function handleLotteryRequest(uri, method, callback = () => {}) {
const form = new FormData();
form.append("formkey", formkey());
form.append("quantity", purchaseQuantity);
- const xhr = createXhrWithFormKey(`/lottery/${uri}`, method, form);
+ const xhr = createXhrWithFormKey(`/lottery/${uri}`, form, method);
xhr[0].onload = handleLotteryResponse.bind(null, xhr[0], method, callback);
xhr[0].send(xhr[1]);
}
@@ -76,7 +76,7 @@ function handleLotteryResponse(xhr, method, callback) {
const toast = document.getElementById("lottery-post-success");
const toastMessage = document.getElementById("lottery-post-success-text");
- toastMessage.innerText = response.message;
+ toastMessage.textContent = response.message;
bootstrap.Toast.getOrCreateInstance(toast).show();
@@ -86,7 +86,7 @@ function handleLotteryResponse(xhr, method, callback) {
const toast = document.getElementById("lottery-post-error");
const toastMessage = document.getElementById("lottery-post-error-text");
- toastMessage.innerText =
+ toastMessage.textContent =
(response && response.details) || "Error, please try again later.";
bootstrap.Toast.getOrCreateInstance(toast).show();
diff --git a/files/assets/js/markdown.js b/files/assets/js/markdown.js
index 29bd2e0aa..9247342e6 100644
--- a/files/assets/js/markdown.js
+++ b/files/assets/js/markdown.js
@@ -111,12 +111,12 @@ const findAllEmoteEndings = (word) => {
continue;
}
- if(currWord.endsWith('heart')) {
+ if(currWord.endsWith('love')) {
if(currEndings.indexOf(MODIFIERS.LOVE) !== -1) {
hasReachedNonModifer = true;
continue;
}
- currWord = currWord.slice(0, -5);
+ currWord = currWord.slice(0, -4);
currEndings.push(MODIFIERS.LOVE);
continue;
}
@@ -269,7 +269,7 @@ function charLimit(form, text) {
text.style.color = "#A0AEC0";
}
- text.innerText = length + ' / ' + maxLength;
+ text.textContent = length + ' / ' + maxLength;
}
function remove_dialog() {
diff --git a/files/assets/js/new_comments.js b/files/assets/js/new_comments.js
index 95948436c..0deb2a17d 100644
--- a/files/assets/js/new_comments.js
+++ b/files/assets/js/new_comments.js
@@ -8,6 +8,8 @@ for (let twoattrs of document.getElementsByClassName("twoattrs")) {
pcc = twoattrs[1]
const lastCount = comments[pid]
if (lastCount) {
+ const title = document.getElementById(`${pid}-title`)
+ if (title) title.classList.add('visited')
const newComments = pcc - lastCount.c
if (newComments > 0) {
const elems = document.getElementsByClassName(`${pid}-new-comments`)
diff --git a/files/assets/js/orgy_file.js b/files/assets/js/orgy_file.js
new file mode 100644
index 000000000..952fcacb1
--- /dev/null
+++ b/files/assets/js/orgy_file.js
@@ -0,0 +1,40 @@
+const orgy_file = document.getElementById('orgy-file');
+const break_file = document.getElementById('break-file');
+
+addEventListener("load", () => {
+ orgy_file.play()
+});
+document.addEventListener('click', () => {
+ if (orgy_file.paused) orgy_file.play();
+}, {once : true});
+
+function add_playing_listener() {
+ orgy_file.addEventListener('playing', () => {
+ const now = Date.now() / 1000;
+ const created_utc = orgy_file.dataset.created_utc
+ orgy_file.currentTime = now - created_utc
+ }, {once : true});
+}
+
+add_playing_listener()
+
+orgy_file.addEventListener('pause', () => {
+ add_playing_listener()
+})
+
+orgy_file.addEventListener("timeupdate", function(){
+ if (break_file.dataset.run == "0" && parseInt(orgy_file.currentTime) == 3000) {
+ break_file.dataset.run = "1"
+ orgy_file.pause();
+ orgy_file.classList.add('d-none');
+ break_file.classList.remove('d-none');
+ break_file.play()
+ setTimeout(function () {
+ break_file.pause()
+ break_file.classList.add('d-none');
+ orgy_file.classList.remove('d-none');
+ orgy_file.dataset.created_utc = parseInt(orgy_file.dataset.created_utc) + 303
+ orgy_file.play()
+ }, 300000);
+ }
+});
diff --git a/files/assets/js/search.js b/files/assets/js/search.js
index 641ce5821..9fd92ef75 100644
--- a/files/assets/js/search.js
+++ b/files/assets/js/search.js
@@ -1,5 +1,5 @@
function addParam(t, bool) {
- let text = t.innerText;
+ let text = t.textContent;
if (bool)
text = text + ' '
else
diff --git a/files/assets/js/submit.js b/files/assets/js/submit.js
index 2c7585ab1..298500c17 100644
--- a/files/assets/js/submit.js
+++ b/files/assets/js/submit.js
@@ -178,8 +178,9 @@ function submit(form) {
xhr.onload = function() {
upload_prog.classList.add("d-none")
+ const success = xhr.status >= 200 && xhr.status < 300
- if (xhr.status >= 200 && xhr.status < 300) {
+ if (success) {
const res = JSON.parse(xhr.response)
const post_id = res['post_id'];
@@ -200,16 +201,8 @@ function submit(form) {
location.href = "/post/" + post_id
} else {
submitButton.disabled = false;
- document.getElementById('toast-post-error-text').innerText = "Error, please try again later."
- try {
- let data=JSON.parse(xhr.response);
- bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-error')).show();
- document.getElementById('toast-post-error-text').innerText = data["error"];
- if (data && data["details"]) document.getElementById('toast-post-error-text').innerText = data["details"];
- } catch(e) {
- bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-success')).hide();
- bootstrap.Toast.getOrCreateInstance(document.getElementById('toast-post-error')).show();
- }
+ const data = JSON.parse(xhr.response);
+ showToast(success, getMessageFromJsonData(success, data));
}
};
diff --git a/files/assets/js/userpage_v.js b/files/assets/js/userpage_v.js
index b2b522e9c..8a9228c70 100644
--- a/files/assets/js/userpage_v.js
+++ b/files/assets/js/userpage_v.js
@@ -14,13 +14,13 @@ let TRANSFER_TAX = document.getElementById('tax').innerHTML
function updateTax(mobile=false) {
let suf = mobile ? "-mobile" : "";
let amount = parseInt(document.getElementById("coin-transfer-amount" + suf).value);
- if (amount > 0) document.getElementById("coins-transfer-taxed" + suf).innerText = amount - Math.ceil(amount*TRANSFER_TAX);
+ if (amount > 0) document.getElementById("coins-transfer-taxed" + suf).textContent = amount - Math.ceil(amount*TRANSFER_TAX);
}
function updateBux(mobile=false) {
let suf = mobile ? "-mobile" : "";
let amount = parseInt(document.getElementById("bux-transfer-amount" + suf).value);
- if (amount > 0) document.getElementById("bux-transfer-taxed" + suf).innerText = amount;
+ if (amount > 0) document.getElementById("bux-transfer-taxed" + suf).textContent = amount;
}
function transferCoins(t, mobile=false) {
@@ -39,9 +39,9 @@ function transferCoins(t, mobile=false) {
"reason": document.getElementById(mobile ? "coin-transfer-reason-mobile" : "coin-transfer-reason").value
},
() => {
- document.getElementById("user-coins-amount").innerText = parseInt(document.getElementById("user-coins-amount").innerText) - amount;
- document.getElementById("profile-coins-amount-mobile").innerText = parseInt(document.getElementById("profile-coins-amount-mobile").innerText) + transferred;
- document.getElementById("profile-coins-amount").innerText = parseInt(document.getElementById("profile-coins-amount").innerText) + transferred;
+ document.getElementById("user-coins-amount").textContent = parseInt(document.getElementById("user-coins-amount").textContent) - amount;
+ document.getElementById("profile-coins-amount-mobile").textContent = parseInt(document.getElementById("profile-coins-amount-mobile").textContent) + transferred;
+ document.getElementById("profile-coins-amount").textContent = parseInt(document.getElementById("profile-coins-amount").textContent) + transferred;
}
);
}
@@ -60,9 +60,9 @@ function transferBux(t, mobile=false) {
"reason": document.getElementById(mobile ? "bux-transfer-reason-mobile" : "bux-transfer-reason").value
},
() => {
- document.getElementById("user-bux-amount").innerText = parseInt(document.getElementById("user-bux-amount").innerText) - amount;
- document.getElementById("profile-bux-amount-mobile").innerText = parseInt(document.getElementById("profile-bux-amount-mobile").innerText) + amount;
- document.getElementById("profile-bux-amount").innerText = parseInt(document.getElementById("profile-bux-amount").innerText) + amount;
+ document.getElementById("user-bux-amount").textContent = parseInt(document.getElementById("user-bux-amount").textContent) - amount;
+ document.getElementById("profile-bux-amount-mobile").textContent = parseInt(document.getElementById("profile-bux-amount-mobile").textContent) + amount;
+ document.getElementById("profile-bux-amount").textContent = parseInt(document.getElementById("profile-bux-amount").textContent) + amount;
}
);
}
diff --git a/files/classes/badges.py b/files/classes/badges.py
index da02b8eb6..aafe0a1c0 100644
--- a/files/classes/badges.py
+++ b/files/classes/badges.py
@@ -68,6 +68,7 @@ class Badge(Base):
if self.badge_id == 171: return self.user.rainbow
if self.badge_id == 281: return self.user.namechanged
if self.badge_id == 285: return self.user.queen
+ if self.badge_id == 289: return self.user.sharpen
return None
diff --git a/files/classes/comment.py b/files/classes/comment.py
index 215186761..3ea2fe799 100644
--- a/files/classes/comment.py
+++ b/files/classes/comment.py
@@ -23,13 +23,16 @@ def normalize_urls_runtime(body, v):
if v.reddit != 'old.reddit.com':
body = reddit_to_vreddit_regex.sub(rf'\1https://{v.reddit}/\2/', body)
if v.nitter:
- body = twitter_to_nitter_regex.sub(r'\1https://nitter.lacontrevoie.fr/', body)
+ body = twitter_to_nitter_regex.sub(r'\1https://nitter.net/', body)
if v.imginn:
body = body.replace('https://instagram.com/', 'https://imginn.com/')
return body
def add_options(self, body, v):
+ if 'details>' in body or 'summary>' in body:
+ return body
+
if isinstance(self, Comment):
kind = 'comment'
else:
@@ -37,9 +40,9 @@ def add_options(self, body, v):
if self.options:
curr = [x for x in self.options if x.exclusive and x.voted(v)]
- if curr: curr = f" value={kind}-" + str(curr[0].id)
+ if curr: curr = f" value=option-{kind}-" + str(curr[0].id)
else: curr = ''
- body += f''
+ body += f''
winner = [x for x in self.options if x.exclusive == 3]
for o in self.options:
@@ -66,7 +69,7 @@ def add_options(self, body, v):
option_body += ""
else:
input_type = 'radio' if o.exclusive else 'checkbox'
- option_body += f'
' in text or '' in text:
+ return text
+
new_text = ''
for x in text.split(' '):
new_text += f'{x} '
diff --git a/files/helpers/media.py b/files/helpers/media.py
index 8d24589d0..c23da7765 100644
--- a/files/helpers/media.py
+++ b/files/helpers/media.py
@@ -81,7 +81,7 @@ def process_audio(file, v):
size = os.stat(old).st_size
if size > MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024 or not v.patron and size > MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024:
os.remove(old)
- abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for {patron.lower()}s)")
+ abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for {patron}s)")
extension = guess_extension(file.content_type)
if not extension:
@@ -136,11 +136,10 @@ def process_video(file, v):
old = f'/videos/{time.time()}'.replace('.','')
file.save(old)
- if SITE_NAME != 'WPD':
- size = os.stat(old).st_size
- if size > MAX_VIDEO_SIZE_MB_PATRON * 1024 * 1024 or (not v.patron and size > MAX_VIDEO_SIZE_MB * 1024 * 1024):
- os.remove(old)
- abort(413, f"Max video size is {MAX_VIDEO_SIZE_MB} MB ({MAX_VIDEO_SIZE_MB_PATRON} MB for {patron}s)")
+ size = os.stat(old).st_size
+ if size > MAX_VIDEO_SIZE_MB_PATRON * 1024 * 1024 or (not v.patron and size > MAX_VIDEO_SIZE_MB * 1024 * 1024):
+ os.remove(old)
+ abort(413, f"Max video size is {MAX_VIDEO_SIZE_MB} MB ({MAX_VIDEO_SIZE_MB_PATRON} MB for {patron}s)")
extension = guess_extension(file.content_type)
if not extension:
@@ -180,18 +179,20 @@ def process_image(filename, v, resize=0, trim=False, uploader_id=None, db=None):
# thumbnails are processed in a thread and not in the request context
# if an image is too large or webp conversion fails, it'll crash
# to avoid this, we'll simply return None instead
+ original_resize = resize
has_request = has_request_context()
size = os.stat(filename).st_size
- is_patron = bool(v and v.patron)
-
- if size > MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024 or not is_patron and size > MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024:
- os.remove(filename)
- if has_request:
- abort(413, f"Max image/audio size is {MAX_IMAGE_AUDIO_SIZE_MB} MB ({MAX_IMAGE_AUDIO_SIZE_MB_PATRON} MB for {patron}s)")
- return None
+ if v and v.patron:
+ max_size = MAX_IMAGE_AUDIO_SIZE_MB_PATRON * 1024 * 1024
+ else:
+ max_size = MAX_IMAGE_AUDIO_SIZE_MB * 1024 * 1024
try:
with Image.open(filename) as i:
+ if not resize and size > max_size:
+ ratio = max_size / size
+ resize = i.width * ratio
+
oldformat = i.format
params = ["magick"]
if resize == 99: params.append(f"{filename}[0]")
@@ -203,7 +204,7 @@ def process_image(filename, v, resize=0, trim=False, uploader_id=None, db=None):
params.extend(["-resize", f"{resize}>"])
except:
os.remove(filename)
- if has_request:
+ if has_request and not filename.startswith('/chat_images/'):
abort(415)
return None
@@ -219,7 +220,7 @@ def process_image(filename, v, resize=0, trim=False, uploader_id=None, db=None):
size_after_conversion = os.stat(filename).st_size
- if resize:
+ if original_resize:
if size_after_conversion > MAX_IMAGE_SIZE_BANNER_RESIZED_MB * 1024 * 1024:
os.remove(filename)
if has_request:
@@ -234,7 +235,6 @@ def process_image(filename, v, resize=0, trim=False, uploader_id=None, db=None):
hashes = {}
for img in os.listdir(path):
- if resize == 400 and img in {'256.webp','585.webp'}: continue
img_path = f'{path}/{img}'
if img_path == filename: continue
diff --git a/files/helpers/offsitementions.py b/files/helpers/offsitementions.py
index 33c4d0111..4fc95117e 100644
--- a/files/helpers/offsitementions.py
+++ b/files/helpers/offsitementions.py
@@ -123,4 +123,4 @@ def notify_mentions(mentions, send_to=None, mention_str='site mention'):
notif = Notification(comment_id=new_comment.id, user_id=send_to)
g.db.add(notif)
- push_notif({send_to}, f'New mention of you on reddit by /u/{author}', '', f'{SITE_FULL}/comment/{new_comment.id}?read=true#context')
+ push_notif({send_to}, f'New mention of you on reddit by /u/{author}', '', f'{SITE_FULL}/notification/{new_comment.id}')
diff --git a/files/helpers/owoify.py b/files/helpers/owoify.py
index 3693258fe..665b44c15 100644
--- a/files/helpers/owoify.py
+++ b/files/helpers/owoify.py
@@ -28,6 +28,9 @@ OWO_EXCLUDE_PATTERNS = [
]
def owoify(source):
+ if '`' in source or '' in source or '' in source:
+ return source
+
word_matches = OWO_WORD_REGEX.findall(source)
space_matches = OWO_SPACE_REGEX.findall(source)
diff --git a/files/helpers/regex.py b/files/helpers/regex.py
index 2c18a8a4c..75b367fb9 100644
--- a/files/helpers/regex.py
+++ b/files/helpers/regex.py
@@ -9,8 +9,8 @@ NOT_IN_CODE_OR_LINKS = '(?!([^<]*<\/(code|pre|a)>|[^`\n]*`|(.|\n)*```))'
valid_username_regex = re.compile("^[\w\-]{3,25}$", flags=re.A)
valid_username_patron_regex = re.compile("^[\w\-]{1,25}$", flags=re.A)
-mention_regex = re.compile('(?)!(everyone)' + NOT_IN_CODE_OR_LINKS, flags=re.A)
@@ -40,7 +40,7 @@ html_comment_regex = re.compile("", flags=re.A)
title_regex = re.compile("[^\w ]", flags=re.A)
-controversial_regex = re.compile('["> ](https:\/\/old\.reddit\.com/r/\w{3,20}\/comments\/[\w\-.#&/=\?@%+]{5,250})["< ]', flags=re.A)
+controversial_regex = re.compile('["> ](https:\/\/old\.reddit\.com/r/\w{2,20}\/comments\/[\w\-.#&/=\?@%+]{5,250})["< ]', flags=re.A)
spoiler_regex = re.compile('\|\|(.+?)\|\|' + NOT_IN_CODE_OR_LINKS, flags=re.A)
reddit_regex = re.compile('(?|")~{1,2}([^~]+)~{1,2}' + NOT_IN_CODE_OR_LINKS, flags=re.A)
-mute_regex = re.compile("\/mute @?([\w\-]{3,30}) ([0-9]+)", flags=re.A|re.I)
+mute_regex = re.compile("\/mute @?([\w\-]{1,30}) ([0-9]+)", flags=re.A|re.I)
emoji_regex = re.compile(f"\s*(:[!#@\w\-]{{1,72}}:\s*)+<\/p>", flags=re.A)
emoji_regex2 = re.compile(f'(?|[^`]*`))', flags=re.A)
@@ -56,7 +56,7 @@ emoji_regex2 = re.compile(f'(?|
snappy_url_regex = re.compile('(.+?)<\/a>', flags=re.A)
snappy_youtube_regex = re.compile(']*>|{slur_single_words}", flags=re.I|re.A)
slur_regex_upper = re.compile(f"<[^>]*>|{slur_single_words.upper()}", flags=re.A)
@@ -97,11 +97,11 @@ xmaxing_regex = re.compile('(?<=^|(?<=\s))(([a-zA-Z]+?)(s)?max+ing)(?=$|\n|\s|[.
initial_part_regex = re.compile('(?<=^)(>+)', flags=re.I|re.A)
#matches "the" or is, but only if it is not followed by "fucking". https://regex101.com/r/yxuYsQ/2
-the_fucking_regex = re.compile('(?<=^|(?<=\s))((?:the|a)( (?:only))?|((that )?(?:is|are|was|were|will be|would be)( (?:your|her|his|their|no|a|not|to|too|so|this|the|our|what))?( (a|the))?)|is)(?=\s)(?! fucking)', flags=re.I|re.A)
+the_fucking_regex = re.compile('(?<=^|(?<=\s))((?:the|a)( (?:only))?|((that )?(?:is|are|was|were|will be|would be)( (?:your|her|his|their|no|a|not|to|too|so|this|the|our|what))?( (a|the))?)|is)(?=\s)(?! fucking)' + NOT_IN_CODE_OR_LINKS, flags=re.I|re.A)
#matches a single question mark but only if it isn't preceded by ", bitch"
-bitch_question_mark_regex = re.compile('(?' + NOT_IN_CODE_OR_LINKS, flags=re.I|re.A)
+youtube_regex = re.compile('|")https:\/\/old.reddit.com\/(r|u)\/', flags=re.A)
twitter_to_nitter_regex = re.compile('(^|>|")https:\/\/twitter.com\/(?!i\/)', flags=re.A)
-reddit_domain_regex = re.compile("(^|\s|\()https?:\/\/(reddit\.com|(?:(?:[A-z]{2})(?:-[A-z]{2})" "?|beta|i|m|pay|ssl|www|new|alpha)\.reddit\.com|libredd\.it|reddit\.lol)\/(r|u|comments)\/", flags=re.A)
+reddit_domain_regex = re.compile("(^|\s|\()https?:\/\/(reddit\.com|(?:(?:[A-z]{2})(?:-[A-z]{2})" "?|beta|i|m|pay|ssl|www|new|alpha)\.reddit\.com|libredd\.it|reddit\.lol)\/(u|(r\/(\w|-){2,25}\/)?comments)\/", flags=re.A)
color_regex = re.compile("[a-f0-9]{6}", flags=re.A)
@@ -226,6 +226,8 @@ reason_regex_comment = re.compile('(/comment/[0-9]+)', flags=re.A)
numbered_list_regex = re.compile('((\s|^)[0-9]+)\. ', flags=re.A)
-image_link_regex = re.compile(f"https://(i\.)?{SITE}\/(chat_)?images\/[0-9]{{11,17}}r?\.webp", flags=re.A)
+image_link_regex = re.compile(f"https:\/\/(i\.)?{SITE}\/(chat_)?images\/[0-9]{{11,17}}r?\.webp", flags=re.A)
video_link_regex = re.compile(f"https://(videos\.)?{SITE}\/(videos\/)?[0-9a-zA-Z._-]{{4,66}}\.({video_regex_extensions})", flags=re.A)
+
+asset_image_link_regex = re.compile(f"https:\/\/(i\.)?{SITE}\/assets\/images\/[\w\/]+.webp(\?x=\d+)?", flags=re.A)
diff --git a/files/helpers/sanitize.py b/files/helpers/sanitize.py
index 16d9365a3..305effc44 100644
--- a/files/helpers/sanitize.py
+++ b/files/helpers/sanitize.py
@@ -47,7 +47,7 @@ TLDS = ( # Original gTLDs and ccTLDs
'moe','mom','monster','new','news','online','pics','press','pub','site','blog',
'vip','win','world','wtf','xyz','video','host','art','media','wiki','tech',
'cooking','network','party','goog','markets','today','beauty','camp','top',
- 'red','city','quest','works'
+ 'red','city','quest','works','soy',
)
allowed_tags = ('a','audio','b','big','blockquote','br','center','code','del','details','em','g','h1','h2','h3','h4','h5','h6','hr','i','img','li','lite-youtube','marquee','ol','p','pre','rp','rt','ruby','small','span','spoiler','strike','strong','sub','summary','sup','table','tbody','td','th','thead','tr','u','ul','video')
@@ -78,6 +78,7 @@ def allowed_attributes(tag, name, value):
if name in {'g','b','glow','party'} and not value: return True
if name in {'alt','title'}: return True
if name == 'class' and value == 'img': return True
+ if name == 'data-user-submitted' and not value: return True
if tag == 'lite-youtube':
if name == 'params' and value.startswith('autoplay=1&modestbranding=1'): return True
@@ -94,13 +95,14 @@ def allowed_attributes(tag, name, value):
if name == 'preload' and value == 'none': return True
if tag == 'p':
- if name == 'class' and value in {'mb-0','resizable','text-center'}: return True
+ if name == 'class' and value in {'mb-0','resizable','yt','text-center'}: return True
if tag == 'span':
if name == 'data-bs-toggle' and value == 'tooltip': return True
if name == 'title': return True
if name == 'alt': return True
if name == 'cide' and not value: return True
+ if name == 'bounce' and not value: return True
if tag == 'table':
if name == 'class' and value == 'table': return True
@@ -215,46 +217,48 @@ def execute_blackjack(v, target, body, kind):
return True
def find_all_emote_endings(word):
- endings = list()
- curr_word = word
+ endings = []
+
+ if path.isfile(f'files/assets/images/emojis/{word}.webp'):
+ return endings, word
is_non_ending_found = False
while not is_non_ending_found:
- if curr_word.endswith('pat'):
+ if word.endswith('pat'):
if 'pat' in endings:
is_non_ending_found = True
continue
endings.append('pat')
- curr_word = curr_word[:-3]
+ word = word[:-3]
continue
- if curr_word.endswith('talking'):
+ if word.endswith('talking'):
if 'talking' in endings:
is_non_ending_found = True
continue
endings.append('talking')
- curr_word = curr_word[:-7]
+ word = word[:-7]
continue
- if curr_word.endswith('genocide'):
+ if word.endswith('genocide'):
if 'genocide' in endings:
is_non_ending_found = True
continue
endings.append('genocide')
- curr_word = curr_word[:-8]
+ word = word[:-8]
continue
- if curr_word.endswith('heart'):
- if 'heart' in endings:
+ if word.endswith('love'):
+ if 'love' in endings:
is_non_ending_found = True
continue
- endings.append('heart')
- curr_word = curr_word[:-5]
+ endings.append('love')
+ word = word[:-4]
continue
is_non_ending_found = True
- return endings, curr_word
+ return endings, word
def render_emoji(html, regexp, golden, emojis_used, b=False, is_title=False):
@@ -295,7 +299,7 @@ def render_emoji(html, regexp, golden, emojis_used, b=False, is_title=False):
is_talking = 'talking' in ending_modifiers
is_patted = 'pat' in ending_modifiers
is_talking_first = ending_modifiers.index('pat') > ending_modifiers.index('talking') if is_talking and is_patted else False
- is_loved = 'heart' in ending_modifiers
+ is_loved = 'love' in ending_modifiers
is_genocided = 'genocide' in ending_modifiers
is_user = emoji.startswith('@')
@@ -358,7 +362,7 @@ def with_sigalrm_timeout(timeout):
def remove_cuniform(sanitized):
if not sanitized: return ""
- sanitized = sanitized.replace('\u200e','').replace('\u200b','').replace('\u202e','').replace("\ufeff", "")
+ sanitized = sanitized.replace('\u200e','').replace('\u200b','').replace('\u202e','').replace("\ufeff", "").replace("\u033f","").replace("\u0589", ":")
sanitized = sanitized.replace("𒐪","").replace("𒐫","").replace("﷽","")
sanitized = sanitized.replace("\r\n", "\n")
sanitized = sanitized.replace("’", "'")
@@ -384,6 +388,7 @@ def get_youtube_id_and_t(url):
return (id, t)
def handle_youtube_links(url):
+ url = url.replace('&','&')
params = parse_qs(urlparse(url).query, keep_blank_values=True)
html = None
id, t = get_youtube_id_and_t(url)
@@ -417,15 +422,6 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=False, count_emojis
if not sanitized: return ''
- if FEATURES['PING_GROUPS']:
- ping_group_count = len(list(group_mention_regex.finditer(sanitized)))
- if ping_group_count > 5:
- error("You can only ping a maximum of 5 ping groups!")
-
- if "style" in sanitized and "filter" in sanitized:
- if sanitized.count("blur(") + sanitized.count("drop-shadow(") > 5:
- error("Too many filters!")
-
if blackjack and execute_blackjack(g.v, None, sanitized, blackjack):
sanitized = 'g'
@@ -450,9 +446,6 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=False, count_emojis
sanitized = sanitized.replace('/\1', sanitized)
sanitized = sub_regex.sub(r'/\1', sanitized)
@@ -460,7 +453,7 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=False, count_emojis
names = set(m.group(1) for m in mention_regex.finditer(sanitized))
- if limit_pings and len(names) > limit_pings and not v.admin_level >= PERMS['POST_COMMENT_INFINITE_PINGS']:
+ if limit_pings and len(names) > limit_pings and v.admin_level < PERMS['POST_COMMENT_INFINITE_PINGS']:
error("Max ping limit is 5 for comments and 50 for posts!")
users_list = get_users(names, graceful=True)
@@ -527,6 +520,7 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=False, count_emojis
a.append(tag)
tag["data-src"] = tag["data-src"]
+ tag["data-user-submitted"] = ""
sanitized = str(soup).replace('','').replace('','').replace('/>','>')
@@ -656,6 +650,8 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=False, count_emojis
html = handle_youtube_links(i.group(1))
if html:
+ if not chat:
+ html = f'{html}
'
sanitized = sanitized.replace(i.group(0), html)
if '' not in sanitized and blackjack != "rules":
@@ -674,6 +670,10 @@ def sanitize(sanitized, golden=True, limit_pings=0, showmore=False, count_emojis
if pos >= 0:
sanitized = (sanitized[:pos] + showmore_regex.sub(r'\1\2 ', sanitized[pos:], count=1))
+ if "style" in sanitized and "filter" in sanitized:
+ if sanitized.count("blur(") + sanitized.count("drop-shadow(") > 5:
+ error("Too many filters!")
+
return sanitized.strip()
def allowed_attributes_emojis(tag, name, value):
@@ -717,15 +717,17 @@ def filter_emojis_only(title, golden=True, count_emojis=False, graceful=False):
if len(title) > POST_TITLE_HTML_LENGTH_LIMIT and not graceful:
abort(400)
- else:
- return title.strip()
+
+ title = title.strip()
+
+ return title
def is_whitelisted(domain, k):
if domain.endswith('pullpush.io'):
return True
if 'sort' in k.lower() or 'query' in k.lower():
return True
- if k in {'_x_tr_hl','_x_tr_pto','_x_tr_sl','_x_tr_tl','abstract_id','after','article','bill_id','comments','context','count','f','fbid','format','forum_id','i','ID','id','lb','list','oldid','p','page','post_id','postid','q','run','scrollToComments','search','sl','sp','story_fbid','tab','term','text','thread_id','threadid','ticket_form_id','time_continue','title','title_no','tl','token','topic','type','u','udca','url','v','vid','viewkey'}:
+ if k in {'_x_tr_hl','_x_tr_pto','_x_tr_sl','_x_tr_tl','abstract_id','after','article','bill_id','c','clip','comments','context','count','f','fbid','format','forum_id','i','ID','id','lb','list','oldid','p','page','post_id','postid','q','run','scrollToComments','search','sl','sp','story_fbid','tab','term','text','thread_id','threadid','ticket_form_id','time_continue','title','title_no','tl','token','topic','type','tz1','tz2','u','udca','url','v','vid','viewkey'}:
return True
if k == 't' and domain != 'twitter.com':
return True
@@ -746,21 +748,32 @@ def normalize_url(url):
.replace("https://youtube.com/shorts/", "https://youtube.com/watch?v=") \
.replace("https://youtube.com/v/", "https://youtube.com/watch?v=") \
.replace("https://mobile.twitter.com", "https://twitter.com") \
- .replace("https://m.facebook.com", "https://facebook.com") \
- .replace("https://m.wikipedia.org", "https://wikipedia.org") \
+ .replace("https://x.com", "https://twitter.com") \
.replace("https://www.twitter.com", "https://twitter.com") \
+ .replace("https://nitter.net/", "https://twitter.com/") \
+ .replace("https://nitter.42l.fr/", "https://twitter.com/") \
+ .replace("https://nitter.net/", "https://twitter.com/") \
+ .replace("https://m.facebook.com", "https://facebook.com") \
+ .replace("https://en.m.wikipedia.org", "https://en.wikipedia.org") \
.replace("https://www.instagram.com", "https://instagram.com") \
.replace("https://www.tiktok.com", "https://tiktok.com") \
.replace("https://imgur.com/", "https://i.imgur.com/") \
- .replace("https://nitter.net/", "https://twitter.com/") \
- .replace("https://nitter.42l.fr/", "https://twitter.com/") \
- .replace("https://nitter.lacontrevoie.fr/", "https://twitter.com/") \
.replace("/giphy.gif", "/giphy.webp") \
+ .replace('https://www.google.com/amp/s/', 'https://') \
+ .replace('https://amp.', 'https://') \
+ .replace('https://cnn.com/cnn/', 'https://edition.cnn.com/') \
+ .replace('/amp/', '/') \
+
+ if url.endswith('.amp'):
+ url = url.split('.amp')[0]
url = giphy_regex.sub(r'\1.webp', url)
if not url.startswith('/') and not url.startswith('https://rdrama.net') and not url.startswith('https://watchpeopledie.tv'):
- parsed_url = urlparse(url)
+ try: parsed_url = urlparse(url)
+ except:
+ print(url, flush=True)
+ abort(500)
domain = parsed_url.netloc
qd = parse_qs(parsed_url.query, keep_blank_values=True)
filtered = {k: val for k, val in qd.items() if is_whitelisted(domain, k)}
@@ -853,8 +866,9 @@ def torture_object(obj, torture_method):
def complies_with_chud(obj):
#check for cases where u should leave
- if not (obj.author.chud or obj.author.queen): return True
+ if not (obj.chudded or obj.queened): return True
if obj.author.marseyawarded: return True
+
if isinstance(obj, Post):
if obj.id in ADMIGGER_THREADS: return True
if obj.sub == "chudrama": return True
@@ -862,9 +876,7 @@ def complies_with_chud(obj):
if obj.parent_post in ADMIGGER_THREADS: return True
if obj.post.sub == "chudrama": return True
- if obj.author.chud:
- if not obj.chudded: return True
-
+ if obj.chudded:
#perserve old body_html to be used in checking for chud phrase
old_body_html = obj.body_html
@@ -880,7 +892,7 @@ def complies_with_chud(obj):
#torture title_html and check for chud_phrase in plain title and leave if it's there
if isinstance(obj, Post):
obj.title_html = torture_chud(obj.title_html, obj.author.username)
- if obj.author.chud_phrase in obj.title.lower():
+ if not obj.author.chud or obj.author.chud_phrase in obj.title.lower():
return True
#check for chud_phrase in body_html
@@ -890,10 +902,10 @@ def complies_with_chud(obj):
tags = soup.html.body.find_all(lambda tag: tag.name not in excluded_tags and not tag.attrs, recursive=False)
for tag in tags:
for text in tag.find_all(text=True, recursive=False):
- if obj.author.chud_phrase in text.lower():
+ if not obj.author.chud or obj.author.chud_phrase in text.lower():
return True
return False
- elif obj.author.queen:
+ elif obj.queened:
torture_object(obj, torture_queen)
return True
diff --git a/files/helpers/sorting_and_time.py b/files/helpers/sorting_and_time.py
index b156e77e2..ff21b5cde 100644
--- a/files/helpers/sorting_and_time.py
+++ b/files/helpers/sorting_and_time.py
@@ -1,6 +1,7 @@
import time
from sqlalchemy.sql import func
+from flask import g
from files.helpers.config.const import *
@@ -27,6 +28,9 @@ def apply_time_filter(t, objects, cls):
def sort_objects(sort, objects, cls):
+ if not (SITE == 'watchpeopledie.tv' and g.v and g.v.id == GTIX_ID):
+ objects = objects.order_by(cls.is_banned, cls.deleted_utc)
+
if sort == 'hot':
ti = int(time.time()) + 3600
metric = cls.realupvotes
diff --git a/files/helpers/useractions.py b/files/helpers/useractions.py
index 3eef49ad5..cd09b5e86 100644
--- a/files/helpers/useractions.py
+++ b/files/helpers/useractions.py
@@ -4,6 +4,7 @@ from files.classes.badges import Badge
from files.helpers.alerts import send_repeatable_notification
def badge_grant(user, badge_id, notify=True, check_if_exists=True):
+ g.db.flush()
existing = g.db.query(Badge).filter_by(user_id=user.id, badge_id=badge_id).one_or_none()
if existing: return
diff --git a/files/routes/admin.py b/files/routes/admin.py
index eed10d90d..0cc59c4ae 100644
--- a/files/routes/admin.py
+++ b/files/routes/admin.py
@@ -90,12 +90,12 @@ def edit_rules_post(v):
with open(f'files/templates/rules_{SITE_NAME}.html', 'w+', encoding="utf-8") as f:
f.write(rules)
- # ma = ModAction(
- # kind="edit_rules",
- # user_id=v.id,
- # )
- # g.db.add(ma)
- return render_template('admin/edit_rules.html', v=v, rules=rules, msg='Rules edited successfully!')
+ ma = ModAction(
+ kind="edit_rules",
+ user_id=v.id,
+ )
+ g.db.add(ma)
+ return {"message": "Rules edited successfully!"}
@app.post("/@/make_admin")
@limiter.limit('1/second', scope=rpath)
@@ -161,9 +161,6 @@ def distribute(v, kind, option_id):
autojanny = get_account(AUTOJANNY_ID)
if autojanny.coins == 0: abort(400, "@AutoJanny has 0 coins")
- try: option_id = int(option_id)
- except: abort(400)
-
if kind == 'post': cls = PostOption
else: cls = CommentOption
@@ -186,6 +183,10 @@ def distribute(v, kind, option_id):
g.db.add(autojanny)
votes = option.votes
+
+ if not votes:
+ abort(400, "Nobody voted on that, it can't be the winner!")
+
coinsperperson = int(pool / len(votes))
text = f"You won {coinsperperson} coins betting on {parent.permalink} :marseyparty:"
@@ -193,7 +194,7 @@ def distribute(v, kind, option_id):
for vote in votes:
u = vote.user
u.pay_account('coins', coinsperperson)
- add_notif(cid, u.id, text)
+ add_notif(cid, u.id, text, pushnotif_url=parent.permalink)
text = f"You lost the {POLL_BET_COINS} coins you bet on {parent.permalink} :marseylaugh:"
cid = notif_comment(text)
@@ -202,7 +203,7 @@ def distribute(v, kind, option_id):
if o.exclusive == 2:
losing_voters.extend([x.user_id for x in o.votes])
for uid in losing_voters:
- add_notif(cid, uid, text)
+ add_notif(cid, uid, text, pushnotif_url=parent.permalink)
if isinstance(parent, Post):
ma = ModAction(
@@ -278,26 +279,13 @@ def revert_actions(v, username):
return {"message": f"@{revertee.username}'s admin actions have been reverted!"}
@app.get("/admin/shadowbanned")
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@limiter.limit(DEFAULT_RATELIMIT)
+@limiter.limit(DEFAULT_RATELIMIT, key_func=get_ID)
@admin_level_required(PERMS['USER_SHADOWBAN'])
def shadowbanned(v):
- users = g.db.query(User).filter(
- User.shadowbanned != None,
- ).order_by(User.truescore.desc()).all()
+ users = g.db.query(User).filter(User.shadowbanned != None).order_by(User.ban_reason).all()
- collected_users = []
- collected_alts = set()
-
- for u in users:
- if u.id in collected_alts:
- continue
- collected_users.append(u)
- collected_alts = collected_alts | get_alt_graph_ids(u.id)
-
- collected_users = sorted(collected_users, key=lambda x: x.ban_reason)
-
- return render_template("admin/shadowbanned.html", v=v, users=collected_users)
+ return render_template("admin/shadowbanned.html", v=v, users=users)
@app.get("/admin/image_posts")
@@ -472,16 +460,12 @@ def badge_grant_post(v):
usernames = request.values.get("usernames", "").strip()
if not usernames:
- error = "You must enter usernames!"
- if v.client: return {"error": error}
- return render_template("admin/badge_admin.html", v=v, badge_types=badges, grant=True, error=error)
+ abort(400, "You must enter usernames!")
for username in usernames.split():
user = get_user(username, graceful=True)
if not user:
- error = "User not found!"
- if v.client: return {"error": error}
- return render_template("admin/badge_admin.html", v=v, badge_types=badges, grant=True, error=error)
+ abort(400, "User not found!")
try: badge_id = int(request.values.get("badge_id"))
except: abort(400)
@@ -492,7 +476,7 @@ def badge_grant_post(v):
description = request.values.get("description")
url = request.values.get("url", "").strip()
- if badge_id in {63,66,74,149,178,180,240,241,242,248,286,291,293} and not url:
+ if badge_id in {63,74,149,178,180,240,241,242,248,286,291,293} and not url:
abort(400, "This badge requires a url!")
if url:
@@ -532,10 +516,7 @@ def badge_grant_post(v):
)
g.db.add(ma)
-
- msg = "Badge granted to users successfully!"
- if v.client: return {"message": msg}
- return render_template("admin/badge_admin.html", v=v, badge_types=badges, grant=True, msg=msg)
+ return {"message": "Badge granted to users successfully!"}
@app.post("/admin/badge_remove")
@feature_required('BADGES')
@@ -549,14 +530,12 @@ def badge_remove_post(v):
usernames = request.values.get("usernames", "").strip()
if not usernames:
- error = "You must enter usernames!"
- if v.client: return {"error": error}
- return render_template("admin/badge_admin.html", v=v, badge_types=badges, grant=False, error=error)
+ abort(400, "You must enter usernames!")
for username in usernames.split():
user = get_user(username, graceful=True)
if not user:
- return render_template("admin/badge_admin.html", v=v, badge_types=badges, grant=False, error="User not found!")
+ abort(400, "User not found!")
try: badge_id = int(request.values.get("badge_id"))
except: abort(400)
@@ -580,10 +559,7 @@ def badge_remove_post(v):
g.db.add(ma)
g.db.delete(badge)
-
- msg = "Badge removed from users successfully!"
- if v.client: return {"message": msg}
- return render_template("admin/badge_admin.html", v=v, badge_types=badges, grant=False, msg=msg)
+ return {"message": "Badge removed from users successfully!"}
@app.get("/admin/alt_votes")
@@ -935,9 +911,9 @@ def admin_title_change(user_id, v):
user = get_account(user_id)
- new_name=request.values.get("title")[:256].strip()
+ new_name = request.values.get("title")[:256].strip()
- user.customtitleplain=new_name
+ user.customtitleplain = new_name
new_name = filter_emojis_only(new_name)
new_name = censor_slurs(new_name, None)
@@ -956,7 +932,7 @@ def admin_title_change(user_id, v):
if user.flairchanged: kind = "set_flair_locked"
else: kind = "set_flair_notlocked"
- ma=ModAction(
+ ma = ModAction(
kind=kind,
user_id=v.id,
target_user_id=user.id,
@@ -1042,7 +1018,7 @@ def ban_user(fullname, v):
send_repeatable_notification(user.id, text)
note = f'duration: {duration}, reason: "{reason}"'
- ma=ModAction(
+ ma = ModAction(
kind="ban_user",
user_id=v.id,
target_user_id=user.id,
@@ -1095,9 +1071,6 @@ def chud(fullname, v):
if user.chud == 1:
abort(403, f"@{user.username} is already chudded permanently!")
- if user.marsify:
- abort(403, f"You can't chud someone while they're marsified!")
-
days = 0.0
try:
days = float(request.values.get("days"))
@@ -1142,7 +1115,7 @@ def chud(fullname, v):
note = f'duration: {duration}'
if reason: note += f', reason: "{reason}"'
- ma=ModAction(
+ ma = ModAction(
kind="chud",
user_id=v.id,
target_user_id=user.id,
@@ -1213,7 +1186,7 @@ def unban_user(fullname, v):
x.ban_reason = None
g.db.add(x)
- ma=ModAction(
+ ma = ModAction(
kind="unban_user",
user_id=v.id,
target_user_id=user.id,
@@ -1287,7 +1260,7 @@ def progstack_post(post_id, v):
post.realupvotes = floor(post.realupvotes * PROGSTACK_MUL)
g.db.add(post)
- ma=ModAction(
+ ma = ModAction(
kind="progstack_post",
user_id=v.id,
target_post_id=post.id,
@@ -1308,7 +1281,7 @@ def unprogstack_post(post_id, v):
post.is_approved = None
g.db.add(post)
- ma=ModAction(
+ ma = ModAction(
kind="unprogstack_post",
user_id=v.id,
target_post_id=post.id,
@@ -1329,7 +1302,7 @@ def progstack_comment(comment_id, v):
comment.realupvotes = floor(comment.realupvotes * PROGSTACK_MUL)
g.db.add(comment)
- ma=ModAction(
+ ma = ModAction(
kind="progstack_comment",
user_id=v.id,
target_comment_id=comment.id,
@@ -1350,7 +1323,7 @@ def unprogstack_comment(comment_id, v):
comment.is_approved = None
g.db.add(comment)
- ma=ModAction(
+ ma = ModAction(
kind="unprogstack_comment",
user_id=v.id,
target_comment_id=comment.id,
@@ -1375,7 +1348,7 @@ def remove_post(post_id, v):
post.ban_reason = v.username
g.db.add(post)
- ma=ModAction(
+ ma = ModAction(
kind="ban_post",
user_id=v.id,
target_post_id=post.id,
@@ -1495,7 +1468,7 @@ def sticky_post(post_id, v):
g.db.add(post)
- ma=ModAction(
+ ma = ModAction(
kind="pin_post",
user_id=v.id,
target_post_id=post.id,
@@ -1627,13 +1600,17 @@ def remove_comment(c_id, v):
comment.is_approved = None
comment.ban_reason = v.username
g.db.add(comment)
- ma=ModAction(
+ ma = ModAction(
kind="ban_comment",
user_id=v.id,
target_comment_id=comment.id,
)
g.db.add(ma)
+ if comment.parent_post:
+ for sort in COMMENT_SORTS:
+ cache.delete(f'post_{comment.parent_post}_{sort}')
+
return {"message": "Comment removed!"}
@@ -1663,6 +1640,10 @@ def approve_comment(c_id, v):
g.db.add(comment)
+ if comment.parent_post:
+ for sort in COMMENT_SORTS:
+ cache.delete(f'post_{comment.parent_post}_{sort}')
+
return {"message": "Comment approved!"}
@@ -1701,9 +1682,8 @@ def admin_distinguish_comment(c_id, v):
@admin_level_required(PERMS['DOMAINS_BAN'])
def admin_banned_domains(v):
banned_domains = g.db.query(BannedDomain) \
- .order_by(BannedDomain.reason).all()
- return render_template("admin/banned_domains.html", v=v,
- banned_domains=banned_domains)
+ .order_by(BannedDomain.created_utc).all()
+ return render_template("admin/banned_domains.html", v=v, banned_domains=banned_domains)
@app.post("/admin/ban_domain")
@limiter.limit('1/second', scope=rpath)
@@ -1713,10 +1693,10 @@ def admin_banned_domains(v):
@admin_level_required(PERMS['DOMAINS_BAN'])
def ban_domain(v):
- domain=request.values.get("domain", "").strip().lower()
+ domain = request.values.get("domain", "").strip().lower()
if not domain: abort(400)
- reason=request.values.get("reason", "").strip()
+ reason = request.values.get("reason", "").strip()
if not reason: abort(400, 'Reason is required!')
if len(reason) > 100:
@@ -1736,7 +1716,7 @@ def ban_domain(v):
)
g.db.add(ma)
- return redirect("/admin/banned_domains/")
+ return {"message": "Domain banned successfully!"}
@app.post("/admin/unban_domain/")
@@ -1769,7 +1749,7 @@ def unban_domain(v, domain):
@admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def admin_nuke_user(v):
- user=get_user(request.values.get("user"))
+ user = get_user(request.values.get("user"))
for post in g.db.query(Post).filter_by(author_id=user.id):
if post.is_banned:
@@ -1787,7 +1767,7 @@ def admin_nuke_user(v):
comment.ban_reason = v.username
g.db.add(comment)
- ma=ModAction(
+ ma = ModAction(
kind="nuke_user",
user_id=v.id,
target_user_id=user.id,
@@ -1805,7 +1785,7 @@ def admin_nuke_user(v):
@admin_level_required(PERMS['POST_COMMENT_MODERATION'])
def admin_nunuke_user(v):
- user=get_user(request.values.get("user"))
+ user = get_user(request.values.get("user"))
for post in g.db.query(Post).filter_by(author_id=user.id):
if not post.is_banned:
@@ -1825,7 +1805,7 @@ def admin_nunuke_user(v):
comment.is_approved = v.id
g.db.add(comment)
- ma=ModAction(
+ ma = ModAction(
kind="unnuke_user",
user_id=v.id,
target_user_id=user.id,
@@ -1898,22 +1878,25 @@ def delete_media_post(v):
url = request.values.get("url")
if not url:
- return render_template("admin/delete_media.html", v=v, url=url, error="No url provided!")
+ abort(400, "No url provided!")
- if not image_link_regex.fullmatch(url) and not video_link_regex.fullmatch(url):
- return render_template("admin/delete_media.html", v=v, url=url, error="Invalid url!")
+ if not image_link_regex.fullmatch(url) and not video_link_regex.fullmatch(url) and not asset_image_link_regex.fullmatch(url):
+ abort(400, "Invalid url")
path = url.split(SITE)[1]
if path.startswith('/1'):
path = '/videos' + path
+ if path.startswith('/assets/images'):
+ path = 'files' + path.split('?x=')[0]
+
if not os.path.isfile(path):
- return render_template("admin/delete_media.html", v=v, url=url, error="File not found on the server!")
+ abort(400, "File not found on the server!")
os.remove(path)
- ma=ModAction(
+ ma = ModAction(
kind="delete_media",
user_id=v.id,
_note=url,
@@ -1921,7 +1904,7 @@ def delete_media_post(v):
g.db.add(ma)
purge_files_in_cache(url)
- return render_template("admin/delete_media.html", v=v, msg="Media deleted successfully!")
+ return {"message": "Media deleted successfully!"}
@app.post("/admin/reset_password/")
@limiter.limit('1/second', scope=rpath)
@@ -1956,18 +1939,46 @@ def orgy_control(v):
@app.post("/admin/start_orgy")
@admin_level_required(PERMS['ORGIES'])
def start_orgy(v):
- link = request.values.get("link")
- title = request.values.get("title")
+ link = request.values.get("link", "").strip()
+ title = request.values.get("title", "").strip()
- assert link
- assert title
+ if not link:
+ abort(400, "A link is required!")
- create_orgy(link, title)
+ if not title:
+ abort(400, "A title is required!")
- return redirect("/chat")
+ if get_orgy():
+ abort(400, "An orgy is already in progress")
+
+ normalized_link = normalize_url(link)
+
+ if re.match(bare_youtube_regex, normalized_link):
+ orgy_type = 'youtube'
+ data, _ = get_youtube_id_and_t(normalized_link)
+ elif re.match(rumble_regex, normalized_link):
+ orgy_type = 'rumble'
+ data = normalized_link
+ elif re.match(twitch_regex, normalized_link):
+ orgy_type = 'twitch'
+ data = re.search(twitch_regex, normalized_link).group(3)
+ elif normalized_link.endswith('.mp4'):
+ orgy_type = 'file'
+ data = normalized_link
+ else:
+ abort(400)
+
+ orgy = Orgy(
+ title=title,
+ type=orgy_type,
+ data=data
+ )
+ g.db.add(orgy)
+
+ return {"message": "Orgy started successfully!"}
@app.post("/admin/stop_orgy")
@admin_level_required(PERMS['ORGIES'])
def stop_orgy(v):
- end_orgy()
- return redirect("/chat")
+ g.db.query(Orgy).delete()
+ return {"message": "Orgy stopped successfully!"}
diff --git a/files/routes/asset_submissions.py b/files/routes/asset_submissions.py
index 733c83bcc..7fd823420 100644
--- a/files/routes/asset_submissions.py
+++ b/files/routes/asset_submissions.py
@@ -34,9 +34,11 @@ def submit_emojis(v):
emoji.author = g.db.query(User.username).filter_by(id=emoji.author_id).one()[0]
emoji.submitter = g.db.query(User.username).filter_by(id=emoji.submitter_id).one()[0]
- return render_template("submit_emojis.html", v=v, emojis=emojis, msg=get_msg())
+ return render_template("submit_emojis.html", v=v, emojis=emojis)
+emoji_modifiers = ('pat', 'talking', 'genocide', 'love')
+
@app.post("/submit/emojis")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
@@ -50,46 +52,41 @@ def submit_emoji(v):
username = request.values.get('author', '').lower().strip()
kind = request.values.get('kind', '').strip()
- def error(error):
- if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_EMOJIS']: emojis = g.db.query(Emoji).filter(Emoji.submitter_id != None)
- else: emojis = g.db.query(Emoji).filter(Emoji.submitter_id == v.id)
- emojis = emojis.order_by(Emoji.created_utc.desc()).all()
- for emoji in emojis:
- emoji.author = g.db.query(User.username).filter_by(id=emoji.author_id).one()[0]
- emoji.submitter = g.db.query(User.username).filter_by(id=emoji.submitter_id).one()[0]
- return render_template("submit_emojis.html", v=v, emojis=emojis, error=error, name=name, kind=kind, tags=tags, username=username), 400
+ for modifier in emoji_modifiers:
+ if name.endswith(modifier):
+ abort(400, f'Submitted emoji names should NOT end with the word "{modifier}"')
if kind not in EMOJI_KINDS:
- return error("Invalid emoji kind!")
+ abort(400, "Invalid emoji kind!")
if kind in {"Platy", "Wolf", "Tay", "Carp", "Capy"} and not name.startswith(kind.lower()):
- return error(f'The name of this emoji should start with the word "{kind.lower()}"')
+ abort(400, f'The name of this emoji should start with the word "{kind.lower()}"')
if kind == "Marsey" and not name.startswith("marsey") and not name.startswith("marcus"):
- return error('The name of this emoji should start with the word "Marsey" or "Marcus"')
+ abort(400, 'The name of this emoji should start with the word "Marsey" or "Marcus"')
if kind == "Marsey Flags" and not name.startswith("marseyflag"):
- return error('The name of this emoji should start with the word "marseyflag"')
+ abort(400, 'The name of this emoji should start with the word "marseyflag"')
if g.is_tor:
- return error("Image uploads are not allowed through TOR!")
+ abort(400, "Image uploads are not allowed through TOR!")
if not file or not file.content_type.startswith('image/'):
- return error("You need to submit an image!")
+ abort(400, "You need to submit an image!")
if not emoji_name_regex.fullmatch(name):
- return error("Invalid name!")
+ abort(400, "Invalid name!")
existing = g.db.query(Emoji.name).filter_by(name=name).one_or_none()
if existing:
- return error("Someone already submitted an emoji with this name!")
+ abort(400, "Someone already submitted an emoji with this name!")
if not tags_regex.fullmatch(tags):
- return error("Invalid tags!")
+ abort(400, "Invalid tags!")
author = get_user(username, v=v, graceful=True)
if not author:
- return error(f"A user with the name '{username}' was not found!")
+ abort(400, f"A user with the name '{username}' was not found!")
highquality = f'/asset_submissions/emojis/{name}'
file.save(highquality)
@@ -101,7 +98,8 @@ def submit_emoji(v):
emoji = Emoji(name=name, kind=kind, author_id=author.id, tags=tags, count=0, submitter_id=v.id)
g.db.add(emoji)
- return redirect(f"/submit/emojis?msg='{name}' submitted successfully!")
+ return {"message": f"'{name}' submitted successfully!"}
+
def verify_permissions_and_get_asset(cls, asset_type, v, name, make_lower=False):
if cls not in ASSET_TYPES: raise Exception("not a valid asset type")
@@ -156,30 +154,30 @@ def approve_emoji(v, name):
if emoji.kind == "Marsey":
all_by_author = g.db.query(Emoji).filter_by(kind="Marsey", author_id=author.id).count()
- if all_by_author >= 100:
+ if all_by_author >= 99:
badge_grant(badge_id=143, user=author)
- elif all_by_author >= 10:
+ elif all_by_author >= 9:
badge_grant(badge_id=16, user=author)
else:
badge_grant(badge_id=17, user=author)
elif emoji.kind == "Capy":
all_by_author = g.db.query(Emoji).filter_by(kind="Capy", author_id=author.id).count()
- if all_by_author >= 10:
+ if all_by_author >= 9:
badge_grant(badge_id=115, user=author)
badge_grant(badge_id=114, user=author)
elif emoji.kind == "Carp":
all_by_author = g.db.query(Emoji).filter_by(kind="Carp", author_id=author.id).count()
- if all_by_author >= 10:
+ if all_by_author >= 9:
badge_grant(badge_id=288, user=author)
badge_grant(badge_id=287, user=author)
elif emoji.kind == "Wolf":
all_by_author = g.db.query(Emoji).filter_by(kind="Wolf", author_id=author.id).count()
- if all_by_author >= 10:
+ if all_by_author >= 9:
badge_grant(badge_id=111, user=author)
badge_grant(badge_id=110, user=author)
elif emoji.kind == "Platy":
all_by_author = g.db.query(Emoji).filter_by(kind="Platy", author_id=author.id).count()
- if all_by_author >= 10:
+ if all_by_author >= 9:
badge_grant(badge_id=113, user=author)
badge_grant(badge_id=112, user=author)
@@ -268,11 +266,9 @@ def remove_emoji(v, name):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def submit_hats(v):
- if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None)
- else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id)
- hats = hats.order_by(HatDef.created_utc.desc()).all()
+ hats = g.db.query(HatDef).filter(HatDef.submitter_id != None).order_by(HatDef.created_utc.desc()).all()
- return render_template("submit_hats.html", v=v, hats=hats, msg=get_msg())
+ return render_template("submit_hats.html", v=v, hats=hats)
@app.post("/submit/hats")
@@ -286,32 +282,26 @@ def submit_hat(v):
description = request.values.get('description', '').strip()
username = request.values.get('author', '').strip()
- def error(error):
- if v.admin_level >= PERMS['VIEW_PENDING_SUBMITTED_HATS']: hats = g.db.query(HatDef).filter(HatDef.submitter_id != None)
- else: hats = g.db.query(HatDef).filter(HatDef.submitter_id == v.id)
- hats = hats.order_by(HatDef.created_utc.desc()).all()
- return render_template("submit_hats.html", v=v, hats=hats, error=error, name=name, description=description, username=username), 400
-
if g.is_tor:
- return error("Image uploads are not allowed through TOR!")
+ abort(400, "Image uploads are not allowed through TOR!")
file = request.files["image"]
if not file or not file.content_type.startswith('image/'):
- return error("You need to submit an image!")
+ abort(400, "You need to submit an image!")
if not hat_regex.fullmatch(name):
- return error("Invalid name!")
+ abort(400, "Invalid name!")
existing = g.db.query(HatDef.name).filter_by(name=name).one_or_none()
if existing:
- return error("A hat with this name already exists!")
+ abort(400, "A hat with this name already exists!")
if not description_regex.fullmatch(description):
- return error("Invalid description!")
+ abort(400, "Invalid description!")
author = get_user(username, v=v, graceful=True)
if not author:
- return error(f"A user with the name '{username}' was not found!")
+ abort(400, f"A user with the name '{username}' was not found!")
highquality = f'/asset_submissions/hats/{name}'
file.save(highquality)
@@ -319,7 +309,7 @@ def submit_hat(v):
with Image.open(highquality) as i:
if i.width > 100 or i.height > 130:
os.remove(highquality)
- return error("Images must be 100x130")
+ abort(400, "Images must be 100x130")
if len(list(Iterator(i))) > 1: price = 1000
else: price = 500
@@ -331,7 +321,7 @@ def submit_hat(v):
hat = HatDef(name=name, author_id=author.id, description=description, price=price, submitter_id=v.id)
g.db.add(hat)
- return redirect(f"/submit/hats?msg='{name}' submitted successfully!")
+ return {"message": f"'{name}' submitted successfully!"}
@app.post("/admin/approve/hat/")
@@ -363,13 +353,13 @@ def approve_hat(v, name):
all_by_author = g.db.query(HatDef).filter_by(author_id=author.id).count()
- if all_by_author >= 250:
+ if all_by_author >= 249:
badge_grant(badge_id=166, user=author)
- elif all_by_author >= 100:
+ elif all_by_author >= 99:
badge_grant(badge_id=165, user=author)
- elif all_by_author >= 50:
+ elif all_by_author >= 49:
badge_grant(badge_id=164, user=author)
- elif all_by_author >= 10:
+ elif all_by_author >= 9:
badge_grant(badge_id=163, user=author)
hat_copy = Hat(
@@ -434,20 +424,17 @@ def update_emoji(v):
tags = request.values.get('tags', '').lower().strip()
kind = request.values.get('kind', '').strip()
- def error(error):
- return render_template("admin/update_assets.html", v=v, error=error, name=name, tags=tags, kind=kind, type="Emoji")
-
existing = g.db.get(Emoji, name)
if not existing:
- return error("An emoji with this name doesn't exist!")
+ abort(400, "An emoji with this name doesn't exist!")
updated = False
if file:
if g.is_tor:
- return error("Image uploads are not allowed through TOR!")
+ abort(400, "Image uploads are not allowed through TOR!")
if not file.content_type.startswith('image/'):
- return error("You need to submit an image!")
+ abort(400, "You need to submit an image!")
for x in IMAGE_FORMATS:
if path.isfile(f'/asset_submissions/emojis/original/{name}.{x}'):
@@ -480,7 +467,7 @@ def update_emoji(v):
updated = True
if not updated:
- return error("You need to actually update something!")
+ abort(400, "You need to actually update something!")
g.db.add(existing)
@@ -494,7 +481,7 @@ def update_emoji(v):
cache.delete("emojis")
cache.delete(f"emoji_list_{existing.kind}")
- return render_template("admin/update_assets.html", v=v, msg=f"'{name}' updated successfully!", name=name, tags=tags, kind=kind, type="Emoji")
+ return {"message": f"'{name}' updated successfully!"}
@app.get("/admin/update/hats")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@@ -514,21 +501,18 @@ def update_hat(v):
file = request.files["image"]
name = request.values.get('name', '').strip()
- def error(error):
- return render_template("admin/update_assets.html", v=v, error=error, type="Hat")
-
if g.is_tor:
- return error("Image uploads are not allowed through TOR!")
+ abort(400, "Image uploads are not allowed through TOR!")
if not file or not file.content_type.startswith('image/'):
- return error("You need to submit an image!")
+ abort(400, "You need to submit an image!")
if not hat_regex.fullmatch(name):
- return error("Invalid name!")
+ abort(400, "Invalid name!")
existing = g.db.query(HatDef.name).filter_by(name=name).one_or_none()
if not existing:
- return error("A hat with this name doesn't exist!")
+ abort(400, "A hat with this name doesn't exist!")
highquality = f"/asset_submissions/hats/{name}"
file.save(highquality)
@@ -536,7 +520,7 @@ def update_hat(v):
with Image.open(highquality) as i:
if i.width > 100 or i.height > 130:
os.remove(highquality)
- return error("Images must be 100x130")
+ abort(400, "Images must be 100x130")
format = i.format.lower()
new_path = f'/asset_submissions/hats/original/{name}.{format}'
@@ -557,4 +541,4 @@ def update_hat(v):
_note=f'{name}'
)
g.db.add(ma)
- return render_template("admin/update_assets.html", v=v, msg=f"'{name}' updated successfully!", type="Hat")
+ return {"message": f"'{name}' updated successfully!"}
diff --git a/files/routes/awards.py b/files/routes/awards.py
index 2447812e0..8b10ddea3 100644
--- a/files/routes/awards.py
+++ b/files/routes/awards.py
@@ -150,7 +150,6 @@ def award_thing(v, thing_type, id):
if v.shadowbanned: abort(500)
author = thing.author
- if author.shadowbanned and not v.admin_level: abort(404)
AWARDS = deepcopy(AWARDS_ENABLED)
if v.house:
@@ -350,6 +349,10 @@ def award_thing(v, thing_type, id):
badge_grant(user=author, badge_id=285)
+ if thing_type == 'comment' and not thing.author.deflector:
+ thing.queened = True
+ g.db.add(thing)
+
elif kind == "chud":
if thing_type == 'post' and thing.sub == 'chudrama' \
or thing_type == 'comment' and thing.post and thing.post.sub == 'chudrama':
@@ -361,9 +364,6 @@ def award_thing(v, thing_type, id):
if author.marseyawarded:
abort(409, f"{safe_username} under the effect of a conflicting award: Marsey award!")
- if author.marsify:
- abort(409, f"{safe_username} under the effect of a conflicting award: Marsify award!")
-
if author.owoify:
abort(409, f"{safe_username} under the effect of a conflicting award: OwOify award!")
@@ -385,9 +385,8 @@ def award_thing(v, thing_type, id):
badge_grant(user=author, badge_id=58)
- if thing_type == 'comment':
+ if thing_type == 'comment' and not thing.author.deflector:
thing.chudded = True
-
elif kind == "flairlock":
new_name = note[:100]
if not new_name and author.flairchanged:
@@ -516,7 +515,8 @@ def award_thing(v, thing_type, id):
if thing_type == 'comment' and not thing.author.deflector:
body = thing.body
body = owoify(body)
- if author.marsify: body = marsify(body)
+ if author.marsify and not author.chud:
+ body = marsify(body)
thing.body_html = sanitize(body, limit_pings=5, showmore=True)
g.db.add(thing)
elif ("Edgy" in kind and kind == v.house) or kind == 'sharpen':
@@ -531,11 +531,15 @@ def award_thing(v, thing_type, id):
body = thing.body
body = sharpen(body)
thing.body_html = sanitize(body, limit_pings=5, showmore=True)
+ thing.sharpened = True
g.db.add(thing)
elif ("Femboy" in kind and kind == v.house) or kind == 'rainbow':
if author.rainbow: author.rainbow += 86400
else: author.rainbow = int(time.time()) + 86400
badge_grant(user=author, badge_id=171)
+ if thing_type == 'comment' and not thing.author.deflector:
+ thing.rainbowed = True
+ g.db.add(thing)
elif kind == "spider":
if author.spider: author.spider += 86400
else: author.spider = int(time.time()) + 86400
diff --git a/files/routes/chat.py b/files/routes/chat.py
index c3ad1449b..b086987f1 100644
--- a/files/routes/chat.py
+++ b/files/routes/chat.py
@@ -47,13 +47,16 @@ def is_not_permabanned_socketio(f):
wrapper.__name__ = f.__name__
return wrapper
+CHAT_ERROR_MESSAGE = f"To prevent spam, you'll need {TRUESCORE_CC_CHAT_MINIMUM} truescore (this is {TRUESCORE_CC_CHAT_MINIMUM} votes, either up or down, on any threads or comments you've made) in order to access chat. Sorry! I love you 💖"
+
@app.get("/chat")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@is_not_permabanned
def chat(v):
- if not v.admin_level and TRUESCORE_CHAT_MINIMUM and v.truescore < TRUESCORE_CHAT_MINIMUM:
- abort(403, f"Need at least {TRUESCORE_CHAT_MINIMUM} truescore for access to chat!")
+ if not v.allowed_in_chat:
+ abort(403, CHAT_ERROR_MESSAGE)
+
orgy = get_orgy()
displayed_messages = {k: val for k, val in messages.items() if val["user_id"] not in v.userblocks}
@@ -63,18 +66,6 @@ def chat(v):
else:
return render_template("chat.html", v=v, messages=displayed_messages)
-@app.get("/old_chat")
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
-@is_not_permabanned
-def old_chat(v):
- if not v.admin_level and TRUESCORE_CHAT_MINIMUM and v.truescore < TRUESCORE_CHAT_MINIMUM:
- abort(403, f"Need at least {TRUESCORE_CHAT_MINIMUM} truescore for access to chat!")
-
- displayed_messages = {k: val for k, val in messages.items() if val["user_id"] not in v.userblocks}
-
- return render_template("chat.html", v=v, messages=displayed_messages)
-
@socketio.on('speak')
@is_not_banned_socketio
def speak(data, v):
@@ -85,7 +76,7 @@ def speak(data, v):
f.write(data['file'])
image = process_image(name, v)
- if TRUESCORE_CHAT_MINIMUM and v.truescore < TRUESCORE_CHAT_MINIMUM:
+ if not v.allowed_in_chat:
return '', 403
global messages
@@ -137,6 +128,7 @@ def speak(data, v):
"user_id": v.id,
"username": v.username,
"namecolor": v.name_color,
+ "patron": v.patron,
"text": text,
"text_censored": censor_slurs(text, 'chat'),
"text_html": text_html,
@@ -173,15 +165,15 @@ def refresh_online():
@is_not_permabanned_socketio
def connect(v):
- if any(v.id in session for session in sessions) and [v.username, v.id, v.name_color] not in online:
+ if any(v.id in session for session in sessions) and [v.username, v.id, v.name_color, v.patron] not in online:
# user has previous running sessions with a different username or name_color
for chat_user in online:
if v.id == chat_user[1]:
online.remove(chat_user)
sessions.append([v.id, request.sid])
- if [v.username, v.id, v.name_color] not in online:
- online.append([v.username, v.id, v.name_color])
+ if [v.username, v.id, v.name_color, v.patron] not in online:
+ online.append([v.username, v.id, v.name_color, v.patron])
refresh_online()
diff --git a/files/routes/comments.py b/files/routes/comments.py
index 502490a99..b02e99855 100644
--- a/files/routes/comments.py
+++ b/files/routes/comments.py
@@ -21,6 +21,7 @@ from files.helpers.treasure import *
from files.routes.front import comment_idlist
from files.routes.routehelpers import execute_shadowban_viewers_and_voters
from files.routes.wrappers import *
+from files.routes.static import badge_list
from files.__main__ import app, cache, limiter
def _mark_comment_as_read(cid, vid):
@@ -45,15 +46,18 @@ def _mark_comment_as_read(cid, vid):
def post_pid_comment_cid(cid, v, pid=None, anything=None, sub=None):
comment = get_comment(cid, v=v)
- if not User.can_see(v, comment): abort(403)
- if v and request.values.get("read"):
- gevent.spawn(_mark_comment_as_read, comment.id, v.id)
+ if not User.can_see(v, comment): abort(403)
if comment.parent_post:
post = comment.parent_post
+ elif comment.wall_user_id:
+ return redirect(f"/id/{comment.wall_user_id}/wall/comment/{comment.id}")
else:
- post = NOTIFICATION_THREAD
+ return redirect(f"/notification/{comment.id}")
+
+ if v and request.values.get("read"):
+ gevent.spawn(_mark_comment_as_read, comment.id, v.id)
post = get_post(post, v=v)
@@ -64,15 +68,20 @@ def post_pid_comment_cid(cid, v, pid=None, anything=None, sub=None):
except: context = 8
comment_info = comment
c = comment
- while context and c.level > 1:
- c = c.parent_comment
- context -= 1
- top_comment = c
if post.new: defaultsortingcomments = 'new'
elif v: defaultsortingcomments = v.defaultsortingcomments
else: defaultsortingcomments = "hot"
- sort=request.values.get("sort", defaultsortingcomments)
+ sort = request.values.get("sort", defaultsortingcomments)
+
+ while context and c.level > 1:
+ parent = c.parent_comment
+ replies = parent.replies(sort)
+ replies.remove(c)
+ parent.replies2 = [c] + replies
+ c = parent
+ context -= 1
+ top_comment = c
if v:
# this is required because otherwise the vote and block
@@ -137,7 +146,9 @@ def comment(v):
- if not User.can_see(v, parent): abort(403)
+ if posting_to_post and not User.can_see(v, parent):
+ abort(403)
+
if not isinstance(parent, User) and parent.deleted_utc != 0:
if isinstance(parent, Post):
abort(403, "You can't reply to deleted posts!")
@@ -161,15 +172,15 @@ def comment(v):
elif v.bird and len(body) > 140:
abort(403, "You have to type less than 140 characters!")
- if not body and not request.files.get('file'): abort(400, "You need to actually write something!")
+ if not body and not request.files.get('file'):
+ abort(400, "You need to actually write something!")
- if not v.admin_level >= PERMS['POST_COMMENT_MODERATION'] and parent_user.any_block_exists(v):
- abort(403, "You can't reply to users who have blocked you or users that you have blocked!")
+ if parent_user.has_blocked(v):
+ notify_op = False
if request.files.get("file") and not g.is_tor:
files = request.files.getlist('file')[:20]
-
if files:
media_ratelimit(v)
@@ -218,6 +229,7 @@ def comment(v):
copyfile(oldname, filename)
process_image(filename, v, resize=300, trim=True)
purge_files_in_cache(f"{SITE_FULL_IMAGES}/i/{SITE_NAME}/badges/{badge.id}.webp")
+ cache.delete_memoized(badge_list)
except Exception as e:
abort(400, str(e))
body = body.replace(f'[{file.filename}]', f' {image} ', 1)
@@ -240,7 +252,7 @@ def comment(v):
body_for_sanitize = body
if v.owoify: body_for_sanitize = owoify(body_for_sanitize)
- if v.marsify: body_for_sanitize = marsify(body_for_sanitize)
+ if v.marsify and not v.chud: body_for_sanitize = marsify(body_for_sanitize)
if v.sharpen: body_for_sanitize = sharpen(body_for_sanitize)
body_html = sanitize(body_for_sanitize, limit_pings=5, showmore=(not v.marseyawarded), count_emojis=not v.marsify)
@@ -281,7 +293,10 @@ def comment(v):
body=body,
ghost=ghost,
chudded=chudded,
- )
+ rainbowed=bool(v.rainbow),
+ queened=bool(v.queen),
+ sharpened=bool(v.sharpen),
+ )
c.upvotes = 1
g.db.add(c)
@@ -355,7 +370,7 @@ def comment(v):
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
- if parent_user.id != v.id and not v.shadowbanned:
+ if parent_user.id != v.id and notify_op:
if isinstance(parent, User):
title = f"New comment on your wall by @{c.author_name}"
else:
@@ -397,6 +412,8 @@ def comment(v):
).one_or_none()
if n: g.db.delete(n)
+ g.db.flush()
+
if c.parent_post:
for sort in COMMENT_SORTS:
cache.delete(f'post_{c.parent_post}_{sort}')
@@ -407,11 +424,13 @@ def comment(v):
@app.post("/delete/comment/")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def delete_comment(cid, v):
- if v.id == 253: abort(403)
+ if SITE == 'rdrama.net' and v.id == 253:
+ abort(403)
+
c = get_comment(cid, v=v)
if not c.deleted_utc:
if c.author_id != v.id: abort(403)
@@ -423,6 +442,11 @@ def delete_comment(cid, v):
g.db.add(v)
cache.delete_memoized(comment_idlist)
+
+ if c.parent_post:
+ for sort in COMMENT_SORTS:
+ cache.delete(f'post_{c.parent_post}_{sort}')
+
return {"message": "Comment deleted!"}
@app.post("/undelete/comment/")
@@ -443,6 +467,11 @@ def undelete_comment(cid, v):
g.db.add(v)
cache.delete_memoized(comment_idlist)
+
+ if c.parent_post:
+ for sort in COMMENT_SORTS:
+ cache.delete(f'post_{c.parent_post}_{sort}')
+
return {"message": "Comment undeleted!"}
@app.post("/pin_comment/")
@@ -505,9 +534,9 @@ def unpin_comment(cid, v):
@auth_required
def save_comment(cid, v):
- comment=get_comment(cid)
+ comment = get_comment(cid)
- save=g.db.query(CommentSaveRelationship).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
+ save = g.db.query(CommentSaveRelationship).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
if not save:
new_save=CommentSaveRelationship(user_id=v.id, comment_id=comment.id)
@@ -524,9 +553,9 @@ def save_comment(cid, v):
@auth_required
def unsave_comment(cid, v):
- comment=get_comment(cid)
+ comment = get_comment(cid)
- save=g.db.query(CommentSaveRelationship).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
+ save = g.db.query(CommentSaveRelationship).filter_by(user_id=v.id, comment_id=comment.id).one_or_none()
if save:
g.db.delete(save)
@@ -564,7 +593,7 @@ def diff_words(answer, guess):
def toggle_comment_nsfw(cid, v):
comment = get_comment(cid)
- if comment.author_id != v.id and not v.admin_level >= PERMS['POST_COMMENT_MODERATION'] and not (comment.post.sub and v.mods(comment.post.sub)):
+ if comment.author_id != v.id and v.admin_level < PERMS['POST_COMMENT_MODERATION'] and not (comment.post.sub and v.mods(comment.post.sub)):
abort(403)
if comment.over_18 and v.is_permabanned:
@@ -596,8 +625,8 @@ def toggle_comment_nsfw(cid, v):
@app.post("/edit_comment/")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
-@limiter.limit("10/minute;100/hour;200/day", deduct_when=lambda response: response.status_code < 400)
-@limiter.limit("10/minute;100/hour;200/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@is_not_permabanned
def edit_comment(cid, v):
c = get_comment(cid, v=v)
@@ -606,7 +635,9 @@ def edit_comment(cid, v):
and v.admin_level < PERMS["IGNORE_1WEEk_EDITING_LIMIT"] and v.id not in EXEMPT_FROM_1WEEK_EDITING_LIMIT:
abort(403, "You can't edit comments older than 1 week!")
- if c.author_id != v.id: abort(403)
+ if c.author_id != v.id and v.admin_level < PERMS['POST_COMMENT_EDITING']:
+ abort(403)
+
if not c.parent_post and not c.wall_user_id:
abort(403)
@@ -617,10 +648,12 @@ def edit_comment(cid, v):
abort(400, "You have to actually type something!")
if body != c.body or request.files.get("file") and not g.is_tor:
- if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
- abort(403, "You have to type more than 280 characters!")
- elif v.bird and len(body) > 140:
- abort(403, "You have to type less than 140 characters!")
+
+ if v.id == c.author_id:
+ if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
+ abort(403, "You have to type more than 280 characters!")
+ elif v.bird and len(body) > 140:
+ abort(403, "You have to type less than 140 characters!")
execute_antispam_comment_check(body, v)
@@ -628,18 +661,21 @@ def edit_comment(cid, v):
body = body[:COMMENT_BODY_LENGTH_LIMIT].strip() # process_files potentially adds characters to the post
body_for_sanitize = body
- if v.owoify:
- body_for_sanitize = owoify(body_for_sanitize)
- if v.marsify:
- body_for_sanitize = marsify(body_for_sanitize)
- if v.sharpen:
+
+ if v.id == c.author_id:
+ if v.owoify:
+ body_for_sanitize = owoify(body_for_sanitize)
+ if v.marsify and not v.chud:
+ body_for_sanitize = marsify(body_for_sanitize)
+
+ if c.sharpened:
body_for_sanitize = sharpen(body_for_sanitize)
body_html = sanitize(body_for_sanitize, golden=False, limit_pings=5, showmore=(not v.marseyawarded))
if len(body_html) > COMMENT_BODY_HTML_LENGTH_LIMIT: abort(400)
- if v.marseyawarded and marseyaward_body_regex.search(body_html):
+ if v.id == c.author_id and v.marseyawarded and marseyaward_body_regex.search(body_html):
abort(403, "You can only type marseys!")
oldtext = c.body
@@ -655,8 +691,16 @@ def edit_comment(cid, v):
process_poll_options(v, c)
- if int(time.time()) - c.created_utc > 60 * 3:
- c.edited_utc = int(time.time())
+ if v.id == c.author_id:
+ if int(time.time()) - c.created_utc > 60 * 3:
+ c.edited_utc = int(time.time())
+ else:
+ ma=ModAction(
+ kind="edit_comment",
+ user_id=v.id,
+ target_comment_id=c.id
+ )
+ g.db.add(ma)
g.db.add(c)
@@ -670,9 +714,13 @@ def edit_comment(cid, v):
if not notif:
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
- if not v.shadowbanned:
- push_notif({x}, f'New mention of you by @{c.author_name}', c.body, c)
+ push_notif({x}, f'New mention of you by @{c.author_name}', c.body, c)
g.db.flush()
- return {"body": c.body, "comment": c.realbody(v)}
+ return {
+ "body": c.body,
+ "comment": c.realbody(v),
+ "ping_cost": c.ping_cost,
+ "edited_string": c.edited_string,
+ }
diff --git a/files/routes/errors.py b/files/routes/errors.py
index cca37fe81..f1c6dd4a3 100644
--- a/files/routes/errors.py
+++ b/files/routes/errors.py
@@ -67,5 +67,4 @@ def error_500(e):
def allow_nsfw():
session["over_18_cookies"] = int(time.time()) + 3600
redir = request.values.get("redir", "/")
- if is_site_url(redir): return redirect(redir)
- return redirect('/')
+ return '', 204
diff --git a/files/routes/front.py b/files/routes/front.py
index 58fc1b6e5..ce816c650 100644
--- a/files/routes/front.py
+++ b/files/routes/front.py
@@ -36,14 +36,17 @@ def front_all(v, sub=None):
if sub: defaultsorting = "new"
- sort=request.values.get("sort", defaultsorting)
- t=request.values.get('t', defaulttime)
+ sort = request.values.get("sort", defaultsorting)
+ t = request.values.get('t', defaulttime)
- try: gt=int(request.values.get("after", 0))
- except: gt=0
+ if SITE == 'rdrama.net' and t == 'all' and sort == 'hot' and page > 6000:
+ sort = 'top'
- try: lt=int(request.values.get("before", 0))
- except: lt=0
+ try: gt = int(request.values.get("after", 0))
+ except: gt = 0
+
+ try: lt = int(request.values.get("before", 0))
+ except: lt = 0
if sort == 'hot': default = True
else: default = False
@@ -77,7 +80,7 @@ def front_all(v, sub=None):
result = render_template("home.html", v=v, listing=posts, total=total, sort=sort, t=t, page=page, sub=sub, home=True, pins=pins, size=size)
if not v:
- cache.set(f'frontpage_{sort}_{t}_{page}_{sub}_{pins}', result, timeout=3600)
+ cache.set(f'frontpage_{sort}_{t}_{page}_{sub}_{pins}', result, timeout=900)
return result
@@ -161,12 +164,6 @@ def frontlist(v=None, sort="hot", page=1, t="all", ids_only=True, filter_words='
pins = pins.order_by(Post.created_utc.desc()).all()
posts = pins + posts
- if v and (time.time() - v.created_utc) > (364 * 86400):
- badge_grant(user=v, badge_id=134)
-
- if v and (time.time() - v.created_utc) > (729 * 86400):
- badge_grant(user=v, badge_id=237)
-
if ids_only: posts = [x.id for x in posts]
return posts, total, size
@@ -238,8 +235,8 @@ def comment_idlist(v=None, page=1, sort="new", t="day", gt=0, lt=0):
def all_comments(v):
page = get_page()
- sort=request.values.get("sort", "new")
- t=request.values.get("t", "hour")
+ sort = request.values.get("sort", "new")
+ t = request.values.get("t", "hour")
try: gt=int(request.values.get("after", 0))
except: gt=0
diff --git a/files/routes/groups.py b/files/routes/groups.py
index bc5d7e3eb..6734553d0 100644
--- a/files/routes/groups.py
+++ b/files/routes/groups.py
@@ -13,7 +13,7 @@ from files.__main__ import app, limiter
@auth_required
def ping_groups(v):
groups = g.db.query(Group).order_by(Group.created_utc).all()
- return render_template('groups.html', v=v, groups=groups, cost=GROUP_COST, msg=get_msg(), error=get_error())
+ return render_template('groups.html', v=v, groups=groups, cost=GROUP_COST)
@app.post("/create_group")
@limiter.limit('1/second', scope=rpath)
@@ -27,16 +27,16 @@ def create_group(v):
name = name.strip().lower()
if name.startswith('slots') or name.startswith('remindme'):
- return redirect(f"/ping_groups?error=You can't make a group with that name!")
+ abort(400, "You can't make a group with that name!")
if not valid_sub_regex.fullmatch(name):
- return redirect(f"/ping_groups?error=Name does not match the required format!")
+ abort(400, "Name does not match the required format!")
if name in {'everyone', 'jannies', 'followers'} or g.db.get(Group, name):
- return redirect(f"/ping_groups?error=This group already exists!")
+ abort(400, "This group already exists!")
if not v.charge_account('combined', GROUP_COST)[0]:
- return redirect(f"/ping_groups?error=You don't have enough coins or marseybux!")
+ abort(403, "You don't have enough coins or marseybux!")
g.db.add(v)
if v.shadowbanned: abort(500)
@@ -56,7 +56,7 @@ def create_group(v):
for admin in admins:
send_repeatable_notification(admin, f":!marseyparty: !{group} has been created by @{v.username} :marseyparty:")
- return redirect(f'/ping_groups?msg=!{group} created successfully!')
+ return {"message": f"!{group} created successfully!"}
@app.post("/!/apply")
@limiter.limit('1/second', scope=rpath)
@@ -201,6 +201,7 @@ def group_reject(v, group_name, user_id):
g.db.delete(membership)
+ g.db.flush()
count = g.db.query(GroupMembership).filter_by(group_name=group.name).count()
if not count:
g.db.commit() #need it to fix "Dependency rule tried to blank-out primary key column 'group_memberships.group_name' on instance"
diff --git a/files/routes/hats.py b/files/routes/hats.py
index 2f2321bc1..dc1c60f04 100644
--- a/files/routes/hats.py
+++ b/files/routes/hats.py
@@ -23,6 +23,8 @@ def hats(v):
else:
hats = g.db.query(HatDef)
+ hats = hats.filter(HatDef.submitter_id == None)
+
if sort and sort != "owners":
if sort == "name":
key = HatDef.name
@@ -49,13 +51,13 @@ def hats(v):
hats = hats[firstrange:secondrange]
else:
if v.equipped_hat_ids:
- equipped = hats.filter(HatDef.submitter_id == None, HatDef.id.in_(owned_hat_ids), HatDef.id.in_(v.equipped_hat_ids)).order_by(HatDef.price, HatDef.name).all()
- not_equipped = hats.filter(HatDef.submitter_id == None, HatDef.id.in_(owned_hat_ids), HatDef.id.notin_(v.equipped_hat_ids)).order_by(HatDef.price, HatDef.name).all()
+ equipped = hats.filter(HatDef.id.in_(owned_hat_ids), HatDef.id.in_(v.equipped_hat_ids)).order_by(HatDef.price, HatDef.name).all()
+ not_equipped = hats.filter(HatDef.id.in_(owned_hat_ids), HatDef.id.notin_(v.equipped_hat_ids)).order_by(HatDef.price, HatDef.name).all()
owned = equipped + not_equipped
else:
- owned = hats.filter(HatDef.submitter_id == None, HatDef.id.in_(owned_hat_ids)).order_by(HatDef.price, HatDef.name).all()
+ owned = hats.filter(HatDef.id.in_(owned_hat_ids)).order_by(HatDef.price, HatDef.name).all()
- not_owned = hats.filter(HatDef.submitter_id == None, HatDef.id.notin_(owned_hat_ids)).order_by(HatDef.price == 0, HatDef.price, HatDef.name).all()
+ not_owned = hats.filter(HatDef.id.notin_(owned_hat_ids)).order_by(HatDef.price == 0, HatDef.price, HatDef.name).all()
hats = owned + not_owned
firstrange = PAGE_SIZE * (page - 1)
@@ -74,9 +76,6 @@ def hats(v):
@limiter.limit('100/minute;1000/3 days', deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def buy_hat(v, hat_id):
- try: hat_id = int(hat_id)
- except: abort(404, "Hat not found!")
-
hat = g.db.query(HatDef).filter_by(submitter_id=None, id=hat_id).one_or_none()
if not hat: abort(404, "Hat not found!")
@@ -104,11 +103,11 @@ def buy_hat(v, hat_id):
f":marseycapitalistmanlet: @{v.username} has just bought `{hat.name}`, you have received your 10% cut ({int(hat.price * 0.1)} coins) :!marseycapitalistmanlet:"
)
- if v.num_of_owned_hats >= 250:
+ if v.num_of_owned_hats >= 249:
badge_grant(user=v, badge_id=154)
- elif v.num_of_owned_hats >= 100:
+ elif v.num_of_owned_hats >= 99:
badge_grant(user=v, badge_id=153)
- elif v.num_of_owned_hats >= 25:
+ elif v.num_of_owned_hats >= 24:
badge_grant(user=v, badge_id=152)
return {"message": f"'{hat.name}' bought!"}
@@ -121,9 +120,6 @@ def buy_hat(v, hat_id):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def equip_hat(v, hat_id):
- try: hat_id = int(hat_id)
- except: abort(404, "Hat not found!")
-
hat = g.db.query(Hat).filter_by(hat_id=hat_id, user_id=v.id).one_or_none()
if not hat: abort(403, "You don't own this hat!")
@@ -139,9 +135,6 @@ def equip_hat(v, hat_id):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def unequip_hat(v, hat_id):
- try: hat_id = int(hat_id)
- except: abort(404, "Hat not found!")
-
hat = g.db.query(Hat).filter_by(hat_id=hat_id, user_id=v.id).one_or_none()
if not hat: abort(403, "You don't own this hat!")
@@ -155,9 +148,6 @@ def unequip_hat(v, hat_id):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def hat_owners(v, hat_id):
- try: hat_id = int(hat_id)
- except: abort(404, "Hat not found!")
-
page = get_page()
users = g.db.query(User, Hat.created_utc).join(Hat.owners).filter(Hat.hat_id == hat_id)
diff --git a/files/routes/jinja2.py b/files/routes/jinja2.py
index 67813e8b9..8bf4ea4c3 100644
--- a/files/routes/jinja2.py
+++ b/files/routes/jinja2.py
@@ -4,6 +4,7 @@ from os import environ, listdir, path
from flask import g, session, has_request_context, request
from jinja2 import pass_context
+from PIL import ImageColor
from files.classes.user import User
from files.helpers.assetcache import assetcache_path
@@ -18,6 +19,10 @@ from files.__main__ import app, cache
from urllib.parse import parse_qs, urlencode, urlsplit
+@app.template_filter("rgb")
+def rgb(color):
+ return str(ImageColor.getcolor(f"#{color}", "RGB"))[1:-1]
+
@app.template_filter("formkey")
def formkey(u):
return get_formkey(u)
@@ -107,12 +112,11 @@ def inject_constants():
"DEFAULT_THEME":DEFAULT_THEME, "DESCRIPTION":DESCRIPTION,
"has_sidebar":has_sidebar, "has_logo":has_logo,
"FP":FP, "patron":patron, "get_setting": get_setting,
- "SIDEBAR_THREAD":SIDEBAR_THREAD, "BANNER_THREAD":BANNER_THREAD,
+ "SIDEBAR_THREAD":SIDEBAR_THREAD, "BANNER_THREAD":BANNER_THREAD, "BUG_THREAD":BUG_THREAD,
"BADGE_THREAD":BADGE_THREAD, "SNAPPY_THREAD":SNAPPY_THREAD, "CHANGELOG_THREAD":CHANGELOG_THREAD,
"approved_embed_hosts":approved_embed_hosts, "POST_BODY_LENGTH_LIMIT":POST_BODY_LENGTH_LIMIT,
"SITE_SETTINGS":get_settings(), "EMAIL":EMAIL, "max": max, "min": min, "user_can_see":User.can_see,
- "TELEGRAM_ID":TELEGRAM_ID, "EMAIL_REGEX_PATTERN":EMAIL_REGEX_PATTERN,
- "TRUESCORE_DONATE_MINIMUM":TRUESCORE_DONATE_MINIMUM, "PROGSTACK_ID":PROGSTACK_ID,
+ "TELEGRAM_ID":TELEGRAM_ID, "TRUESCORE_DONATE_MINIMUM":TRUESCORE_DONATE_MINIMUM, "PROGSTACK_ID":PROGSTACK_ID,
"DONATE_LINK":DONATE_LINK, "DONATE_SERVICE":DONATE_SERVICE,
"HOUSE_JOIN_COST":HOUSE_JOIN_COST, "HOUSE_SWITCH_COST":HOUSE_SWITCH_COST, "IMAGE_FORMATS":','.join(IMAGE_FORMATS),
"PAGE_SIZES":PAGE_SIZES, "THEMES":THEMES, "COMMENT_SORTS":COMMENT_SORTS, "SORTS":SORTS,
@@ -122,5 +126,8 @@ def inject_constants():
"BIO_FRIENDS_ENEMIES_LENGTH_LIMIT":BIO_FRIENDS_ENEMIES_LENGTH_LIMIT,
"IMMUNE_TO_AWARDS": IMMUNE_TO_AWARDS, "SITE_FULL_IMAGES": SITE_FULL_IMAGES,
"IS_FISTMAS":IS_FISTMAS, "IS_HOMOWEEN":IS_HOMOWEEN, "IS_DKD":IS_DKD, "IS_EVENT":IS_EVENT, "IS_BIRTHGAY":IS_BIRTHGAY,
- "CHUD_PHRASES":CHUD_PHRASES, "hasattr":hasattr, "calc_users":calc_users, "HOLE_INACTIVITY_DELETION":HOLE_INACTIVITY_DELETION
- }
+ "CHUD_PHRASES":CHUD_PHRASES, "hasattr":hasattr, "calc_users":calc_users, "HOLE_INACTIVITY_DELETION":HOLE_INACTIVITY_DELETION,
+ "MAX_IMAGE_AUDIO_SIZE_MB":MAX_IMAGE_AUDIO_SIZE_MB, "MAX_IMAGE_AUDIO_SIZE_MB_PATRON":MAX_IMAGE_AUDIO_SIZE_MB_PATRON,
+ "MAX_VIDEO_SIZE_MB":MAX_VIDEO_SIZE_MB, "MAX_VIDEO_SIZE_MB_PATRON":MAX_VIDEO_SIZE_MB_PATRON,
+ "CURSORMARSEY_DEFAULT":CURSORMARSEY_DEFAULT,
+ }
diff --git a/files/routes/login.py b/files/routes/login.py
index 75c8d4dcb..65ee3cb43 100644
--- a/files/routes/login.py
+++ b/files/routes/login.py
@@ -77,13 +77,13 @@ def login_post(v):
try:
if now - int(request.values.get("time")) > 600:
- return redirect('/login')
+ return render_template("login/login.html", failed=True, redirect=redir)
except:
abort(400)
formhash = request.values.get("hash")
if not validate_hash(f"{account.id}+{request.values.get('time')}+2fachallenge", formhash):
- return redirect("/login")
+ return render_template("login/login.html", failed=True, redirect=redir)
if not account.validate_2fa(request.values.get("2fa_token", "").strip()):
hash = generate_hash(f"{account.id}+{now}+2fachallenge")
@@ -105,7 +105,7 @@ def login_post(v):
return redirect('/')
def log_failed_admin_login_attempt(account, type):
- if not account or account.admin_level < PERMS['SITE_WARN_ON_INVALID_AUTH']: return
+ if not account or account.admin_level < PERMS['WARN_ON_FAILED_LOGIN']: return
ip = get_CF()
print(f"A site admin from {ip} failed to login to account @{account.user_name} (invalid {type})")
t = time.strftime("%d/%B/%Y %H:%M:%S UTC", time.gmtime(time.time()))
@@ -115,6 +115,7 @@ def on_login(account, redir=None):
session.permanent = True
session["lo_user"] = account.id
g.v = account
+ g.vid = account.username
session["login_nonce"] = account.login_nonce
check_for_alts(account, include_current_session=True)
@@ -278,6 +279,7 @@ def sign_up_post(v):
return signup_error("Invalid email!")
else: email = None
+ g.db.flush()
existing_account = get_user(username, graceful=True)
if existing_account:
return signup_error("An account with that username already exists!")
@@ -294,13 +296,14 @@ def sign_up_post(v):
x = requests.post(url, data=data, timeout=5)
- if not x.json().get("success"):
+ try:
+ if not x.json().get("success"):
+ return signup_error("Unable to verify captcha [2].")
+ except:
return signup_error("Unable to verify captcha [2].")
session.pop("signup_token")
- users_count = g.db.query(User).count()
-
profileurl = None
if PFP_DEFAULT_MARSEY:
profileurl = '/e/' + random.choice(marseys_const) + '.webp'
@@ -314,14 +317,14 @@ def sign_up_post(v):
profileurl=profileurl
)
- if users_count == 4:
- new_user.admin_level = 4
- session["history"] = []
-
g.db.add(new_user)
g.db.flush()
+ if new_user.id == 5:
+ new_user.admin_level = 4
+ session["history"] = []
+
if ref_id:
ref_user = get_account(ref_id)
@@ -340,6 +343,7 @@ def sign_up_post(v):
session.permanent = True
session["lo_user"] = new_user.id
g.v = new_user
+ g.vid = new_user.username
check_for_alts(new_user, include_current_session=True)
send_notification(new_user.id, WELCOME_MSG)
@@ -350,7 +354,6 @@ def sign_up_post(v):
g.db.add(new_follow)
signup_autofollow.stored_subscriber_count += 1
g.db.add(signup_autofollow)
- send_notification(signup_autofollow.id, f"A new user - @{new_user.username} - has followed you automatically!")
elif CARP_ID:
send_notification(CARP_ID, f"A new user - @{new_user.username} - has signed up!")
@@ -482,15 +485,15 @@ def lost_2fa(v):
@limiter.limit('1/second', scope=rpath)
@limiter.limit("6/minute;200/hour;1000/day", deduct_when=lambda response: response.status_code < 400)
def lost_2fa_post():
- username=request.values.get("username")
- user=get_user(username, graceful=True)
+ username = request.values.get("username")
+ user = get_user(username, graceful=True)
if not user or not user.email or not user.mfa_secret:
return render_template("message.html",
title="Removal request received",
message="If username, password, and email match, we will send you an email."), 202
- email=request.values.get("email").strip().lower()
+ email = request.values.get("email").strip().lower()
if not email_regex.fullmatch(email):
abort(400, "Invalid email")
@@ -501,10 +504,10 @@ def lost_2fa_post():
title="Removal request received",
message="If username, password, and email match, we will send you an email."), 202
- valid=int(time.time())
- token=generate_hash(f"{user.id}+{user.username}+disable2fa+{valid}+{user.mfa_secret}+{user.login_nonce}")
+ valid = int(time.time())
+ token = generate_hash(f"{user.id}+{user.username}+disable2fa+{valid}+{user.mfa_secret}+{user.login_nonce}")
- action_url=f"{SITE_FULL}/reset_2fa?id={user.id}&t={valid}&token={token}"
+ action_url = f"{SITE_FULL}/reset_2fa?id={user.id}&t={valid}&token={token}"
send_mail(to_address=user.email,
subject="Two-factor Authentication Removal Request",
@@ -520,7 +523,7 @@ def lost_2fa_post():
@app.get("/reset_2fa")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
def reset_2fa():
- now=int(time.time())
+ now = int(time.time())
t = request.values.get("t")
if not t: abort(400)
try:
@@ -531,10 +534,10 @@ def reset_2fa():
if now > t+3600*24:
abort(410, "This two-factor authentication reset link has expired!")
- token=request.values.get("token")
- uid=request.values.get("id")
+ token = request.values.get("token")
+ uid = request.values.get("id")
- user=get_account(uid)
+ user = get_account(uid)
if not validate_hash(f"{user.id}+{user.username}+disable2fa+{t}+{user.mfa_secret}+{user.login_nonce}", token):
abort(403)
diff --git a/files/routes/notifications.py b/files/routes/notifications.py
index bdfd11c59..1a31da1a5 100644
--- a/files/routes/notifications.py
+++ b/files/routes/notifications.py
@@ -9,6 +9,7 @@ from files.helpers.config.const import *
from files.helpers.config.modaction_types import *
from files.helpers.get import *
from files.routes.wrappers import *
+from files.routes.comments import _mark_comment_as_read
from files.__main__ import app
@app.post("/clear")
@@ -410,6 +411,8 @@ def notifications(v):
all_cids = set(all_cids)
output = get_comments_v_properties(v, None, Comment.id.in_(all_cids))[1]
+ g.db.flush()
+
if v.client: return {"data":[x.json for x in listing]}
return render_template("notifications.html",
@@ -420,3 +423,26 @@ def notifications(v):
standalone=True,
render_replies=True,
)
+
+
+@app.get("/notification//")
+@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@auth_required
+def notification(v, cid):
+ comment = get_comment(cid, v=v)
+
+ if not User.can_see(v, comment): abort(403)
+
+ comment.unread = True
+
+ gevent.spawn(_mark_comment_as_read, comment.id, v.id)
+
+ return render_template("notifications.html",
+ v=v,
+ notifications=[comment],
+ total=1,
+ page=1,
+ standalone=True,
+ render_replies=True,
+ )
diff --git a/files/routes/oauth.py b/files/routes/oauth.py
index 0ba6d537f..421b281c5 100644
--- a/files/routes/oauth.py
+++ b/files/routes/oauth.py
@@ -31,13 +31,8 @@ def authorize(v):
return {"oauth_error": "Invalid `client_id`"}, 400
access_token = secrets.token_urlsafe(128)[:128]
- try:
- new_auth = ClientAuth(oauth_client = application.id, user_id = v.id, access_token=access_token)
- g.db.add(new_auth)
- except sqlalchemy.exc.IntegrityError:
- g.db.rollback()
- old_auth = g.db.query(ClientAuth).filter_by(oauth_client = application.id, user_id = v.id).one()
- access_token = old_auth.access_token
+ new_auth = ClientAuth(oauth_client = application.id, user_id = v.id, access_token=access_token)
+ g.db.add(new_auth)
return redirect(f"{application.redirect_uri}?token={access_token}")
@@ -93,9 +88,9 @@ def request_api_keys(v):
notif = Notification(comment_id=new_comment.id, user_id=admin_id)
g.db.add(notif)
- push_notif(admin_ids, 'New notification', body, f'{SITE_FULL}/comment/{new_comment.id}?read=true#context')
+ push_notif(admin_ids, 'New notification', body, f'{SITE_FULL}/admin/apps')
- return redirect('/settings/apps')
+ return {"message": "API keys requested successfully!"}
@app.post("/delete_app/")
@@ -145,8 +140,7 @@ def edit_oauth_app(v, aid):
g.db.add(app)
-
- return redirect('/settings/apps')
+ return {"message": "App edited successfully!"}
@app.post("/admin/app/approve/")
diff --git a/files/routes/polls.py b/files/routes/polls.py
index 50b4f9b1a..391d37dbc 100644
--- a/files/routes/polls.py
+++ b/files/routes/polls.py
@@ -43,6 +43,7 @@ def vote_option(option_id, v):
for x in vote:
g.db.delete(x)
+ g.db.flush()
existing = g.db.query(PostOptionVote).filter_by(option_id=option_id, user_id=v.id).one_or_none()
if not existing:
vote = PostOptionVote(
@@ -98,6 +99,7 @@ def vote_option_comment(option_id, v):
for x in vote:
g.db.delete(x)
+ g.db.flush()
existing = g.db.query(CommentOptionVote).filter_by(option_id=option_id, user_id=v.id).one_or_none()
if not existing:
vote = CommentOptionVote(
diff --git a/files/routes/posts.py b/files/routes/posts.py
index 86eaf916c..a3e52bb83 100644
--- a/files/routes/posts.py
+++ b/files/routes/posts.py
@@ -17,6 +17,7 @@ from files.helpers.actions import *
from files.helpers.alerts import *
from files.helpers.config.const import *
from files.helpers.get import *
+from files.helpers.sharpen import *
from files.helpers.regex import *
from files.helpers.sanitize import *
from files.helpers.settings import get_setting
@@ -198,14 +199,12 @@ def post_id(pid, v, anything=None, sub=None):
return result
-@app.get("/view_more///")
+@app.get("/view_more///")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@auth_desired_with_logingate
def view_more(v, pid, sort, offset):
p = get_post(pid, v=v)
- try:
- offset = int(offset)
- except: abort(400)
+
try: ids = set(int(x) for x in request.values.get("ids").split(','))
except: abort(400)
@@ -255,9 +254,6 @@ def view_more(v, pid, sort, offset):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@auth_desired_with_logingate
def more_comments(v, cid):
- try: cid = int(cid)
- except: abort(404)
-
tcid = g.db.query(Comment.top_comment_id).filter_by(id=cid).one_or_none()[0]
if v:
@@ -411,8 +407,8 @@ def is_repost(v):
@app.post("/h//submit")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
-@limiter.limit(POST_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
-@limiter.limit(POST_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@limiter.limit('20/day', deduct_when=lambda response: response.status_code < 400)
+@limiter.limit('20/day', deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@is_not_banned
def submit_post(v, sub=None):
url = request.values.get("url", "").strip()
@@ -432,6 +428,9 @@ def submit_post(v, sub=None):
if SITE == 'rdrama.net' and (v.chud == 1 or v.id == 253):
sub = 'chudrama'
+
+ if SITE == 'rdrama.net' and v.id == 10947:
+ sub = 'mnn'
title_html = filter_emojis_only(title, graceful=True, count_emojis=True)
@@ -510,7 +509,10 @@ def submit_post(v, sub=None):
body = process_files(request.files, v, body)
body = body[:POST_BODY_LENGTH_LIMIT(v)].strip() # process_files() adds content to the body, so we need to re-strip
- body_html = sanitize(body, count_emojis=True, limit_pings=100)
+ body_for_sanitize = body
+ if v.sharpen: body_for_sanitize = sharpen(body_for_sanitize)
+
+ body_html = sanitize(body_for_sanitize, count_emojis=True, limit_pings=100)
if v.marseyawarded and marseyaward_body_regex.search(body_html):
abort(400, "You can only type marseys!")
@@ -553,6 +555,9 @@ def submit_post(v, sub=None):
sub=sub,
ghost=flag_ghost,
chudded=flag_chudded,
+ rainbowed=bool(v.rainbow),
+ queened=bool(v.queen),
+ sharpened=bool(v.sharpen),
)
g.db.add(p)
@@ -647,13 +652,13 @@ def submit_post(v, sub=None):
autojanny.comment_count += 1
g.db.add(autojanny)
- v.post_count = g.db.query(Post).filter_by(author_id=v.id, deleted_utc=0).count()
+ v.post_count += 1
g.db.add(v)
execute_lawlz_actions(v, p)
if (SITE == 'rdrama.net'
- and v.id in {TGTW_ID, SNALLY_ID}
+ and v.id in {2008, 3336}
and not (p.sub and p.subr.stealth)) and p.sub != 'slavshit' and not p.ghost:
p.stickied_utc = int(time.time()) + 28800
p.stickied = "AutoJanny"
@@ -665,14 +670,10 @@ def submit_post(v, sub=None):
cache.delete_memoized(frontlist)
cache.delete_memoized(userpagelisting)
- key_pattern = app.config["CACHE_KEY_PREFIX"] + 'frontpage_*'
- for key in redis_instance.scan_iter(key_pattern):
- redis_instance.delete(key)
-
if not p.private:
execute_snappy(p, v)
- g.db.commit() #Necessary, do NOT remove
+ g.db.flush() #Necessary, do NOT remove
if not p.thumburl and p.url and p.domain != SITE:
gevent.spawn(thumbnail_thread, p.url, p.id)
@@ -682,11 +683,11 @@ def submit_post(v, sub=None):
p.voted = 1
return {"post_id": p.id, "success": True}
-@app.post("/delete_post/")
+@app.post("/delete/post/")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def delete_post_pid(pid, v):
p = get_post(pid)
@@ -702,7 +703,7 @@ def delete_post_pid(pid, v):
cache.delete_memoized(frontlist)
cache.delete_memoized(userpagelisting)
- v.post_count = g.db.query(Post).filter_by(author_id=v.id, deleted_utc=0).count()
+ v.post_count -= 1
g.db.add(v)
for sort in COMMENT_SORTS:
@@ -727,7 +728,7 @@ def undelete_post_pid(pid, v):
cache.delete_memoized(frontlist)
cache.delete_memoized(userpagelisting)
- v.post_count = g.db.query(Post).filter_by(author_id=v.id, deleted_utc=0).count()
+ v.post_count += 1
g.db.add(v)
for sort in COMMENT_SORTS:
@@ -746,7 +747,7 @@ def undelete_post_pid(pid, v):
def mark_post_nsfw(pid, v):
p = get_post(pid)
- if p.author_id != v.id and not v.admin_level >= PERMS['POST_COMMENT_MODERATION'] and not (p.sub and v.mods(p.sub)):
+ if p.author_id != v.id and v.admin_level < PERMS['POST_COMMENT_MODERATION'] and not (p.sub and v.mods(p.sub)):
abort(403)
if p.over_18 and v.is_permabanned:
@@ -785,7 +786,7 @@ def mark_post_nsfw(pid, v):
def unmark_post_nsfw(pid, v):
p = get_post(pid)
- if p.author_id != v.id and not v.admin_level >= PERMS['POST_COMMENT_MODERATION'] and not (p.sub and v.mods(p.sub)):
+ if p.author_id != v.id and v.admin_level < PERMS['POST_COMMENT_MODERATION'] and not (p.sub and v.mods(p.sub)):
abort(403)
if p.over_18 and v.is_permabanned:
@@ -868,7 +869,7 @@ def pin_post(post_id, v):
else: return {"message": "Post unpinned!"}
return abort(404, "Post not found!")
-@app.put("/post//new")
+@app.post("/post//new")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
@@ -890,7 +891,7 @@ def set_new_sort(post_id, v):
return {"message": "Changed the the default sorting of comments on this post to 'new'"}
-@app.delete("/post//new")
+@app.post("/post//hot")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
@@ -949,8 +950,8 @@ def get_post_title(v):
@app.post("/edit_post/")
@limiter.limit('1/second', scope=rpath)
@limiter.limit('1/second', scope=rpath, key_func=get_ID)
-@limiter.limit("10/minute;100/hour;200/day", deduct_when=lambda response: response.status_code < 400)
-@limiter.limit("10/minute;100/hour;200/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+@limiter.limit(DELETE_EDIT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@is_not_permabanned
def edit_post(pid, v):
p = get_post(pid)
@@ -1004,7 +1005,10 @@ def edit_post(pid, v):
body = body[:POST_BODY_LENGTH_LIMIT(v)].strip() # process_files() may be adding stuff to the body
if body != p.body:
- body_html = sanitize(body, golden=False, limit_pings=100)
+ body_for_sanitize = body
+ if p.sharpened: body_for_sanitize = sharpen(body_for_sanitize)
+
+ body_html = sanitize(body_for_sanitize, golden=False, limit_pings=100)
if v.id == p.author_id and v.marseyawarded and marseyaward_body_regex.search(body_html):
abort(403, "You can only type marseys!")
@@ -1028,8 +1032,8 @@ def edit_post(pid, v):
if v.id == p.author_id:
- if int(time.time()) - p.created_utc > 60 * 3: p.edited_utc = int(time.time())
- g.db.add(p)
+ if int(time.time()) - p.created_utc > 60 * 3:
+ p.edited_utc = int(time.time())
else:
ma=ModAction(
kind="edit_post",
diff --git a/files/routes/reporting.py b/files/routes/reporting.py
index 076de5b50..7c4129d41 100644
--- a/files/routes/reporting.py
+++ b/files/routes/reporting.py
@@ -113,10 +113,6 @@ def report_comment(cid, v):
@limiter.limit("100/minute;300/hour;2000/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@admin_level_required(PERMS['REPORTS_REMOVE'])
def remove_report_post(v, pid, uid):
- try:
- pid = int(pid)
- uid = int(uid)
- except: abort(404)
report = g.db.query(Report).filter_by(post_id=pid, user_id=uid).one_or_none()
if report:
@@ -139,10 +135,6 @@ def remove_report_post(v, pid, uid):
@limiter.limit("100/minute;300/hour;2000/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@admin_level_required(PERMS['REPORTS_REMOVE'])
def remove_report_comment(v, cid, uid):
- try:
- cid = int(cid)
- uid = int(uid)
- except: abort(404)
report = g.db.query(CommentReport).filter_by(comment_id=cid, user_id=uid).one_or_none()
if report:
@@ -174,6 +166,8 @@ def move_post(post, v, reason):
if not can_move_post: return False
if sub_to == None:
+ if HOLE_REQUIRED:
+ abort(403, "All posts are required to be flaired!")
sub_to_in_notif = 'the main feed'
else:
sub_to_in_notif = f'/h/{sub_to}'
@@ -206,7 +200,7 @@ def move_post(post, v, reason):
sub_to_str = 'main feed' if sub_to is None else \
f'/h/{sub_to}'
- if v.admin_level:
+ if v.admin_level >= PERMS['POST_COMMENT_MODERATION']:
ma = ModAction(
kind='move_hole',
user_id=v.id,
diff --git a/files/routes/routehelpers.py b/files/routes/routehelpers.py
index dce5696e3..477babf4b 100644
--- a/files/routes/routehelpers.py
+++ b/files/routes/routehelpers.py
@@ -51,9 +51,14 @@ def get_alt_graph(uid):
return g.db.query(User).filter(User.id.in_(alt_ids)).order_by(User.username).all()
def add_alt(user1, user2):
+ if session.get("GLOBAL"):
+ return
+
if AEVANN_ID in (user1, user2) or CARP_ID in (user1, user2):
return
li = [user1, user2]
+
+ g.db.flush()
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if not existing:
new_alt = Alt(user1=user1, user2=user2)
@@ -63,6 +68,9 @@ def add_alt(user1, user2):
cache.delete_memoized(get_alt_graph_ids, user2)
def check_for_alts(current, include_current_session=False):
+ if session.get("GLOBAL"):
+ return
+
current_id = current.id
ids = [x[0] for x in g.db.query(User.id)]
past_accs = set(session.get("history", [])) if include_current_session else set()
diff --git a/files/routes/search.py b/files/routes/search.py
index 8c5ee94b9..fb77214bf 100644
--- a/files/routes/search.py
+++ b/files/routes/search.py
@@ -59,7 +59,7 @@ def searchposts(v):
sort = request.values.get("sort", "new").lower()
t = request.values.get('t', 'all').lower()
- criteria=searchparse(query)
+ criteria = searchparse(query)
posts = g.db.query(Post).options(load_only(Post.id)) \
.join(Post.author) \
diff --git a/files/routes/settings.py b/files/routes/settings.py
index 4c6240471..23fc3033a 100644
--- a/files/routes/settings.py
+++ b/files/routes/settings.py
@@ -38,9 +38,9 @@ def settings(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def settings_personal(v):
- return render_template("settings/personal.html", v=v, error=get_error(), msg=get_msg())
+ return render_template("settings/personal.html", v=v, error=get_error())
-@app.delete('/settings/background')
+@app.post('/settings/remove_background')
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
@@ -110,7 +110,7 @@ def settings_personal_post(v):
request_flag = int(time.time())
setattr(v, column_name, request_flag)
if badge_id: badge_grant(v, badge_id)
- return render_template("settings/personal.html", v=v, msg=f"You have set the {friendly_name} permanently! Enjoy your new badge!")
+ return {"message": f"You have set the {friendly_name} permanently! Enjoy your new badge!"}
elif current_value != request_flag:
setattr(v, column_name, request_flag)
return True
@@ -133,7 +133,7 @@ def settings_personal_post(v):
updated = True
elif request.values.get("reddit", v.reddit) != v.reddit:
reddit = request.values.get("reddit")
- if reddit in {'old.reddit.com', 'reddit.com', 'i.reddit.com', 'reddit.lol', 'libredd.it'}:
+ if reddit in {'old.reddit.com', 'reddit.com', 'i.reddit.com', 'reddit.lol', 'libreddit.hu'}:
updated = True
v.reddit = reddit
elif request.values.get("poor", v.poor) != v.poor:
@@ -199,43 +199,43 @@ def settings_personal_post(v):
v.bio = None
v.bio_html = None
g.db.add(v)
- return render_template("settings/personal.html", v=v, msg="Your bio has been updated!")
+ return {"message": "Your bio has been updated."}
elif not updated and request.values.get("sig") == "":
v.sig = None
v.sig_html = None
g.db.add(v)
- return render_template("settings/personal.html", v=v, msg="Your sig has been updated!")
+ return {"message": "Your sig has been updated."}
elif not updated and request.values.get("friends") == "":
v.friends = None
v.friends_html = None
g.db.add(v)
- return render_template("settings/personal.html", v=v, msg="Your friends list has been updated!")
+ return {"message": "Your friends list has been updated."}
elif not updated and request.values.get("enemies") == "":
v.enemies = None
v.enemies_html = None
g.db.add(v)
- return render_template("settings/personal.html", v=v, msg="Your enemies list has been updated!")
+ return {"message": "Your enemies list has been updated."}
elif not updated and request.values.get("sig"):
if not v.patron:
abort(403, f"Signatures are only available to {patron}s!")
sig = request.values.get("sig")[:200].replace('\n','').replace('\r','')
+
+ sig = process_files(request.files, v, sig)
+ sig = sig[:200].strip() # process_files potentially adds characters to the post
+
sig_html = sanitize(sig, blackjack="signature")
if len(sig_html) > 1000:
- return render_template("settings/personal.html",
- v=v,
- error="Your sig is too long")
+ abort(400, "Your sig is too long")
- v.sig = sig[:200]
+ v.sig = sig
v.sig_html=sig_html
g.db.add(v)
- return render_template("settings/personal.html",
- v=v,
- msg="Your sig has been updated.")
+ return {"message": "Your sig has been updated."}
elif not updated and FEATURES['USERS_PROFILE_BODYTEXT'] and request.values.get("friends"):
friends = request.values.get("friends")[:BIO_FRIENDS_ENEMIES_LENGTH_LIMIT]
@@ -243,9 +243,7 @@ def settings_personal_post(v):
friends_html = sanitize(friends, blackjack="friends")
if len(friends_html) > BIO_FRIENDS_ENEMIES_HTML_LENGTH_LIMIT:
- return render_template("settings/personal.html",
- v=v,
- error="Your friends list is too long")
+ abort(400, "Your friends list is too long")
friends = friends[:BIO_FRIENDS_ENEMIES_LENGTH_LIMIT]
@@ -257,14 +255,12 @@ def settings_personal_post(v):
alert_everyone(cid)
else:
for x in notify_users:
- add_notif(cid, x, text)
+ add_notif(cid, x, text, pushnotif_url=f'{SITE_FULL}{v.url}')
v.friends = friends
v.friends_html=friends_html
g.db.add(v)
- return render_template("settings/personal.html",
- v=v,
- msg="Your friends list has been updated.")
+ return {"message": "Your friends list has been updated."}
elif not updated and FEATURES['USERS_PROFILE_BODYTEXT'] and request.values.get("enemies"):
@@ -273,9 +269,7 @@ def settings_personal_post(v):
enemies_html = sanitize(enemies, blackjack="enemies")
if len(enemies_html) > BIO_FRIENDS_ENEMIES_HTML_LENGTH_LIMIT:
- return render_template("settings/personal.html",
- v=v,
- error="Your enemies list is too long")
+ abort(400, "Your enemies list is too long")
enemies = enemies[:BIO_FRIENDS_ENEMIES_LENGTH_LIMIT]
@@ -287,14 +281,12 @@ def settings_personal_post(v):
alert_everyone(cid)
else:
for x in notify_users:
- add_notif(cid, x, text)
+ add_notif(cid, x, text, pushnotif_url=f'{SITE_FULL}{v.url}')
v.enemies = enemies
v.enemies_html=enemies_html
g.db.add(v)
- return render_template("settings/personal.html",
- v=v,
- msg="Your enemies list has been updated.")
+ return {"message": "Your enemies list has been updated."}
elif not updated and FEATURES['USERS_PROFILE_BODYTEXT'] and \
@@ -305,17 +297,13 @@ def settings_personal_post(v):
bio_html = sanitize(bio, blackjack="bio")
if len(bio_html) > BIO_FRIENDS_ENEMIES_HTML_LENGTH_LIMIT:
- return render_template("settings/personal.html",
- v=v,
- error="Your bio is too long")
+ abort(400, "Your bio is too long")
v.bio = bio[:BIO_FRIENDS_ENEMIES_LENGTH_LIMIT]
v.bio_html=bio_html
g.db.add(v)
- return render_template("settings/personal.html",
- v=v,
- msg="Your bio has been updated.")
+ return {"message": "Your bio has been updated."}
frontsize = request.values.get("frontsize")
@@ -372,17 +360,18 @@ def settings_personal_post(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def filters(v):
- filters=request.values.get("filters")[:1000].strip()
+ filters = request.values.get("filters")[:1000].strip()
if filters == v.custom_filter_list:
- return redirect("/settings/advanced?error=You didn't change anything!")
+ abort(400, "You didn't change anything!")
v.custom_filter_list=filters
g.db.add(v)
- return redirect("/settings/advanced?msg=Your custom filters have been updated!")
+ return {"message": "Your custom filters have been updated!"}
-def set_color(v, attr, color):
+def set_color(v, attr):
+ color = request.values.get(attr)
current = getattr(v, attr)
color = color.strip().lower() if color else None
if color:
@@ -402,7 +391,7 @@ def set_color(v, attr, color):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def namecolor(v):
- return set_color(v, "namecolor", request.values.get("namecolor"))
+ return set_color(v, "namecolor")
@app.post("/settings/themecolor")
@limiter.limit('1/second', scope=rpath)
@@ -411,7 +400,7 @@ def namecolor(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def themecolor(v):
- return set_color(v, "themecolor", request.values.get("themecolor"))
+ return set_color(v, "themecolor")
@app.post("/settings/titlecolor")
@limiter.limit('1/second', scope=rpath)
@@ -420,7 +409,7 @@ def themecolor(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def titlecolor(v):
- return set_color(v, "titlecolor", request.values.get("titlecolor"))
+ return set_color(v, "titlecolor")
@app.post("/settings/verifiedcolor")
@limiter.limit('1/second', scope=rpath)
@@ -430,7 +419,7 @@ def titlecolor(v):
@auth_required
def verifiedcolor(v):
if not v.verified: abort(403, "You don't have a checkmark to edit its color!")
- return set_color(v, "verifiedcolor", request.values.get("verifiedcolor"))
+ return set_color(v, "verifiedcolor")
@app.post("/settings/security")
@limiter.limit('1/second', scope=rpath)
@@ -441,18 +430,18 @@ def verifiedcolor(v):
def settings_security_post(v):
if request.values.get("new_password"):
if request.values.get("new_password") != request.values.get("cnf_password"):
- return render_template("settings/security.html", v=v, error="Passwords do not match!")
+ abort(400, "Passwords do not match!")
if not valid_password_regex.fullmatch(request.values.get("new_password")):
- return render_template("settings/security.html", v=v, error="Password must be between 8 and 100 characters!")
+ abort(400, "Password must be between 8 and 100 characters!")
if not v.verifyPass(request.values.get("old_password")):
- return render_template("settings/security.html", v=v, error="Incorrect password")
+ abort(400, "Incorrect password")
v.passhash = hash_password(request.values.get("new_password"))
g.db.add(v)
- return render_template("settings/security.html", v=v, msg="Your password has been changed!")
+ return {"message": "Your password has been changed successfully!"}
if request.values.get("new_email"):
if not v.verifyPass(request.values.get('password')):
@@ -483,29 +472,29 @@ def settings_security_post(v):
if request.values.get("2fa_token"):
if not v.verifyPass(request.values.get('password')):
- return render_template("settings/security.html", v=v, error="Invalid password!")
+ abort(400, "Invalid password!")
secret = request.values.get("2fa_secret")
x = pyotp.TOTP(secret)
if not x.verify(request.values.get("2fa_token"), valid_window=1):
- return render_template("settings/security.html", v=v, error="Invalid token!")
+ abort(400, "Invalid token!")
v.mfa_secret = secret
g.db.add(v)
- return render_template("settings/security.html", v=v, msg="Two-factor authentication enabled!")
+ return {"message": "Two-factor authentication enabled!"}
if request.values.get("2fa_remove"):
if not v.verifyPass(request.values.get('password')):
- return render_template("settings/security.html", v=v, error="Invalid password!")
+ abort(400, "Invalid password!")
token = request.values.get("2fa_remove")
if not token or not v.validate_2fa(token):
- return render_template("settings/security.html", v=v, error="Invalid token!")
+ abort(400, "Invalid token!")
v.mfa_secret = None
g.db.add(v)
- return render_template("settings/security.html", v=v, msg="Two-factor authentication disabled!")
+ return {"message": "Two-factor authentication disabled!"}
@app.post("/settings/log_out_all_others")
@limiter.limit('1/second', scope=rpath)
@@ -516,13 +505,13 @@ def settings_security_post(v):
def settings_log_out_others(v):
submitted_password = request.values.get("password", "").strip()
if not v.verifyPass(submitted_password):
- return redirect("/settings/security?error=Incorrect password!")
+ abort(400, "Incorrect password!")
v.login_nonce += 1
session["login_nonce"] = v.login_nonce
g.db.add(v)
- return redirect("/settings/security?msg=All other devices have been logged out!")
+ return {"message": "All other devices have been logged out!"}
@app.post("/settings/images/profile")
@@ -611,6 +600,7 @@ def settings_images_profile_background(v):
remove_media_using_link(v.profile_background)
v.profile_background = profile_background
g.db.add(v)
+ badge_grant(badge_id=193, user=v)
return redirect("/settings/personal?msg=Profile background successfully updated!")
@@ -619,7 +609,7 @@ def settings_images_profile_background(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def settings_css_get(v):
- return render_template("settings/css.html", v=v, msg=get_msg(), profilecss=v.profilecss)
+ return render_template("settings/css.html", v=v, profilecss=v.profilecss)
@app.post("/settings/css")
@limiter.limit('1/second', scope=rpath)
@@ -628,12 +618,12 @@ def settings_css_get(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def settings_css(v):
- if v.chud: abort(400, "Chuded users can't edit CSS!")
+ if v.chud:
+ abort(400, "Chudded users can't edit CSS!")
css = request.values.get("css", v.css).strip().replace('\\', '')[:CSS_LENGTH_LIMIT].strip()
v.css = css
g.db.add(v)
-
- return render_template("settings/css.html", v=v, msg="Custom CSS successfully updated!", profilecss=v.profilecss)
+ return {"message": "Custom CSS successfully updated!"}
@app.post("/settings/profilecss")
@limiter.limit('1/second', scope=rpath)
@@ -645,10 +635,10 @@ def settings_profilecss(v):
profilecss = request.values.get("profilecss", v.profilecss).replace('\\', '')[:CSS_LENGTH_LIMIT].strip()
valid, error = validate_css(profilecss)
if not valid:
- return render_template("settings/css.html", error=error, v=v, profilecss=profilecss)
+ abort(400, error)
v.profilecss = profilecss
g.db.add(v)
- return redirect("/settings/css?msg=Profile CSS successfully updated!")
+ return {"message": "Profile CSS successfully updated!"}
@app.get("/settings/security")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@@ -659,8 +649,6 @@ def settings_security(v):
v=v,
mfa_secret=pyotp.random_base32() if not v.mfa_secret else None,
now=int(time.time()),
- error=get_error(),
- msg=get_msg()
)
@app.get("/settings/blocks")
@@ -726,7 +714,7 @@ def settings_apps(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def settings_advanced_get(v):
- return render_template("settings/advanced.html", v=v, msg=get_msg(), error=get_error())
+ return render_template("settings/advanced.html", v=v)
@app.post("/settings/name_change")
@limiter.limit('1/second', scope=rpath)
@@ -742,12 +730,10 @@ def settings_name_change(v):
if v.shadowbanned: abort(500)
- new_name=request.values.get("name").strip()
+ new_name = request.values.get("name").strip()
if new_name==v.username:
- return render_template("settings/personal.html",
- v=v,
- error="You didn't change anything")
+ abort(400, "You didn't change anything")
if v.patron:
used_regex = valid_username_patron_regex
@@ -755,9 +741,7 @@ def settings_name_change(v):
used_regex = valid_username_regex
if not used_regex.fullmatch(new_name):
- return render_template("settings/personal.html",
- v=v,
- error="This isn't a valid username.")
+ abort(400, "This isn't a valid username.")
search_name = new_name.replace('\\', '').replace('_','\_').replace('%','')
@@ -770,15 +754,13 @@ def settings_name_change(v):
).one_or_none()
if x and x.id != v.id:
- return render_template("settings/personal.html",
- v=v,
- error=f"Username `{new_name}` is already in use.")
+ abort(400, f"Username `{new_name}` is already in use.")
v.username = new_name
v.name_changed_utc = int(time.time())
g.db.add(v)
- return redirect("/settings/personal?msg=Name successfully changed!")
+ return {"message": "Name successfully changed!"}
@app.post("/settings/song_change_mp3")
@feature_required('USERS_PROFILE_SONG')
@@ -853,7 +835,7 @@ def _change_song_youtube(vid, id):
@limiter.limit("10/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def settings_song_change(v):
- song=request.values.get("song").strip()
+ song = request.values.get("song").strip()
if song == "" and v.song:
if path.isfile(f"/songs/{v.song}.mp3") and g.db.query(User).filter_by(song=v.song).count() == 1:
@@ -883,7 +865,12 @@ def settings_song_change(v):
if YOUTUBE_KEY != DEFAULT_CONFIG_VALUE:
req = requests.get(f"https://www.googleapis.com/youtube/v3/videos?id={id}&key={YOUTUBE_KEY}&part=contentDetails", timeout=5).json()
- duration = req['items'][0]['contentDetails']['duration']
+
+ try:
+ duration = req['items'][0]['contentDetails']['duration']
+ except:
+ return redirect("/settings/personal?error=Anthem change failed, please try another video!")
+
if duration == 'P0D':
return redirect("/settings/personal?error=Can't use a live youtube video!")
@@ -900,17 +887,17 @@ def settings_song_change(v):
return redirect("/settings/personal?msg=Profile Anthem successfully updated. Wait 5 minutes for the change to take effect.")
-def process_settings_plaintext(value, current, length):
+def process_settings_plaintext(value, current, length, default_value):
value = request.values.get(value, "").strip()
if not value:
- return redirect("/settings/personal?error=You didn't enter anything!")
+ return default_value
if len(value) > 100:
- return redirect("/settings/personal?error=The value you entered exceeds the character limit (100 characters)")
+ abort(400, "The value you entered exceeds the character limit (100 characters)")
if value == current:
- return redirect("/settings/personal?error=You didn't change anything!")
+ abort(400, "You didn't change anything!")
return value
@@ -924,21 +911,22 @@ def process_settings_plaintext(value, current, length):
def settings_title_change(v):
if v.flairchanged: abort(403)
- processed = process_settings_plaintext("title", v.customtitleplain, 100)
- if not isinstance(processed, str):
- return processed
+ customtitleplain = process_settings_plaintext("title", v.customtitleplain, 100, None)
- customtitle = filter_emojis_only(processed)
- customtitle = censor_slurs(customtitle, None)
+ if customtitleplain:
+ customtitle = filter_emojis_only(customtitleplain)
+ customtitle = censor_slurs(customtitle, None)
- if len(customtitle) > 1000:
- return redirect("/settings/personal?error=Flair too long!")
+ if len(customtitle) > 1000:
+ abort(400, "Flair too long!")
+ else:
+ customtitle = None
- v.customtitleplain = processed
+ v.customtitleplain = customtitleplain
v.customtitle = customtitle
g.db.add(v)
- return redirect("/settings/personal?msg=Flair successfully updated!")
+ return {"message": "Flair successfully updated!"}
@app.post("/settings/pronouns_change")
@@ -949,13 +937,10 @@ def settings_title_change(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def settings_pronouns_change(v):
- processed = process_settings_plaintext("pronouns", v.pronouns, 15)
- if not isinstance(processed, str):
- return processed
+ pronouns = process_settings_plaintext("pronouns", v.pronouns, 15, "they/them")
- pronouns = processed
if not pronouns_regex.fullmatch(pronouns):
- return redirect("/settings/personal?error=The pronouns you entered don't match the required format!")
+ abort(400, "The pronouns you entered don't match the required format!")
bare_pronouns = pronouns.lower().replace('/', '')
if 'nig' in bare_pronouns: pronouns = 'BI/POC'
@@ -964,7 +949,7 @@ def settings_pronouns_change(v):
v.pronouns = pronouns
g.db.add(v)
- return redirect("/settings/personal?msg=Pronouns successfully updated!")
+ return {"message": "Pronouns successfully updated!"}
@app.post("/settings/checkmark_text")
@@ -977,10 +962,6 @@ def settings_checkmark_text(v):
if not v.verified:
abort(403, "You don't have a checkmark to edit its hover text!")
- processed = process_settings_plaintext("checkmark-text", v.verified, 100)
- if not isinstance(processed, str):
- return processed
-
- v.verified = processed
+ v.verified = process_settings_plaintext("checkmark-text", v.verified, 100, "Verified")
g.db.add(v)
- return redirect("/settings/personal?msg=Checkmark Text successfully updated!")
+ return {"message": "Checkmark Text successfully updated!"}
diff --git a/files/routes/special.py b/files/routes/special.py
index 0f7ac83dd..d3ad71a2d 100644
--- a/files/routes/special.py
+++ b/files/routes/special.py
@@ -1,96 +1,96 @@
-from flask import g, render_template
-from sqlalchemy.sql import text
+# from flask import g, render_template
+# from sqlalchemy.sql import text
-from files.helpers.get import get_accounts_dict
-from files.helpers.config.const import *
+# from files.helpers.get import get_accounts_dict
+# from files.helpers.config.const import *
-from files.routes.wrappers import *
+# from files.routes.wrappers import *
-from files.__main__ import app, cache, limiter
+# from files.__main__ import app, cache, limiter
-_special_leaderboard_query = text("""
-WITH bet_options AS (
- SELECT p.id AS parent_id, so.id AS option_id, so.exclusive, cnt.count
- FROM post_options so
- JOIN posts p ON so.parent_id = p.id
- JOIN (
- SELECT option_id, COUNT(*) FROM post_option_votes
- GROUP BY option_id
- ) AS cnt ON so.id = cnt.option_id
- WHERE p.author_id = 7465 AND p.created_utc > 1688950032
- AND so.exclusive IN (2, 3) AND p.title ilike 'women''s world cup betting: %'
-),
-post_payouts AS (
- SELECT
- sq_total.parent_id,
- sq_winners.sum AS bettors,
- floor((sq_total.sum * 200) / sq_winners.sum) AS winner_payout
- FROM (
- SELECT parent_id, SUM(count)
- FROM bet_options GROUP BY parent_id
- ) AS sq_total
- JOIN (
- SELECT parent_id, SUM(count)
- FROM bet_options WHERE exclusive = 3 GROUP BY parent_id
- ) AS sq_winners ON sq_total.parent_id = sq_winners.parent_id
-),
-bet_votes AS (
- SELECT
- opt.option_id AS option_id,
- opt.exclusive,
- sov.user_id,
- CASE
- WHEN opt.exclusive = 2 THEN -200
- WHEN opt.exclusive = 3 THEN (post_payouts.winner_payout - 200)
- END payout
- FROM post_option_votes sov
- LEFT OUTER JOIN bet_options AS opt
- ON opt.option_id = sov.option_id
- LEFT OUTER JOIN post_payouts
- ON opt.parent_id = post_payouts.parent_id
- WHERE opt.option_id IS NOT NULL
-),
-bettors AS (
- SELECT
- COALESCE(bet_won.user_id, bet_lost.user_id) AS user_id,
- (COALESCE(bet_won.count_won, 0)
- + COALESCE(bet_lost.count_lost, 0)) AS bets_total,
- COALESCE(bet_won.count_won, 0) AS bets_won
- FROM (
- SELECT user_id, COUNT(*) AS count_won FROM bet_votes
- WHERE exclusive = 3 GROUP BY user_id) AS bet_won
- FULL OUTER JOIN (
- SELECT user_id, COUNT(*) AS count_lost FROM bet_votes
- WHERE exclusive = 2 GROUP BY user_id
- ) AS bet_lost ON bet_won.user_id = bet_lost.user_id
-)
-SELECT
- bettors.user_id,
- bettors.bets_won,
- bettors.bets_total,
- bet_payout.net AS payout
-FROM bettors
-LEFT OUTER JOIN (
- SELECT user_id, SUM(payout) AS net FROM bet_votes GROUP BY user_id
-) AS bet_payout ON bettors.user_id = bet_payout.user_id
-ORDER BY payout DESC, bets_won DESC, bets_total ASC;
-""")
+# _special_leaderboard_query = text("""
+# WITH bet_options AS (
+# SELECT p.id AS parent_id, so.id AS option_id, so.exclusive, cnt.count
+# FROM post_options so
+# JOIN posts p ON so.parent_id = p.id
+# JOIN (
+# SELECT option_id, COUNT(*) FROM post_option_votes
+# GROUP BY option_id
+# ) AS cnt ON so.id = cnt.option_id
+# WHERE p.author_id = 7465 AND p.created_utc > 1688950032
+# AND so.exclusive IN (2, 3) AND p.title ilike 'women''s world cup betting: %'
+# ),
+# post_payouts AS (
+# SELECT
+# sq_total.parent_id,
+# sq_winners.sum AS bettors,
+# floor((sq_total.sum * 200) / sq_winners.sum) AS winner_payout
+# FROM (
+# SELECT parent_id, SUM(count)
+# FROM bet_options GROUP BY parent_id
+# ) AS sq_total
+# JOIN (
+# SELECT parent_id, SUM(count)
+# FROM bet_options WHERE exclusive = 3 GROUP BY parent_id
+# ) AS sq_winners ON sq_total.parent_id = sq_winners.parent_id
+# ),
+# bet_votes AS (
+# SELECT
+# opt.option_id AS option_id,
+# opt.exclusive,
+# sov.user_id,
+# CASE
+# WHEN opt.exclusive = 2 THEN -200
+# WHEN opt.exclusive = 3 THEN (post_payouts.winner_payout - 200)
+# END payout
+# FROM post_option_votes sov
+# LEFT OUTER JOIN bet_options AS opt
+# ON opt.option_id = sov.option_id
+# LEFT OUTER JOIN post_payouts
+# ON opt.parent_id = post_payouts.parent_id
+# WHERE opt.option_id IS NOT NULL
+# ),
+# bettors AS (
+# SELECT
+# COALESCE(bet_won.user_id, bet_lost.user_id) AS user_id,
+# (COALESCE(bet_won.count_won, 0)
+# + COALESCE(bet_lost.count_lost, 0)) AS bets_total,
+# COALESCE(bet_won.count_won, 0) AS bets_won
+# FROM (
+# SELECT user_id, COUNT(*) AS count_won FROM bet_votes
+# WHERE exclusive = 3 GROUP BY user_id) AS bet_won
+# FULL OUTER JOIN (
+# SELECT user_id, COUNT(*) AS count_lost FROM bet_votes
+# WHERE exclusive = 2 GROUP BY user_id
+# ) AS bet_lost ON bet_won.user_id = bet_lost.user_id
+# )
+# SELECT
+# bettors.user_id,
+# bettors.bets_won,
+# bettors.bets_total,
+# bet_payout.net AS payout
+# FROM bettors
+# LEFT OUTER JOIN (
+# SELECT user_id, SUM(payout) AS net FROM bet_votes GROUP BY user_id
+# ) AS bet_payout ON bettors.user_id = bet_payout.user_id
+# ORDER BY payout DESC, bets_won DESC, bets_total ASC;
+# """)
-@cache.memoize()
-def _special_leaderboard_get():
- result = g.db.execute(_special_leaderboard_query).all()
- return result
+# @cache.memoize()
+# def _special_leaderboard_get():
+# result = g.db.execute(_special_leaderboard_query).all()
+# return result
-@app.get('/womenworldcup2023')
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
-@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
-@auth_required
-def get_leaderboard(v):
- if SITE_NAME != 'rDrama':
- abort(404)
+# @app.get('/womenworldcup2023')
+# @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
+# @limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
+# @auth_required
+# def get_leaderboard(v):
+# if SITE_NAME != 'rDrama':
+# abort(404)
- result = _special_leaderboard_get()
- if g.is_api_or_xhr: return result
- users = get_accounts_dict([r[0] for r in result], v=v, graceful=True)
- return render_template("special/worldcup22_leaderboard.html",
- v=v, result=result, users=users)
+# result = _special_leaderboard_get()
+# if g.is_api_or_xhr: return result
+# users = get_accounts_dict([r[0] for r in result], v=v, graceful=True)
+# return render_template("special/worldcup22_leaderboard.html",
+# v=v, result=result, users=users)
diff --git a/files/routes/static.py b/files/routes/static.py
index 4e8378118..8319f13d7 100644
--- a/files/routes/static.py
+++ b/files/routes/static.py
@@ -52,7 +52,7 @@ def get_emoji_list(kind):
@app.get("/marseys")
@app.get("/emojis")
def marseys_redirect():
- return redirect("/emojis/Platy")
+ return redirect("/emojis/Marsey")
@app.get("/emojis/")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@@ -212,10 +212,7 @@ def log(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@auth_required
def log_item(id, v):
- try: id = int(id)
- except: abort(404)
-
- action=g.db.get(ModAction, id)
+ action = g.db.get(ModAction, id)
if not action: abort(404)
@@ -260,7 +257,7 @@ def api(v):
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
@auth_desired
def contact(v):
- return render_template("contact.html", v=v, msg=get_msg())
+ return render_template("contact.html", v=v)
@app.post("/contact")
@limiter.limit('1/second', scope=rpath)
@@ -295,8 +292,12 @@ def submit_contact(v):
new_comment.top_comment_id = new_comment.id
admin_ids = [x[0] for x in g.db.query(User.id).filter(User.admin_level >= PERMS['NOTIFICATIONS_MODMAIL'])]
- if SITE == 'watchpeopledie.tv' and AEVANN_ID in admin_ids:
- admin_ids.remove(AEVANN_ID)
+
+ if SITE == 'watchpeopledie.tv':
+ if AEVANN_ID in admin_ids:
+ admin_ids.remove(AEVANN_ID)
+ if 'delete' in new_comment.body.lower() and 'account' in new_comment.body.lower():
+ admin_ids.remove(15447)
for admin_id in admin_ids:
notif = Notification(comment_id=new_comment.id, user_id=admin_id)
@@ -304,7 +305,7 @@ def submit_contact(v):
push_notif(admin_ids, f'New modmail from @{new_comment.author_name}', new_comment.body, f'{SITE_FULL}/notifications/modmail')
- return redirect("/contact?msg=Your message has been sent to the admins!")
+ return {"message": "Your message has been sent to the admins!"}
patron_badges = (22,23,24,25,26,27,28,257,258,259,260,261)
@@ -374,9 +375,6 @@ def dismiss_mobile_tip():
@auth_required
def transfers_id(id, v):
- try: id = int(id)
- except: abort(404)
-
transfer = g.db.get(Comment, id)
if not transfer: abort(404)
diff --git a/files/routes/subs.py b/files/routes/subs.py
index e71722cbb..68bf655e5 100644
--- a/files/routes/subs.py
+++ b/files/routes/subs.py
@@ -87,7 +87,7 @@ def unexile(v, sub, uid):
u = get_account(uid)
if not v.mods(sub): abort(403)
- if v.shadowbanned: return redirect(f'/h/{sub}/exilees')
+ if v.shadowbanned: abort(403)
if u.exiler_username(sub):
exile = g.db.query(Exile).filter_by(user_id=u.id, sub=sub).one_or_none()
@@ -103,11 +103,7 @@ def unexile(v, sub, uid):
)
g.db.add(ma)
- if g.is_api_or_xhr:
- return {"message": f"@{u.username} has been unexiled from /h/{sub} successfully!"}
-
-
- return redirect(f'/h/{sub}/exilees')
+ return {"message": f"@{u.username} has been unexiled from /h/{sub} successfully!"}
@app.post("/h//block")
@limiter.limit('1/second', scope=rpath)
@@ -275,7 +271,7 @@ def add_mod(v, sub):
if SITE_NAME == 'WPD': abort(403)
sub = get_sub_by_name(sub).name
if not v.mods(sub): abort(403)
- if v.shadowbanned: return redirect(f'/h/{sub}/mods')
+ if v.shadowbanned: abort(400)
user = request.values.get('user')
@@ -303,7 +299,7 @@ def add_mod(v, sub):
)
g.db.add(ma)
- return redirect(f'/h/{sub}/mods')
+ return {"message": "Mod added successfully!"}
@app.post("/h//remove_mod")
@limiter.limit('1/second', scope=rpath)
@@ -356,7 +352,7 @@ def create_sub(v):
if not v.can_create_hole:
abort(403)
- return render_template("sub/create_hole.html", v=v, cost=HOLE_COST, error=get_error())
+ return render_template("sub/create_hole.html", v=v, cost=HOLE_COST)
@app.post("/create_hole")
@limiter.limit('1/second', scope=rpath)
@@ -373,15 +369,15 @@ def create_sub2(v):
name = name.strip().lower()
if not valid_sub_regex.fullmatch(name):
- return redirect(f"/create_hole?error=Name does not match the required format!")
+ abort(400, "Name does not match the required format!")
if not v.charge_account('combined', HOLE_COST)[0]:
- return redirect(f"/create_hole?error=You don't have enough coins or marseybux!")
+ abort(400, "You don't have enough coins or marseybux!")
sub = get_sub_by_name(name, graceful=True)
if sub:
- return redirect(f"/create_hole?error=/h/{sub} already exists!")
+ abort(400, f"/h/{sub} already exists!")
g.db.add(v)
if v.shadowbanned: abort(500)
@@ -396,7 +392,7 @@ def create_sub2(v):
for admin in admins:
send_repeatable_notification(admin, f":!marseyparty: /h/{sub} has been created by @{v.username} :marseyparty:")
- return redirect(f'/h/{sub}')
+ return {"message": f"/h/{sub} created successfully!"}
@app.post("/kick/")
@limiter.limit('1/second', scope=rpath)
@@ -452,13 +448,13 @@ def sub_settings(v, sub):
def post_sub_sidebar(v, sub):
sub = get_sub_by_name(sub)
if not v.mods(sub.name): abort(403)
- if v.shadowbanned: return redirect(f'/h/{sub}/settings')
+ if v.shadowbanned: abort(400)
sub.sidebar = request.values.get('sidebar', '')[:10000].strip()
sidebar_html = sanitize(sub.sidebar, blackjack=f"/h/{sub} sidebar")
if len(sidebar_html) > 20000:
- return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, error="Sidebar is too big!", css=sub.css)
+ abort(400, "Sidebar is too big! (max 20000 characters)")
sub.sidebar_html = sidebar_html
g.db.add(sub)
@@ -470,7 +466,7 @@ def post_sub_sidebar(v, sub):
)
g.db.add(ma)
- return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, msg='CSS changed successfully!', css=sub.css)
+ return {"message": "Sidebar changed successfully!"}
@app.post('/h//css')
@@ -485,15 +481,14 @@ def post_sub_css(v, sub):
if not sub: abort(404)
if not v.mods(sub.name): abort(403)
- if v.shadowbanned: return redirect(f'/h/{sub}/settings')
+ if v.shadowbanned: abort(400)
if len(css) > 6000:
- error = "CSS is too long (max 6000 characters)"
- return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, error=error, css=css)
+ abort(400, "CSS is too long (max 6000 characters)")
valid, error = validate_css(css)
if not valid:
- return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, error=error, css=css)
+ abort(400, error)
sub.css = css
g.db.add(sub)
@@ -505,14 +500,14 @@ def post_sub_css(v, sub):
)
g.db.add(ma)
- return render_template('sub/settings.html', v=v, sidebar=sub.sidebar, sub=sub, msg='CSS changed successfully!', css=sub.css)
+ return {"message": "CSS changed successfully!"}
@app.get("/h//css")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
def get_sub_css(sub):
sub = g.db.query(Sub.css).filter_by(name=sub.strip().lower()).one_or_none()
if not sub: abort(404)
- resp=make_response(sub.css or "")
+ resp = make_response(sub.css or "")
resp.headers.add("Content-Type", "text/css")
return resp
@@ -548,7 +543,7 @@ def upload_sub_banner(v, sub):
return redirect(f'/h/{sub}/settings')
-@app.delete("/h//settings/banners/")
+@app.post("/h//settings/banners/delete/")
@limiter.limit("1/second;30/day", deduct_when=lambda response: response.status_code < 400)
@limiter.limit("1/second;30/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@is_not_permabanned
@@ -579,7 +574,7 @@ def delete_sub_banner(v, sub, index):
return {"message": f"Deleted banner {index} from /h/{sub} successfully"}
-@app.delete("/h//settings/banners/")
+@app.post("/h//settings/banners/delete_all")
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400)
@limiter.limit("1/10 second;30/day", deduct_when=lambda response: response.status_code < 400, key_func=get_ID)
@is_not_permabanned
@@ -897,10 +892,8 @@ def hole_log_item(id, v, sub):
sub = get_sub_by_name(sub)
if not User.can_see(v, sub):
abort(403)
- try: id = int(id)
- except: abort(404)
- action=g.db.get(SubAction, id)
+ action = g.db.get(SubAction, id)
if not action: abort(404)
diff --git a/files/routes/users.py b/files/routes/users.py
index 7840a3736..f4274a441 100644
--- a/files/routes/users.py
+++ b/files/routes/users.py
@@ -42,6 +42,7 @@ def claim_rewards_all_users():
emails = [x[0] for x in g.db.query(Transaction.email).filter_by(claimed=None)]
users = g.db.query(User).filter(User.email.in_(emails)).order_by(User.truescore.desc()).all()
for user in users:
+ g.db.flush()
transactions = g.db.query(Transaction).filter_by(email=user.email, claimed=None).all()
highest_tier = 0
@@ -81,6 +82,7 @@ def claim_rewards_all_users():
for x in range(22, badge_id+1):
badge_grant(badge_id=x, user=user)
+ g.db.flush()
user.lifetimedonated = g.db.query(func.sum(Transaction.amount)).filter_by(email=user.email).scalar()
if user.lifetimedonated >= 100:
@@ -518,9 +520,6 @@ def leaderboard(v):
@app.get("//css")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
def get_css(id):
- try: id = int(id)
- except: abort(404)
-
css, bg = g.db.query(User.css, User.background).filter_by(id=id).one_or_none()
if bg:
@@ -542,9 +541,6 @@ def get_css(id):
@app.get("//profilecss")
@limiter.limit(DEFAULT_RATELIMIT, deduct_when=lambda response: response.status_code < 400)
def get_profilecss(id):
- try: id = int(id)
- except: abort(404)
-
css, bg = g.db.query(User.profilecss, User.profile_background).filter_by(id=id).one_or_none()
if bg:
@@ -661,12 +657,11 @@ def message2(v, username=None, id=None):
g.db.add(notif)
- if not v.shadowbanned:
- title = f'New message from @{c.author_name}'
+ title = f'New message from @{c.author_name}'
- url = f'{SITE_FULL}/notifications/messages'
+ url = f'{SITE_FULL}/notifications/messages'
- push_notif({user.id}, title, body, url)
+ push_notif({user.id}, title, body, url)
return {"message": "Message sent!"}
@@ -743,12 +738,11 @@ def messagereply(v):
notif = Notification(comment_id=c.id, user_id=user_id)
g.db.add(notif)
- if not v.shadowbanned:
- title = f'New message from @{c.author_name}'
+ title = f'New message from @{c.author_name}'
- url = f'{SITE_FULL}/notifications/messages'
+ url = f'{SITE_FULL}/notifications/messages'
- push_notif({user_id}, title, body, url)
+ push_notif({user_id}, title, body, url)
top_comment = c.top_comment
@@ -796,7 +790,7 @@ def mfa_qr(v, secret):
@limiter.limit("100/day", deduct_when=lambda response: response.status_code < 400)
def is_available(name):
- name=name.strip()
+ name = name.strip()
if len(name)<3 or len(name)>25:
return {name:False}
@@ -956,7 +950,7 @@ def u_username_wall(v, username):
is_following = v and u.has_follower(v)
- if v and v.id != u.id and not v.admin_level and not session.get("GLOBAL"):
+ if v and v.id != u.id and v.admin_level < PERMS['USER_SHADOWBAN'] and not session.get("GLOBAL"):
gevent.spawn(_add_profile_view, v.id, u.id)
page = get_page()
@@ -1003,7 +997,7 @@ def u_username_wall_comment(v, username, cid):
is_following = v and u.has_follower(v)
- if v and v.id != u.id and not v.admin_level and not session.get("GLOBAL"):
+ if v and v.id != u.id and v.admin_level < PERMS['USER_SHADOWBAN'] and not session.get("GLOBAL"):
gevent.spawn(_add_profile_view, v.id, u.id)
if v and request.values.get("read"):
@@ -1048,7 +1042,7 @@ def u_username(v, username):
abort(403, f"@{u.username}'s userpage is private")
return render_template("userpage/private.html", u=u, v=v, is_following=is_following), 403
- if v and v.id != u.id and not v.admin_level and not session.get("GLOBAL"):
+ if v and v.id != u.id and v.admin_level < PERMS['USER_SHADOWBAN'] and not session.get("GLOBAL"):
gevent.spawn(_add_profile_view, v.id, u.id)
sort = request.values.get("sort", "new")
@@ -1115,13 +1109,13 @@ def u_username_comments(username, v):
abort(403, f"@{u.username}'s userpage is private")
return render_template("userpage/private.html", u=u, v=v, is_following=is_following), 403
- if v and v.id != u.id and not v.admin_level and not session.get("GLOBAL"):
+ if v and v.id != u.id and v.admin_level < PERMS['USER_SHADOWBAN'] and not session.get("GLOBAL"):
gevent.spawn(_add_profile_view, v.id, u.id)
page = get_page()
- sort=request.values.get("sort","new")
- t=request.values.get("t","all")
+ sort = request.values.get("sort","new")
+ t = request.values.get("t","all")
comment_post_author = aliased(User)
comments = g.db.query(Comment).options(load_only(Comment.id)) \
@@ -1162,7 +1156,7 @@ def u_username_comments(username, v):
@auth_required
def u_username_info(username, v):
- user=get_user(username, v=v, include_blocks=True)
+ user = get_user(username, v=v, include_blocks=True)
if hasattr(user, 'is_blocking') and user.is_blocking:
abort(401, f"You're blocking @{user.username}")
@@ -1205,7 +1199,7 @@ def follow_user(username, v):
new_follow = Follow(user_id=v.id, target_id=target.id)
g.db.add(new_follow)
- target.stored_subscriber_count = g.db.query(Follow).filter_by(target_id=target.id).count()
+ target.stored_subscriber_count += 1
g.db.add(target)
if not v.shadowbanned:
@@ -1229,7 +1223,7 @@ def unfollow_user(username, v):
if follow:
g.db.delete(follow)
- target.stored_subscriber_count = g.db.query(Follow).filter_by(target_id=target.id).count()
+ target.stored_subscriber_count -= 1
g.db.add(target)
if not v.shadowbanned:
@@ -1256,7 +1250,7 @@ def remove_follow(username, v):
g.db.delete(follow)
- v.stored_subscriber_count = g.db.query(Follow).filter_by(target_id=v.id).count()
+ v.stored_subscriber_count -= 1
g.db.add(v)
send_repeatable_notification(target.id, f"@{v.username} has removed your follow!")
@@ -1299,7 +1293,7 @@ def get_saves_and_subscribes(v, template, relationship_cls, page, standalone=Fal
ids = [x[0] for x in listing]
extra = None
- if not v.admin_level >= PERMS['POST_COMMENT_MODERATION']:
+ if v.admin_level < PERMS['POST_COMMENT_MODERATION']:
extra = lambda q:q.filter(cls.is_banned == False, cls.deleted_utc == 0)
if cls is Post:
@@ -1357,6 +1351,7 @@ def fp(v, fp):
users += alts
for u in users:
li = [v.id, u.id]
+ g.db.flush()
existing = g.db.query(Alt).filter(Alt.user1.in_(li), Alt.user2.in_(li)).one_or_none()
if existing: continue
add_alt(user1=v.id, user2=u.id)
@@ -1374,9 +1369,7 @@ def toggle_pins(sub, sort):
pins = session.get(f'{sub}_{sort}', default)
session[f'{sub}_{sort}'] = not pins
- if is_site_url(request.referrer):
- return redirect(request.referrer)
- return redirect('/')
+ return {"message": "Pins toggled successfully!"}
@app.get("/badge_owners/")
@@ -1385,9 +1378,6 @@ def toggle_pins(sub, sort):
@auth_required
def bid_list(v, bid):
- try: bid = int(bid)
- except: abort(400)
-
page = get_page()
users = g.db.query(User, Badge.created_utc).join(User.badges).filter(Badge.badge_id==bid)
diff --git a/files/routes/votes.py b/files/routes/votes.py
index d8e998fb8..188721d8f 100644
--- a/files/routes/votes.py
+++ b/files/routes/votes.py
@@ -40,6 +40,7 @@ def vote_post_comment(target_id, new, v, cls, vote_cls):
coin_mult = 1
+ g.db.flush()
existing = g.db.query(vote_cls).filter_by(user_id=v.id)
if vote_cls == Vote:
existing = existing.filter_by(post_id=target.id)
@@ -76,7 +77,7 @@ def vote_post_comment(target_id, new, v, cls, vote_cls):
elif new != 0:
imlazy = 3
- real = new == -1 or (not alt and v.is_votes_real)
+ real = new == -1 and not alt and v.has_real_votes
vote = None
if vote_cls == Vote:
vote = Vote(user_id=v.id,
@@ -96,6 +97,7 @@ def vote_post_comment(target_id, new, v, cls, vote_cls):
# this is hacky but it works, we should probably do better later
def get_vote_count(dir, real_instead_of_dir):
+ g.db.flush()
votes = g.db.query(vote_cls)
if real_instead_of_dir:
votes = votes.filter(vote_cls.real == True)
@@ -109,8 +111,7 @@ def vote_post_comment(target_id, new, v, cls, vote_cls):
else:
return 0
- try: return votes.count()
- except: abort(500)
+ return votes.count()
target.upvotes = get_vote_count(1, False)
target.downvotes = get_vote_count(-1, False)
@@ -126,21 +127,21 @@ def vote_post_comment(target_id, new, v, cls, vote_cls):
elif cls == Post and (any(i in target.title.lower() for i in ENCOURAGED) or any(i in str(target.url).lower() for i in ENCOURAGED2)):
mul = PROGSTACK_MUL
send_notification(AEVANN_ID, target.permalink)
- elif target.author.progressivestack or (target.author.admin_level and target.author.id != SCHIZO_ID):
+ elif target.author.progressivestack or target.author.admin_level >= PERMS['IS_PERMA_PROGSTACKED']:
mul = 2
elif SITE == 'rdrama.net' and cls == Post:
if (target.domain.endswith('.win')
- or 'forum' in target.domain or 'chan' in target.domain or 'lemmy' in target.domain
+ or 'forum' in target.domain or 'chan' in target.domain or 'lemmy' in target.domain or 'mastodon' in target.domain
or (target.domain in BOOSTED_SITES and not target.url.startswith('/'))):
mul = 2
- elif target.sub in STEALTH_HOLES or target.sub == 'countryclub':
+ elif target.sub in STEALTH_HOLES or target.sub in {'countryclub', 'highrollerclub'}:
mul = 2
elif 6 <= datetime.fromtimestamp(target.created_utc).hour <= 10:
mul = 2
elif target.sub in BOOSTED_HOLES:
mul = 1.25
- if target.body_html and target.author.id != LNTERNETCUSTODIAN_ID:
+ if target.body_html and target.author.id != 8768:
x = target.body_html.count('" target="_blank" rel="nofollow noopener">')
x += target.body_html.count('" rel="nofollow noopener" target="_blank">')
target.realupvotes += min(x*2, 20)
diff --git a/files/routes/wrappers.py b/files/routes/wrappers.py
index cee0249c8..eec911c5a 100644
--- a/files/routes/wrappers.py
+++ b/files/routes/wrappers.py
@@ -22,7 +22,8 @@ def get_ID():
elif session.get("lo_user"):
x = session.get("lo_user")
else:
- x = "logged_out"
+ check_session_id()
+ x = f"logged_out-{session['session_id']}"
return f'{SITE}-{x}'
@@ -99,13 +100,15 @@ def get_logged_in_user():
else:
session.pop("lo_user")
- if request.method.lower() != "get" and get_setting('read_only_mode') and not (v and v.admin_level >= PERMS['SITE_BYPASS_READ_ONLY_MODE']):
+ if request.method.lower() != "get" and get_setting('read_only_mode') and not (v and v.admin_level >= PERMS['BYPASS_SITE_READ_ONLY_MODE']):
abort(403, "Site is in read-only mode right now. It will be back shortly!")
if get_setting('offline_mode') and not (v and v.admin_level >= PERMS['SITE_OFFLINE_MODE']):
abort(403, "Site is in offline mode right now. It will be back shortly!")
g.v = v
+ if v:
+ g.vid = v.username
if not v and SITE == 'rdrama.net' and request.headers.get("Cf-Ipcountry") == 'EG':
abort(404)
diff --git a/files/templates/admin/app.html b/files/templates/admin/app.html
index 0ce6669cc..4bc74b844 100644
--- a/files/templates/admin/app.html
+++ b/files/templates/admin/app.html
@@ -26,7 +26,7 @@
-
- Action successful!
-
-
-
-
- Error, please try again later.
-
-
-
-
-
{% endblock %}
diff --git a/files/templates/admin/apps.html b/files/templates/admin/apps.html
index 7de1483c8..f027a4f27 100644
--- a/files/templates/admin/apps.html
+++ b/files/templates/admin/apps.html
@@ -51,17 +51,4 @@
-
-
- Action successful!
-
-
-
-
- Error, please try again later.
-
-
-
-
-
{% endblock %}
diff --git a/files/templates/admin/badge_admin.html b/files/templates/admin/badge_admin.html
index dfd23e660..df2e322d2 100644
--- a/files/templates/admin/badge_admin.html
+++ b/files/templates/admin/badge_admin.html
@@ -5,12 +5,9 @@
-{% if error %}{{macros.alert(error, true)}}{% endif %}
-{% if msg %}{{macros.alert(msg, false)}}{% endif %}
-
{% set form_action = "/admin/badge_grant" if grant else "/admin/badge_remove" %}
-
-