comments.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  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. if pil.verbose:
  46. logger.plain(json.dumps(comments_res))
  47. comments = comments_res.get('comments', [])
  48. first_comment_created_at = (
  49. comments[0]['created_at_utc'] if comments else int(time.time() - 5))
  50. comments_collected.extend(comments)
  51. after_count = len(comments_collected)
  52. if after_count > before_count:
  53. broadcast = self.broadcast.copy()
  54. broadcast.pop('segments', None) # save space
  55. broadcast['comments'] = comments_collected
  56. with open(self.destination_file, 'w') as outfile:
  57. json.dump(broadcast, outfile, indent=2)
  58. self.comments = comments_collected
  59. except (SSLError, timeout, URLError, HTTPException, SocketError) as e:
  60. logger.warn('Comment downloading error: %s' % e)
  61. except ClientError as e:
  62. if e.code == 500:
  63. logger.warn('Comment downloading ClientError: %d %s' % (e.code, e.error_response))
  64. elif e.code == 400 and not e.msg:
  65. logger.warn('Comment downloading ClientError: %d %s' % (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. if pil.verbose:
  83. logger.plain(json.dumps(comments_res))
  84. starting_offset = comments_res.get('ending_offset', 0)
  85. comments = comments_res.get('comments', [])
  86. comments_collected.extend(comments)
  87. if not comments_res.get('comments') or not starting_offset:
  88. break
  89. time.sleep(4)
  90. except Exception:
  91. pass
  92. if comments_collected:
  93. self.broadcast['comments'] = comments_collected
  94. self.broadcast['initial_buffered_duration'] = 0
  95. with open(self.destination_file, 'w') as outfile:
  96. json.dump(self.broadcast, outfile, indent=2)
  97. self.comments = comments_collected
  98. def save(self):
  99. broadcast = self.broadcast.copy()
  100. broadcast.pop('segments', None)
  101. broadcast['comments'] = self.comments
  102. with open(self.destination_file, 'w') as outfile:
  103. json.dump(broadcast, outfile, indent=2)
  104. @staticmethod
  105. def generate_log(comments, download_start_time, log_file, comments_delay=10.0):
  106. python_version = sys.version.split(' ')[0]
  107. comments_timeline = {}
  108. wide_build = sys.maxunicode > 65536
  109. for c in comments:
  110. if 'offset' in c:
  111. for k in c.get('comment').keys():
  112. c[k] = c.get('comment', {}).get(k)
  113. c['created_at_utc'] = download_start_time + c.get('offset')
  114. created_at_utc = str(2 * (c.get('created_at_utc') // 2))
  115. comment_list = comments_timeline.get(created_at_utc) or []
  116. comment_list.append(c)
  117. comments_timeline[created_at_utc] = comment_list
  118. if comments_timeline:
  119. comment_errors = 0
  120. total_comments = 0
  121. timestamps = sorted(comments_timeline.keys())
  122. subs = []
  123. for tc in timestamps:
  124. t = comments_timeline[tc]
  125. clip_start = int(tc) - int(download_start_time) + int(comments_delay)
  126. if clip_start < 0:
  127. clip_start = 0
  128. comments_log = ''
  129. for c in t:
  130. try:
  131. if python_version.startswith('3'):
  132. if c.get('user', {}).get('is_verified'):
  133. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  134. '{} {}: {}'.format(c.get('user', {}).get('username'),
  135. "(v)", c.get('text')))
  136. else:
  137. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  138. '{}: {}'.format(c.get('user', {}).get('username'),
  139. c.get('text')))
  140. else:
  141. if not wide_build:
  142. if c.get('user', {}).get('is_verified'):
  143. comments_log += '{}{}\n\n'.format(
  144. time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  145. '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)",
  146. c.get('text').encode('ascii', 'ignore')))
  147. else:
  148. comments_log += '{}{}\n\n'.format(
  149. time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  150. '{}: {}'.format(c.get('user', {}).get('username'),
  151. c.get('text').encode('ascii', 'ignore')))
  152. else:
  153. if c.get('user', {}).get('is_verified'):
  154. comments_log += '{}{}\n\n'.format(
  155. time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  156. '{} {}: {}'.format(c.get('user', {}).get('username'), "(v)", c.get('text')))
  157. else:
  158. comments_log += '{}{}\n\n'.format(
  159. time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  160. '{}: {}'.format(c.get('user', {}).get('username'), c.get('text')))
  161. except Exception:
  162. comment_errors += 1
  163. try:
  164. if c.get('user', {}).get('is_verified'):
  165. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  166. '{} {}: {}'.format(c.get('user', {}).get('username'),
  167. "(v)",
  168. c.get('text').encode('ascii',
  169. 'ignore')))
  170. else:
  171. comments_log += '{}{}\n\n'.format(time.strftime('%H:%M:%S\n', time.gmtime(clip_start)),
  172. '{}: {}'.format(c.get('user', {}).get('username'),
  173. c.get('text').encode('ascii',
  174. 'ignore')))
  175. except Exception:
  176. pass
  177. total_comments += 1
  178. subs.append(comments_log)
  179. with codecs.open(log_file, 'w', 'utf-8-sig') as log_outfile:
  180. if python_version.startswith('2') and not wide_build:
  181. log_outfile.write(
  182. 'This log was generated using Python {:s} without wide unicode support. This means characters '
  183. 'such as emotes are not saved.\nUser comments without any text usually are comments that only '
  184. 'had emotes.\nBuild Python 2 with the --enable-unicode=ucs4 argument or use Python 3 for full '
  185. 'unicode support.\n\n'.format(
  186. python_version) + ''.join(subs))
  187. else:
  188. log_outfile.write(''.join(subs))
  189. return comment_errors, total_comments