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 += `
  • ` if (admin_level && Object.keys(data[1]).includes(u[0].toLowerCase())) online += 'X ' - online += `${u[0]}
  • ` + online += `${u[0]}` online2 += `
    @${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'
    ''' + option_body += f'"> - {o.upvotes} votes''' if o.exclusive > 1: s = '##' elif o.exclusive: s = '&&' @@ -139,10 +142,13 @@ class Comment(Base): ban_reason = Column(String) treasure_amount = Column(String) slots_result = Column(String) - ping_cost = Column(Integer) + ping_cost = Column(Integer, default=0) blackjack_result = Column(String) casino_game_id = Column(Integer, ForeignKey("casino_games.id")) chudded = Column(Boolean, default=False) + rainbowed = Column(Boolean, default=False) + queened = Column(Boolean, default=False) + sharpened = Column(Boolean, default=False) oauth_app = relationship("OauthApp") post = relationship("Post", back_populates="comments") @@ -187,6 +193,7 @@ class Comment(Base): @property @lazy def edited_string(self): + if not self.edited_utc: return None return make_age_string(self.edited_utc) @property @@ -260,11 +267,7 @@ class Comment(Base): @lazy def award_count(self, kind, v): - if v and v.poor and kind.islower(): return 0 - num = len([x for x in self.awards if x.kind == kind]) - if num > 4 and kind not in {"shit", "fireflies", "gingerbread"}: - return 4 - return num + return len([x for x in self.awards if x.kind == kind]) @property @lazy diff --git a/files/classes/mod_logs.py b/files/classes/mod_logs.py index 0696d28e2..b2fdeccc7 100644 --- a/files/classes/mod_logs.py +++ b/files/classes/mod_logs.py @@ -18,7 +18,7 @@ class ModAction(Base): target_user_id = Column(Integer, ForeignKey("users.id")) target_post_id = Column(Integer, ForeignKey("posts.id")) target_comment_id = Column(Integer, ForeignKey("comments.id")) - _note=Column(String) + _note = Column(String) created_utc = Column(Integer) user = relationship("User", primaryjoin="User.id==ModAction.user_id") diff --git a/files/classes/orgy.py b/files/classes/orgy.py index 8307be00c..65c523f2e 100644 --- a/files/classes/orgy.py +++ b/files/classes/orgy.py @@ -1,60 +1,35 @@ import time -from flask import g +from flask import g, abort from sqlalchemy import Column from sqlalchemy.sql.sqltypes import * from files.classes import Base -from files.helpers.config.const import * -from files.helpers.regex import * -from files.helpers.sanitize import normalize_url, get_youtube_id_and_t + +from files.helpers.lazy import lazy class Orgy(Base): __tablename__ = "orgies" - id = Column(Integer, primary_key = True) - type = Column(Integer, primary_key = True) + type = Column(String, primary_key=True) data = Column(String) title = Column(String) + created_utc = Column(Integer) - def __init__(self, **kwargs): - super().__init__(**kwargs) - - def is_youtube(self): - return self.type == OrgyTypes.YOUTUBE - def is_rumble(self): - return self.type == OrgyTypes.RUMBLE - def is_twitch(self): - return self.type == OrgyTypes.TWITCH + def __init__(self, *args, **kwargs): + if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time()) + super().__init__(*args, **kwargs) def __repr__(self): - return f"<{self.__class__.__name__}(id={self.id}, type={self.type}, data={self.data} title={self.title})>" + return f"<{self.__class__.__name__}(type={self.type}, data={self.data} title={self.title})>" + @property + @lazy + def real_created_utc(self): + t = self.created_utc + if int(time.time()) - t > 3000: + t += 303 + return t def get_orgy(): - orgy = g.db.query(Orgy).one_or_none() - return orgy - -def create_orgy(link, title): - assert not get_orgy() - normalized_link = normalize_url(link) - data = None - orgy_type = -1 - if re.match(bare_youtube_regex, normalized_link): - orgy_type = OrgyTypes.YOUTUBE - data, _ = get_youtube_id_and_t(normalized_link) - elif re.match(rumble_regex, normalized_link): - orgy_type = OrgyTypes.RUMBLE - data = normalized_link - elif re.match(twitch_regex, normalized_link): - orgy_type = OrgyTypes.TWITCH - data = re.search(twitch_regex, normalized_link).group(3) - else: - assert False - - orgy = Orgy(title=title, id=0, type = orgy_type, data = data) - g.db.add(orgy) - -def end_orgy(): - assert get_orgy() - g.db.query(Orgy).delete() + return g.db.query(Orgy).one_or_none() diff --git a/files/classes/post.py b/files/classes/post.py index 4f44484d7..08a8493fb 100644 --- a/files/classes/post.py +++ b/files/classes/post.py @@ -48,7 +48,7 @@ class Post(Base): upvotes = Column(Integer, default=1) downvotes = Column(Integer, default=0) realupvotes = Column(Integer, default=1) - app_id=Column(Integer, ForeignKey("oauth_apps.id")) + app_id = Column(Integer, ForeignKey("oauth_apps.id")) title = Column(String) title_html = Column(String) url = Column(String) @@ -60,7 +60,10 @@ class Post(Base): new = Column(Boolean) notify = Column(Boolean) chudded = Column(Boolean, default=False) - ping_cost = Column(Integer) + rainbowed = Column(Boolean, default=False) + queened = Column(Boolean, default=False) + sharpened = Column(Boolean, default=False) + ping_cost = Column(Integer, default=0) bump_utc = Column(Integer) author = relationship("User", primaryjoin="Post.author_id==User.id") @@ -123,7 +126,7 @@ class Post(Base): link = f"/post/{self.id}" if self.sub: link = f"/h/{self.sub}{link}" - if self.sub and self.sub in {'chudrama', 'countryclub'}: + if self.sub and self.sub in {'chudrama', 'countryclub', 'highrollerclub'}: output = '-' else: title = self.plaintitle(None).lower() @@ -249,6 +252,9 @@ class Post(Base): if v and v.poor: return 0 + if self.distinguish_level and SITE_NAME == 'WPD': + return 0 + num = len([x for x in self.awards if x.kind == kind]) if num > 4 and kind not in {"shit", "fireflies", "gingerbread"}: return 4 diff --git a/files/classes/saves.py b/files/classes/saves.py index da6679e4e..0eeef40d7 100644 --- a/files/classes/saves.py +++ b/files/classes/saves.py @@ -7,10 +7,10 @@ from sqlalchemy.sql.sqltypes import * from files.classes import Base class SaveRelationship(Base): - __tablename__="save_relationship" + __tablename__ = "save_relationship" - user_id=Column(Integer, ForeignKey("users.id"), primary_key=True) - post_id=Column(Integer, ForeignKey("posts.id"), primary_key=True) + user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) + post_id = Column(Integer, ForeignKey("posts.id"), primary_key=True) created_utc = Column(Integer) post = relationship("Post", uselist=False) @@ -25,10 +25,10 @@ class SaveRelationship(Base): class CommentSaveRelationship(Base): - __tablename__="comment_save_relationship" + __tablename__ = "comment_save_relationship" - user_id=Column(Integer, ForeignKey("users.id"), primary_key=True) - comment_id=Column(Integer, ForeignKey("comments.id"), primary_key=True) + user_id = Column(Integer, ForeignKey("users.id"), primary_key=True) + comment_id = Column(Integer, ForeignKey("comments.id"), primary_key=True) created_utc = Column(Integer) comment = relationship("Comment", uselist=False) diff --git a/files/classes/sub_logs.py b/files/classes/sub_logs.py index ebd65a509..99ca14f08 100644 --- a/files/classes/sub_logs.py +++ b/files/classes/sub_logs.py @@ -19,7 +19,7 @@ class SubAction(Base): target_user_id = Column(Integer, ForeignKey("users.id")) target_post_id = Column(Integer, ForeignKey("posts.id")) target_comment_id = Column(Integer, ForeignKey("comments.id")) - _note=Column(String) + _note = Column(String) created_utc = Column(Integer) user = relationship("User", primaryjoin="User.id==SubAction.user_id") diff --git a/files/classes/user.py b/files/classes/user.py index 7c1df12cd..9f614cdd1 100644 --- a/files/classes/user.py +++ b/files/classes/user.py @@ -270,6 +270,17 @@ class User(Base): def over_18(self): return session.get('over_18', False) + @property + @lazy + def allowed_in_chat(self): + if self.admin_level >= PERMS['BYPASS_CHAT_TRUESCORE_REQUIREMENT']: + return True + if self.truescore >= TRUESCORE_CC_CHAT_MINIMUM: + return True + if self.patron: + return True + return False + @property @lazy def num_of_bought_awards(self): @@ -380,7 +391,7 @@ class User(Base): @property @lazy - def is_votes_real(self): + def has_real_votes(self): if self.patron: return True if self.is_permabanned or self.shadowbanned: return False if self.chud: return False @@ -508,7 +519,7 @@ class User(Base): if isinstance(target, Comment) and not target.post: return False if self.id == target.author_id: return True if not isinstance(target, Post): return False - return bool(self.admin_level >= PERMS['POST_EDITING']) + return bool(self.admin_level >= PERMS['POST_COMMENT_EDITING']) @property @lazy @@ -550,12 +561,6 @@ class User(Base): def has_blocked(self, target): return g.db.query(UserBlock).filter_by(user_id=self.id, target_id=target.id).one_or_none() - @lazy - def any_block_exists(self, other): - return g.db.query(UserBlock).filter( - or_(and_(UserBlock.user_id == self.id, UserBlock.target_id == other.id), and_( - UserBlock.user_id == other.id, UserBlock.target_id == self.id))).first() - @property @lazy def all_twoway_blocks(self): @@ -779,7 +784,7 @@ class User(Base): if self.id == AEVANN_ID and SITE_NAME != 'rDrama': return 0 - if self.admin_level: + if self.admin_level >= PERMS['NOTIFICATIONS_MODERATOR_ACTIONS']: q = g.db.query(ModAction).filter( ModAction.created_utc > self.last_viewed_log_notifs, ModAction.user_id != self.id, @@ -1032,7 +1037,7 @@ class User(Base): raise TypeError("Relationships supported is SaveRelationship, Subscription, CommentSaveRelationship") query = g.db.query(query).join(join).filter(relationship_cls.user_id == self.id) - if not self.admin_level >= PERMS['POST_COMMENT_MODERATION']: + if self.admin_level < PERMS['POST_COMMENT_MODERATION']: query = query.filter(cls.is_banned == False, cls.deleted_utc == 0) return query.count() @@ -1126,10 +1131,10 @@ class User(Base): if request.headers.get("Cf-Ipcountry") == 'NZ': if 'christchurch' in other.title.lower(): return False - if SITE == 'watchpeopledie.tv' and other.id == 5: + if SITE == 'watchpeopledie.tv' and other.id in {5, 17212, 22653, 23814}: return False else: - if hasattr(other, 'is_blocking') and other.is_blocking: + if hasattr(other, 'is_blocking') and other.is_blocking and not request.path.endswith(f'/{other.id}'): return False if other.parent_post: return cls.can_see(user, other.post) @@ -1184,7 +1189,7 @@ class User(Base): if self.can_see_restricted_holes != None: return self.can_see_restricted_holes - if self.truescore >= TRUESCORE_CC_MINIMUM: return True + if self.truescore >= TRUESCORE_CC_CHAT_MINIMUM: return True if SITE == 'rdrama.net' and self.id == 5237: return True @@ -1328,8 +1333,7 @@ class User(Base): @property @lazy def ordered_badges(self): - x = sorted(self.badges, key=badge_ordering_func) - return x + return sorted(self.badges, key=badge_ordering_func) @lazy def rendered_sig(self, v): diff --git a/files/helpers/actions.py b/files/helpers/actions.py index 629532be9..b9997a147 100644 --- a/files/helpers/actions.py +++ b/files/helpers/actions.py @@ -39,7 +39,7 @@ def _archiveorg(url): def archive_url(url): gevent.spawn(_archiveorg, url) if url.startswith('https://twitter.com/'): - url = url.replace('https://twitter.com/', 'https://nitter.lacontrevoie.fr/') + url = url.replace('https://twitter.com/', 'https://nitter.net/') gevent.spawn(_archiveorg, url) if url.startswith('https://instagram.com/'): url = url.replace('https://instagram.com/', 'https://imginn.com/') @@ -55,13 +55,11 @@ def execute_snappy(post, v): if post.sub and g.db.query(Exile.user_id).filter_by(user_id=SNAPPY_ID, sub=post.sub).one_or_none(): return - group_members = [] - ghost = post.ghost snappy = get_account(SNAPPY_ID) - ping_cost = None + ping_cost = 0 post_ping_group_count = len(list(group_mention_regex.finditer(post.body))) @@ -131,7 +129,7 @@ def execute_snappy(post, v): elif body == '!pinggroup': group = g.db.query(Group).order_by(func.random()).first() - cost = len(group.member_ids) * 10 + cost = len(group.member_ids) * 5 snappy.charge_account('coins', cost) body = f'!{group.name}' @@ -206,12 +204,10 @@ def execute_snappy(post, v): app_id=None, body=body, body_html=body_html, - ghost=ghost + ghost=ghost, + ping_cost=ping_cost, ) - if ping_cost: - c.ping_cost = ping_cost - g.db.add(c) check_slots_command(c, v, snappy) @@ -231,7 +227,7 @@ def execute_snappy(post, v): text = f"@Snappy has banned you for **{days}** days for the following reason:\n\n> {reason}" send_repeatable_notification(v.id, text) duration = f"for {days} days" - ma=ModAction( + ma = ModAction( kind="ban_user", user_id=snappy.id, target_user_id=v.id, @@ -242,10 +238,11 @@ def execute_snappy(post, v): g.db.flush() - for x in group_members: - n = Notification(comment_id=c.id, user_id=x) - g.db.add(n) - push_notif({x}, f'New mention of you by @Snappy', c.body, c) + if c.ping_cost: + for x in group.member_ids: + n = Notification(comment_id=c.id, user_id=x) + g.db.add(n) + push_notif({x}, f'New mention of you by @Snappy', c.body, c) c.top_comment_id = c.id @@ -369,7 +366,7 @@ def tempban_for_spam(v): send_repeatable_notification(v.id, text) v.ban(reason="Spam", days=1) - ma=ModAction( + ma = ModAction( kind="ban_user", user_id=AUTOJANNY_ID, target_user_id=v.id, @@ -379,7 +376,8 @@ def tempban_for_spam(v): def execute_antispam_post_check(title, v, url): - if v.admin_level: return True + if v.admin_level >= PERMS['BYPASS_ANTISPAM_CHECKS']: + return True now = int(time.time()) cutoff = now - 60 * 60 * 24 @@ -421,15 +419,16 @@ def execute_antispam_post_check(title, v, url): return True def execute_antispam_duplicate_comment_check(v, body_html): - if v.admin_level: return + if v.admin_level >= PERMS['BYPASS_ANTISPAM_CHECKS']: + return + if v.id in ANTISPAM_BYPASS_IDS: + return + if v.age >= NOTIFICATION_SPAM_AGE_THRESHOLD: + return + if len(body_html) < 16: + return - ''' - Sanity check for newfriends - ''' ANTISPAM_DUPLICATE_THRESHOLD = 3 - if v.id in ANTISPAM_BYPASS_IDS or v.admin_level: return - if v.age >= NOTIFICATION_SPAM_AGE_THRESHOLD: return - if len(body_html) < 16: return compare_time = int(time.time()) - 60 * 60 * 24 count = g.db.query(Comment.id).filter(Comment.body_html == body_html, Comment.created_utc >= compare_time).count() @@ -441,7 +440,8 @@ def execute_antispam_duplicate_comment_check(v, body_html): abort(403, "Too much spam!") def execute_antispam_comment_check(body, v): - if v.admin_level: return + if v.admin_level >= PERMS['BYPASS_ANTISPAM_CHECKS']: + return if v.id in ANTISPAM_BYPASS_IDS: return if len(body) <= COMMENT_SPAM_LENGTH_THRESHOLD: return @@ -497,12 +497,15 @@ def execute_under_siege(v, target, body, kind): if SITE == 'watchpeopledie.tv': execute_dylan(v) if v.shadowbanned: return + if kind != 'post': return if not get_setting("under_siege"): return - if v.admin_level >= PERMS['SITE_BYPASS_UNDER_SIEGE_MODE']: return + if v.admin_level >= PERMS['BYPASS_UNDER_SIEGE_MODE']: return if kind in {'message', 'report'} and SITE == 'rdrama.net': threshold = 86400 + elif kind == 'post' and SITE == 'watchpeopledie.tv': + threshold = 86400 else: threshold = UNDER_SIEGE_AGE_THRESHOLD @@ -549,18 +552,18 @@ def execute_lawlz_actions(v, p): p.stickied = "AutoJanny" p.distinguish_level = 6 p.flair = filter_emojis_only(":ben10: Required Reading") - ma_1=ModAction( + ma_1 = ModAction( kind="pin_post", user_id=AUTOJANNY_ID, target_post_id=p.id, _note='for 1 day' ) - ma_2=ModAction( + ma_2 = ModAction( kind="distinguish_post", user_id=AUTOJANNY_ID, target_post_id=p.id ) - ma_3=ModAction( + ma_3 = ModAction( kind="flair_post", user_id=AUTOJANNY_ID, target_post_id=p.id, diff --git a/files/helpers/alerts.py b/files/helpers/alerts.py index 94dcf3486..8dee5305d 100644 --- a/files/helpers/alerts.py +++ b/files/helpers/alerts.py @@ -41,14 +41,14 @@ def send_repeatable_notification(uid, text): notif = Notification(comment_id=c.id, user_id=uid) g.db.add(notif) - push_notif({uid}, 'New notification', text, f'{SITE_FULL}/comment/{c.id}?read=true#context') + push_notif({uid}, 'New notification', text, f'{SITE_FULL}/notification/{c.id}') return cid = create_comment(text_html) notif = Notification(comment_id=cid, user_id=uid) g.db.add(notif) - push_notif({uid}, 'New notification', text, f'{SITE_FULL}/comment/{cid}?read=true#context') + push_notif({uid}, 'New notification', text, f'{SITE_FULL}/notification/{cid}') def send_notification(uid, text): @@ -111,7 +111,7 @@ def add_notif(cid, uid, text, pushnotif_url=''): g.db.add(notif) if not pushnotif_url: - pushnotif_url = f'{SITE_FULL}/comment/{cid}?read=true#context' + pushnotif_url = f'{SITE_FULL}/notification/{cid}' if ' has mentioned you: [' in text: text = text.split(':')[0] + '!' @@ -126,6 +126,10 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, log_cost=None, followers_pi return set() text = text.lower() + + if oldtext: + oldtext = oldtext.lower() + notify_users = set() for word, id in NOTIFIED_USERS.items(): @@ -134,6 +138,10 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, log_cost=None, followers_pi names = set(m.group(1) for m in mention_regex.finditer(text)) + if oldtext: + oldnames = set(m.group(1) for m in mention_regex.finditer(oldtext)) + names = names - oldnames + user_ids = get_users(names, ids_only=True, graceful=True) notify_users.update(user_ids) @@ -153,13 +161,13 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, log_cost=None, followers_pi continue if i.group(1) == 'everyone' and not v.shadowbanned: - cost = g.db.query(User).count() * 10 - if cost > v.coins: - abort(403, f"You need {cost} coins to mention these ping groups!") + cost = g.db.query(User).count() * 5 + if cost > v.coins + v.marseybux: + abort(403, f"You need {cost} currency to mention these ping groups!") v.charge_account('combined', cost) if log_cost: - log_cost.ping_cost = cost + log_cost.ping_cost += cost return 'everyone' elif i.group(1) == 'jannies': group = None @@ -181,12 +189,12 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, log_cost=None, followers_pi if (ghost or v.id not in member_ids) and i.group(1) != 'followers': if group and group.name == 'verifiedrich': abort(403, f"Only !verifiedrich members can mention it!") - cost += len(members) * 10 - if cost > v.coins: - abort(403, f"You need {cost} coins to mention these ping groups!") + cost += len(members) * 5 + if cost > v.coins + v.marseybux: + abort(403, f"You need {cost} currency to mention these ping groups!") if log_cost: - log_cost.ping_cost = cost + log_cost.ping_cost += cost if i.group(1) in {'biofoids','neofoids','jannies'}: coin_receivers.update(member_ids) @@ -197,10 +205,19 @@ def NOTIFY_USERS(text, v, oldtext=None, ghost=False, log_cost=None, followers_pi if coin_receivers: g.db.query(User).options(load_only(User.id)).filter(User.id.in_(coin_receivers)).update({ User.coins: User.coins + 10 }) + if SITE == 'rdrama.net' and v.id in {256, 9287, 10489, 18701}: + notify_users.discard(AEVANN_ID) + + if len(notify_users) > 400 and v.admin_level < PERMS['POST_COMMENT_INFINITE_PINGS']: + abort(403, "You can only notify a maximum of 400 users.") + return notify_users - BOT_IDs - {v.id, 0} - v.all_twoway_blocks def push_notif(uids, title, body, url_or_comment): + if hasattr(g, 'v') and g.v and g.v.shadowbanned: + return + if VAPID_PUBLIC_KEY == DEFAULT_CONFIG_VALUE: return diff --git a/files/helpers/config/awards.py b/files/helpers/config/awards.py index 359d621ae..b10acd08c 100644 --- a/files/helpers/config/awards.py +++ b/files/helpers/config/awards.py @@ -340,7 +340,7 @@ AWARDS = { "beano": { "kind": "beano", "title": "Beano", - "description": "Award it to yourself to stop fart noises on the site.", + "description": "Makes the recipient not hear fart noises on the site.", "icon": "fas fa-gas-pump-slash", "color": "text-green", "price": 100, @@ -361,6 +361,30 @@ AWARDS = { "ghost": False, "enabled": True, }, + "rainbow": { + "kind": "rainbow", + "title": "Rainbow", + "description": "Makes the recipient's comments and posts in rainbow text for 24 hours.", + "icon": "fas fa-cloud-rainbow", + "color": "text-pink", + "price": 200, + "deflectable": True, + "cosmetic": False, + "ghost": False, + "enabled": not FEATURES['HOUSES'], + }, + "sharpen": { + "kind": "sharpen", + "title": "Sharpen", + "description": "Adds a badass edge to all the user's comments for 24 hours.", + "icon": "fas fa-fire", + "color": "text-danger", + "price": 200, + "deflectable": True, + "cosmetic": False, + "ghost": False, + "enabled": not FEATURES['HOUSES'], + }, "shit": { "kind": "shit", "title": "Shit", @@ -481,8 +505,6 @@ AWARDS = { "ghost": False, "enabled": True, }, - - #for non-rdrama sites "owoify": { "kind": "owoify", "title": "OwOify", @@ -493,33 +515,8 @@ AWARDS = { "deflectable": True, "cosmetic": False, "ghost": False, - "enabled": SITE_NAME != 'rDrama', + "enabled": not FEATURES['HOUSES'], }, - "rainbow": { - "kind": "rainbow", - "title": "Rainbow", - "description": "Makes the recipient's comments and posts in rainbow text for 24 hours.", - "icon": "fas fa-cloud-rainbow", - "color": "text-pink", - "price": 200, - "deflectable": True, - "cosmetic": False, - "ghost": False, - "enabled": SITE_NAME != 'rDrama', - }, - "sharpen": { - "kind": "sharpen", - "title": "Sharpen", - "description": "Adds a badass edge to all the user's comments for 24 hours.", - "icon": "fas fa-fire", - "color": "text-danger", - "price": 200, - "deflectable": True, - "cosmetic": False, - "ghost": False, - "enabled": SITE_NAME != 'rDrama', - }, - "flairlock": { "kind": "flairlock", "title": "1-Day Flairlock", @@ -736,18 +733,6 @@ AWARDS = { "ghost": False, "enabled": True, }, - "unblockable": { - "kind": "unblockable", - "title": "Unblockable", - "description": "Makes the recipient unblockable and removes all blocks on them.", - "icon": "fas fa-laugh-squint", - "color": "text-lightgreen", - "price": 20000, - "deflectable": True, - "cosmetic": False, - "ghost": False, - "enabled": True, - }, "pause": { "kind": "pause", "title": "Pause", @@ -772,6 +757,18 @@ AWARDS = { "ghost": False, "enabled": True, }, + "unblockable": { + "kind": "unblockable", + "title": "Unblockable", + "description": "Makes the recipient unblockable and removes all blocks on them.", + "icon": "fas fa-laugh-squint", + "color": "text-lightgreen", + "price": 40000, + "deflectable": True, + "cosmetic": False, + "ghost": False, + "enabled": True, + }, "alt": { "kind": "alt", "title": "Alt-Seeing Eye", diff --git a/files/helpers/config/boosted_sites.py b/files/helpers/config/boosted_sites.py index b6ed4dafc..139016187 100644 --- a/files/helpers/config/boosted_sites.py +++ b/files/helpers/config/boosted_sites.py @@ -11,6 +11,8 @@ BOOSTED_SITES = { 'lolcow.farm', '8kun.top', 'soyjak.party', + 'soyjaks.party', + 'jakparty.soy', 'crystal.cafe', 'desuarchive.org', @@ -23,11 +25,10 @@ BOOSTED_SITES = { 'blacktwitterapp.com', 'trp.red', ##fediverse + #'all sites with the word "mastodon" in the domain', 'kiwifarms.cc', 'freespeechextremist.com' 'mstdn.social', - 'mastodon.social', - 'mastodon.online', 'poa.st', 'shitposter.club', 'sneed.social', @@ -37,9 +38,7 @@ BOOSTED_SITES = { #forums #'all sites with the word "forum" in the domain', - 'kiwifarms.net', - 'sneed.today', - 'kiwifarms.pl', + 'kiwifarms.st', 'onionfarms.com', 'tattle.life', 'stormfront.org', @@ -73,10 +72,13 @@ BOOSTED_SITES = { 'fanclubs.org', 'dcurbanmom.com', 'godlike.com', + 'linustechtips.com', + 'defense.pk', + 'smogon.com', #nested-comments #'all sites with .win TLD', - #'all sites with the word 'lemmy' in the domain', + #'all sites with the word "lemmy" in the domain', 'rdrama.net', 'funnyjunk.com', 'news.ycombinator.com', @@ -106,10 +108,13 @@ BOOSTED_SITES = { 'voat.xyz', 'talk.lol', 'coracle.social', + 'satellite.earth', + 'comsta.net', 'discuit.net', 'mainchan.com', 'klique.io', 'retalk.com', + 'readup.org', 'arete.network', 'trustcafe.io', 'discussions.app', @@ -119,6 +124,7 @@ BOOSTED_SITES = { 'phuks.co', 'narwhal.city', 'headcycle.com', + 'shpong.com', 'discardedtruth.com', 'dojo.press', 'commentcastles.org', diff --git a/files/helpers/config/const.py b/files/helpers/config/const.py index eeb6b1a7d..1944dc3c6 100644 --- a/files/helpers/config/const.py +++ b/files/helpers/config/const.py @@ -48,9 +48,9 @@ class Service(Enum): RDRAMA = auto() CHAT = auto() -POST_RATELIMIT = '20/day' DEFAULT_RATELIMIT = "30/minute;200/hour;1000/day" -CASINO_RATELIMIT = "100/minute;2000/hour;12000/day" +CASINO_RATELIMIT = "100/minute;5000/hour;20000/day" +DELETE_EDIT_RATELIMIT = "10/minute;50/day" PUSH_NOTIF_LIMIT = 1000 @@ -241,11 +241,13 @@ if SITE_NAME == 'rDrama': "(?*xoxo Carp* 💋" + WELCOME_MSG = f"Hi there! It's me, your soon-to-be favorite rDrama user @carpathianflorist here to give you a brief rundown on some of the sick features we have here. You'll probably want to start by following me, though. So go ahead and click my name and then smash that Follow button. This is actually really important, so go on. Hurry.\n\nThanks!\n\nNext up: If you're a member of the media, similarly just shoot me a DM and I'll set about verifying you and then we can take care of your sad journalism stuff.\n\n**FOR EVERYONE ELSE**\n\n Begin by navigating to [the settings page](/settings/profile) (we'll be prettying this up so it's less convoluted soon, don't worry) and getting some basic customization done.\n\n### Themes\n\nDefinitely change your theme right away, the default one (Midnight) is pretty enough, but why not use something *exotic* like Win98, or *flashy* like Tron? Even Coffee is super tasteful and way more fun than the default. More themes to come when we get around to it!\n\n### Avatar/pfp\n\nYou'll want to set this pretty soon. Set the banner too while you're at it. Your profile is important!\n\n### Flairs\n\nSince you're already on the settings page, you may as well set a flair, too. As with your username, you can - obviously - choose the color of this, either with a hex value or just from the preset colors. And also like your username, you can change this at any time. Paypigs can even further relive the glory days of 90s-00s internet and set obnoxious signatures.\n\n### PROFILE ANTHEMS\n\nSpeaking of profiles, hey, remember MySpace? Do you miss autoplaying music assaulting your ears every time you visited a friend's page? Yeah, we brought that back. Enter a YouTube URL, wait a few seconds for it to process, and then BAM! you've got a profile anthem which people cannot mute. Unless they spend 20,000 dramacoin in the shop for a mute button. Which you can then remove from your profile by spending 40,000 dramacoin on an unmuteable anthem. Get fucked poors!\n\n### Dramacoin?\n\nDramacoin is basically our take on the karma system. Except unlike the karma system, it's not gay and boring and stupid and useless. Dramacoin can be spent at [Marsey's Dramacoin Emporium](/shop/awards) on upgrades to your user experience (many more coming than what's already listed there), and best of all on tremendously annoying awards to fuck with your fellow dramautists. We're always adding more, so check back regularly in case you happen to miss one of the announcement posts.\n\nLike karma, dramacoin is obtained by getting upvotes on your threads and comments. *Unlike* karma, it's also obtained by getting downvotes on your threads and comments. Downvotes don't really do anything here - they pay the same amount of dramacoin and they increase thread/comment ranking just the same as an upvote. You just use them to express petty disapproval and hopefully start a fight. Because all votes are visible here. To hell with your anonymity.\n\nDramacoin can also be traded amongst users from their profiles. Note that there is a 3% transaction fee.\n\n### Badges\n\nRemember all those neat little metallic icons you saw on my profile when you were following me? If not, scroll back up and go have a look. And doublecheck to make sure you pressed the Follow button. Anyway, those are badges. You earn them by doing a variety of things. Some of them even offer benefits, like discounts at the shop. A [complete list of badges and their requirements can be found here](/badges), though I add more pretty regularly, so keep an eye on the [changelog](/post/{CHANGELOG_THREAD}).\n\n### Other stuff\n\nWe're always adding new features, and we take a fun-first approach to development. If you have a suggestion for something that would be fun, funny, annoying - or best of all, some combination of all three - definitely make a thread about it. Or just DM me if you're shy. Weirdo. Anyway there's also the [leaderboards](/leaderboard), boring stuff like two-factor authentication you can toggle on somewhere in the settings page (psycho), the ability to save posts and comments, more than a thousand emojis already (most of which are rDrama originals), and on and on and on and on. This is just the basics, mostly to help you get acquainted with some of the things you can do here to make it more easy on the eyes, customizable, and enjoyable. If you don't enjoy it, just go away! We're not changing things to suit you! Get out of here loser! And no, you can't delete your account :na:\n\nI love you.
    *xoxo Carp* 💋" REDDIT_NOTIFS_USERS = { - 'idio3': IDIO_ID, 'aevann': AEVANN_ID, 'carpflo': CARP_ID, 'carpathianflorist': CARP_ID, 'carpathian florist': CARP_ID, 'the_homocracy': 147, - 'justcool393': JUSTCOOL_ID } elif SITE == 'watchpeopledie.tv': - POST_RATELIMIT = '30/day' - NOTIFICATION_SPAM_AGE_THRESHOLD = 0.5 * 86400 EMAIL = "wpd@watchpeopledie.tv" @@ -779,15 +741,15 @@ elif SITE == 'watchpeopledie.tv': DESCRIPTION = "People die and this is the place to see it. You only have one life, don't make the mistakes seen here." PIN_LIMIT = 4 - WELCOME_MSG = """Hi, you! Welcome to WatchPeopleDie.tv, this really cool site where you can go to watch people die. I'm @CLiTPEELER! If you have any questions about how things work here, or suggestions on how to make them work better than they already do, definitely slide on into my DMs (no fat chicks).\n\nThere's an enormously robust suite of fun features we have here and we're always looking for more to add. Way, way too many to go over in an automated welcome message. And you're probably here for the videos of people dying more than any sort of weird, paradoxical digital community aspect anyway, so I won't bore you with a tedious overview of them. Just head on over to [your settings page](https://watchpeopledie.tv/settings/profile) and have a look at some of the basic profile stuff, at least. You can change your profile picture, username, flair, colors, banners, bio, profile anthem (autoplaying song on your page, like it's MySpace or some shit, hell yeah), CSS, all sorts of things.\n\nOr you can just go back to the main feed and carry on with watching people die. That's what the site is for, after all. Have fun!\n\nAnyway, in closing, WPD is entirely open source. We don't really need new full-time coders or anything, but if you'd like to take a look at our repo - or even submit a PR to change, fix, or add some things - go right ahead! Our codebase lives at https://fsdfsd.net/rDrama/rDrama\n\nWell, that's all. Thanks again for signing up. It's an automated message and all, but I really do mean that. Thank you, specifically. I love you. Romantically. Deeply. Passionately.\n\nHave fun!""" + WELCOME_MSG = """Hi, you! Welcome to WatchPeopleDie.tv, this really cool site where you can go to watch people die. I'm @CLiTPEELER! If you have any questions about how things work here, or suggestions on how to make them work better than they already do, definitely slide on into my DMs (no fat chicks).\n\nThere's an enormously robust suite of fun features we have here and we're always looking for more to add. Way, way too many to go over in an automated welcome message. And you're probably here for the videos of people dying more than any sort of weird, paradoxical digital community aspect anyway, so I won't bore you with a tedious overview of them. Just head on over to [your settings page](/settings/profile) and have a look at some of the basic profile stuff, at least. You can change your profile picture, username, flair, colors, banners, bio, profile anthem (autoplaying song on your page, like it's MySpace or some shit, hell yeah), CSS, all sorts of things.\n\nOr you can just go back to the main feed and carry on with watching people die. That's what the site is for, after all. Have fun!\n\nAnyway, in closing, WPD is entirely open source. We don't really need new full-time coders or anything, but if you'd like to take a look at our repo - or even submit a PR to change, fix, or add some things - go right ahead! Our codebase lives at https://fsdfsd.net/rDrama/rDrama\n\nWell, that's all. Thanks again for signing up. It's an automated message and all, but I really do mean that. Thank you, specifically. I love you. Romantically. Deeply. Passionately.\n\nHave fun!""" FEATURES['PATRON_ICONS'] = True FEATURES['NSFW_MARKING'] = False FEATURES['BOTS'] = False PERMS['HOLE_CREATE'] = 2 - PERMS['POST_EDITING'] = 2 - PERMS['USER_BLACKLIST'] = 6 + PERMS['POST_COMMENT_EDITING'] = 3 + PERMS['MODS_EVERY_HOLE'] = 3 SUB_BANNER_LIMIT = 69420 @@ -821,15 +783,18 @@ elif SITE == 'watchpeopledie.tv': ERROR_MARSEYS[403] = "marseyconfused" - NOTIFICATION_THREAD = 27855 + BUG_THREAD = 61549 SIDEBAR_THREAD = 5403 BANNER_THREAD = 9869 BADGE_THREAD = 52519 CHANGELOG_THREAD = 56363 - ADMIGGER_THREADS = {SIDEBAR_THREAD, BANNER_THREAD, BADGE_THREAD, CHANGELOG_THREAD} + ADMIGGER_THREADS = {SIDEBAR_THREAD, BANNER_THREAD, BADGE_THREAD, CHANGELOG_THREAD, 22937} - TRUESCORE_CHAT_MINIMUM = 10 + MAX_VIDEO_SIZE_MB = 500 + MAX_VIDEO_SIZE_MB_PATRON = 500 + + TRUESCORE_CC_CHAT_MINIMUM = 50 HOLE_NAME = 'flair' HOLE_STYLE_FLAIR = True @@ -842,7 +807,7 @@ elif SITE == 'watchpeopledie.tv': CARP_ID = 48 AEVANN_ID = 9 - SNAKES_ID = 32 + GTIX_ID = 77694 GIFT_NOTIF_ID = CARP_ID SIGNUP_FOLLOW_ID = CARP_ID @@ -853,6 +818,8 @@ elif SITE == 'watchpeopledie.tv': ' capy': AEVANN_ID, 'carp': CARP_ID, 'clit': CARP_ID, + 'g-tix': GTIX_ID, + 'gtix': GTIX_ID, } TIER_TO_NAME = { @@ -873,6 +840,7 @@ elif SITE == 'devrama.net': FEATURES['HOUSES'] = True FEATURES['USERS_PERMANENT_WORD_FILTERS'] = True PERMS["SITE_SETTINGS"] = 4 + PERMS["ORGIES"] = 4 else: # localhost or testing environment implied FEATURES['ASSET_SUBMISSIONS'] = True FEATURES['PRONOUNS'] = True @@ -965,12 +933,8 @@ approved_embed_hosts = [ 'external-preview.redd.it', 'pbs.twimg.com/media', 'i.pinimg.com', - 'kiwifarms.net/attachments', - 'uploads.kiwifarms.net/data/attachments', - 'sneed.today/attachments', - 'uploads.sneed.today/data/attachments', - 'kiwifarms.pl/attachments', - 'uploads.kiwifarms.pl/data/attachments', + 'kiwifarms.st/attachments', + 'uploads.kiwifarms.st/data/attachments', 'upload.wikimedia.org/wikipedia', 'live.staticflickr.com', 'substackcdn.com/image', @@ -1034,8 +998,6 @@ forced_hats = { } -EMAIL_REGEX_PATTERN = '[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{2,63}\.[A-Za-z]{2,63}' - IMAGE_FORMATS = ('png','jpg','jpeg','webp','gif') VIDEO_FORMATS = ('mp4','webm','mov','avi','mkv','flv','m4v','3gp') AUDIO_FORMATS = ('mp3','wav','ogg','aac','m4a','flac') @@ -1109,6 +1071,7 @@ CHUD_PHRASES = ( "Climate action now", "Long live the CCP", "I stand with Ukraine", + "Leo Frank was innocent", ) HEADERS = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"} @@ -1149,13 +1112,8 @@ GIRL_NAMES = { 'Z': ['Zoe', 'Zoey', 'Zaria', 'Zoie'] } -class OrgyTypes: - YOUTUBE = 1 - RUMBLE = 2 - TWITCH = 3 - from sqlalchemy.engine.create import create_engine from sqlalchemy.orm import scoped_session, sessionmaker engine = create_engine(environ.get("DATABASE_URL").strip(), connect_args={"options": "-c statement_timeout=5000 -c idle_in_transaction_session_timeout=40000"}) -db_session = scoped_session(sessionmaker(bind=engine)) +db_session = scoped_session(sessionmaker(bind=engine, autoflush=False)) diff --git a/files/helpers/config/modaction_types.py b/files/helpers/config/modaction_types.py index 8fa9b65f5..a3b28a67e 100644 --- a/files/helpers/config/modaction_types.py +++ b/files/helpers/config/modaction_types.py @@ -116,6 +116,11 @@ MODACTION_TYPES = { "icon": 'fa-dollar-sign', "color": 'bg-success' }, + 'edit_comment': { + "str": 'edited {self.target_link}', + "icon": 'fa-edit', + "color": 'bg-primary' + }, 'edit_post': { "str": 'edited {self.target_link}', "icon": 'fa-edit', diff --git a/files/helpers/const_stateful.py b/files/helpers/const_stateful.py index 58b95fff8..d81eeff21 100644 --- a/files/helpers/const_stateful.py +++ b/files/helpers/const_stateful.py @@ -40,10 +40,12 @@ def const_initialize(): if IS_FISTMAS(): filename = f"snappy_fistmas_{SITE_NAME}.txt" else: - filename = f"snappy_{SITE_NAME}.txt" + filename = f"snappy_rDrama.txt" try: with open(filename, "r", encoding="utf-8") as f: SNAPPY_QUOTES = f.read().split("\n{[para]}\n") + if SITE_NAME == 'WPD': + SNAPPY_QUOTES = [x for x in SNAPPY_QUOTES if 'drama' not in x.lower() and 'deux' not in x.lower()] except FileNotFoundError: pass diff --git a/files/helpers/cron.py b/files/helpers/cron.py index 76bcc5c3e..4485e2ed7 100644 --- a/files/helpers/cron.py +++ b/files/helpers/cron.py @@ -5,6 +5,7 @@ from shutil import make_archive from hashlib import md5 from collections import Counter from sqlalchemy.orm import load_only, InstrumentedAttribute +from sqlalchemy.sql import text import click import requests @@ -16,7 +17,7 @@ import files.routes.static as route_static from files.routes.front import frontlist from files.__main__ import cache from files.classes import * -from files.helpers.alerts import send_repeatable_notification +from files.helpers.alerts import send_repeatable_notification, notif_comment from files.helpers.config.const import * from files.helpers.get import * from files.helpers.lottery import check_if_end_lottery_task @@ -40,6 +41,8 @@ def cron_fn(every_5m, every_1d): #offsitementions.offsite_mentions_task(cache) _award_timers_task() _unpin_expired() + _grant_one_year_badges() + _grant_two_year_badges() if every_1d: _generate_emojis_zip() @@ -54,6 +57,7 @@ def cron_fn(every_5m, every_1d): stats.generate_charts_task(SITE) cache.set('stats', stats.stats(), timeout=CRON_CACHE_TIMEOUT) + g.db.commit() except: print(traceback.format_exc(), flush=True) @@ -69,6 +73,36 @@ def cron_fn(every_5m, every_1d): def cron(every_5m, every_1d): cron_fn(every_5m, every_1d) +def _grant_one_year_badges(): + one_year_ago = int(time.time()) - 364 * 86400 + + notif_text = f"@AutoJanny has given you the following profile badge:\n\n{SITE_FULL_IMAGES}/i/{SITE_NAME}/badges/134.webp\n\n**1 Year Old 🥰**\n\nThis user has wasted an ENTIRE YEAR of their life here! Happy birthday!" + cid = notif_comment(notif_text) + _notif_query = text(f"""insert into notifications + select id, {cid}, false, extract(epoch from now()) + from users where created_utc < {one_year_ago} and id not in (select user_id from badges where badge_id=134);""") + g.db.execute(_notif_query) + + _badge_query = text(f"""insert into badges + select 134, id, null, null, extract(epoch from now()) + from users where created_utc < {one_year_ago} and id not in (select user_id from badges where badge_id=134);""") + g.db.execute(_badge_query) + +def _grant_two_year_badges(): + two_years_ago = int(time.time()) - 729 * 86400 + + notif_text = f"@AutoJanny has given you the following profile badge:\n\n{SITE_FULL_IMAGES}/i/{SITE_NAME}/badges/237.webp\n\n**2 Years Old 🥰🥰**\n\nThis user has wasted TWO WHOLE BUTT YEARS of their life here! Happy birthday!" + cid = notif_comment(notif_text) + _notif_query = text(f"""insert into notifications + select id, {cid}, false, extract(epoch from now()) + from users where created_utc < {two_years_ago} and id not in (select user_id from badges where badge_id=237);""") + g.db.execute(_notif_query) + + _badge_query = text(f"""insert into badges + select 237, id, null, null, extract(epoch from now()) + from users where created_utc < {two_years_ago} and id not in (select user_id from badges where badge_id=237);""") + g.db.execute(_badge_query) + def _sub_inactive_purge_task(): if not HOLE_INACTIVITY_DELETION: return False @@ -213,7 +247,7 @@ def _award_timers_task(): _process_timer(User.progressivestack, [94], "The progressive stack award you received has expired!") _process_timer(User.bird, [95], "The bird site award you received has expired!") _process_timer(User.longpost, [97], "The pizzashill award you received has expired!") - _process_timer(User.marseyawarded, [98], "The marsey award you received has expired!") + _process_timer(User.marseyawarded, [98], "The hieroglyphs award you received has expired!") _process_timer(User.rehab, [109], "The rehab award you received has expired!") _process_timer(User.owoify, [167], "The OwOify award you received has expired!") _process_timer(User.sharpen, [289], "The Sharpen award you received has expired!") diff --git a/files/helpers/get.py b/files/helpers/get.py index 7231729d2..13949cbdf 100644 --- a/files/helpers/get.py +++ b/files/helpers/get.py @@ -15,7 +15,8 @@ def get_id(username, graceful=False): username = sanitize_username(username) if not username: if graceful: return None - abort(404) + abort(400, "Empty username.") + user = g.db.query( User.id ).filter( @@ -28,19 +29,20 @@ def get_id(username, graceful=False): if not user: if graceful: return None - abort(404) + abort(404, "User not found.") return user[0] def get_user(username, v=None, graceful=False, include_blocks=False, id_only=False): if not username: if graceful: return None - abort(404) + abort(400, "Empty username.") username = sanitize_username(username) if not username: if graceful: return None - abort(404) + abort(400, "Empty username.") +\ user = g.db.query( User ).filter( @@ -58,7 +60,7 @@ def get_user(username, v=None, graceful=False, include_blocks=False, id_only=Fal if not user: if graceful: return None - abort(404) + abort(404, "User not found.") if v and include_blocks: user = add_block_props(user, v) @@ -69,7 +71,7 @@ def get_users(usernames, ids_only=False, graceful=False): usernames = [sanitize_username(n) for n in usernames] if not any(usernames): if graceful and len(usernames) == 0: return [] - abort(404) + abort(400, "Empty usernames.") if ids_only: users = g.db.query(User.id) @@ -85,7 +87,7 @@ def get_users(usernames, ids_only=False, graceful=False): ).all() if len(users) != len(usernames) and not graceful: - abort(404) + abort(404, "Users not found.") if ids_only: users = [x[0] for x in users] @@ -97,12 +99,12 @@ def get_account(id, v=None, graceful=False, include_blocks=False): id = int(id) except: if graceful: return None - abort(404) + abort(400, "User ID needs to be an integer.") user = g.db.get(User, id) if not user: - if not graceful: abort(404) + if not graceful: abort(404, "User not found.") else: return None if include_blocks: @@ -116,22 +118,24 @@ def get_accounts_dict(ids, v=None, graceful=False): ids = set([int(id) for id in ids]) except: if graceful: return None - abort(404) + abort(400, "User IDs need to be an integer.") users = g.db.query(User).filter(User.id.in_(ids)) users = users.all() - if len(users) != len(ids) and not graceful: abort(404) + if len(users) != len(ids) and not graceful: + abort(404, "Users not found.") + return {u.id:u for u in users} def get_post(i, v=None, graceful=False): try: i = int(i) except: if graceful: return None - else: abort(404) + else: abort(400, "Post ID needs to be an integer.") if not i: if graceful: return None - else: abort(404) + else: abort(400, "Empty post ID.") if v: vt = g.db.query(Vote).filter_by(user_id=v.id, post_id=i).subquery() @@ -156,7 +160,7 @@ def get_post(i, v=None, graceful=False): if not post: if graceful: return None - else: abort(404) + else: abort(404, "Post not found.") x = post[0] x.voted = post[1] or 0 @@ -165,7 +169,7 @@ def get_post(i, v=None, graceful=False): post = g.db.get(Post, i) if not post: if graceful: return None - else: abort(404) + else: abort(404, "Post not found.") x=post return x @@ -221,16 +225,16 @@ def get_comment(i, v=None, graceful=False): try: i = int(i) except: if graceful: return None - abort(404) + abort(404, "Comment ID needs to be an integer.") if not i: if graceful: return None - else: abort(404) + else: abort(404, "Empty comment ID.") - comment=g.db.get(Comment, i) + comment = g.db.get(Comment, i) if not comment: if graceful: return None - else: abort(404) + else: abort(404, "Comment not found.") return add_vote_and_block_props(comment, v, CommentVote) @@ -363,12 +367,6 @@ def get_error(): else: return None -def get_msg(): - if request.referrer and request.referrer.split('?')[0] == request.base_url: - return request.values.get("msg") - else: - return None - def get_page(): try: return max(int(request.values.get("page", 1)), 1) except: return 1 diff --git a/files/helpers/marsify.py b/files/helpers/marsify.py index 949d6f3d1..a212e0a6a 100644 --- a/files/helpers/marsify.py +++ b/files/helpers/marsify.py @@ -3,6 +3,9 @@ from random import choice from .const_stateful import marsey_mappings def marsify(text): + if '`' in text or '
    ' 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" %} -
    +
    diff --git a/files/templates/admin/banned_domains.html b/files/templates/admin/banned_domains.html index 294eaed6a..924c266dd 100644 --- a/files/templates/admin/banned_domains.html +++ b/files/templates/admin/banned_domains.html @@ -29,7 +29,7 @@
    - + diff --git a/files/templates/admin/delete_media.html b/files/templates/admin/delete_media.html index 12fd91800..966ed81d5 100644 --- a/files/templates/admin/delete_media.html +++ b/files/templates/admin/delete_media.html @@ -3,9 +3,7 @@ {% block pagetitle %}Delete Media{% endblock %} {% block content %} -{% if error %}{{macros.alert(error, true)}}{% endif %} -{% if msg %}{{macros.alert(msg, false)}}{% endif %} - +
    diff --git a/files/templates/admin/edit_rules.html b/files/templates/admin/edit_rules.html index d7225362d..1c9f3a333 100644 --- a/files/templates/admin/edit_rules.html +++ b/files/templates/admin/edit_rules.html @@ -3,7 +3,6 @@ {% block pagetitle %}Edit {{SITE_NAME}}'s rules{% endblock %} {% block content %} -{% if msg %}{{macros.alert(msg, false)}}{% endif %}
    @@ -13,7 +12,7 @@
    - + diff --git a/files/templates/admin/orgy_control.html b/files/templates/admin/orgy_control.html index 006644079..f426c9192 100644 --- a/files/templates/admin/orgy_control.html +++ b/files/templates/admin/orgy_control.html @@ -3,7 +3,6 @@ {% block pagetitle %}Orgy Control Panel{% endblock %} {% block content %} -{% if msg %}{{macros.alert(msg, false)}}{% endif %}
    @@ -13,8 +12,8 @@
    - {%if not orgy%} - + {% if not orgy %} +
    @@ -25,7 +24,7 @@
    - +
    @@ -33,17 +32,17 @@
    - +
    - {%else%} -
    + {% else %} +
    - {%endif%} + {% endif %}
    diff --git a/files/templates/admin/update_assets.html b/files/templates/admin/update_assets.html index b540d8e65..da431ab48 100644 --- a/files/templates/admin/update_assets.html +++ b/files/templates/admin/update_assets.html @@ -2,15 +2,12 @@ {% block pagetitle %}Update {{type}}{% endblock %} {% block pagetype %}message{% endblock %} {% block content %} - {% if error %}{{macros.alert(error, true)}}{% endif %} - {% if msg %}{{macros.alert(msg, false)}}{% endif %} -

    Update {{type}}

    -
    + diff --git a/files/templates/api.html b/files/templates/api.html index ece0598a8..d4a50a721 100644 --- a/files/templates/api.html +++ b/files/templates/api.html @@ -19,11 +19,11 @@

    Python example:

    	import requests
     
    -	headers={"Authorization": "access_token_goes_here"}
    +	headers = {"Authorization": "access_token_goes_here"}
     
    -	url="{{SITE_FULL}}/?sort=comments"
    +	url = "{{SITE_FULL}}/?sort=comments"
     
    -	r=requests.get(url, headers=headers)
    +	r = requests.get(url, headers=headers)
     
     	print(r.json())
     
    @@ -34,11 +34,11 @@

    Aother python example:

    	import requests
     
    -	headers={"Authorization": "access_token_goes_here"}
    +	headers = {"Authorization": "access_token_goes_here"}
     
    -	url="{{SITE_FULL}}/unread"
    +	url = "{{SITE_FULL}}/unread"
     
    -	r=requests.get(url, headers=headers)
    +	r = requests.get(url, headers=headers)
     
     	print(r.json())
     
    @@ -71,11 +71,11 @@

    Python example:

    	import requests
     
    -	headers={"Authorization": "access_token_goes_here"}
    +	headers = {"Authorization": "access_token_goes_here"}
     
    -	url="{{SITE_FULL}}/?sort=comments"
    +	url = "{{SITE_FULL}}/?sort=comments"
     
    -	r=requests.get(url, headers=headers)
    +	r = requests.get(url, headers=headers)
     
     	print(r.json())
     
    @@ -86,11 +86,11 @@

    Aother python example:

    	import requests
     
    -	headers={"Authorization": "access_token_goes_here"}
    +	headers = {"Authorization": "access_token_goes_here"}
     
    -	url="{{SITE_FULL}}/unread"
    +	url = "{{SITE_FULL}}/unread"
     
    -	r=requests.get(url, headers=headers)
    +	r = requests.get(url, headers=headers)
     
     	print(r.json())
     
    diff --git a/files/templates/awards.html b/files/templates/awards.html index cbe103299..b85b351bd 100644 --- a/files/templates/awards.html +++ b/files/templates/awards.html @@ -11,20 +11,20 @@ {% if p.award_count("wholesome", v) %} {% set wholesome = '/e/marseywholesome.webp' %} - {{ stackable_award('wholesome', wholesome, ':#marseywholesome:') }} + {{stackable_award('wholesome', wholesome, ':#marseywholesome:')}} {% endif %} {% if p.award_count("train", v) %} - {{ stackable_award('train', '/e/marseytrain.webp', ':#marseytrain:') }} + {{stackable_award('train', '/e/marseytrain.webp', ':#marseytrain:')}} {% endif %} {% if p.award_count("scooter", v) %} - {{ stackable_award('scooter', '/e/marseyscooter.webp', ':#marseyscooter:') }} + {{stackable_award('scooter', '/e/marseyscooter.webp', ':#marseyscooter:')}} {% endif %} {% if p.award_count("firework", v) %} - {{ stackable_award('firework') }} + {{stackable_award('firework')}} {% endif %} {% if p.award_count("confetti", v) and IS_BIRTHGAY() %} diff --git a/files/templates/casino.html b/files/templates/casino.html index 9ea9b21b5..2b9850afc 100644 --- a/files/templates/casino.html +++ b/files/templates/casino.html @@ -1,7 +1,7 @@ {% extends "default.html" %} {% block pagetitle %}Casino{% endblock %} {# Title (~25char max), Description (~80char max), -Icon (fa-foo-bar), Color (#ff0000), URL (/post/12345/) #} +Icon (fa-foo-bar), Color (#ff0000), URL (/casino/roulette) #} {%- set GAME_INDEX = [ ( 'Roulette', diff --git a/files/templates/chat.html b/files/templates/chat.html index f0c348668..972c8a8a7 100644 --- a/files/templates/chat.html +++ b/files/templates/chat.html @@ -7,12 +7,12 @@ {% include "modals/expanded_image.html" %} {% include "modals/emoji.html" %} {% include "util/macros.html" %} - {% set vlink = '' %} -
    + {% set vlink = '
    - {{ macros.chat_users_online() }} + {{macros.chat_users_online()}}
    {{macros.chat_group_template()}} @@ -28,17 +28,6 @@ {{macros.chat_users_list()}}
    - -
    -
    - Action successful! -
    -
    -
    -
    - Error, please try again later. -
    -
    diff --git a/files/templates/comments.html b/files/templates/comments.html index 0b9db4971..9e699fd37 100644 --- a/files/templates/comments.html +++ b/files/templates/comments.html @@ -23,36 +23,31 @@ {% set replies=c.replies(sort=sort) %} {% endif %} -{% if c.is_blocking and not c.ghost or (c.is_banned or c.deleted_utc) and not (v and v.admin_level >= PERMS['POST_COMMENT_MODERATION']) and not (v and v.id==c.author_id) %} - +{% if (c.is_banned or c.deleted_utc) and not (v and v.admin_level >= PERMS['POST_COMMENT_MODERATION']) and not (v and v.id==c.author_id) %}
    - - -
    -
    - -
    - - {% if render_replies %} -
    - {% if level<9 or request.path.startswith('/notifications') %} - {% for reply in replies %} - {{single_comment(reply, level=level+1)}} - {% endfor %} - {% elif replies %} - - More comments - {% endif %} + +
    +
    +
    - {% endif %} + {% if render_replies %} +
    + {% if level<9 or request.path.startswith('/notifications') %} + {% for reply in replies %} + {{single_comment(reply, level=level+1)}} + {% endfor %} + {% elif replies %} + + More comments + {% endif %} +
    + {% endif %} +
    -
    - - {% else %} {% set score=c.score %} @@ -75,7 +70,7 @@ {% if c.parent_post %} {% if c.author_id==v.id and replies and is_notification_page %} Comment {{'Replies' if (replies | length)>1 else 'Reply'}}: {{c.post.realtitle(v) | safe}} - {% elif c.post.author_id==v.id and c.level == 1 and is_notification_page%} + {% elif c.post.author_id==v.id and c.level == 1 and is_notification_page %} Post Reply: {{c.post.realtitle(v) | safe}} {% elif is_notification_page and c.parent_post in v.subscribed_idlist %} Subscribed Thread: {{c.post.realtitle(v) | safe}} @@ -119,7 +114,7 @@ {% endif %}
    -
    +
    -
    +
    diff --git a/files/templates/modals/punish.html b/files/templates/modals/punish.html index 101670c67..8e4b3c781 100644 --- a/files/templates/modals/punish.html +++ b/files/templates/modals/punish.html @@ -4,7 +4,7 @@ diff --git a/files/templates/oauth.html b/files/templates/oauth.html index 3545d407a..b1fa4d79a 100644 --- a/files/templates/oauth.html +++ b/files/templates/oauth.html @@ -6,14 +6,14 @@

    wants to access your @{{v.username}} account.

    It will not be able to see your password, or change your account settings.

    - - - - - - - - No, back to {{SITE_NAME}} + + + + + + + + No, back to {{SITE_NAME}}
    {% endblock %} diff --git a/files/templates/orgy.html b/files/templates/orgy.html index 4ecb1965c..eca5ec1c2 100644 --- a/files/templates/orgy.html +++ b/files/templates/orgy.html @@ -7,42 +7,41 @@ {% include "modals/expanded_image.html" %} {% include "modals/emoji.html" %} {% include "util/macros.html" %} - {% set vlink = '' %} -
    - -
    -
    -

    {{orgy.title}}

    -
    - {% if orgy.is_youtube() %} + {% set vlink = ' +
    +

    {{orgy.title}}

    +
    +

    + {% if orgy.type == 'youtube' %} - {% elif orgy.is_rumble() %} - - {% elif orgy.is_twitch() %} - - {%endif%} + {% elif orgy.type == 'rumble' %} + + {% elif orgy.type == 'twitch' %} + + {% elif orgy.type == 'file' %} + + + + + {% endif %} +

    +
    +
    + +
    +
    + {{macros.chat_group_template()}}
    -
    Old Chat - {{macros.chat_users_list()}}
    -
    -
    - {{macros.chat_group_template()}} -
    -
    - -
    - {{macros.chat_line_template()}} -
    - {{macros.chat_users_online()}} - {{macros.chat_window(vlink)}} +
    + {{macros.chat_line_template()}}
    - + {{macros.chat_users_online()}} + {{macros.chat_window(vlink)}}
    @@ -50,6 +49,8 @@ + + diff --git a/files/templates/owners.html b/files/templates/owners.html index c5979ac69..438b305ce 100644 --- a/files/templates/owners.html +++ b/files/templates/owners.html @@ -13,7 +13,7 @@ {% for user, created_utc in users %} {% include "user_in_table.html" %} - 1599343262 %}data-time="{{created_utc}}"{% endif %}> + {% endfor %} diff --git a/files/templates/post.html b/files/templates/post.html index 045ac83d5..871857e86 100644 --- a/files/templates/post.html +++ b/files/templates/post.html @@ -37,8 +37,8 @@ @@ -91,17 +91,6 @@ Unable to copy link
    - -
    -
    - Action successful! -
    -
    -
    -
    - Error, please try again later. -
    -
    {% block mobilenavbar %}{% include "mobile_navigation_bar.html" %}{% endblock %} {% block scripts %}{% endblock %} {% endblock %} diff --git a/files/templates/sidebar_WPD.html b/files/templates/sidebar_WPD.html index 02d6a1cc5..08ec9bb06 100644 --- a/files/templates/sidebar_WPD.html +++ b/files/templates/sidebar_WPD.html @@ -11,7 +11,7 @@ {%- elif v -%} {%- set image = macros.random_image("assets/images/" ~ SITE_NAME ~ "/sidebar") -%} {%- else -%} - {%- set image = '/i/' ~ SITE_NAME ~ '/sidebar.webp?x=6' -%} + {%- set image = SITE_FULL_IMAGES ~ '/i/' ~ SITE_NAME ~ '/sidebar.webp?x=6' -%} {%- endif -%} {% if request.path != '/sidebar' %} @@ -21,7 +21,7 @@ {% endif %}

    - + @@ -30,45 +30,45 @@