-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathyoutubeDiscordRelay.py
363 lines (289 loc) · 14.9 KB
/
youtubeDiscordRelay.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
import json
import re
#youtube stuff imported
import httplib2
import os
import sys
from apiclient.discovery import build
from apiclient.errors import HttpError
from oauth2client.client import flow_from_clientsecrets
from oauth2client.file import Storage
from oauth2client.tools import argparser, run_flow
import time
#discord stuff imported
import discord #gets the discord and asyncio librarys
import asyncio
##problems
#unsure what will happen in a headless enviroment if the oauth hasnt been set
##if the token and you input a invalid token the first time it will continue to say invalid token for tokens that are even valid
##ideas
#possiblity of use of file io to get the information of the client token for discord and stuff.
#use regex to help format the chat (honestly not needed)
##youtube chat porition
#this will handle getting chat from youtube which will then be pushed to discord
#!/usr/bin/python
####variables
config = {"channelName": "", "pageToken": "", "serverName": "", "discordToken": "","discordToYoutubeFormating": "", "youtubeToDiscordFormatting":"","discordToYoutube":True,"youtubeToDiscord":True}
botName = "none"
botUserID = "empty"
youtube = ""
firstRun = "off"
#used as global varibles and were defined before we start using them to avoid problems down the road
channelToUse = ""
# The CLIENT_SECRETS_FILE variable specifies the name of a file that contains
# the OAuth 2.0 information for this application, including its client_id and
# client_secret. You can acquire an OAuth 2.0 client ID and client secret from
# the {{ Google Cloud Console }} at
# {{ https://cloud.google.com/console }}.
# Please ensure that you have enabled the YouTube Data API for your project.
# For more information about using OAuth2 to access the YouTube Data API, see:
# https://developers.google.com/youtube/v3/guides/authentication
# For more information about the client_secrets.json file format, see:
# https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
CLIENT_SECRETS_FILE = "client_secrets.json"
# This OAuth 2.0 access scope allows for full read/write access to the
# authenticated user's account.
YOUTUBE_READ_WRITE_SCOPE = "https://www.googleapis.com/auth/youtube"
YOUTUBE_API_SERVICE_NAME = "youtube"
YOUTUBE_API_VERSION = "v3"
# This variable defines a message to display if the CLIENT_SECRETS_FILE is
# missing.
MISSING_CLIENT_SECRETS_MESSAGE = """
WARNING: Please configure OAuth 2.0
To make this sample run you will need to populate the client_secrets.json file
found at:
%s
with information from the {{ Cloud Console }}
{{ https://cloud.google.com/console }}
For more information about the client_secrets.json file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
""" % os.path.abspath(os.path.join(os.path.dirname(__file__),
CLIENT_SECRETS_FILE))
def get_authenticated_service(args):
flow = flow_from_clientsecrets(CLIENT_SECRETS_FILE,
scope=YOUTUBE_READ_WRITE_SCOPE,
message=MISSING_CLIENT_SECRETS_MESSAGE)
storage = Storage("%s-oauth2.json" % sys.argv[0])
credentials = storage.get()
if credentials is None or credentials.invalid:
credentials = run_flow(flow, storage, args)
return build(YOUTUBE_API_SERVICE_NAME, YOUTUBE_API_VERSION,
http=credentials.authorize(httplib2.Http()))
# Retrieve a list of the liveStream resources associated with the currently
# authenticated user's channel.
def getLiveId(youtube): #this gets the live chat id
global liveChatId,botUserID #pulls in the bots livechatid and botuserid for further saving and modifying
list_streams_request = youtube.liveBroadcasts().list( #checks for the live chat id through this
part="snippet", #this is what we look through to get the live chat id
broadcastStatus="all", #we need both of these to get the live chat id
broadcastType="all"
).execute() #executes it so its not just some object
liveChatId = list_streams_request["items"][0]["snippet"]["liveChatId"]#sifts through the output to get the live chat id and saves it
botUserID = list_streams_request["items"][1]["snippet"]["channelId"] #saves the bots channel user id that we will use as a identifier
print("liveID {0}".format(liveChatId)) #print the live chat id
async def listChat(youtube):
global pageToken #pulls in the page token
global liveChatId #pulls in the liveChatID
global botUserID #pulls in the bots channel ID
global config
try:
continuation = True
try:
list_chatmessages = youtube.liveChatMessages().list( #lists the chat messages
part="id,snippet,authorDetails", #gets the author details needed and the snippet all of which giving me the message and username
liveChatId=liveChatId,
maxResults=200,
pageToken=config["pageToken"] #gives the previous token so it loads a new section of the chat
).execute() #executes it so its not just some object
config["pageToken"] = list_chatmessages["nextPageToken"] #page token for next use
except googleapiclient.errors.HttpError:
continuation = False
#print(list_chatmessages)
if continuation == True:
msgCheckRegex = re.compile(r'(:)') #setup for if we happen to need this it should never change either way
for temp in list_chatmessages["items"]: #goes through all the stuff in the list messages list
message = temp["snippet"]["displayMessage"] #gets the display message
username = temp["authorDetails"]["displayName"] #gets the users name
userID = temp["authorDetails"]["channelId"]
if message != "" and username != "" and config["youtubeToDiscord"] == True: #this makes sure that the message and username slot arent empty before putting this to the discord chat
print(temp)
fileSave("youtubeMsgJson.json", temp)
if userID != botUserID:
print(config["youtubeToDiscordFormatting"].format(username,message))
msg = (config["youtubeToDiscordFormatting"].format(username,message))
await channelToUse.send(msg)
elif userID == botUserID: #if the userId is the bots then check the message to see if the bot sent it.
try:
msgCheckComplete = msgCheckRegex.search(message) #checks the message against the previously created regex for ":"
if msgCheckComplete.group(1) != ":": #if its this then go and send the message as normal
print(config["youtubeToDiscordFormatting"].format(username,message))
msg = (config["youtubeToDiscordFormatting"].format(username,message))
print("sending")
print(botUserID)
await channelToUse.send(msg)
except AttributeError as error:
msg = (config["youtubeToDiscordFormatting"].format(username,message))
print("sending")
print(botUserID)
await channelToUse.send(msg)
except ConnectionResetError:
login()
print("Youtube Connection Error... Reconnecting")
async def sendLiveChat(msg): #sends messages to youtube live chat
list_chatmessages_inset = youtube.liveChatMessages().insert(
part = "snippet",
body = dict (
snippet = dict(
liveChatId = liveChatId,
type = "textMessageEvent",
textMessageDetails = dict(
messageText = msg
)
)
)
)
list_chatmessages_inset.execute()
#print(list_chatmessages_inset.execute()) #debug for sending live chat messages
def login():
global youtube
args = argparser.parse_args()
youtube = get_authenticated_service(args) #authenticates the api and saves it to youtube
getLiveId(youtube)
if __name__ == "__main__":
login()
#args = argparser.parse_args()
#youtube = get_authenticated_service(args) #authenticates the api and saves it to youtube
#getLiveId(youtube)
##discord portion of the bot
#this will be the main code
client = discord.Client() #sets this to just client for reasons cuz y not? (didnt have to be done like this honestly could of been just running discord.Client().)
async def discordSendMsg(msg): #this is for sending messages to discord
global config
global channelToUse #pulls in t{0} : {1}".format(author,msg)he global variable
await channelToUse.send(msg) #sends the message to the channel specified in the beginning
@client.event
async def on_ready(): #when the discord api has logged in and is ready then this even is fired
if firstRun == "off":
#these 2 variables are used to keep track of what channel is thre real channel to use when sending messages to discord
global config , botName
global channelToUse #this is where we save the channel information (i think its a class)
global channelUsed #this is the channel name we are looking for
#this is just to show what the name of the bot is and the id
print('Logged in as') ##these things could be changed a little bit here
print(client.user.name+ "#" + client.user.discriminator)
botName = client.user.name + "#" + client.user.discriminator #gets and saves the bots name and discord tag
print(client.user.id)
for guild in client.guilds: #this sifts through all the bots servers and gets the channel we want
#should probly add a check in for the server in here im guessing
for channel in guild.channels:
if channel.name == config["channelName"] and str(channel.type) == "text": #checks if the channel name is what we want and that its a text channel
channelToUse = channel #saves the channel that we wanna use since we found it
await youtubeChatImport() #runs the youtube chat import code
else:
await getFirstRunInfo()
async def youtubeChatImport(): #this is used to pull the from youtube to discord
i = 0
while True:
i += 1
try:
await listChat(youtube) #checks the youtube chat
except:
pass
if i == 8:
fileSave("config.json", config)
i = 0
await asyncio.sleep(5) #this works
@client.event
async def on_message(message): #waits for the discord message event and pulls it somewhere
global config
global channelToUse #pulls in the global variable
if firstRun == "off":
if str(channelToUse.name) == str(message.channel) and str(message.author.name) != botName and str(message.author.name) != client.user.name and str(message.author.name) != client.user.discriminator and config["discordToYoutube"] == True:
print(config["discordToYoutubeFormating"].format(message.author,message.content)) #prints this to the screen
await sendLiveChat(config["discordToYoutubeFormating"].format(message.author.display_name,message.content)) #prints this to the screen
##file load and save stuff
def fileSave(filename, config):
print("Saving")
f = open(filename, 'w') #opens the file your saving to with write permissions
f.write(json.dumps(config) + "\n") #writes the string to a file
f.close() #closes the file io
def fileLoad():
f = open("config.json", 'r') #opens the file your saving to with read permissions
config = ""
for line in f: #gets the information from the file
config = json.loads(line) #this will unserialize the table
return config
##first run stuff
def getToken(): #gets the token
global config
realToken = "false" #this is just for the while loop
while realToken == "false":
config["discordToken"] = input("Discord Token: ") #gets the user input
try:
client.run(config["discordToken"]) #atempts to run it and if it fails then execute the next bit of code if not then save it and go on
except:
print("Please enter a valid token")
sys.exit(0) #this is a work around for the bug that causes the code not think the discord token is valid even tho it is after the first time of it being invalid
else:
realToken = "true"
async def getFirstRunInfo():
global config
print('Logged in as') ##these things could be changed a little bit here
print(client.user.name)
print(client.user.id)
while config["serverName"] == "":
for guild in client.guilds: #this sifts through all the bots servers and gets the channel we want
print(guild.name)
if input("If this is the server you want type yes if not hit enter: ") == "yes":
config["serverName"] = guild.name
break
while config["channelName"] == "":
for guild in client.guilds: #this sifts through all the bots servers and gets the channel we want
#should probly add a check in for the server in here im guessing
#print(server.name)
for channel in guild.channels:
if str(channel.type) == "text":
print(channel.name)
if input("If this is the channel you want type yes if not hit enter: ") == "yes":
config["channelName"] = channel.name
break
while config["youtubeToDiscordFormatting"] == "": #fills the youtube to discord formating
config["youtubeToDiscordFormatting"] = input("""Please enter the chat formatting for chat coming from youtube to go to discord.
{0} is the placeholder for the username
{1} is the placeholder for the message
Ex. "{0} : {1}: """)
while config["discordToYoutubeFormating"] == "": #fills the discord to youtube formating
config["discordToYoutubeFormating"] = input("""Please enter the chat formatting for chat coming from discord to go to youtube.
{0} is the placeholder for the username
{1} is the placeholder for the message
Ex. "{0} : {1}": """)
print("Configuration complete")
fileSave("config.json", config) #saves the file
print("Please run the command normally to run the bot")
await client.close()
if os.path.isfile("config.json") == False:#checks if the file exists and if it doesnt then we go to creating it
print("Config missing. This may mean this is your first time setting this up")
firstRun = "on"
else:
config = fileLoad() #if it exists try to load it
if firstRun == "on":
config = {"channelName": "", "pageToken": "", "serverName": "", "discordToken": "","discordToYoutubeFormating": "", "youtubeToDiscordFormatting":"","youtubeToDiscord": True, "discordToYoutube": True}
getToken()
def discordStart(token):
while True:
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(client.start(token))
except (discord.ConnectionClosed, discord.GatewayNotFound) as error:
loop.run_until_complete(client.logout())
loop.close()
discordStart(token)
print("Client Connection Lost")
# cancel all tasks lingering
except KeyboardInterrupt:
loop.run_until_complete(client.logout())
finally:
print("Client Closed")
loop.close()
print("Reconnecting")
discordStart(config["discordToken"])