class ZenWorker(object): def __init__(self, url, email, api, search_string): self.zendesk = Zendesk(url, zdesk_email=email, zdesk_api=api) self.barcode_re = re.compile(r"barcode\s(\d+)") self.username_re = re.compile(r"assigned_to\s(\w+)") self.search = search_string self.me = self.zendesk.users_me()['user'] def run(self): """ Iterates through the tickets found by SEARCH_STRING and """ try: result = self.zendesk.search(query=self.search, get_all_pages=True) except Exception as exc: logging.error("encountered an exception during a zendesk search: %s", str(exc)) return logging.debug("found %s tickets in the queue", str(result['count'])) tickets = {} # iterating over the tickets which haven't been touched for ticket in result['results']: try: comments = self.zendesk.ticket_comments(ticket_id=ticket['id']) except Exception as exc: logging.error("encountered an error %s while trying to fetch comments " "for ticket: %s", str(exc), str(ticket['id'])) # check if it's open and there's a cmment from a human if ticket['status'] == 'open' and comments.get("count", 0) > 1: #and #comments['comments'][-1].get('author_id') != self.me['id']): logging.info("ticket %s is open and has comments", str(ticket['id'])) continue # get the user, barcode and ticket id user_match = self.username_re.search(ticket['description']) barcode_match = self.barcode_re.search(ticket['description']) if not (user_match and barcode_match): continue # TODO: uncomment user = user_match.group(1) barcode = barcode_match.group(1) tickets[ticket['id']] = TicketData(user, barcode, ticket['status']) return tickets def update_ticket(self, ticket_id, message): """ Updates a ticket with given ticket_id with a message. """ data = { "ticket": { "id": ticket_id, "comment": { "public": True, "body": message } } } response = self.zendesk.ticket_update(ticket_id, data) logging.debug(response)
def sync_remote(*args, **kwargs): zendesk = Zendesk( settings.ZENDESK_BASE_URL, settings.ZENDESK_EMAIL, settings.ZENDESK_PASSWORD, api_version=settings.ZENDESK_API_VERSION, ) field_names = Ticket.get_all_field_names("pk", "_fields") queryfmt = "type:ticket organization_id:{0.organization_external}" for company in Company.objects.all(): next_page = True page = 1 registers = [] while next_page: try: results = zendesk.search(query=queryfmt.format(company), page=page) except ZendeskError as err: # view does not exist if "error" in err.msg and err.error_code == 404: break if "results" not in results: break for ticket in results["results"]: params = {} for name in field_names: params[name] = ticket[name] obj = None try: obj = Ticket.objects.get(pk=params["id"]) for name, value in params.iteritems(): # update setattr(obj, name, value) except Ticket.DoesNotExist: obj = Ticket.objects.create(**params) finally: obj.fields = ticket["fields"] obj.save() registers.append(obj.pk) next_page = results.get("next_page", False) page += 1 # database clean Ticket.objects.filter(organization_id=company.organization_external).exclude(pk__in=registers).delete() return Ticket.objects.count()
def zdeskNotify(self): room = "55a12b9bb82d04e3b232a99c" zendesk = Zendesk(**testconfig) results = zendesk.search(query='type:ticket sort:desc', page=1) for ticket in results['results']: if ticket['status'] == 'solved': #send message to ws #print "Hey man problem resolved" self.sayAnswer(room, "Андрей, мы нашли проблему и все исправили, пишите если опять будет регулярно повторяться проблема") ticket_id = ticket['id'] data = { "ticket": { "id": ticket_id, "status": "closed" } } response = zendesk.ticket_update(ticket_id, data) #self.sayAnswer(room, "ticket %s closed" % ticket_id) print "tiket %s closed" % ticket_id
} } result = zendesk.group_create(data=new_group) group_id = get_id_from_url(result) # Show zendesk.group_show(id=group_id) # Delete zendesk.group_delete(id=group_id) ################################################################ # TAGS ################################################################ # List zendesk.tags_list() ################################################################ # TICKET TYPES ################################################################ zendesk.ticket_fields_list() ################################################################ # SEARCH ################################################################ results = zendesk.search(query='type:ticket sort:desc', page=1)
'agent': 123 }, ] } } result = zendesk.group_create(data=new_group) group_id = get_id_from_url(result) # Show zendesk.group_show(id=group_id) # Delete zendesk.group_delete(id=group_id) ################################################################ ## TAGS ################################################################ # List zendesk.tags_list() ################################################################ ## TICKET TYPES ################################################################ zendesk.ticket_fields_list() ################################################################ ## SEARCH ################################################################ results = zendesk.search(query='type:ticket sort:desc', page=1)
def zdgrab(verbose=False, tickets=None, work_dir=os.path.join(os.path.expanduser('~'), 'zdgrab'), agent='me', ss_host=None, ss_id=None, ss_secret=None, ss_command=None): "Download attachments from Zendesk tickets." # SendsafelyGrab will only be invoked if the comment body contains a link. # See the corresponding REGEX used by them, which has been ported to Python: # https://github.com/SendSafely/Windows-Client-API/blob/master/SendsafelyAPI/Utilities/ParseLinksUtility.cs ss_link_re = r'https://[-a-zA-Z\.]+/receive/\?[-A-Za-z0-9&=]+packageCode=[-A-Za-z0-9_]+#keyCode=[-A-Za-z0-9_]+' ss_link_pat = re.compile(ss_link_re) vp = verbose_printer(verbose) cfg = zdgrab.getconfig() if cfg['zdesk_url'] and ( cfg['zdesk_oauth'] or (cfg['zdesk_email'] and cfg['zdesk_password']) or (cfg['zdesk_email'] and cfg['zdesk_api']) ): vp.print('Configuring Zendesk with:\n' ' url: {}\n' ' email: {}\n' ' token: {}\n' ' password/oauth/api: (hidden)\n'.format(cfg['zdesk_url'], cfg['zdesk_email'], repr(cfg['zdesk_token']))) zd = Zendesk(**cfg) else: msg = textwrap.dedent("""\ Error: Need Zendesk config to continue. Config file (~/.zdeskcfg) should be something like: [zdesk] url = https://example.zendesk.com email = [email protected] api = dneib393fwEF3ifbsEXAMPLEdhb93dw343 # or # oauth = ndei393bEwF3ifbEsssX [zdgrab] agent = [email protected] """) print(msg) return 1 # Log the cfg vp.print('Running with zdgrab config:\n' ' verbose: {}\n' ' tickets: {}\n' ' work_dir: {}\n' ' agent: {}\n'.format(verbose, tickets, work_dir, agent)) # tickets=None means default to getting all of the attachments for this # user's open tickets. If tickets is given, try to split it into ints if tickets: # User gave a list of tickets try: tickets = [int(i) for i in tickets.split(',')] except ValueError: print('Error: Could not convert to integers: {}'.format(tickets)) return 1 # dict of paths to attachments retrieved to return. format is: # { 'path/to/ticket/1': [ 'path/to/attachment1', 'path/to/attachment2' ], # 'path/to/ticket/2': [ 'path/to/attachment1', 'path/to/attachment2' ] } grabs = {} # Save the current directory so we can go back once done start_dir = os.getcwd() # Normalize all of the given paths to absolute paths work_dir = os.path.abspath(work_dir) # Check for and create working directory if not os.path.isdir(work_dir): os.makedirs(work_dir) # Change to working directory to begin file output os.chdir(work_dir) vp.print('Retrieving tickets') if tickets: # tickets given, query for those response = zd.tickets_show_many(ids=','.join([s for s in map(str, tickets)]), get_all_pages=True) result_field = 'tickets' else: # List of tickets not given. Get all of the attachments for all of this # user's open tickets. q = 'status<solved assignee:{}'.format(agent) response = zd.search(query=q, get_all_pages=True) result_field = 'results' if response['count'] == 0: # No tickets from which to get attachments print("No tickets provided for attachment retrieval.") return {} else: vp.print("Located {} tickets".format(response['count'])) results = response[result_field] # Fix up some headers to use for downloading the attachments. # We're going to borrow the zdesk object's httplib client. headers = {} if zd.zdesk_email is not None and zd.zdesk_password is not None: headers["Authorization"] = "Basic {}".format( base64.b64encode(zd.zdesk_email.encode('ascii') + b':' + zd.zdesk_password.encode('ascii'))) # Get the attachments from the given zendesk tickets for ticket in results: if result_field == 'results' and ticket['result_type'] != 'ticket': # This is not actually a ticket. Weird. Skip it. continue vp.print('Ticket {}'.format(ticket['id'])) ticket_dir = os.path.join(work_dir, str(ticket['id'])) ticket_com_dir = os.path.join(ticket_dir, 'comments') comment_num = 0 response = zd.ticket_audits(ticket_id=ticket['id'], get_all_pages=True) audits = response['audits'] for audit in audits: for event in audit['events']: if event['type'] != 'Comment': # This event isn't a comment. Skip it. continue comment_num += 1 comment_dir = os.path.join(ticket_com_dir, str(comment_num)) for attachment in event['attachments']: name = attachment['file_name'] if os.path.isfile(os.path.join(comment_dir, name)): vp.print(' Attachment {} already present'.format(name)) continue # Get this attachment vp.print(' Downloading attachment {}'.format(name)) # Check for and create the download directory if not os.path.isdir(comment_dir): os.makedirs(comment_dir) os.chdir(comment_dir) response = zd.client.request('GET', attachment['content_url'], headers=headers) if response.status_code != 200: print('Error downloading {}'.format( attachment['content_url'])) continue with open(name, 'wb') as f: f.write(response.content) # Check for and create the grabs entry to return if ticket_dir not in grabs: grabs[ticket_dir] = [] grabs[ticket_dir].append( os.path.join('comments', str(comment_num), name)) # Let's try to extract this if it's compressed zdsplode(name, verbose=verbose) if ss_command: if not ss_link_pat.search(event['body']): # Don't bother invoking SendSafelyGrab if the body has # no SendSafely links. continue try: ss_output = subprocess.check_output(ss_command.split() + ["-v", "-h", ss_host, "-k", ss_id, "-s", ss_secret, "-d", comment_dir, event['body']], stderr=sys.stderr) if ss_output: # Check for and create the grabs entry to return if ss_output and (ticket_dir not in grabs): grabs[ticket_dir] = [] for name in ss_output.splitlines(): namestr = name.decode() grabs[ticket_dir].append( os.path.join('comments', str(comment_num), namestr)) # Let's try to extract this if it's compressed os.chdir(comment_dir) zdsplode(namestr, verbose=verbose) except subprocess.CalledProcessError: # SendSafelyGrab.exe will print its own errors pass os.chdir(start_dir) return grabs
class Command(BaseCommand): option_list = BaseCommand.option_list + ( make_option( '--to-csv', action='store_true', dest='to-csv', default=False, help='Export all relevant zendesk items to csv file'), ) def handle(self, *args, **options): self.load_settings() self.init_zen() if options["to-csv"]: self.to_csv() def load_settings(self): with open(os.path.expanduser("~/.hmcts-zendesk"), "r") as config_file: self.settings = yaml.load(config_file.read()) def init_csv(self, f, dialect=csv.excel): self.writer = csv.writer(f, dialect=dialect) def init_zen(self): self.zendesk = Zendesk( self.settings["url"], self.settings["email"], self.settings["password"]) def get_all_fieldnames(self): return \ SIMPLE_FIELDS + \ NEWLINE_FIELDS + \ STRUCTURED_FIELDS def to_csv(self): self.outfile = self.settings.get('outfile', 'zendump.csv') self.batch_size = 100 with open(self.outfile, "wb") as f: self.init_csv(f) self.writerow(self.get_all_fieldnames()) # CSV Header for ticket in self.yield_tickets(): self.writerow(self.ticket_to_row(ticket)) def writerow(self, row): self.writer.writerow([ cell.encode("utf-8") for cell in row]) def ticket_to_row(self, ticket): return \ self.get_simple_fields(ticket) + \ self.get_newline_fields(ticket) + \ self.get_structured_fields(ticket) def parse_simple_field(self, field): if field is None: return "" else: try: return field.encode("utf-8") except AttributeError: # ints return str(field).encode("utf-8") def get_simple_fields(self, ticket): return [ self.parse_simple_field(ticket.get(field)) for field in SIMPLE_FIELDS] def parse_newline_field(self, field): return field or "" def get_newline_fields(self, ticket): return [ self.parse_newline_field(ticket.get(field)) for field in NEWLINE_FIELDS] def parse_structured_field(self, field): if field is None: return "" else: try: return field.encode("utf-8") except AttributeError: # ints return str(field).encode("utf-8") def get_structured_fields(self, ticket): return [ self.parse_structured_field(ticket.get(field)) for field in STRUCTURED_FIELDS] def yield_tickets(self): """Generates batches of tickets""" for raw_query in self.settings["raw_queries"]: first_page = self.zendesk.search(raw_query=raw_query) for item in first_page["results"]: yield item count = first_page["count"] print "{} tickets from ZenDesk match filter.".format(count) page_count, remainder = divmod(count, self.batch_size) page_count = page_count + 1 if remainder else page_count for page_id in range(2, page_count): qpage = " page:{}".format(page_id) page = self.zendesk.search(raw_query=raw_query + qpage) for item in page["results"]: yield item
def zdgrab(verbose=False, tickets=None, work_dir=os.path.join(os.path.expanduser('~'), 'zdgrab'), agent='me'): "Download attachments from Zendesk tickets." cfg = zdgrab.getconfig() if cfg['zdesk_url'] and cfg['zdesk_email'] and cfg['zdesk_password']: if verbose: print('Configuring Zendesk with:\n' ' url: {}\n' ' email: {}\n' ' token: {}\n' ' password: (hidden)\n'.format( cfg['zdesk_url'], cfg['zdesk_email'], repr(cfg['zdesk_token']) )) zd = Zendesk(**cfg) else: msg = textwrap.dedent("""\ Error: Need Zendesk config to continue. Config file (~/.zdeskcfg) should be something like: [zdesk] url = https://example.zendesk.com email = [email protected] password = dneib393fwEF3ifbsEXAMPLEdhb93dw343 token = 1 [zdgrab] agent = [email protected] """) print(msg) return 1 # Log the cfg if verbose: print('Running with zdgrab config:\n' ' verbose: {}\n' ' tickets: {}\n' ' work_dir: {}\n' ' agent: {}\n'.format(verbose, tickets, work_dir, agent)) # tickets=None means default to getting all of the attachments for this # user's open tickets. If tickets is given, try to split it into ints if tickets: # User gave a list of tickets try: tickets = [int(i) for i in tickets.split(',')] except ValueError: print('Error: Could not convert to integers: {}'.format(tickets)) return 1 # dict of paths to attachments retrieved to return. format is: # { 'path/to/ticket/1': [ 'path/to/attachment1', 'path/to/attachment2' ], # 'path/to/ticket/2': [ 'path/to/attachment1', 'path/to/attachment2' ] } grabs = {} # Save the current directory so we can go back once done start_dir = os.getcwd() # Normalize all of the given paths to absolute paths work_dir = os.path.abspath(work_dir) # Check for and create working directory if not os.path.isdir(work_dir): os.makedirs(work_dir) # Change to working directory to begin file output os.chdir(work_dir) if verbose: print('Retrieving tickets') if tickets: # tickets given, query for those response = zd.tickets_show_many(ids=','.join([s for s in map(str,tickets)]), get_all_pages=True) result_field = 'tickets' else: # List of tickets not given. Get all of the attachments for all of this # user's open tickets. q = 'status<solved assignee:{}'.format(agent) response = zd.search(query=q, get_all_pages=True) result_field = 'results' if response['count'] == 0: # No tickets from which to get attachments print("No tickets provided for attachment retrieval.") return {} results = response[result_field] # Fix up some headers to use for downloading the attachments. # We're going to borrow the zdesk object's httplib client. headers = {} if zd.zdesk_email is not None and zd.zdesk_password is not None: headers["Authorization"] = "Basic {}".format( base64.b64encode(zd.zdesk_email.encode('ascii') + b':' + zd.zdesk_password.encode('ascii'))) # Get the attachments from the given zendesk tickets for ticket in results: if result_field == 'results' and ticket['result_type'] != 'ticket': # This is not actually a ticket. Weird. Skip it. continue if verbose: print('Ticket {}'.format(ticket['id'])) ticket_dir = os.path.join(work_dir, str(ticket['id'])) ticket_com_dir = os.path.join(ticket_dir, 'comments') comment_num = 0 if verbose: print('Retrieving audits') response = zd.ticket_audits(ticket_id=ticket['id'], get_all_pages=True) audits = response['audits'] for audit in audits: for event in audit['events']: if event['type'] != 'Comment': # This event isn't a comment. Skip it. continue comment_num += 1 comment_dir = os.path.join(ticket_com_dir, str(comment_num)) if verbose and event['attachments']: print('Comment {}'.format(comment_num)) for attachment in event['attachments']: name = attachment['file_name'] if os.path.isfile(os.path.join(comment_dir, name)): if verbose: print('Attachment {} already present'.format(name)) continue # Get this attachment if verbose: print('Attachment {}'.format(name)) # Check for and create the download directory if not os.path.isdir(comment_dir): os.makedirs(comment_dir) os.chdir(comment_dir) response = zd.client.request('GET', attachment['content_url'], headers=headers) if response.status_code != 200: print('Error downloading {}'.format(attachment['content_url'])) continue with open(name, 'wb') as f: f.write(response.content) # Check for and create the grabs entry to return if ticket_dir not in grabs: grabs[ticket_dir] = [] grabs[ticket_dir].append( os.path.join('comments', str(comment_num), name) ) # Let's try to extract this if it's compressed zdsplode(name, verbose=verbose) os.chdir(start_dir) return grabs