· last year · Sep 06, 2024, 02:30 PM
1import logging
2import pytz
3import sqlite3
4from datetime import datetime, time, timedelta
5from telegram import Update
6from telegram.constants import ParseMode
7from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
8from geopy.distance import geodesic
9
10# Konfigurasi
11BOT_TOKEN = 'xxxxxxx' # Ganti dengan token bot Anda
12LOKASI_SMK = (-xxxxxxx, xxxxxxx) # Koordinat lokasi SMK
13BATAS_JAM_MASUK = time(7, 15)
14JAM_MULAI_ABSEN = time(5, 0)
15JAM_PULANG_SENIN_KAMIS = time(15, 15)
16JAM_PULANG_JUMAT = time(11, 0)
17
18# Nama manual (user_id: nama)
19NAMA_MANUAL = {
20 123456789: "John Doe, S.Kom.",
21 987654321: "Jane Smith",
22 # Tambahkan user_id dan nama manual lainnya di sini
23}
24
25# Konfigurasi Logging
26logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
27logger = logging.getLogger(__name__)
28
29# Fungsi untuk menghubungkan ke database SQLite
30def connect_db():
31 conn = sqlite3.connect('absensi.db')
32 return conn
33
34# Fungsi untuk membuat tabel jika belum ada
35def create_table():
36 conn = connect_db()
37 cursor = conn.cursor()
38 cursor.execute('''
39 CREATE TABLE IF NOT EXISTS absensi (
40 user_id INTEGER,
41 nama TEXT,
42 tanggal DATE,
43 hadir BOOLEAN,
44 terlambat BOOLEAN,
45 pulang_awal BOOLEAN
46 )
47 ''')
48 cursor.execute('''
49 CREATE TABLE IF NOT EXISTS pengguna (
50 user_id INTEGER PRIMARY KEY,
51 nama TEXT
52 )
53 ''')
54 conn.commit()
55 conn.close()
56
57# Panggil fungsi untuk membuat tabel saat pertama kali
58create_table()
59
60# Menyimpan nama pengguna ke database
61def simpan_nama(user_id, nama):
62 conn = connect_db()
63 cursor = conn.cursor()
64 cursor.execute('''
65 INSERT OR REPLACE INTO pengguna (user_id, nama)
66 VALUES (?, ?)
67 ''', (user_id, nama))
68 conn.commit()
69 conn.close()
70
71# Mengambil nama pengguna dari database
72def ambil_nama(user_id):
73 conn = connect_db()
74 cursor = conn.cursor()
75 cursor.execute('''
76 SELECT nama FROM pengguna
77 WHERE user_id = ?
78 ''', (user_id,))
79 nama = cursor.fetchone()
80 conn.close()
81 return nama[0] if nama else None
82
83# Mengisi nama manual ke dalam database
84def isi_nama_manual():
85 for user_id, nama in NAMA_MANUAL.items():
86 simpan_nama(user_id, nama)
87
88# Menyimpan absensi ke database
89def simpan_absensi(user_id, nama, tanggal, hadir, terlambat, pulang_awal):
90 conn = connect_db()
91 cursor = conn.cursor()
92 cursor.execute('''
93 INSERT INTO absensi (user_id, nama, tanggal, hadir, terlambat, pulang_awal)
94 VALUES (?, ?, ?, ?, ?, ?)
95 ''', (user_id, nama, tanggal, hadir, terlambat, pulang_awal))
96 conn.commit()
97 conn.close()
98
99# Menghitung Kehadiran
100def hitung_kehadiran(data_absensi, tanggal_mulai, tanggal_akhir):
101 total_hari_kerja = 0
102 kehadiran = 0
103 keterlambatan = 0
104 pulang_awal = 0
105 tidak_masuk = 0
106
107 current_date = tanggal_mulai
108
109 while current_date <= tanggal_akhir:
110 # Hitung hanya hari Senin-Jumat sebagai hari kerja
111 if current_date.weekday() < 5: # 0=Senin, 4=Jumat
112 total_hari_kerja += 1
113
114 # Periksa rekapan absensi untuk tanggal saat ini
115 absensi_hari_ini = [absen for absen in data_absensi if absen['tanggal'] == current_date]
116
117 if len(absensi_hari_ini) == 0:
118 tidak_masuk += 1
119 else:
120 hadir = absensi_hari_ini[0]['hadir']
121 terlambat = absensi_hari_ini[0]['terlambat']
122 pulang_awal = absensi_hari_ini[0]['pulang_awal']
123
124 if terlambat:
125 keterlambatan += 1
126 if pulang_awal:
127 pulang_awal += 1
128 if tidak_masuk:
129 tidak_masuk += 1
130
131 current_date += timedelta(days=1)
132
133 # Menghitung total kehadiran
134 pengurangan_keterlambatan = keterlambatan * 0.5
135 pengurangan_pulang_awal = pulang_awal * 0.5
136 pengurangan_tidak_masuk = tidak_masuk * 1
137
138 kehadiran = total_hari_kerja - (pengurangan_keterlambatan + pengurangan_pulang_awal + pengurangan_tidak_masuk)
139
140 return total_hari_kerja, kehadiran, keterlambatan, pulang_awal, tidak_masuk
141
142# Rekap Absensi untuk Satu Orang
143def rekap_absensi(nama, tanggal_mulai, tanggal_akhir):
144 conn = connect_db()
145 cursor = conn.cursor()
146 cursor.execute('''
147 SELECT * FROM absensi
148 WHERE nama = ? AND tanggal BETWEEN ? AND ?
149 ''', (nama, tanggal_mulai, tanggal_akhir))
150 data_absensi = cursor.fetchall()
151 conn.close()
152
153 filtered_data = [{
154 'tanggal': row[2],
155 'hadir': row[3],
156 'terlambat': row[4],
157 'pulang_awal': row[5]
158 } for row in data_absensi]
159
160 total_hari_kerja, kehadiran, keterlambatan, pulang_awal, tidak_masuk = hitung_kehadiran(filtered_data, tanggal_mulai, tanggal_akhir)
161 return total_hari_kerja, kehadiran, keterlambatan, pulang_awal, tidak_masuk
162
163# Rekap Absensi untuk Semua Orang
164def rekap_semua_orang(tanggal_mulai, tanggal_akhir):
165 conn = connect_db()
166 cursor = conn.cursor()
167 cursor.execute('SELECT DISTINCT nama FROM absensi')
168 nama_list = [row[0] for row in cursor.fetchall()]
169 conn.close()
170
171 rekap_hasil = ""
172 for nama in nama_list:
173 total_hari_kerja, kehadiran, keterlambatan, pulang_awal, tidak_masuk = rekap_absensi(nama, tanggal_mulai, tanggal_akhir)
174 rekap_hasil += (
175 f"**Nama: {nama}**\n"
176 f"**Periode:** {tanggal_mulai.strftime('%d/%m/%Y')} - {tanggal_akhir.strftime('%d/%m/%Y')}\n"
177 f"**Total Hari Kerja:** {total_hari_kerja} hari\n\n"
178 f"**Kehadiran:**\n"
179 f"- **Jumlah Kehadiran:** {kehadiran} hari\n"
180 f"- **Jumlah Keterlambatan:** {keterlambatan} kali\n"
181 f"- **Jumlah Pulang Awal:** {pulang_awal} kali\n"
182 f"- **Jumlah Tidak Masuk:** {tidak_masuk} kali\n\n"
183 f"**Pengurangan Kehadiran:**\n"
184 f"- **Keterlambatan:** {keterlambatan * 0.5}\n"
185 f"- **Pulang Awal:** {pulang_awal * 0.5}\n"
186 f"- **Tidak Masuk:** {tidak_masuk * 1}\n\n"
187 f"**Total Pengurangan:** {keterlambatan * 0.5 + pulang_awal * 0.5 + tidak_masuk * 1}\n\n"
188 f"**Kehadiran Bersih:** {kehadiran}\n\n"
189 )
190 return rekap_hasil
191
192# Fungsi Absensi Masuk
193async def absensi_masuk(update: Update, context: ContextTypes.DEFAULT_TYPE):
194 user_id = update.message.from_user.id
195 nama = ambil_nama(user_id) or update.message.from_user.full_name
196 user_location = update.message.location
197 now = datetime.now(pytz.timezone('Asia/Jakarta')).time()
198
199 logger.info(f"Absensi masuk dari {nama} (user_id {user_id}) pada {now}")
200
201 if user_location is None:
202 await update.message.reply_text("Lokasi tidak ditemukan. Harap kirimkan lokasi Anda.")
203 logger.warning(f"Absensi masuk gagal dari {nama} (user_id {user_id}) karena lokasi tidak ditemukan.")
204 return
205
206 if not verifikasi_lokasi(user_location):
207 await update.message.reply_text("Lokasi Anda tidak sesuai dengan lokasi yang diharapkan.")
208 logger.warning(f"Absensi masuk gagal dari {nama} (user_id {user_id}) karena lokasi tidak sesuai.")
209 return
210
211 if now < JAM_MULAI_ABSEN:
212 await update.message.reply_text("Anda terlalu awal untuk absensi masuk.")
213 logger.info(f"Absensi masuk gagal dari {nama} (user_id {user_id}) karena terlalu awal.")
214 return
215
216 hadir = terlambat = pulang_awal = False
217
218 if now <= BATAS_JAM_MASUK:
219 hadir = True
220 else:
221 terlambat = True
222
223 simpan_absensi(user_id, nama, datetime.now(pytz.timezone('Asia/Jakarta')).date(), hadir, terlambat, pulang_awal)
224 await update.message.reply_text(f"Absensi masuk Anda telah dicatat sebagai {'hadir' if hadir else 'terlambat'}.")
225 logger.info(f"Absensi masuk berhasil dicatat untuk {nama} (user_id {user_id}).")
226
227# Fungsi Absensi Pulang
228async def absensi_pulang(update: Update, context: ContextTypes.DEFAULT_TYPE):
229 user_id = update.message.from_user.id
230 nama = ambil_nama(user_id) or update.message.from_user.full_name
231 user_location = update.message.location
232 now = datetime.now(pytz.timezone('Asia/Jakarta')).time()
233
234 logger.info(f"Absensi pulang dari {nama} (user_id {user_id}) pada {now}")
235
236 if user_location is None:
237 await update.message.reply_text("Lokasi tidak ditemukan. Harap kirimkan lokasi Anda.")
238 logger.warning(f"Absensi pulang gagal dari {nama} (user_id {user_id}) karena lokasi tidak ditemukan.")
239 return
240
241 if not verifikasi_lokasi(user_location):
242 await update.message.reply_text("Lokasi Anda tidak sesuai dengan lokasi yang diharapkan.")
243 logger.warning(f"Absensi pulang gagal dari {nama} (user_id {user_id}) karena lokasi tidak sesuai.")
244 return
245
246 hadir = terlambat = pulang_awal = False
247
248 if now <= JAM_PULANG_SENIN_KAMIS:
249 pulang_awal = True
250 elif now >= JAM_PULANG_JUMAT:
251 pulang_awal = True
252
253 simpan_absensi(user_id, nama, datetime.now(pytz.timezone('Asia/Jakarta')).date(), hadir, terlambat, pulang_awal)
254 await update.message.reply_text(f"Absensi pulang Anda telah dicatat sebagai {'pulang awal' if pulang_awal else 'pulang tepat waktu'}.")
255 logger.info(f"Absensi pulang berhasil dicatat untuk {nama} (user_id {user_id}).")
256
257# Fungsi untuk verifikasi lokasi
258def verifikasi_lokasi(lokasi_user):
259 lokasi_user_tuple = (lokasi_user.latitude, lokasi_user.longitude)
260 jarak = geodesic(lokasi_user_tuple, LOKASI_SMK).meters
261 return jarak <= 100 # Radius 100 meter
262
263# Fungsi untuk mengirimkan rekap absensi
264async def rekap(update: Update, context: ContextTypes.DEFAULT_TYPE):
265 if len(context.args) < 2:
266 await update.message.reply_text("Format perintah salah. Gunakan /rekap <tanggal_mulai> <tanggal_akhir>.")
267 return
268
269 try:
270 tanggal_mulai = datetime.strptime(context.args[0], "%Y-%m-%d").date()
271 tanggal_akhir = datetime.strptime(context.args[1], "%Y-%m-%d").date()
272 except ValueError:
273 await update.message.reply_text("Format tanggal salah. Gunakan YYYY-MM-DD.")
274 return
275
276 rekap_hasil = rekap_semua_orang(tanggal_mulai, tanggal_akhir)
277 await update.message.reply_text(rekap_hasil, parse_mode=ParseMode.MARKDOWN)
278 logger.info(f"Rekap absensi dari {tanggal_mulai} sampai {tanggal_akhir} dikirim.")
279
280# Fungsi untuk mengirimkan rekap absensi semua orang
281async def rekap_semua(update: Update, context: ContextTypes.DEFAULT_TYPE):
282 if len(context.args) < 2:
283 await update.message.reply_text("Format perintah salah. Gunakan /rekap_semua <tanggal_mulai> <tanggal_akhir>.")
284 return
285
286 try:
287 tanggal_mulai = datetime.strptime(context.args[0], "%Y-%m-%d").date()
288 tanggal_akhir = datetime.strptime(context.args[1], "%Y-%m-%d").date()
289 except ValueError:
290 await update.message.reply_text("Format tanggal salah. Gunakan YYYY-MM-DD.")
291 return
292
293 rekap_hasil = rekap_semua_orang(tanggal_mulai, tanggal_akhir)
294 await update.message.reply_text(rekap_hasil, parse_mode=ParseMode.MARKDOWN)
295 logger.info(f"Rekap absensi semua orang dari {tanggal_mulai} sampai {tanggal_akhir} dikirim.")
296
297# Fungsi untuk menangani perintah /start
298async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
299 await update.message.reply_text(
300 "Selamat datang di sistem absensi!\n\n"
301 "Gunakan perintah berikut:\n"
302 "/absensi_masuk - Untuk absensi masuk\n"
303 "/absensi_pulang - Untuk absensi pulang\n"
304 "/rekap <tanggal_mulai> <tanggal_akhir> - Untuk rekap absensi individu\n"
305 "/rekap_semua <tanggal_mulai> <tanggal_akhir> - Untuk rekap absensi semua orang\n"
306 "/set_nama <nama> - Untuk mengatur nama Anda (untuk admin)\n"
307 )
308 logger.info("Pengguna baru memulai bot.")
309
310# Fungsi untuk menyimpan nama pengguna secara manual
311async def set_nama(update: Update, context: ContextTypes.DEFAULT_TYPE):
312 user_id = update.message.from_user.id
313 if len(context.args) == 1:
314 nama = context.args[0]
315 simpan_nama(user_id, nama)
316 await update.message.reply_text(f"Nama Anda telah disimpan sebagai {nama}.")
317 logger.info(f"Nama disimpan untuk user_id {user_id}: {nama}")
318 else:
319 await update.message.reply_text("Format perintah salah. Gunakan /set_nama <nama>.")
320
321# Fungsi utama
322def main():
323 # Mengisi nama manual ke dalam database
324 isi_nama_manual()
325
326 application = Application.builder().token(BOT_TOKEN).build()
327
328 application.add_handler(CommandHandler("start", start))
329 application.add_handler(CommandHandler("absensi_masuk", absensi_masuk))
330 application.add_handler(CommandHandler("absensi_pulang", absensi_pulang))
331 application.add_handler(CommandHandler("rekap", rekap))
332 application.add_handler(CommandHandler("rekap_semua", rekap_semua))
333 application.add_handler(CommandHandler("set_nama", set_nama))
334
335 # Menangani lokasi
336 application.add_handler(MessageHandler(filters.LOCATION, absensi_masuk))
337 application.add_handler(MessageHandler(filters.LOCATION, absensi_pulang))
338
339 application.run_polling()
340
341if __name__ == '__main__':
342 main()
343