11import sys
22import os
3- import time , datetime
3+ import time , datetime , pytz
44import urllib , hashlib
55import pickle
66import traceback
7+ import logging
78from dateutil import parser
89from zenpy import Zenpy
910sys .path .append (os .path .join (os .path .dirname (os .path .realpath (__file__ )), os .pardir ))
1011from discordWebhooks import Webhook , Attachment , Field
1112
12- # CONFIGS
13+ logging .basicConfig ()
14+ logger = logging .getLogger ('ZDWB' )
15+ logger .setLevel (logging .INFO )
1316
14- url = "<discord webhook url>"
17+ if os .environ ['ZDWB_HISTORY_MINUTES' ]:
18+ history_minutes = int (os .environ ['ZDWB_HISTORY_MINUTES' ])
19+ else :
20+ history_minutes = 0
21+
22+ url = os .environ ['ZDWB_DISCORD_WEBHOOK' ]
1523
1624creds = {
17- 'email' : 'user@example.org' ,
18- 'token' : '<zendesk token>' ,
19- 'subdomain' : '<zendesk subdomain>'
25+ 'email' : os . environ [ 'ZDWB_ZENDESK_EMAIL' ] ,
26+ 'token' : os . environ [ 'ZDWB_ZENDESK_TOKEN' ] ,
27+ 'subdomain' : os . environ [ 'ZDWB_ZENDESK_SUBDOMAIN' ]
2028}
2129
22- # END CONFIGS
23-
2430status_color = {
2531 'new' : '#F5CA00' ,
2632 'open' : '#E82A2A' ,
2733 'pending' : '#59BBE0' ,
28- 'hold' : '#000 ' ,
34+ 'hold' : '#000000 ' ,
2935 'solved' : '#828282' ,
30- 'closed' : '#ddd '
36+ 'closed' : '#DDDDDD '
3137}
3238
39+ default_icon = "https://d1eipm3vz40hy0.cloudfront.net/images/logos/favicons/favicon.ico"
40+
3341zenpy = Zenpy (** creds )
3442
3543tickets = {}
3644
45+ # Check if we know the last timestamp Zendesk was audited from
3746if os .path .isfile ('lza.p' ) is True : # lza = Last Zendesk Audit
3847 lza = pickle .load (open ('lza.p' ,'rb' ))
48+ first_run = False
3949else :
50+ lza = datetime .datetime .utcnow ().replace (tzinfo = pytz .UTC )
4051 first_run = True
41- lza = datetime .datetime .utcnow ()
4252
43- print ( lza )
53+ logger . info ( 'Last Zendesk Audit: {}' . format ( lza ) )
4454
4555pickle .dump (lza ,open ('lza.p' ,'wb' ))
4656
4757def get_gravatar (email ):
48- default = "https://{}.zendesk.com/images/favicon_2.ico" .format (creds ['subdomain' ])
49- avatar = "https://www.gravatar.com/avatar/" + hashlib .md5 (email .encode ("utf8" ).lower ()).hexdigest () + "?"
50- avatar += urllib .parse .urlencode ({'d' :default })
58+ # This will display the default Gravatar icon if the user has no Gravatar
59+ avatar = "https://www.gravatar.com/avatar/" + hashlib .md5 (email .encode ("utf8" ).lower ()).hexdigest ()
5160 return avatar
5261
5362def post_webhook (event ):
5463 try :
5564 ticket = zenpy .tickets (id = event .ticket_id )
5665 requester = zenpy .users (id = ticket .requester_id )
66+
67+ # Updater ID 0 is generally for Zendesk automation/non-user actions
5768 if event .updater_id > 0 :
5869 updater = zenpy .users (id = event .updater_id )
5970 updater_name = updater .name
@@ -62,44 +73,58 @@ def post_webhook(event):
6273 updater_name = "Zendesk System"
6374 updater_email = "support@zendesk.com"
6475
76+ # If the user has no Zendesk profile photo, use Gravatar
6577 if requester .photo is not None :
66- avatar = requester .photo . content_url
78+ avatar = requester .photo [ ' content_url' ]
6779 else :
6880 avatar = get_gravatar (requester .email )
6981
82+ # Initialize an empty Discord Webhook object with the specified Webhook URL
7083 wh = Webhook (url , "" , "" , "" )
7184
85+ # Prepare the base ticket info embed (attachment)
7286 at = Attachment (
7387 author_name = '{} ({})' .format (requester .name ,requester .email ),
7488 author_icon = avatar ,
7589 color = status_color [ticket .status ],
7690 title = '[Ticket #{}] {}' .format (ticket .id ,ticket .raw_subject ),
7791 title_link = "https://{}.zendesk.com/agent/#/tickets/{}" .format (creds ['subdomain' ],ticket .id ),
7892 footer = ticket .status .title (),
79- ts = int (parser .parse (ticket .created_at ).strftime ('%s' )))
93+ ts = int (parser .parse (ticket .created_at ).strftime ('%s' ))) # TODO: always UTC, config timezone
8094
81- for child in event ._child_events :
95+ # If this is a new ticket, post it, ignore the rest.
96+ # This will only handle the first 'Create' child event
97+ # I have yet to see any more than one child event for new tickets
98+ for child in event .child_events :
8299 if child ['event_type' ] == 'Create' :
83100 if first_run is True :
84101 wh = Webhook (url , "" , "" , "" )
85102 else :
86103 wh = Webhook (url , "@here, New Ticket!" , "" , "" )
104+
105+
87106 description = ticket .description
107+
108+ # Strip any double newlines from the description
88109 while "\n \n " in description :
89110 description = description .replace ("\n \n " , "\n " )
111+
90112 field = Field ("Description" , ticket .description , False )
91113 at .addField (field )
114+
92115 wh .addAttachment (at )
93116 wh .post ()
117+
94118 return
95119
96120 wh .addAttachment (at )
97121
122+ # Updater ID 0 is either Zendesk automation or non-user actions
98123 if int (event .updater_id ) < 0 :
99124 at = Attachment (
100125 color = status_color [ticket .status ],
101126 footer = "Zendesk System" ,
102- footer_icon = "https://{}.zendesk.com/images/favicon_2.ico" . format ( creds [ 'subdomain' ]) ,
127+ footer_icon = default_icon ,
103128 ts = int (parser .parse (event .created_at ).strftime ('%s' )))
104129 else :
105130 at = Attachment (
@@ -108,15 +133,18 @@ def post_webhook(event):
108133 footer_icon = get_gravatar (updater_email ),
109134 ts = int (parser .parse (event .created_at ).strftime ('%s' )))
110135
111- for child in event ._child_events :
136+ for child in event .child_events :
112137 if child ['event_type' ] == 'Comment' :
113- for comment in zenpy .tickets .comments (ticket .id ).values :
114- if comment ['id' ] == child ['id' ]:
115- comment_body = comment ['plain_body' ]
138+ for comment in zenpy .tickets .comments (ticket .id ):
139+ if comment .id == child ['id' ]:
140+ comment_body = comment .body
141+
116142 while "\n \n " in comment_body :
117143 comment_body = comment_body .replace ("\n \n " ,"\n " )
118- field = Field ("New Comment" , comment ['plain_body' ], False )
144+
145+ field = Field ("Comment" , comment_body , False )
119146 at .addField (field )
147+
120148 elif child ['event_type' ] == 'Change' :
121149 if 'status' not in child .keys ():
122150 if 'tags' in child .keys ():
@@ -139,43 +167,46 @@ def post_webhook(event):
139167 field = Field ("Type Change" , '`{}`' .format (child ['type' ]), True )
140168 at .addField (field )
141169 else :
142- print (child )
170+ logger . debug (child )
143171 else :
144- field = Field ("Status Change" , "{} from {}" .format (child ['status ' ].title (),child ['previous_value ' ].title ()), True )
172+ field = Field ("Status Change" , "{} to {}" .format (child ['previous_value ' ].title (), child ['status ' ].title ()), True )
145173 at .addField (field )
146174 else :
147- print ("Event not handled" )
175+ logger . error ("Event not handled" )
148176
149177 wh .addAttachment (at )
178+
150179 i = 0
151180 while i < 4 :
181+ logger .debug ('Posting to Discord' )
152182 r = wh .post ()
153183 i += 1
154- if r .text is not 'ok' :
155- if r .headers ['X-RateLimit-Remaining' ] == 0 :
156- now = int (time .time ())
157- then = int (r .headers ['X-RateLimit-Reset' ])
158- ttw = then - now # ttw = Time To Wait
159- if ttw > 0 :
160- print ("Hit Rate Limit, sleeping for {}" .format (str (ttw )))
161- time .sleep (ttw )
162- else :
163- break
184+ if r .text != 'ok' :
185+ logger .error (r )
186+ logger .info ('Discord webhook retry {}/3' .format (i ))
187+ else :
188+ break
164189 time .sleep (1 )
165190
166191 except Exception as e :
167192 if "RecordNotFound" in str (e ):
168193 pass
169194 else :
170- traceback .print_exc ()
195+ logger . error ( traceback .print_exc () )
171196
172197if first_run is True :
173- for event in zenpy .tickets .events ("1970-01-01T00:00:00Z" ):
198+ today = datetime .datetime .utcnow () - datetime .timedelta (minutes = history_minutes )
199+ for event in zenpy .tickets .events (today .replace (tzinfo = pytz .UTC )):
200+ logger .debug ('Incoming Zendesk Event' )
201+ logger .debug (event .event_type )
202+ for child in event .child_events :
203+ logger .debug ('Child Event' )
204+ logger .debug (child ['event_type' ])
174205 post_webhook (event )
175206
176207while True :
177208 for event in zenpy .tickets .events (lza ):
178209 post_webhook (event )
179- lza = datetime .datetime .utcnow ()
210+ lza = datetime .datetime .utcnow (). replace ( tzinfo = pytz . UTC )
180211 pickle .dump (lza ,open ('lza.p' ,'wb' ))
181- time .sleep (5 )
212+ time .sleep (15 )
0 commit comments