cevheri commited on
Commit
8a58abc
Β·
0 Parent(s):

first commit

Browse files
Files changed (8) hide show
  1. .gitattributes +35 -0
  2. .github/workflows/push-hf.yml +85 -0
  3. .gitignore +3 -0
  4. Dockerfile +24 -0
  5. README.md +11 -0
  6. app.py +7 -0
  7. bot_telegram.py +261 -0
  8. requirements.txt +5 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.github/workflows/push-hf.yml ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Push to HuggingFace Spaces
2
+ on:
3
+ push:
4
+ branches:
5
+ - release/*
6
+ - main
7
+
8
+ env:
9
+ IMAGE_NAME: ${{ github.repository }}
10
+ IMAGE_TAG: ${{ github.sha }}
11
+ VERSION: latest
12
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
13
+ HF_ORGANIZATION: ${{ vars.HF_ORGANIZATION }}
14
+ HF_SPACE: ${{ vars.HF_SPACE }}
15
+
16
+ jobs:
17
+ push-to-hf:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ # checkout the repository
21
+ - name: Checkout the repository
22
+ uses: actions/checkout@v4
23
+ with:
24
+ fetch-depth: 0
25
+ lfs: true
26
+
27
+ # - name: setup git lfs
28
+ # run: |
29
+ # git lfs install
30
+ # git lfs track "*.csv"
31
+ # git add .gitattributes
32
+ # git commit -m "Add .gitattributes"
33
+
34
+ # - name: Configure Git LFS for large files
35
+ # run: |
36
+ # find . -name "*.csv" -type f -size +10M | while read file; do
37
+ # git lfs track "$file"
38
+ # git add "$file"
39
+ # done
40
+
41
+ # print environment variables
42
+ - name: Print environment variables
43
+ run: |
44
+ echo "IMAGE_NAME: $IMAGE_NAME"
45
+ echo "IMAGE_TAG: $IMAGE_TAG"
46
+ echo "VERSION: $VERSION"
47
+ echo HF_TOKEN: $HF_TOKEN
48
+ echo HF_ORGANIZATION: $HF_ORGANIZATION
49
+ echo HF_SPACE: $HF_SPACE
50
+
51
+ # extract version from branch name and set it as an environment variable when release branch is pushed
52
+ - name: Extract version from branch name
53
+ if: startsWith(github.ref, 'refs/heads/release/')
54
+ id: extract_version
55
+ run: |
56
+ echo "IMAGE_TAG=${GITHUB_REF#refs/heads/release/}" >> $GITHUB_ENV
57
+
58
+ # print version
59
+ - name: Print version
60
+ id: print_version
61
+ run: |
62
+ echo "VERSION: $VERSION"
63
+
64
+ # build the docker image and push ghcr.io
65
+ - name: Build and push the docker image
66
+ if: startsWith(github.ref, 'refs/heads/release/')
67
+ run: |
68
+ docker build -t $IMAGE_NAME:$IMAGE_TAG .
69
+ docker tag $IMAGE_NAME:$IMAGE_TAG ghcr.io/$IMAGE_NAME:latest
70
+
71
+ docker push ghcr.io/$IMAGE_NAME:$IMAGE_TAG
72
+ docker push ghcr.io/$IMAGE_NAME:latest
73
+
74
+ # push to HuggingFace Spaces when branch name is main
75
+ - name: Push to HF Space
76
+ if: startsWith(github.ref, 'refs/heads/main')
77
+ run: |
78
+ git config --global user.email "actions@github.com"
79
+ git config --global user.name "GitHub Actions"
80
+ git remote add hf "https://api:$HF_TOKEN@huggingface.co/spaces/$HF_ORGANIZATION/$HF_SPACE"
81
+ git fetch hf
82
+ git push hf main --force
83
+
84
+
85
+
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ .venv
2
+ .idea
3
+ __pycache__
Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ RUN useradd -m -u 1000 chameleon
4
+
5
+ USER chameleon
6
+
7
+ ENV PATH="/home/chameleon/.local/bin:$PATH"
8
+
9
+ WORKDIR /app
10
+
11
+ COPY --chown=chameleon requirements.txt requirements.txt
12
+
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=chameleon . .
16
+
17
+ # HuggingFace Space port
18
+ ENV PORT=7860
19
+
20
+ # Expose the port
21
+ EXPOSE 7860
22
+
23
+ # Start command for loop
24
+ CMD ["sh", "-c", "python bot_telegram.py & tail -f /dev/null"]
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Telegram Bot
3
+ emoji: πŸ”₯
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: it is making not accessible for other people
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+
3
+ app = FastAPI()
4
+
5
+ @app.get("/")
6
+ def greet_json():
7
+ return {"Hello": "World!"}
bot_telegram.py ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import asyncio
4
+ import requests
5
+ from telegram import Update
6
+ from telegram.ext import Application, CommandHandler, MessageHandler, filters, CallbackContext
7
+ import aiohttp
8
+ import io
9
+
10
+ # Configure logging
11
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
12
+
13
+ # Load environment variables from Hugging Face Secrets
14
+ BOT_TOKEN = os.getenv("BOT_TOKEN")
15
+ BASE_URL = os.getenv("BASE_URL")
16
+ HF_TOKEN = os.getenv("HF_TOKEN")
17
+ API_USERNAME = os.getenv("API_USERNAME")
18
+ API_PASSWORD = os.getenv("API_PASSWORD")
19
+
20
+ # Set the secret password for authentication
21
+ SECRET_PASSWORD = "secure123" # Change this to your desired password
22
+
23
+ # Dictionary to store authenticated users
24
+ AUTHENTICATED_USERS = set()
25
+ AWAITING_PASSWORD = set()
26
+
27
+
28
+ class TelegramBot:
29
+ """A Telegram bot with password-based authentication."""
30
+
31
+ def __init__(self, bot_token, base_url, username, password):
32
+ """Initialize the bot with Telegram API token, API credentials, and authentication."""
33
+ self.bot_token = bot_token
34
+ self.base_url = base_url
35
+ # self.username = username
36
+ # self.password = password
37
+ # self.auth_token = None
38
+
39
+ # API Endpoints
40
+ self.login_url = f"{self.base_url}/api/v1/auth/login"
41
+ self.ai_url = f"{self.base_url}/api/v1/questions/text"
42
+ self.excel_url = f"{self.base_url}/api/v1/questions/excel"
43
+
44
+ # Start Telegram Bot
45
+ self.app = Application.builder().token(self.bot_token).build()
46
+ self.setup_handlers()
47
+
48
+ # Authenticate with API
49
+ logging.info("Authenticating with API...")
50
+ # self.authenticate()
51
+
52
+ # def authenticate(self):
53
+ # """Authenticate with the API and retrieve an access token."""
54
+ # payload = {"username": self.username, "password": self.password}
55
+ # headers = {"Content-Type": "application/json", "accept": "application/json"}
56
+ #
57
+ # try:
58
+ # response = requests.post(self.login_url, headers=headers, json=payload)
59
+ #
60
+ # if response.status_code == 200:
61
+ # self.auth_token = response.json().get("access_token")
62
+ # logging.info("Successfully authenticated with API")
63
+ # else:
64
+ # logging.error(f"Authentication failed: {response.status_code} - {response.text}")
65
+ #
66
+ # except Exception as e:
67
+ # logging.error(f"Authentication Error: {e}")
68
+
69
+ async def start_command(self, update: Update, context: CallbackContext):
70
+ """Handles the /start command and asks for a password if the user is not authenticated."""
71
+ user_id = update.message.from_user.id
72
+
73
+ if user_id in AUTHENTICATED_USERS:
74
+ await update.message.reply_text(
75
+ "βœ… You are already authenticated!\n\n"
76
+ "You can:\n"
77
+ "1. Send me any question as text\n"
78
+ "2. Send me an Excel file with questions (must have a 'question' column in 'rfp' sheet)\n\n"
79
+ "Note: Excel files must contain no more than 50 questions."
80
+ )
81
+ else:
82
+ AWAITING_PASSWORD.add(user_id)
83
+ await update.message.reply_text("πŸ”‘ Please enter the secret password to access the bot.")
84
+
85
+ async def handle_message(self, update: Update, context: CallbackContext):
86
+ """Handles all incoming messages."""
87
+ user_id = update.message.from_user.id
88
+ user_message = update.message.text.strip()
89
+
90
+ # If user is waiting to enter a password, validate it
91
+ if user_id in AWAITING_PASSWORD:
92
+ await self.check_password(update, context)
93
+ return
94
+
95
+ # If user is authenticated, process AI request
96
+ if user_id in AUTHENTICATED_USERS:
97
+ await self.chat_with_ai(update, context)
98
+ else:
99
+ await update.message.reply_text("❌ You are not authenticated. Please enter the password first.")
100
+
101
+ async def check_password(self, update: Update, context: CallbackContext):
102
+ """Checks if the password is correct and authenticates the user."""
103
+ user_id = update.message.from_user.id
104
+ user_message = update.message.text.strip()
105
+
106
+ if user_message == SECRET_PASSWORD:
107
+ AUTHENTICATED_USERS.add(user_id)
108
+ AWAITING_PASSWORD.discard(user_id)
109
+ logging.info(f"User {user_id} authenticated successfully.")
110
+ await update.message.reply_text(
111
+ "βœ… Authentication successful!\n\n"
112
+ "You can:\n"
113
+ "1. Send me any question as text\n"
114
+ "2. Send me an Excel file with questions (must have a 'question' column in 'rfp' sheet)\n\n"
115
+ "Note: Excel files must contain no more than 50 questions."
116
+ )
117
+ else:
118
+ await update.message.reply_text("❌ Wrong password. Try again.")
119
+
120
+ async def chat_with_ai(self, update: Update, context: CallbackContext):
121
+ """Handles messages and sends them to the AI API."""
122
+ user_id = update.message.from_user.id
123
+
124
+ if user_id not in AUTHENTICATED_USERS:
125
+ await update.message.reply_text("❌ You are not authenticated. Please enter the password first.")
126
+ return
127
+
128
+ # if not self.auth_token:
129
+ # self.authenticate()
130
+ #
131
+ # if not self.auth_token:
132
+ # await update.message.reply_text("Authentication failed. Please try again later.")
133
+ # return
134
+
135
+ user_message = update.message.text
136
+
137
+ hf_authorization = "Bearer " + HF_TOKEN
138
+ headers = {
139
+ "Authorization": hf_authorization,
140
+ "accept": "application/json"
141
+ }
142
+
143
+
144
+ json_payload = {"question": user_message}
145
+ form_payload = {"question": user_message}
146
+
147
+ try:
148
+ logging.info(f"Sending payload as JSON: {json_payload}")
149
+ response = requests.post(self.ai_url, headers={**headers, "Content-Type": "application/json"}, json=json_payload)
150
+
151
+ if response.status_code == 422:
152
+ logging.warning("JSON format rejected. Retrying with form-data...")
153
+ response = requests.post(self.ai_url, headers={**headers, "Content-Type": "application/x-www-form-urlencoded"},
154
+ data=form_payload)
155
+
156
+ if response.status_code == 200:
157
+ bot_reply = response.json().get("answer", "I didn't understand that.")
158
+ elif response.status_code == 401:
159
+ logging.warning("Authorization expired. Re-authenticating...")
160
+ # self.authenticate()
161
+ await self.chat_with_ai(update, context)
162
+ return
163
+ else:
164
+ logging.error(f"Error: {response.status_code}")
165
+ bot_reply = f"Error: {response.status_code}"
166
+
167
+ except Exception as e:
168
+ logging.error(f"Connection error: {e}")
169
+ bot_reply = f"Connection error: {e}"
170
+
171
+ await update.message.reply_text(bot_reply)
172
+
173
+ async def handle_excel(self, update: Update, context: CallbackContext):
174
+ """Handles Excel file uploads."""
175
+ user_id = update.message.from_user.id
176
+
177
+ if user_id not in AUTHENTICATED_USERS:
178
+ await update.message.reply_text("❌ You are not authenticated. Please enter the password first.")
179
+ return
180
+
181
+ # if not self.auth_token:
182
+ # self.authenticate()
183
+ #
184
+ # if not self.auth_token:
185
+ # await update.message.reply_text("Authentication failed. Please try again later.")
186
+ # return
187
+
188
+ document = update.message.document
189
+ if not document.file_name.endswith(('.xls', '.xlsx')):
190
+ await update.message.reply_text("Please send only Excel files (.xls or .xlsx)")
191
+ return
192
+
193
+ try:
194
+ # Send initial processing message
195
+ processing_msg = await update.message.reply_text("πŸ“Š Processing your Excel file... This may take a few minutes.")
196
+
197
+ # Get file from Telegram
198
+ file = await context.bot.get_file(document.file_id)
199
+ file_bytes = await file.download_as_bytearray()
200
+
201
+ headers = {
202
+ "Authorization": f"Bearer {HF_TOKEN}",
203
+ "accept": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
204
+ }
205
+
206
+ async with aiohttp.ClientSession() as session:
207
+ # Create form data with the file
208
+ form_data = aiohttp.FormData()
209
+ form_data.add_field('file',
210
+ file_bytes,
211
+ filename=document.file_name,
212
+ content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
213
+
214
+ # Send file to API
215
+ async with session.post(self.excel_url, headers=headers, data=form_data) as response:
216
+ if response.status == 200:
217
+ # Get the Excel file content directly
218
+ file_content = await response.read()
219
+
220
+ # Send the Excel file back to Telegram
221
+ await context.bot.send_document(
222
+ chat_id=update.effective_chat.id,
223
+ document=io.BytesIO(file_content),
224
+ filename='rfp_responses.xlsx',
225
+ caption="βœ… Here's your processed Excel file with answers!"
226
+ )
227
+ await processing_msg.delete()
228
+
229
+ elif response.status == 401:
230
+ logging.warning("Authorization expired. Re-authenticating...")
231
+ # self.authenticate()
232
+ await self.handle_excel(update, context)
233
+ else:
234
+ error_text = await response.text()
235
+ await processing_msg.edit_text(f"❌ Error processing Excel file: {error_text}")
236
+
237
+ except Exception as e:
238
+ logging.error(f"Error handling Excel file: {e}")
239
+ await update.message.reply_text(f"❌ Error processing Excel file: {str(e)}")
240
+
241
+ def setup_handlers(self):
242
+ """Set up Telegram command and message handlers."""
243
+ self.app.add_handler(CommandHandler("start", self.start_command))
244
+ self.app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message))
245
+ self.app.add_handler(MessageHandler(filters.Document.FileExtension("xlsx") | filters.Document.FileExtension("xls"), self.handle_excel))
246
+
247
+ def run(self):
248
+ """Start the bot and listen for messages."""
249
+ logging.info("Starting Telegram bot...")
250
+ self.app.run_polling()
251
+
252
+
253
+ if __name__ == "__main__":
254
+ bot = TelegramBot(
255
+ bot_token=BOT_TOKEN,
256
+ base_url=BASE_URL,
257
+ username=API_USERNAME,
258
+ password=API_PASSWORD
259
+ )
260
+ bot.run()
261
+
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ python-telegram-bot
2
+ requests
3
+ aiohttp
4
+ fastapi
5
+ uvicorn[standard]