comments.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. # -*- coding: utf-8 -*-
  2. import codecs
  3. import json
  4. import sys
  5. import time
  6. import os
  7. from socket import error as SocketError
  8. from socket import timeout
  9. from ssl import SSLError
  10. try:
  11. # py2
  12. from urllib2 import URLError
  13. from httplib import HTTPException
  14. except ImportError:
  15. # py3
  16. from urllib.error import URLError
  17. from http.client import HTTPException
  18. try:
  19. import logger
  20. import helpers
  21. import pil
  22. import dlfuncs
  23. except ImportError:
  24. from . import logger
  25. from . import helpers
  26. from . import pil
  27. from . import dlfuncs
  28. from instagram_private_api import ClientError
  29. """
  30. The content of this file was originally written by https://github.com/taengstagram
  31. The code has been edited for use in PyInstaLive.
  32. """
  33. class CommentsDownloader(object):
  34. def __init__(self, destination_file):
  35. self.api = pil.ig_api
  36. self.broadcast = pil.livestream_obj
  37. self.destination_file = destination_file
  38. self.comments = []
  39. def get_live(self, first_comment_created_at=0):
  40. comments_collected = self.comments
  41. before_count = len(comments_collected)
  42. try:
  43. comments_res = self.api.broadcast_comments(
  44. self.broadcast.get('id'), last_comment_ts=first_comment_created_at)
  45. comments = comments_res.get('comments', [])
  46. first_comment_created_at = (
  47. comments[0]['created_at_utc'] if comments else int(time.time() - 5))
  48. comments_collected.extend(comments)
  49. after_count = len(comments_collected)
  50. if after_count > before_count:
  51. broadcast = self.broadcast.copy()
  52. broadcast.pop('segments', None) # save space
  53. broadcast['comments'] = comments_collected
  54. with open(self.destination_file, 'w') as outfile:
  55. json.dump(broadcast, outfile, indent=2)
  56. self.comments = comments_collected
  57. except (SSLError, timeout, URLError, HTTPException, SocketError) as e:
  58. logger.warn('Comment downloading error: %s' % e)
  59. except ClientError as e:
  60. if e.code == 500:
  61. logger.warn('Comment downloading ClientError: %d %s' %
  62. (e.code, e.error_response))
  63. elif e.code == 400 and not e.msg:
  64. logger.warn('Comment downloading ClientError: %d %s' %
  65. (e.code, e.error_response))
  66. else:
  67. raise e
  68. finally:
  69. try:
  70. time.sleep(4)
  71. except KeyboardInterrupt:
  72. return first_comment_created_at
  73. return first_comment_created_at
  74. def get_replay(self):
  75. comments_collected = []
  76. starting_offset = 0
  77. encoding_tag = self.broadcast.get('encoding_tag')
  78. while True:
  79. try:
  80. comments_res = self.api.replay_broadcast_comments(
  81. self.broadcast.get('id'), starting_offset=starting_offset, encoding_tag=encoding_tag)
  82. starting_offset = comments_res.get('ending_offset', 0)
  83. comments = comments_res.get('comments', [])
  84. comments_collected.extend(comments)
  85. if not comments_res.get('comments') or not starting_offset:
  86. break
  87. time.sleep(4)
  88. except Exception:
  89. pass
  90. if comments_collected:
  91. self.broadcast['comments'] = comments_collected
  92. self.broadcast['initial_buffered_duration'] = 0
  93. with open(self.destination_file, 'w') as outfile:
  94. json.dump(self.broadcast, outfile, indent=2)
  95. self.comments = comments_collected
  96. def save(self):
  97. broadcast = self.broadcast.copy()
  98. broadcast.pop('segments', None)
  99. broadcast['comments'] = self.comments
  100. with open(self.destination_file, 'w') as outfile:
  101. json.dump(broadcast, outfile, indent=2)
  102. @staticmethod
  103. def generate_log(comments={}, download_start_time=0, log_file="", comments_delay=10.0, gen_from_arg=False):
  104. try:
  105. if gen_from_arg:
  106. with open(pil.gencomments_arg, 'r') as comments_json:
  107. comments = json.load(comments_json).get("comments", None)
  108. if comments:
  109. log_file = os.path.join(
  110. pil.dl_path, os.path.basename(pil.gencomments_arg.replace(".json", ".log")))
  111. logger.info("Generating comments file from input...")
  112. else:
  113. logger.warn(
  114. "The input file does not contain any comments.")
  115. logger.separator()
  116. return None
  117. python_version = sys.version.split(' ')[0]
  118. comments_timeline = {}
  119. wide_build = sys.maxunicode > 65536
  120. for c in comments:
  121. if 'offset' in c:
  122. for k in list(c.get('comment')):
  123. c[k] = c.get('comment', {}).get(k)
  124. c['created_at_utc'] = download_start_time + c.get('offset')
  125. created_at_utc = str(2 * (c.get('created_at_utc') // 2))
  126. comment_list = comments_timeline.get(created_at_utc) or []
  127. comment_list.append(c)
  128. comments_timeline[created_at_utc] = comment_list
  129. if comments_timeline:
  130. comment_errors = 0
  131. total_comments = 0
  132. timestamps = sorted(list(comments_timeline))
  133. subs = []
  134. for tc in timestamps:
  135. t = comments_timeline[tc]
  136. clip_start = int(tc) - int(download_start_time) + \
  137. int(comments_delay)
  138. if clip_start < 0:
  139. clip_start = 0
  140. comments_log = ''
  141. for c in t:
  142. try:
  143. if python_version.startswith('3'):
  144. if c.get('user', {}).get('is_verified'):
  145. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  146. '{} {}: {}'.format(c.get('user', {}).get('username'),
  147. "(v)", c.get('text')))
  148. else:
  149. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  150. '{}: {}'.format(c.get('user', {}).get('username'),
  151. c.get('text')))
  152. else:
  153. if not wide_build:
  154. if c.get('user', {}).get('is_verified'):
  155. comments_log += '{}{}\n\n'.format(
  156. time.strftime(
  157. '%H:%M:%S\n', time.gmtime(clip_start)),
  158. '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)",
  159. c.get('text').encode('ascii', 'ignore')))
  160. else:
  161. comments_log += '{}{}\n\n'.format(
  162. time.strftime(
  163. '%H:%M:%S\n', time.gmtime(clip_start)),
  164. '{}: {}'.format(c.get('user', {}).get('username'),
  165. c.get('text').encode('ascii', 'ignore')))
  166. else:
  167. if c.get('user', {}).get('is_verified'):
  168. comments_log += '{}{}\n\n'.format(
  169. time.strftime(
  170. '%H:%M:%S\n', time.gmtime(clip_start)),
  171. '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)", c.get('text')))
  172. else:
  173. comments_log += '{}{}\n\n'.format(
  174. time.strftime(
  175. '%H:%M:%S\n', time.gmtime(clip_start)),
  176. '{}: {}'.format(c.get('user', {}).get('username'), c.get('text')))
  177. except Exception:
  178. comment_errors += 1
  179. try:
  180. if c.get('user', {}).get('is_verified'):
  181. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  182. '{} {}: {}'.format(c.get('user', {}).get('username'),
  183. "(v)",
  184. c.get('text').encode('ascii',
  185. 'ignore')))
  186. else:
  187. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  188. '{}: {}'.format(c.get('user', {}).get('username'),
  189. c.get('text').encode('ascii',
  190. 'ignore')))
  191. except Exception:
  192. pass
  193. total_comments += 1
  194. subs.append(comments_log)
  195. with codecs.open(log_file, 'w', 'utf-8-sig') as log_outfile:
  196. if python_version.startswith('2') and not wide_build:
  197. log_outfile.write(
  198. 'This log was generated using Python {:s} without wide unicode support. This means characters '
  199. 'such as emotes are not saved.\nUser comments without any text usually are comments that only '
  200. 'had emotes.\nBuild Python 2 with the --enable-unicode=ucs4 argument or use Python 3 for full '
  201. 'unicode support.\n\n'.format(
  202. python_version) + ''.join(subs))
  203. else:
  204. log_outfile.write(''.join(subs))
  205. if gen_from_arg:
  206. if comment_errors:
  207. logger.warn(
  208. "Successfully saved {:s} comments but {:s} comments are (partially) missing.".format(
  209. str(total_comments), str(comment_errors)))
  210. else:
  211. logger.info("Successfully saved {:s} comments.".format(
  212. str(total_comments)))
  213. logger.separator()
  214. return comment_errors, total_comments
  215. except Exception as e:
  216. logger.error(
  217. "An error occurred while saving comments: {:s}".format(str(e)))
  218. logger.separator()