twiins.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. from selenium import webdriver
  2. from selenium.webdriver.common.by import By
  3. from selenium.webdriver.chrome.options import Options
  4. from selenium.webdriver.support.ui import WebDriverWait
  5. from selenium.webdriver.support import expected_conditions as EC
  6. from selenium.common.exceptions import TimeoutException
  7. from tabulate import tabulate
  8. import time
  9. import typer
  10. import click_spinner
  11. app = typer.Typer(help="Minimal Twitch information gatherer")
  12. SEP = '==&&=='
  13. def func_return(result, should_print, pretty=False):
  14. if should_print:
  15. if pretty:
  16. table = []
  17. for row in result.split('\n'):
  18. table.append(row.split(SEP))
  19. print(tabulate(table, tablefmt="plain"))
  20. else:
  21. print(result)
  22. else:
  23. return result
  24. def get_driver():
  25. with click_spinner.spinner():
  26. typer.echo("Providing driver...")
  27. options = webdriver.FirefoxOptions()
  28. options.add_argument('--headless')
  29. options.set_preference('permissions.default.image', 2)
  30. options.set_preference(
  31. 'dom.ipc.plugins.enabled.libflashplayer.so', 'false')
  32. driver = webdriver.Firefox(options=options)
  33. return driver
  34. @app.command()
  35. def check_live(
  36. channel: str,
  37. should_print: bool = typer.Option(False, '--print', '-p')
  38. ):
  39. """
  40. Checks if CHANNEL is livestreaming.
  41. SHOULDPRINT determines if result is returned to stdout or function caller.
  42. """
  43. sel = 'a[data-a-target="watch-mode-to-home"]'
  44. URL = f'https://www.twitch.tv/{channel}'
  45. driver = get_driver()
  46. driver.get(URL)
  47. is_live = None
  48. try:
  49. with click_spinner.spinner():
  50. typer.echo("Looking up channel...")
  51. element = WebDriverWait(driver=driver, timeout=5).until(
  52. EC.presence_of_element_located((By.CSS_SELECTOR, sel)))
  53. if driver.find_element(by=By.CSS_SELECTOR, value=sel):
  54. is_live = True
  55. except TimeoutException:
  56. is_live = False
  57. driver.close()
  58. return func_return(is_live, should_print)
  59. @app.command()
  60. def get_categories(
  61. should_print: bool = typer.Option(False, '--print', '-p'),
  62. pretty: bool = typer.Option(False, '--pretty', '-t')
  63. ):
  64. """
  65. Gets top 30 Twitch categories.
  66. SHOULDPRINT determines if result is returned to stdout
  67. or to function caller.
  68. PRETTY determines if output is pretty printed or not.
  69. """
  70. URL = "https://www.twitch.tv/directory"
  71. driver = get_driver()
  72. driver.get(URL)
  73. with click_spinner.spinner():
  74. typer.echo("Looking up categories...")
  75. element = WebDriverWait(driver=driver, timeout=5).until(
  76. EC.presence_of_element_located(
  77. (By.CSS_SELECTOR, 'div[data-target="directory-first-item"]'))
  78. )
  79. # init results string
  80. results = ""
  81. # get categories first entry
  82. firstEl = driver.find_element(
  83. by=By.CSS_SELECTOR, value='div[data-target="directory-first-item"]')
  84. results += firstEl.find_element(by=By.CSS_SELECTOR,
  85. value='h2[title]').text + SEP
  86. results += firstEl.find_element(by=By.CSS_SELECTOR,
  87. value='p a').text.replace('viewers', '') + SEP
  88. results += firstEl.find_element(
  89. by=By.CSS_SELECTOR,
  90. value='a[data-a-target="tw-box-art-card-link"]'
  91. ).get_attribute('href') + '\n'
  92. # get categories other entries
  93. for item in driver.find_elements(by=By.CSS_SELECTOR, value='div[data-target=""]'):
  94. results += item.find_element(by=By.CSS_SELECTOR,
  95. value='h2[title]').text + SEP
  96. results += item.find_element(by=By.CSS_SELECTOR,
  97. value='p a').text.replace('viewers', '') + SEP
  98. results += item.find_element(
  99. by=By.CSS_SELECTOR,
  100. value='a[data-a-target="tw-box-art-card-link"]'
  101. ).get_attribute('href') + '\n'
  102. driver.close()
  103. return func_return(results, should_print, pretty)
  104. @app.command()
  105. def get_channels(
  106. target: str,
  107. should_print: bool = typer.Option(False, '--print', '-p'),
  108. pretty: bool = typer.Option(False, '--pretty', '-t')
  109. ):
  110. """
  111. Gets live channels in TARGET, it can be a channel link, or just channel name.
  112. SHOULDPRINT determines if result is returned to stdout or function caller.
  113. PRETTY determines if output is pretty printed or not.
  114. """
  115. if target.find("https://") > -1:
  116. URL = f'{target}?sort=VIEWER_COUNT'
  117. else:
  118. URL = f'https://www.twitch.tv/directory/game/{target}?sort=VIEWER_COUNT'
  119. driver = get_driver()
  120. driver.get(URL)
  121. with click_spinner.spinner():
  122. typer.echo("Lookin up channels...")
  123. element = WebDriverWait(driver=driver, timeout=5).until(
  124. EC.presence_of_element_located(
  125. (By.CSS_SELECTOR, 'div[data-target="directory-container"]'))
  126. )
  127. el = driver.find_element(
  128. by=By.CSS_SELECTOR, value='div[data-target="directory-container"]')
  129. # init results
  130. results = ""
  131. # get channels first entry
  132. firstEl = el.find_element(
  133. by=By.CSS_SELECTOR, value='div[data-target="directory-first-item"]')
  134. results += firstEl.find_element(
  135. by=By.CSS_SELECTOR,
  136. value='a[data-a-target="preview-card-channel-link"]'
  137. ).get_attribute('href').split('/')[-2] + SEP
  138. results += firstEl.find_element(by=By.CSS_SELECTOR,
  139. value='h3[title]').text + '\n'
  140. # get channels other entries
  141. for item in el.find_elements(by=By.CSS_SELECTOR, value='div[data-target=""]'):
  142. results += item.find_element(
  143. by=By.CSS_SELECTOR,
  144. value='a[data-a-target="preview-card-channel-link"]'
  145. ).get_attribute('href').split('/')[-2] + SEP
  146. results += item.find_element(by=By.CSS_SELECTOR,
  147. value='h3[title]').text + '\n'
  148. driver.close()
  149. return func_return(results, should_print, pretty)
  150. @app.command()
  151. def choose(pretty: bool = typer.Option(False, '--pretty', '-t')):
  152. """
  153. Gets top 30 Twitch categories, requests user to choose one
  154. and then gets current live channels.
  155. PRETTY determines if output is pretty printed or not.
  156. """
  157. categories = get_categories(should_print=False)
  158. splitCategories = []
  159. for cat in categories.split('\n'):
  160. splited = cat.split(SEP)
  161. if len(splited) < 3:
  162. continue
  163. catObj = {
  164. "name": splited[0].strip(),
  165. "views": splited[1].strip(),
  166. "link": splited[2].strip()
  167. }
  168. splitCategories.append(catObj)
  169. if pretty:
  170. print(tabulate(splitCategories, showindex="always",
  171. headers="keys", tablefmt="plain"))
  172. else:
  173. for i, cat in enumerate(splitCategories):
  174. print(i, cat["name"], cat["views"])
  175. try:
  176. choosen = int(input("\nChoose a category by index number:"))
  177. if choosen > len(splitCategories) or choosen < 0:
  178. raise Exception()
  179. except:
  180. typer.secho("ERROR: Invalid index",
  181. fg=typer.colors.WHITE, bg=typer.colors.RED)
  182. raise typer.Exit(code=1)
  183. get_channels(splitCategories[choosen]["link"],
  184. should_print=True, pretty=pretty)
  185. if __name__ == "__main__":
  186. app()