Advertisement
kosteksyk

YouTubeLogin.py

Sep 19th, 2013
749
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.80 KB | None | 0 0
  1. '''
  2.    YouTube plugin for XBMC
  3.    Copyright (C) 2010-2012 Tobias Ussing And Henrik Mosgaard Jensen
  4.  
  5.    This program is free software: you can redistribute it and/or modify
  6.    it under the terms of the GNU General Public License as published by
  7.    the Free Software Foundation, either version 3 of the License, or
  8.    (at your option) any later version.
  9.  
  10.    This program is distributed in the hope that it will be useful,
  11.    but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13.    GNU General Public License for more details.
  14.  
  15.    You should have received a copy of the GNU General Public License
  16.    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. '''
  18.  
  19. import re
  20. import sys
  21. import time
  22. try: import simplejson as json
  23. except ImportError: import json
  24.  
  25. # ERRORCODES:
  26. # 0 = Ignore
  27. # 200 = OK
  28. # 303 = See other (returned an error message)
  29. # 500 = uncaught error
  30.  
  31.  
  32. class YouTubeLogin():
  33.     APIKEY = "AI39si6hWF7uOkKh4B9OEAX-gK337xbwR9Vax-cdeF9CF9iNAcQftT8NVhEXaORRLHAmHxj6GjM-Prw04odK4FxACFfKkiH9lg"
  34.  
  35.     urls = {}
  36.     urls[u"oauth_api_login"] = u"https://accounts.google.com/o/oauth2/auth?client_id=208795275779.apps.googleusercontent.com&redirect_uri=urn:ietf:wg:oauth:2.0:oob&scope=http%3A%2F%2Fgdata.youtube.com&response_type=code"
  37.  
  38.     def __init__(self):
  39.         self.xbmc = sys.modules["__main__"].xbmc
  40.  
  41.         self.pluginsettings = sys.modules["__main__"].pluginsettings
  42.         self.settings = sys.modules["__main__"].settings
  43.         self.language = sys.modules["__main__"].language
  44.         self.plugin = sys.modules["__main__"].plugin
  45.         self.dbg = sys.modules["__main__"].dbg
  46.  
  47.         self.utils = sys.modules["__main__"].utils
  48.         self.core = sys.modules["__main__"].core
  49.         self.common = sys.modules["__main__"].common
  50.  
  51.     def login(self, params={}):
  52.         get = params.get
  53.         self.common.log("")
  54.  
  55.         old_user_name = self.pluginsettings.userName()
  56.         old_user_password = self.pluginsettings.userPassword()
  57.         self.settings.openSettings()
  58.  
  59.         user_name = self.pluginsettings.userName()
  60.         user_password = self.pluginsettings.userPassword()
  61.  
  62.         self.dbg = self.pluginsettings.debugModeIsEnabled()
  63.         result = ""
  64.         status = 500
  65.  
  66.         if not user_name:
  67.             return (result, 200)
  68.  
  69.         refreshed = False
  70.         if get("new", "false") == "false" and self.pluginsettings.authenticationRefreshRoken and old_user_name == user_name and old_user_password == user_password:
  71.             self.common.log("refreshing token: " + str(refreshed))
  72.             refreshed = self.core._oRefreshToken()
  73.  
  74.         if not refreshed:
  75.             result, status = self.authorize()
  76.  
  77.         self.xbmc.executebuiltin("Container.Refresh")
  78.         return (result, status)
  79.  
  80.     def authorize(self):
  81.         self.common.log("token not refresh, or new uname or password")
  82.         self.settings.setSetting("oauth2_access_token", "")
  83.         self.settings.setSetting("oauth2_refresh_token", "")
  84.         self.settings.setSetting("oauth2_expires_at", "")
  85.         (result, status) = self._httpLogin({"new": "true"})
  86.         if status == 200:
  87.             (result, status) = self._apiLogin()
  88.         if status == 200:
  89.             self.utils.showErrorMessage(self.language(30031), result, 303)
  90.         else:
  91.             self.utils.showErrorMessage(self.language(30609), result, status)
  92.         return result, status
  93.  
  94.     def _apiLogin(self):
  95.         self.common.log("")
  96.  
  97.         url = self.urls[u"oauth_api_login"]
  98.  
  99.         logged_in = False
  100.         fetch_options = {"link": url, "no-language-cookie": "true"}
  101.         step = 0
  102.         self.common.log("Part A")
  103.         while not logged_in and fetch_options and step < 6:
  104.             self.common.log("Step : " + str(step))
  105.             step += 1
  106.  
  107.             ret = self.core._fetchPage(fetch_options)
  108.             fetch_options = False
  109.  
  110.             newurl = self.common.parseDOM(ret["content"], "form", attrs={"method": "POST"}, ret="action")
  111.             state_wrapper = self.common.parseDOM(ret["content"], "input", attrs={"id": "state_wrapper"}, ret="value")
  112.  
  113.             if len(newurl) > 0 and len(state_wrapper) > 0:
  114.                 url_data = {"state_wrapper": state_wrapper[0],
  115.                             "submit_access": "true"}
  116.  
  117.                 fetch_options = {"link": newurl[0].replace("&amp;", "&"), "url_data": url_data, "no-language-cookie": "true"}
  118.                 self.common.log("Part B")
  119.                 continue
  120.  
  121.             code = self.common.parseDOM(ret["content"], "input", attrs={"id": "code"}, ret="value")
  122.             if len(code) > 0:
  123.                 url = "https://accounts.google.com/o/oauth2/token"
  124.                 url_data = {"client_id": "208795275779.apps.googleusercontent.com",
  125.                             "client_secret": "sZn1pllhAfyonULAWfoGKCfp",
  126.                             "code": code[0],
  127.                             "redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
  128.                             "grant_type": "authorization_code"}
  129.                 fetch_options = {"link": url, "url_data": url_data}
  130.                 self.common.log("Part C")
  131.                 continue
  132.  
  133.             # use token
  134.             if ret["content"].find("access_token") > -1:
  135.                 self.common.log("Part D")
  136.                 oauth = json.loads(ret["content"])
  137.  
  138.                 if len(oauth) > 0:
  139.                     self.common.log("Part D " + repr(oauth["expires_in"]))
  140.                     self.settings.setSetting("oauth2_expires_at", str(int(oauth["expires_in"]) + time.time()))
  141.                     self.settings.setSetting("oauth2_access_token", oauth["access_token"])
  142.                     self.settings.setSetting("oauth2_refresh_token", oauth["refresh_token"])
  143.  
  144.                     logged_in = True
  145.                     self.common.log("Done:" + self.settings.getSetting("username"))
  146.  
  147.         if logged_in:
  148.             return (self.language(30030), 200)
  149.         else:
  150.             self.common.log("Failed")
  151.             return (self.language(30609), 303)
  152.  
  153.     def _httpLogin(self, params={}):
  154.         get = params.get
  155.         self.common.log("")
  156.         status = 500
  157.  
  158.         if get("new", "false") == "true" or get("page", "false") != "false":
  159.             self.settings.setSetting("login_info", "")
  160.             self.settings.setSetting("SID", "")
  161.             self.settings.setSetting("login_cookies", "")
  162.         elif self.settings.getSetting("login_info") != "":
  163.             self.common.log("returning existing login info: " + self.settings.getSetting("login_info"))
  164.             return (self.settings.getSetting("login_info"), 200)
  165.  
  166.         fetch_options = {"link": get("link", "http://www.youtube.com/")}
  167.  
  168.         step = 0
  169.         galx = ""
  170.         ret = {}
  171.  
  172.         while fetch_options and step < 18:  # 6 steps for 2-factor login
  173.             self.common.log("Step : " + str(step))
  174.             step += 1
  175.  
  176.             if step == 17:
  177.                 return (self.core._findErrors(ret), 303)
  178.  
  179.             ret = self.core._fetchPage(fetch_options)
  180.  
  181.             if ret["content"].find(" captcha") > -1:
  182.                 self.common.log("Captcha needs to be filled")
  183.                 break
  184.             fetch_options = False
  185.  
  186.             # Check if we are logged in.
  187.             nick = self.common.parseDOM(ret["content"], "p", attrs={"class": "masthead-expanded-acct-sw-id2"})
  188.  
  189.             # Check if there are any errors to report
  190.             errors = self.core._findErrors(ret, silent=True)
  191.             if errors:
  192.                 if errors.find("cookie-clear-message-1") == -1 and (errors.find("The code you entered didn") == -1 or (errors.find("The code you entered didn") > -1 and step > 12)):
  193.                     self.common.log("Returning error: " + repr(errors))
  194.                     return (errors, 303)
  195.  
  196.             if len(nick) > 0 and nick[0] != "Sign In":
  197.                 self.common.log("Logged in. Parsing data: " + repr(nick))
  198.                 status = self._getLoginInfo(nick)
  199.                 return(ret, status)
  200.  
  201.             # Click login link on youtube.com
  202.             newurl = self.common.parseDOM(ret["content"], "button", attrs={"href": ".*?ServiceLogin.*?"}, ret="href")
  203.             if len(newurl) > 0:
  204.                 # Start login procedure
  205.                 if newurl[0] != "#":
  206.                     fetch_options = {"link": newurl[0].replace("&amp;", "&"), "referer": ret["location"]}
  207.                     self.common.log("Part A : " + repr(fetch_options))
  208.  
  209.             # Fill out login information and send.
  210.             newurl = self.common.parseDOM(ret["content"].replace("\n", " "), "form", attrs={"id": "gaia_loginform"}, ret="action")
  211.             if len(newurl) > 0:
  212.                 (galx, url_data) = self._fillLoginInfo(ret)
  213.                 if len(galx) > 0 and len(url_data) > 0:
  214.                     fetch_options = {"link": newurl[0], "no-language-cookie": "true", "url_data": url_data, "hidden": "true", "referer": ret["location"]}
  215.                     self.common.log("Part B")
  216.                     self.common.log("fetch options: " + repr(fetch_options), 10)  # WARNING, SHOWS LOGIN INFO/PASSWORD
  217.                     continue
  218.  
  219.             newurl = self.common.parseDOM(ret["content"], "meta", attrs={"http-equiv": "refresh"}, ret="content")
  220.             if len(newurl) > 0:
  221.                 newurl = newurl[0].replace("&amp;", "&")
  222.                 newurl = newurl[newurl.find("&#39;") + 5:newurl.rfind("&#39;")]
  223.                 fetch_options = {"link": newurl, "no-language-cookie": "true", "referer": ret["location"]}
  224.                 self.common.log("Part C: "  + repr(fetch_options))
  225.                 continue
  226.  
  227.             ## 2-factor login start
  228.             if ret["content"].find("smsUserPin") > -1:
  229.                 url_data = self._fillUserPin(ret["content"])
  230.                 if len(url_data) == 0:
  231.                     return (False, 500)
  232.  
  233.                 new_part = self.common.parseDOM(ret["content"], "form", attrs={"name": "verifyForm"}, ret="action")
  234.                 fetch_options = {"link": new_part[0], "url_data": url_data, "no-language-cookie": "true", "referer": ret["location"]}
  235.  
  236.                 self.common.log("Part D: " + repr(fetch_options))
  237.                 continue
  238.  
  239.             smsToken = self.common.parseDOM(ret["content"].replace("\n", ""), "input", attrs={"name": "smsToken"}, ret="value")
  240.  
  241.             if len(smsToken) > 0 and galx != "":
  242.                 url_data = {"smsToken": smsToken[0],
  243.                             "PersistentCookie": "yes",
  244.                             "service": "youtube",
  245.                             "GALX": galx}
  246.  
  247.                 target_url = self.common.parseDOM(ret["content"], "form", attrs={"name": "hiddenpost"}, ret="action")
  248.                 fetch_options = {"link": target_url[0], "url_data": url_data, "no-language-cookie": "true", "referer": ret["location"]}
  249.                 self.common.log("Part E: " + repr(fetch_options))
  250.                 continue
  251.  
  252.             ## 2-factor login finish
  253.             if not fetch_options:
  254.                 # Check for errors.
  255.                 return (self.core._findErrors(ret), 303)
  256.  
  257.         return (ret, status)
  258.  
  259.     def _fillLoginInfo(self, ret):
  260.         content = ret["content"]
  261.         rmShown = self.common.parseDOM(content, "input", attrs={"name": "rmShown"}, ret="value")
  262.         cont = self.common.parseDOM(content, "input", attrs={"name": "continue"}, ret="value")
  263.         uilel = self.common.parseDOM(content, "input", attrs={"name": "uilel"}, ret="value") # Deprecated?
  264.         if len(uilel) == 0: # Deprecated?
  265.             uilel = self.common.parseDOM(content, "input", attrs= {"id":"uilel"}, ret="value")
  266.         if len(uilel) == 0 and ret["new_url"].find("uilel=") > -1:
  267.             uilel = ret["new_url"][ret["new_url"].find("uilel=")+6]
  268.             if uilel.find("&") > -1:
  269.                 uilel = uilel[:uilel.find("&")]
  270.             uilel = [uilel]
  271.         dsh = self.common.parseDOM(content, "input", attrs={"name": "dsh"}, ret="value")
  272.         if len(dsh) == 0:
  273.             dsh = self.common.parseDOM(content, "input", attrs={"id": "dsh"}, ret="value")
  274.  
  275.         galx = self.common.parseDOM(content, "input", attrs={"name": "GALX"}, ret="value")
  276.         uname = self.pluginsettings.userName()
  277.         pword = self.pluginsettings.userPassword()
  278.  
  279.         if pword == "":
  280.             pword = self.common.getUserInput(self.language(30628), hidden=True)
  281.  
  282.         if len(galx) == 0 or len(cont) == 0 or len(uilel) == 0 or len(dsh) == 0 or len(rmShown) == 0 or uname == "" or pword == "":
  283.             self.common.log("_fillLoginInfo missing values for login form " + repr(galx) + repr(cont) + repr(uilel) + repr(dsh) + repr(rmShown) + repr(uname) + str(len(pword)))
  284.             return ("", {})
  285.         else:
  286.             galx = galx[0]
  287.             url_data = {"pstMsg": "0",
  288.                         "ltmpl": "sso",
  289.                         "dnConn": "",
  290.                         "continue": cont[0],
  291.                         "service": "youtube",
  292.                         "uilel": uilel[0],
  293.                         "dsh": dsh[0],
  294.                         "hl": "en_US",
  295.                         "timeStmp": "",
  296.                         "secTok": "",
  297.                         "GALX": galx,
  298.                         "Email": uname,
  299.                         "Passwd": pword,
  300.                         "PersistentCookie": "yes",
  301.                         "rmShown": rmShown[0],
  302.                         "signin": "Sign in",
  303.                         "asts": ""
  304.                         }
  305.         return (galx, url_data)
  306.  
  307.     def _fillUserPin(self, content):
  308.         self.common.log(repr(content), 5)
  309.         smsToken = self.common.parseDOM(content, "input", attrs={"name": "smsToken"}, ret="value")
  310.         self.smsToken = smsToken
  311.         userpin = self.common.getUserInputNumbers(self.language(30627))
  312.  
  313.         if len(userpin) > 0:
  314.             url_data = {"smsToken": smsToken[0],
  315.                         "PersistentCookie": "yes",
  316.                         "smsUserPin": userpin,
  317.                         "smsVerifyPin": "Verify",
  318.                         "timeStmp": "",
  319.                         "secTok": ""}
  320.             self.common.log("Done: " + repr(url_data))
  321.             return url_data
  322.         else:
  323.             self.common.log("Replace this with a message telling users that they didn't enter a pin")
  324.             return {}
  325.  
  326.     def _getLoginInfo(self, nick):
  327.         self.common.log(nick)
  328.         status = 303
  329.  
  330.         # Save cookiefile in settings
  331.         cookies = self.common.getCookieInfoAsHTML()
  332.         login_info = self.common.parseDOM(cookies, "cookie", attrs={"name": "LOGIN_INFO"}, ret="value")
  333.         SID = self.common.parseDOM(cookies, "cookie", attrs={"name": "SID", "domain": ".youtube.com"}, ret="value")
  334.         scookies = {}
  335.         self.common.log("COOKIES:" + repr(cookies))
  336.         tnames = re.compile(" name='(.*?)' ").findall(cookies)
  337.         for key in tnames:
  338.             tval = self.common.parseDOM(cookies, "cookie", attrs={"name": key}, ret="value")
  339.             if len(tval) > 0:
  340.                 scookies[key] = tval[0]
  341.         self.common.log("COOKIES:" + repr(scookies))
  342.  
  343.         if len(login_info) == 1:
  344.             self.common.log("LOGIN_INFO: " + repr(login_info))
  345.             self.settings.setSetting("login_info", login_info[0])
  346.         else:
  347.             self.common.log("Failed to get LOGIN_INFO from youtube: " + repr(login_info))
  348.  
  349.         if len(SID) == 1:
  350.             self.common.log("SID: " + repr(SID))
  351.             self.settings.setSetting("SID", SID[0])
  352.         else:
  353.             self.common.log("Failed to get SID from youtube: " + repr(SID))
  354.  
  355.         if len(SID) == 1 and len(login_info) == 1:
  356.             status = 200
  357.             self.settings.setSetting("login_cookies", repr(scookies))
  358.  
  359.         self.common.log("Done")
  360.         return status
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement