Wajahat698 commited on
Commit
4620064
Β·
verified Β·
1 Parent(s): 6858fe9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +217 -54
app.py CHANGED
@@ -1,97 +1,260 @@
1
  from flask import Flask, request, jsonify
2
  import requests
3
  import base64
4
- import os
5
  import logging
6
 
7
  app = Flask(__name__)
8
-
9
  logging.basicConfig(level=logging.INFO)
10
  logger = logging.getLogger(__name__)
11
 
12
- # Environment variables (set these in Hugging Face Secrets)
13
- SLACK_TOKEN ="xoxb-7025747032292-10683671864694-lhUNPKF1KSpYwRvrvZeP7CPy"
14
- CW_COMPANY_ID = "Intrinsic"
15
- CW_PUBLIC_KEY = "IkrljDywPCSE4s10"
16
- CW_PRIVATE_KEY = "kOwo89oUO6SVVSYi"
17
- CW_CLIENT_ID = "e45cf35f-24a8-4f5f-b47a-19376cafce64"
18
-
19
- BASE_URL = "https://api-na.myconnectwise.net/v4_6_release/apis/3.0"
20
 
21
- auth = f"{CW_COMPANY_ID}+{CW_PUBLIC_KEY}:{CW_PRIVATE_KEY}"
22
  encoded_auth = base64.b64encode(auth.encode()).decode()
23
 
24
- cw_headers = {
25
  "Authorization": f"Basic {encoded_auth}",
26
- "ClientID": CW_CLIENT_ID,
27
- "Content-Type": "application/json"
28
  }
29
 
 
 
 
 
 
 
 
30
 
31
- @app.route("/")
32
- def home():
33
- return "Slack β†’ ConnectWise Bot Running"
34
 
 
35
 
36
- def create_ticket(issue):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
- ticket = {
39
- "summary": issue[:100],
40
- "initialDescription": issue,
41
- "board": {"name": "Service Desk"},
42
- "company": {"identifier": "Intrinsic"},
43
- "priority": {"name": "Priority 1"}
44
- }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  r = requests.post(
47
  f"{BASE_URL}/service/tickets",
48
- headers=cw_headers,
49
- json=ticket
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  )
51
 
52
- logger.info("ConnectWise status: %s", r.status_code)
53
- logger.info("ConnectWise response: %s", r.text)
54
 
55
- if r.status_code in [200, 201]:
56
- return r.json().get("id")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- return "error"
59
  @app.route("/slack/events", methods=["POST"])
60
  def slack_events():
61
-
62
  data = request.get_json()
63
- logger.info("Slack payload: %s", data)
64
 
 
65
  if data.get("type") == "url_verification":
66
  return jsonify({"challenge": data.get("challenge")})
67
 
68
  event = data.get("event", {})
69
 
70
- # Ignore bot messages
71
- if event.get("bot_id"):
72
  return "", 200
73
 
74
  if event.get("type") == "message":
75
-
76
- text = event.get("text")
77
  channel = event.get("channel")
78
 
79
- # Remove bot mention
80
  text = text.replace("<@U0AL3KRRELE>", "").strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- ticket_id = create_ticket(text)
83
-
84
- # Send reply to Slack
85
- requests.post(
86
- "https://slack.com/api/chat.postMessage",
87
- headers={
88
- "Authorization": f"Bearer {SLACK_TOKEN}",
89
- "Content-Type": "application/json"
90
- },
91
- json={
92
- "channel": channel,
93
- "text": f"🎫 Support ticket created\nTicket ID: {ticket_id}"
94
- }
95
- )
96
 
97
- return "", 200
 
 
1
  from flask import Flask, request, jsonify
2
  import requests
3
  import base64
 
4
  import logging
5
 
6
  app = Flask(__name__)
 
7
  logging.basicConfig(level=logging.INFO)
8
  logger = logging.getLogger(__name__)
9
 
10
+ # ── Credentials ────────────────────────────────────────────────────────────────
11
+ SLACK_TOKEN = "xoxb-7025747032292-10683671864694-lhUNPKF1KSpYwRvrvZeP7CPy"
12
+ CW_COMPANY_ID = "Intrinsic"
13
+ CW_PUBLIC_KEY = "IkrljDywPCSE4s10"
14
+ CW_PRIVATE_KEY = "kOwo89oUO6SVVSYi"
15
+ CW_CLIENT_ID = "e45cf35f-24a8-4f5f-b47a-19376cafce64"
16
+ BASE_URL = "https://api-na.myconnectwise.net/v4_6_release/apis/3.0"
 
17
 
18
+ auth = f"{CW_COMPANY_ID}+{CW_PUBLIC_KEY}:{CW_PRIVATE_KEY}"
19
  encoded_auth = base64.b64encode(auth.encode()).decode()
20
 
21
+ CW_HEADERS = {
22
  "Authorization": f"Basic {encoded_auth}",
23
+ "ClientID": CW_CLIENT_ID,
24
+ "Content-Type": "application/json",
25
  }
26
 
27
+ # ── Fallback name lists (tried in order) ───────────────────────────────────────
28
+ BOARD_FALLBACKS = ["Service Desk", "Help Desk", "Support", "Technical Support",
29
+ "IT Support", "Service Board", "Default", "General"]
30
+ COMPANY_FALLBACKS = ["Intrinsic", "intrinsic", "INTRINSIC", "IntrinsicIT",
31
+ "intrinsicit", "Intrinsic IT"]
32
+ PRIORITY_FALLBACKS = ["Priority 1", "P1", "High", "Critical", "Urgent",
33
+ "Medium", "Normal", "Low", "Priority 2", "Priority 3"]
34
 
 
 
 
35
 
36
+ # ── Discovery helpers ──────────────────────────────────────────────────────────
37
 
38
+ def discover_boards():
39
+ """Return list of active service board names from ConnectWise."""
40
+ try:
41
+ r = requests.get(
42
+ f"{BASE_URL}/service/boards",
43
+ headers=CW_HEADERS,
44
+ params={"conditions": "inactive=false", "pageSize": 50},
45
+ timeout=10,
46
+ )
47
+ if r.status_code == 200:
48
+ names = [b.get("name") for b in r.json() if b.get("name")]
49
+ logger.info("Discovered boards: %s", names)
50
+ return names
51
+ except Exception as e:
52
+ logger.warning("Board discovery failed: %s", e)
53
+ return []
54
 
 
 
 
 
 
 
 
55
 
56
+ def discover_companies():
57
+ """Return list of company identifiers from ConnectWise."""
58
+ try:
59
+ r = requests.get(
60
+ f"{BASE_URL}/company/companies",
61
+ headers=CW_HEADERS,
62
+ params={"pageSize": 50},
63
+ timeout=10,
64
+ )
65
+ if r.status_code == 200:
66
+ ids = [c.get("identifier") for c in r.json() if c.get("identifier")]
67
+ logger.info("Discovered companies: %s", ids)
68
+ return ids
69
+ except Exception as e:
70
+ logger.warning("Company discovery failed: %s", e)
71
+ return []
72
+
73
+
74
+ def discover_priorities():
75
+ """Return list of priority names from ConnectWise."""
76
+ try:
77
+ r = requests.get(
78
+ f"{BASE_URL}/service/priorities",
79
+ headers=CW_HEADERS,
80
+ params={"pageSize": 50},
81
+ timeout=10,
82
+ )
83
+ if r.status_code == 200:
84
+ names = [p.get("name") for p in r.json() if p.get("name")]
85
+ logger.info("Discovered priorities: %s", names)
86
+ return names
87
+ except Exception as e:
88
+ logger.warning("Priority discovery failed: %s", e)
89
+ return []
90
+
91
+
92
+ def best_match(discovered, fallbacks):
93
+ """
94
+ Return the first discovered value that appears in fallbacks (case-insensitive),
95
+ then the first discovered value, then the first fallback.
96
+ """
97
+ lower_disc = {d.lower(): d for d in discovered}
98
+ for f in fallbacks:
99
+ if f.lower() in lower_disc:
100
+ return lower_disc[f.lower()]
101
+ if discovered:
102
+ return discovered[0]
103
+ return fallbacks[0] # last resort: just try the first hardcoded name
104
+
105
+
106
+ # ── Ticket creation with progressive fallbacks ─────────────────────────────────
107
+
108
+ def try_create(payload):
109
  r = requests.post(
110
  f"{BASE_URL}/service/tickets",
111
+ headers=CW_HEADERS,
112
+ json=payload,
113
+ timeout=10,
114
+ )
115
+ logger.info("CW status=%s body=%s", r.status_code, r.text[:300])
116
+ return r
117
+
118
+
119
+ def create_ticket(issue):
120
+ # ── Step 1: discover real values ─────────────────────────────────────────
121
+ boards = discover_boards()
122
+ companies = discover_companies()
123
+ priorities = discover_priorities()
124
+
125
+ board_name = best_match(boards, BOARD_FALLBACKS)
126
+ company_id = best_match(companies, COMPANY_FALLBACKS)
127
+ priority_name = best_match(priorities, PRIORITY_FALLBACKS)
128
+
129
+ logger.info("Using board=%s company=%s priority=%s",
130
+ board_name, company_id, priority_name)
131
+
132
+ # ── Step 2: full payload ──────────────────────────────────────────────────
133
+ full_payload = {
134
+ "summary": issue[:100],
135
+ "initialDescription": issue,
136
+ "board": {"name": board_name},
137
+ "company": {"identifier": company_id},
138
+ "priority": {"name": priority_name},
139
+ }
140
+ r = try_create(full_payload)
141
+ if r.status_code in (200, 201):
142
+ return r.json().get("id"), board_name
143
+
144
+ # ── Step 3: try every discovered board one-by-one ────────────────────────
145
+ for b in boards:
146
+ if b == board_name:
147
+ continue
148
+ payload = {**full_payload, "board": {"name": b}}
149
+ r = try_create(payload)
150
+ if r.status_code in (200, 201):
151
+ return r.json().get("id"), b
152
+
153
+ # ── Step 4: drop priority (might be wrong) ────────────────────────────────
154
+ logger.warning("Retrying without priority field")
155
+ no_priority = {k: v for k, v in full_payload.items() if k != "priority"}
156
+ r = try_create(no_priority)
157
+ if r.status_code in (200, 201):
158
+ return r.json().get("id"), board_name
159
+
160
+ # ── Step 5: drop company too ──────────────────────────────────────────────
161
+ logger.warning("Retrying without company or priority")
162
+ minimal_board = {
163
+ "summary": issue[:100],
164
+ "initialDescription": issue,
165
+ "board": {"name": board_name},
166
+ }
167
+ r = try_create(minimal_board)
168
+ if r.status_code in (200, 201):
169
+ return r.json().get("id"), board_name
170
+
171
+ # ── Step 6: bare minimum β€” just summary ───────────────────────────────────
172
+ logger.warning("Retrying with bare minimum payload")
173
+ bare = {"summary": issue[:100], "initialDescription": issue}
174
+ r = try_create(bare)
175
+ if r.status_code in (200, 201):
176
+ return r.json().get("id"), "default"
177
+
178
+ logger.error("All ticket creation attempts failed")
179
+ return None, None
180
+
181
+
182
+ # ── Slack helpers ──────────────────────────────────────────────────────────────
183
+
184
+ def send_slack(channel, text):
185
+ requests.post(
186
+ "https://slack.com/api/chat.postMessage",
187
+ headers={
188
+ "Authorization": f"Bearer {SLACK_TOKEN}",
189
+ "Content-Type": "application/json",
190
+ },
191
+ json={"channel": channel, "text": text},
192
+ timeout=10,
193
  )
194
 
 
 
195
 
196
+ # ── Routes ─────────────────────────────────────────────────────────────────────
197
+
198
+ @app.route("/")
199
+ def home():
200
+ return "Slack β†’ ConnectWise Bot Running βœ…"
201
+
202
+
203
+ @app.route("/debug/cw", methods=["GET"])
204
+ def debug_cw():
205
+ """
206
+ Hit /debug/cw in your browser to see exactly what boards / companies /
207
+ priorities exist in your ConnectWise instance.
208
+ """
209
+ return jsonify({
210
+ "boards": discover_boards(),
211
+ "companies": discover_companies(),
212
+ "priorities": discover_priorities(),
213
+ })
214
+
215
 
 
216
  @app.route("/slack/events", methods=["POST"])
217
  def slack_events():
 
218
  data = request.get_json()
219
+ logger.info("Slack payload type=%s", data.get("type"))
220
 
221
+ # URL verification handshake
222
  if data.get("type") == "url_verification":
223
  return jsonify({"challenge": data.get("challenge")})
224
 
225
  event = data.get("event", {})
226
 
227
+ # Ignore bot messages to prevent loops
228
+ if event.get("bot_id") or event.get("subtype") == "bot_message":
229
  return "", 200
230
 
231
  if event.get("type") == "message":
232
+ text = event.get("text", "")
 
233
  channel = event.get("channel")
234
 
235
+ # Strip bot mention
236
  text = text.replace("<@U0AL3KRRELE>", "").strip()
237
+ if not text:
238
+ return "", 200
239
+
240
+ ticket_id, board_used = create_ticket(text)
241
+
242
+ if ticket_id:
243
+ reply = (
244
+ f"βœ… Support ticket created!\n"
245
+ f"🎫 Ticket ID: `{ticket_id}`\n"
246
+ f"πŸ“‹ Board: {board_used}"
247
+ )
248
+ else:
249
+ reply = (
250
+ "⚠️ Could not create a ConnectWise ticket right now.\n"
251
+ "Please contact support directly or try again shortly."
252
+ )
253
+
254
+ send_slack(channel, reply)
255
+
256
+ return "", 200
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
 
259
+ if __name__ == "__main__":
260
+ app.run(host="0.0.0.0", port=7860)