#!/usr/bin/env python3 ################################### # Library Inclusions ################################### import requests import json import os import time import sys import base64 import urllib.parse import mimetypes ################################### # Function Definitions ################################### # Makes API call to target using either GET or POST and returns the JSON response ## request.post() usage (url, headers = headers, data= payload, files= files) or (url, headers= headers, json= payload) def makeCall (target, verb, key, payload= None, files= None): if verb.lower() == 'get': try: response = requests.get(target, auth=(key, 'X')) if response.status_code != 429: response.raise_for_status() responseData = response.json() if (checkRate(response)): return makeCall(target, verb, key) return responseData except requests.exceptions.HTTPError as http_error: print(f'{RED}HTTP Error:{RESET}\n{http_error}') pint(f'Reponse Content: {response.content}') raise except Exception as error: print(f'{RED}Error:{RESET}\n{error}') raise elif verb.lower() == 'post': try: if files == None: response = requests.post(target, auth=(key, 'X'), json= payload) else: response = requests.post(target, data=payload, auth=(key, 'X'), files= files) if response.status_code != 429: response.raise_for_status() responseData = response.json() if (checkRate(response)): if files == None: return makeCall(target, verb, key, payload) else: return makeCall(target, verb, key, payload, files) return responseData except requests.exceptions.HTTPError as http_error: print(f'{RED}HTTP Error:{RESET}\n{http_error}') print(f'Reponse Content: {response.content}') raise except Exception as error: print(f'{RED}Error:{RESET}\n{error}') raise # Clears out the specified number of console lines def clear(number): for num in range(number): sys.stdout.write('\x1b[1A') sys.stdout.write('\x1b[2K') def checkRate(response): if 'Retry-After' in response.headers: timer = int(response.headers.get('Retry-After')) + 5 while timer > 0: print(f'{YELLOW}*RATE LIMITED*{RESET}...Pausing for {timer} seconds') time.sleep(1) timer -= 1 clear(1) return True else: return False ################################### # Globals Variables ################################### ticketArray = [] groupIdDefs_source = [] sourceDom = '' destDom = '' sourceKey = '' destKey = '' targetGroup = '' targetGroupId = '' RED = '\033[31m' GREEN = '\033[32m' YELLOW = '\033[33m' RESET = '\033[0m' ################################### # Main Loop ################################### if os.name == 'nt': os.system('cls') else: os.system('clear') print(' ______ ___ __ __ ') print(' / ____/________ _____ ___ / | / / ____ / /_ ') print(' / / / ___/ __ `/ __ `__ \______/ /| |______/ / / __ \/ __/ ') print('/ /___/ / / /_/ / / / / / /_____/ ___ /_____/ /___/ /_/ / /_ ') print('\____/_/_ \__,_/_/ /_/ /_/ /_/__|_| /_____/\____/\__/___ ') print(' /_ __(_)____/ /_____ / /_ /_ __/________ _____ _____/ __/__ _____') print(' / / / / ___/ //_/ _ \/ __/ / / / ___/ __ `/ __ \/ ___/ /_/ _ \/ ___/') print(' / / / / /__/ ,< / __/ /_ / / / / / /_/ / / / (__ ) __/ __/ / ') print('/_/ /_/\___/_/|_|\___/\__/ /_/ /_/ \__,_/_/ /_/____/_/ \___/_/ \n') print('=========================================================================') ## Getting transfer settings from user sourceDom = input('Please enter the source domain:\n(ex. "https://your_domain.freshdesk.com")\n> ') if sourceDom == '': print(f'{RED}Invalid Domain Provided{RESET}') sys.exit(1) else: clear(3) sourceDom = "".join(sourceDom.split()) print(f'SETTINGS:\nSource Domain: {GREEN}{sourceDom}{RESET}\n=========================================================================') sourceKey = input('Please provide the API Key for the source account:\n(Hint: You can find this from your profile settings menu in Freshdesk)\n> ') if sourceKey == '': print(f'{RED}Invalid Key Provided{RESET}') sys.exit(1) else: clear(3) sourceKey = "".join(sourceKey.split()) destDom = input('Please enter the destination domain:\n> ') if destDom == '': print(f'{RED}Invalid Domain Provided{RESET}') sys.exit(1) else: clear(4) destDom = "".join(destDom.split()) print(f'Source Domain: {GREEN}{sourceDom}{RESET}\nDestination Domain: {GREEN}{destDom}{RESET}\n=========================================================================') destKey = input('Please provide the API key for the destination account:\n> ') if destKey == '': print(f'{RED}Invalid Key Provided{RESET}') sys.exit(1) else: clear(2) destKey = "".join(destKey.split()) targetGroup = input('What is the name of the Freshdesk group you want to transfer from?\n> ') if targetGroup == '': print(f'{RED}Invalid Group Name Provided{RESET}') sys.exit(1) else: clear(3) print(f'Group: {GREEN}{targetGroup}{RESET}') print(f'=========================================================================') ## Building Definition Arrays print(f'Obtaining account information...◐') groupIdDefs_source = makeCall(sourceDom + '/api/v2/groups', 'get', sourceKey) clear(1) print(f'Obtaining account information...◓') groupIdDefs_dest = makeCall(destDom + '/api/v2/groups', 'get', destKey) clear(1) print(f'Obtaining account information...◑') for i, group in enumerate(groupIdDefs_source): anim = ['◒','◐', '◓', '◑'] if group["name"] == targetGroup: targetGroupId = group["id"] break clear(1) print(f'Obtaining account information...{anim[i % len(anim)]}') clear(1) print(f'Obtaining account information...{GREEN}COMPLETE{RESET}') ## Building Ticket Array from Source print(f'Gathering list of tickets from source...') #ticketArray = makeCall(sourceDom + '/api/v2/search/tickets?query=' + urllib.parse.urlencode(f'"group_id:\'{targetGroupId}\'"'), 'get', sourceKey) ticketArray = makeCall(f'{sourceDom}/api/v2/search/tickets?query="group_id:{targetGroupId}"', 'get', sourceKey) clear(1) print(f'Gathering list of tickets from source...{GREEN}COMPLETE{RESET}') ## Transferring tickets to Destination print(f'{YELLOW}*This may take a while*{RESET}') print(f'Transferring tickets to destination account...◐') for i, ticket in enumerate(ticketArray['results']): attachments = {} anim = ['◓', '◑', '◒', '◐'] ticketData = makeCall(sourceDom + f'/api/v2/tickets/{ticket["id"]}?include=conversations,requester', 'get', sourceKey) if 'attachments' in ticketData: for attachment in ticketData['attachments']: if attachment.get('attachment_url'): fileRes = requests.get(attachment['attachment_url']) if fileRes.status_code == 200: attachments['attachments[]'] = (attachment['name'], fileRes.content) else: print(f'{RED}File Error:{RESET}\nFailed to download {attachment["name"]}') payload = { 'name': ticketData["requester"]["name"], 'email': ticketData["requester"]["email"], 'phone': ticketData["requester"]["phone"], 'subject': ticketData["subject"], 'status': ticketData["status"], 'priority': ticketData["priority"], 'description': ticketData["description"], 'source': ticketData["source"], } newTicket = makeCall(destDom + '/api/v2/tickets', 'post', destKey, payload, attachments if attachments else None) ## Gaterhing conversation history from ticketData, and then updating the ticket to include them in a private note if len(ticketData['conversations']) > 0: convos = ''' =====================
Conversation History
=====================
''' for convo in ticketData['conversations']: if convo['incoming'] == False: agent = (makeCall(sourceDom + f'/api/v2/agents/{convo["user_id"]}', 'get', sourceKey)) username = agent["contact"]["name"] if convo['incoming'] == True: user = (makeCall(sourceDom + f'/api/v2/contacts/{convo["user_id"]}', 'get', sourceKey)) username = user["name"] if convo['private'] == True: convos += f'Private Note from {username}:
{convo["body"]}
---------------------------
' elif convo['private'] == False and convo['incoming'] == True: convos += f'Incoming Reply from {username}:
{convo["body"]}
---------------------------
' elif convo['private'] == False and convo['incoming'] == False: convos += f'Outgoing Reply from {username}:
{convo["body"]}
---------------------------
' makeCall(destDom + f'/api/v2/tickets/{newTicket["id"]}/notes', 'post', destKey,{'body': convos}) clear(1) print(f'Transferring tickets to destination account...{anim[i % len(anim)]}') i += 1 clear(2) print(f'Transferring tickets to destination account...{GREEN}COMPLETE{RESET}') print(f'{GREEN}*TICKET TRANSFER COMPLETED SUCCESSFULLY*{RESET}') for i in range(5, 0, -1): print(f'Exiting in {i} seconds...') time.sleep(1) clear(1) sys.exit(0)