-
Notifications
You must be signed in to change notification settings - Fork 0
/
LovelyUploadBot.py
executable file
·211 lines (196 loc) · 9.14 KB
/
LovelyUploadBot.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
import praw
import os
from time import time, gmtime, sleep, localtime, strftime
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
VERBOSE = True # Setting to false suppresses mundane information and most warnings.
DELAY = 5
INTERVAL = 20
VID_TITLE = 0
VID_URL = 1
GRUMPS_NAME = 'Game Grumps'
GRUMPS_PL = 'UU9CuvdOVfMPvKCiwdGKL3cQ' # Uploads playlist of Game Grumps YouTube channel.
GRUMPS_SUBREDDIT = 'gamegrumps' # Subreddit new videos will be submitted to.
GRUMPOUT_NAME = 'Grump Out'
GRUMPOUT_PL = 'UUAQ0o3l-H3y_n56C3yJ9EHA' # Uploads playlist for Grump Out.
class Youtuber(object):
def __init__(self, youtube, reddit, name, uploadPl, sub):
self.subreddit = reddit.subreddit(sub)
self.name = name
# I use this number to track when a video is added.
self.numVids = youtube.playlistItems().list(part="snippet, ContentDetails", maxResults=3,\
playlistId=uploadPl).execute()['pageInfo']['totalResults']
self.plID = uploadPl
self.uploadQueue = set()
# The video list is used later to track when a new video is uploaded.
self.genVidList(youtube)
verbose("Initialized channel %s." % (self.name))
def genVidList(self, youtube):
# Generates a list of every upload from the given channel.
https, pl = getPlaylistItems(youtube, self.plID)
moreVids = True
self.vidList = set()
while moreVids:
# Adds the video id of each video in the page to the set.
for item in pl['items']:
id = item['contentDetails']['videoId']
self.vidList.add(id)
# Prevents the loop from ending until we know the ID of every video.
if len(self.vidList) >= self.numVids: moreVids = False
# Loads the next page if there is one.
if 'nextPageToken' in pl.keys():
https, pl = getNextPlPage(youtube, https, pl)
elif moreVids:
verbose('Warning: Loop reached while generating video list!', override=False)
https, pl = getPlaylistItems(youtube, self.plID)
def update(self, youtube):
self.getLatestVideo(youtube)
# Posting too much at a time can result in a shadow ban, so I limit the
# number of parallel posts to 3.
if self.uploadQueue and len(self.uploadQueue) <= 3:
verbose("Found new content uploaded by %s!" % (self.name))
for vid in self.uploadQueue.copy():
try:
self.subreddit.submit(title=vid[VID_TITLE], url=vid[VID_URL],\
resubmit=True, send_replies=False)
self.uploadQueue.remove(vid)
verbose('Submitted video \"%s\"' % (vid[VID_TITLE]))
except Exception as e:
verbose(e)
elif self.uploadQueue and len(self.uploadQueue) > 3:
verbose("Warning! More than 3 videos detected. Exiting.")
verbose("These videos were detected: %s" % (str(self.uploadQueue)))
raise Exception("Too many videos detected.")
else: verbose("No new uploads found for channel %s." % (self.name), override=False)
def getLatestVideo(self, youtube):
# This function checks if the total number of videos in the upload playlist
# has increased, and returns the new videos if so.
https, pl = getPlaylistItems(youtube, self.plID)
newNumVids = pl['pageInfo']['totalResults']
if newNumVids == self.numVids: return
elif newNumVids < self.numVids:
# Regenerates the video list if the given channel removes a video.
verbose('Video removed by channel %s, reinitializing.' % (self.name))
self.reInit(youtube)
return
dVids = newNumVids - self.numVids
self.numVids = newNumVids
i = 0
loop = 0
while i < dVids:
# Searches each page of the playlist for new videos until it has found them all.
for item in pl['items']:
id = item['contentDetails']['videoId']
if id not in self.vidList:
# Check if the video was actually uploaded today and add it if yes.
if checkTime(item['contentDetails']['videoPublishedAt']):
title = item['snippet']['title']
url = "https://www.youtube.com/watch?v=%s" % (id)
self.vidList.add(id)
self.uploadQueue.add((title, url))
i += 1
else:
verbose('Found a stray video not uploaded today. Reinitializing.')
self.reInit(youtube)
return
# Loads the next page if there is one.
if 'nextPageToken' in pl.keys():
https, pl = getNextPlPage(youtube, https, pl)
elif i < dVids:
# Because googles information isn't always accurate, we can get
# an infinite search loop.
loop += 1
verbose('Warning: Loop %d reached while searching for latest video!' % (loop))
if loop >= 5:
verbose('Am I trapped? Reinitializing.')
self.reInit(youtube)
return
https, pl = getPlaylistItems(youtube, self.plID)
def reInit(self, youtube):
# Reinits the vidlist.
https, pl = getPlaylistItems(youtube, self.plID)
self.numVids = pl['pageInfo']['totalResults']
self.genVidList(youtube)
def main():
verbose('Initializing...')
youtube, reddit = initAPIs()
if youtube == None and reddit == None: return
gameGrumps = Youtuber(youtube, reddit, GRUMPS_NAME, GRUMPS_PL, GRUMPS_SUBREDDIT)
grumpOut = Youtuber(youtube, reddit, GRUMPOUT_NAME, GRUMPOUT_PL, GRUMPS_SUBREDDIT)
verbose('Done!')
# Checks for new videos and uploads them when they appear.
while True:
try:
verbose("Checking for new uploads...", override=False)
gameGrumps.update(youtube)
grumpOut.update(youtube)
controlledSleep(INTERVAL, DELAY)
except KeyboardInterrupt:
verbose("Keyboard interrupt received, ending violently.")
return
except Exception as e:
verbose(e)
return
def verbose(s, override=True):
timeStamp = "[%s]:" % (strftime("%I:%M:%S %p",localtime(time())))
if VERBOSE or override: print(timeStamp, s)
def initAPIs():
try:
# See README for more information.
path = os.path.dirname(os.path.abspath(__file__)) + '/keys.txt'
with open(path, 'rt') as file:
keys = file.read().splitlines()
YT_API_KEY = keys[0]
REDDIT_ID = keys[1]
REDDIT_SECRET = keys[2]
REDDIT_AGENT = keys[3]
REDDIT_USER = keys[4]
REDDIT_PASS = keys[5]
except Exception as e:
verbose(e)
verbose("FATAL: Problem opening keys.txt! Please view the ReadMe for help.")
return None, None
# This gives me an object that lets me deal with youtube.
youtube = build("youtube", "v3", developerKey=YT_API_KEY)
# This object lets me interact with reddit.
reddit = praw.Reddit(client_id=REDDIT_ID, client_secret=REDDIT_SECRET,\
user_agent=REDDIT_AGENT,\
username=REDDIT_USER, password=REDDIT_PASS)
return youtube, reddit
# Used to determine how often the bot pings youtube. The interval must be < 60.
# Delay is the number of seconds after 00 the bot should wait to ping.
def controlledSleep(interval, delay):
timer = abs((60 + delay) - localtime(time()).tm_sec)
while timer > interval: timer -= interval
sleep(timer)
# A decorator that ensures the playlist functions will execute until they get a response.
def ensureConnection(f):
def g(*args, **kwargs):
timer = 1
while True:
try:
return f(*args, **kwargs)
except:
verbose("Warning! Connection error while getting playlist items!")
verbose("Retrying in %d seconds..." % (timer))
sleep(timer)
timer *= 2
return g
@ensureConnection
def getPlaylistItems(youtube, plID):
https = youtube.playlistItems().list(part="snippet, ContentDetails",\
maxResults=50, playlistId=plID)
return https, https.execute()
@ensureConnection
def getNextPlPage(youtube, https, pl):
https = youtube.playlistItems().list_next(https, pl)
return https, https.execute()
def checkTime(date):
# Verifys that the publication date matches todays date.
# Thanks /u/flashmozzg
year, month, day = date[0:date.find('T')].split('-')
vidDate = (int(year), int(month), int(day))
t = gmtime(time())
today = (t.tm_year, t.tm_mon, t.tm_mday)
return vidDate == today
if __name__ == "__main__": main()