File size: 7,374 Bytes
94ecfcc |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
#!/usr/bin/env python3
# coding: utf-8
import http
import logging
import os
import re
import requests
from bs4 import BeautifulSoup
from config import ENABLE_VIP
from limit import Payment
class Channel(Payment):
def subscribe_channel(self, user_id: int, share_link: str) -> str:
if not re.findall(r"youtube\.com|youtu\.be", share_link):
raise ValueError("Is this a valid YouTube Channel link?")
if ENABLE_VIP:
self.cur.execute("select count(user_id) from subscribe where user_id=%s", (user_id,))
usage = int(self.cur.fetchone()[0])
if usage >= 10:
logging.warning("User %s has subscribed %s channels", user_id, usage)
return "You have subscribed too many channels. Maximum 5 channels."
data = self.get_channel_info(share_link)
channel_id = data["channel_id"]
self.cur.execute("select user_id from subscribe where user_id=%s and channel_id=%s", (user_id, channel_id))
if self.cur.fetchall():
raise ValueError("You have already subscribed this channel.")
self.cur.execute(
"INSERT IGNORE INTO channel values"
"(%(link)s,%(title)s,%(description)s,%(channel_id)s,%(playlist)s,%(last_video)s)",
data,
)
self.cur.execute("INSERT INTO subscribe values(%s,%s, NULL)", (user_id, channel_id))
self.con.commit()
logging.info("User %s subscribed channel %s", user_id, data["title"])
return "Subscribed to {}".format(data["title"])
def unsubscribe_channel(self, user_id: int, channel_id: str) -> int:
affected_rows = self.cur.execute(
"DELETE FROM subscribe WHERE user_id=%s AND channel_id=%s", (user_id, channel_id)
)
self.con.commit()
logging.info("User %s tried to unsubscribe channel %s", user_id, channel_id)
return affected_rows
@staticmethod
def extract_canonical_link(url: str) -> str:
# canonic link works for many websites. It will strip out unnecessary stuff
props = ["canonical", "alternate", "shortlinkUrl"]
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36"
}
cookie = {"CONSENT": "PENDING+197"}
# send head request first
r = requests.head(url, headers=headers, allow_redirects=True, cookies=cookie)
if r.status_code != http.HTTPStatus.METHOD_NOT_ALLOWED and "text/html" not in r.headers.get("content-type", ""):
# get content-type, if it's not text/html, there's no need to issue a GET request
logging.warning("%s Content-type is not text/html, no need to GET for extract_canonical_link", url)
return url
html_doc = requests.get(url, headers=headers, cookies=cookie, timeout=5).text
soup = BeautifulSoup(html_doc, "html.parser")
for prop in props:
element = soup.find("link", rel=prop)
try:
href = element["href"]
if href not in ["null", "", None, "https://consent.youtube.com/m"]:
return href
except Exception as e:
logging.debug("Canonical exception %s %s e", url, e)
return url
def get_channel_info(self, url: str) -> dict:
api_key = os.getenv("GOOGLE_API_KEY")
canonical_link = self.extract_canonical_link(url)
try:
channel_id = canonical_link.split("youtube.com/channel/")[1]
except IndexError:
channel_id = canonical_link.split("/")[-1]
channel_api = (
f"https://www.googleapis.com/youtube/v3/channels?part=snippet,contentDetails&id={channel_id}&key={api_key}"
)
data = requests.get(channel_api).json()
snippet = data["items"][0]["snippet"]
title = snippet["title"]
description = snippet["description"]
playlist = data["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]
return {
"link": url,
"title": title,
"description": description,
"channel_id": channel_id,
"playlist": playlist,
"last_video": self.get_latest_video(playlist),
}
@staticmethod
def get_latest_video(playlist_id: str) -> str:
api_key = os.getenv("GOOGLE_API_KEY")
video_api = (
f"https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&maxResults=1&"
f"playlistId={playlist_id}&key={api_key}"
)
data = requests.get(video_api).json()
video_id = data["items"][0]["snippet"]["resourceId"]["videoId"]
logging.info(f"Latest video %s from %s", video_id, data["items"][0]["snippet"]["channelTitle"])
return f"https://www.youtube.com/watch?v={video_id}"
def has_newer_update(self, channel_id: str) -> str:
self.cur.execute("SELECT playlist,latest_video FROM channel WHERE channel_id=%s", (channel_id,))
data = self.cur.fetchone()
playlist_id = data[0]
old_video = data[1]
newest_video = self.get_latest_video(playlist_id)
if old_video != newest_video:
logging.info("Newer update found for %s %s", channel_id, newest_video)
self.cur.execute("UPDATE channel SET latest_video=%s WHERE channel_id=%s", (newest_video, channel_id))
self.con.commit()
return newest_video
def get_user_subscription(self, user_id: int) -> str:
self.cur.execute(
"""
select title, link, channel.channel_id from channel, subscribe
where subscribe.user_id = %s and channel.channel_id = subscribe.channel_id
""",
(user_id,),
)
data = self.cur.fetchall()
text = ""
for item in data:
text += "[{}]({}) `{}\n`".format(*item)
return text
def group_subscriber(self) -> dict:
# {"channel_id": [user_id, user_id, ...]}
self.cur.execute("select * from subscribe where is_valid=1")
data = self.cur.fetchall()
group = {}
for item in data:
group.setdefault(item[1], []).append(item[0])
logging.info("Checking periodic subscriber...")
return group
def deactivate_user_subscription(self, user_id: int):
self.cur.execute("UPDATE subscribe set is_valid=0 WHERE user_id=%s", (user_id,))
self.con.commit()
def sub_count(self) -> str:
sql = """
select user_id, channel.title, channel.link
from subscribe, channel where subscribe.channel_id = channel.channel_id
"""
self.cur.execute(sql)
data = self.cur.fetchall()
text = f"Total {len(data)} subscriptions found.\n\n"
for item in data:
text += "{} ==> [{}]({})\n".format(*item)
return text
def del_cache(self, user_link: str) -> int:
unique = self.extract_canonical_link(user_link)
caches = self.r.hgetall("cache")
count = 0
for key in caches:
if key.startswith(unique):
count += self.del_send_cache(key)
return count
if __name__ == "__main__":
s = Channel.extract_canonical_link("https://www.youtube.com/shorts/KkbYbknjPBM")
print(s)
|