1556 lines
74 KiB
HTML
1556 lines
74 KiB
HTML
<!DOCTYPE html>
|
|
<!-- saved from url=(0063)file:///Users/home/Downloads/deepseek_html_20260116_9092c1.html -->
|
|
<html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Joke Bot Upgrade: SQLite Database Integration | Beginner's Guide</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
color: #1a1a1a;
|
|
line-height: 1.4;
|
|
height: 100vh;
|
|
overflow: hidden;
|
|
background: linear-gradient(rgba(255, 255, 255, 0.98), rgba(255, 255, 255, 0.99));
|
|
}
|
|
|
|
.presentation-container {
|
|
max-width: 900px;
|
|
height: 100vh;
|
|
margin: 0 auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 0 20px;
|
|
}
|
|
|
|
.slide {
|
|
display: none;
|
|
flex: 1;
|
|
padding: 40px 30px;
|
|
background: transparent;
|
|
overflow-y: auto;
|
|
max-height: calc(100vh - 100px);
|
|
}
|
|
|
|
.active {
|
|
display: block;
|
|
animation: fadeIn 0.5s ease;
|
|
}
|
|
|
|
@keyframes fadeIn {
|
|
from { opacity: 0; transform: translateY(10px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
/* Title Slide */
|
|
.title-slide {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
text-align: center;
|
|
height: 100%;
|
|
}
|
|
|
|
.thesis-title {
|
|
font-weight: 800;
|
|
font-size: 2.8rem;
|
|
color: #2c3e50;
|
|
margin-bottom: 10px;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
.thesis-subtitle {
|
|
font-size: 1.3rem;
|
|
color: #3498db;
|
|
margin-bottom: 40px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* Typography */
|
|
h1 {
|
|
font-weight: 700;
|
|
font-size: 2.2rem;
|
|
color: #2c3e50;
|
|
margin-bottom: 25px;
|
|
line-height: 1.1;
|
|
}
|
|
|
|
h2 {
|
|
font-weight: 600;
|
|
font-size: 1.8rem;
|
|
color: #2c3e50;
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
h3 {
|
|
font-weight: 600;
|
|
font-size: 1.2rem;
|
|
color: #3498db;
|
|
margin-bottom: 15px;
|
|
margin-top: 25px;
|
|
}
|
|
|
|
p {
|
|
font-size: 1rem;
|
|
color: #4a5568;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.lead {
|
|
font-size: 1.1rem;
|
|
color: #2c3e50;
|
|
font-weight: 500;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Code Blocks */
|
|
.code-block {
|
|
background: #1e1e1e;
|
|
color: #d4d4d4;
|
|
padding: 25px;
|
|
border-radius: 8px;
|
|
margin: 20px 0;
|
|
font-family: 'Courier New', Consolas, Monaco, monospace;
|
|
font-size: 0.9rem;
|
|
line-height: 1.5;
|
|
overflow-x: auto;
|
|
white-space: pre;
|
|
}
|
|
|
|
.code-comment { color: #6a9955; }
|
|
.code-keyword { color: #569cd6; }
|
|
.code-function { color: #dcdcaa; }
|
|
.code-string { color: #ce9178; }
|
|
.code-builtin { color: #4ec9b0; }
|
|
.code-number { color: #b5cea8; }
|
|
|
|
/* Feature Boxes */
|
|
.feature-box {
|
|
background: #f8fafc;
|
|
padding: 25px;
|
|
border-radius: 10px;
|
|
margin: 25px 0;
|
|
border-left: 4px solid #3498db;
|
|
}
|
|
|
|
.feature-item {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.feature-number {
|
|
background: #3498db;
|
|
color: white;
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 15px;
|
|
flex-shrink: 0;
|
|
font-weight: bold;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
/* Database Concepts */
|
|
.database-concept {
|
|
background: #fff;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin: 15px 0;
|
|
border: 2px solid #e0e6ed;
|
|
}
|
|
|
|
.concept-title {
|
|
color: #2c3e50;
|
|
font-weight: 600;
|
|
margin-bottom: 10px;
|
|
display: block;
|
|
}
|
|
|
|
/* Navigation */
|
|
.navigation {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 15px 20px;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
border-top: 1px solid #e2e8f0;
|
|
height: 70px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.nav-btn {
|
|
padding: 10px 25px;
|
|
background: #3498db;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 6px;
|
|
cursor: pointer;
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.nav-btn:hover:not(:disabled) {
|
|
background: #2980b9;
|
|
}
|
|
|
|
.nav-btn:disabled {
|
|
background: #cbd5e0;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.slide-counter {
|
|
font-weight: 600;
|
|
color: #4a5568;
|
|
font-size: 0.95rem;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.slide {
|
|
padding: 30px 20px;
|
|
}
|
|
|
|
.thesis-title {
|
|
font-size: 2.2rem;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 1.8rem;
|
|
}
|
|
|
|
h2 {
|
|
font-size: 1.5rem;
|
|
}
|
|
|
|
.code-block {
|
|
padding: 20px;
|
|
font-size: 0.85rem;
|
|
}
|
|
}
|
|
</style>
|
|
<link href="./Joke Bot Upgrade_ SQLite Database Integration _ Beginner's Guide_files/css2" rel="stylesheet">
|
|
</head>
|
|
<body>
|
|
<div class="presentation-container">
|
|
<!-- Slide 1: Title -->
|
|
<div class="slide active" id="slide1">
|
|
<div class="title-slide">
|
|
<div>
|
|
<h1 class="thesis-title">Joke Bot Upgrade</h1>
|
|
<p class="thesis-subtitle">From Simple Lists to SQLite Database with User Interaction</p>
|
|
</div>
|
|
|
|
<div class="author-info" style="margin-top: 50px; padding-top: 30px; border-top: 2px solid #e0e6ed; width: 100%; max-width: 600px;">
|
|
<p><strong>Goal:</strong> Upgrade joke bot with database, user submissions, and ratings</p>
|
|
<p><strong>Prerequisites:</strong> Basic Python knowledge, our existing joke bot</p>
|
|
<p><strong>Time:</strong> 60 minutes to complete upgrade</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 2: What is SQLite? -->
|
|
<div class="slide" id="slide2">
|
|
<h1>What Is SQLite?</h1>
|
|
<p class="lead">Serverless, self-contained SQL database perfect for Telegram bots</p>
|
|
|
|
<div class="feature-box">
|
|
<h3>Why SQLite for Our Joke Bot?</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">1</div>
|
|
<div><strong>No Server Needed:</strong> Database lives in a single file</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">2</div>
|
|
<div><strong>Zero Configuration:</strong> Just import and use</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">3</div>
|
|
<div><strong>Lightweight:</strong> Perfect for small to medium applications</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">4</div>
|
|
<div><strong>SQL Standard:</strong> Uses standard SQL commands</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">5</div>
|
|
<div><strong>Built-in Python:</strong> No extra installation needed</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="database-concept">
|
|
<span class="concept-title">Think of it like this:</span>
|
|
<p>SQLite is like an Excel file that speaks SQL. It's a complete database in a single file that you can include with your application.</p>
|
|
<p>Perfect for Telegram bots, mobile apps, and desktop applications.</p>
|
|
</div>
|
|
|
|
<div class="code-block" style="margin-top: 20px; padding: 15px; background: #2c3e50;">
|
|
<span class="code-comment"># SQLite comes built-in with Python!</span>
|
|
<span class="code-keyword">import</span> sqlite3
|
|
|
|
<span class="code-comment"># Connect to database (creates if doesn't exist)</span>
|
|
conn = sqlite3.connect(<span class="code-string">'joke_bot.db'</span>)
|
|
cursor = conn.cursor()
|
|
|
|
<span class="code-comment"># Create a table</span>
|
|
cursor.execute(<span class="code-string">'''CREATE TABLE jokes (
|
|
id INTEGER PRIMARY KEY,
|
|
text TEXT NOT NULL,
|
|
added_by TEXT,
|
|
likes INTEGER DEFAULT 0,
|
|
dislikes INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)'''</span>)
|
|
|
|
conn.commit()
|
|
conn.close()</div>
|
|
</div>
|
|
|
|
<!-- Slide 3: Our Upgrade Goals -->
|
|
<div class="slide" id="slide3">
|
|
<h2>Our Upgrade Goals</h2>
|
|
<p class="lead">From static list to dynamic, user-powered joke database</p>
|
|
|
|
<div class="database-concept">
|
|
<span class="concept-title">Current Limitations:</span>
|
|
<p>1. Jokes are hardcoded in Python list</p>
|
|
<p>2. No way for users to add new jokes</p>
|
|
<p>3. No rating system to find the best jokes</p>
|
|
<p>4. Jokes are lost when bot restarts</p>
|
|
</div>
|
|
|
|
<h3>New Features We'll Add:</h3>
|
|
<div class="feature-box">
|
|
<div class="feature-item">
|
|
<div class="feature-number">1</div>
|
|
<div>
|
|
<strong>SQLite Database:</strong><br>
|
|
Store jokes permanently in a database file
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">2</div>
|
|
<div>
|
|
<strong>User Submissions:</strong><br>
|
|
<code>/addjoke</code> command for users to submit jokes
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">3</div>
|
|
<div>
|
|
<strong>Rating System:</strong><br>
|
|
<code>/like</code> and <code>/dislike</code> commands to rate jokes
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">4</div>
|
|
<div>
|
|
<strong>Top Jokes:</strong><br>
|
|
<code>/top</code> command to show most popular jokes
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">5</div>
|
|
<div>
|
|
<strong>Random with Weight:</strong><br>
|
|
Better jokes appear more often
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 4: Step 1 - Database Setup -->
|
|
<div class="slide" id="slide4">
|
|
<h2>Step 1: Database Setup (10 minutes)</h2>
|
|
<p class="lead">Create the SQLite database and connection functions</p>
|
|
|
|
<div class="code-block"><span class="code-comment"># Create a new file: database.py</span>
|
|
<span class="code-comment"># This will handle all database operations</span>
|
|
|
|
<span class="code-keyword">import</span> sqlite3
|
|
<span class="code-keyword">from</span> datetime <span class="code-keyword">import</span> datetime
|
|
|
|
<span class="code-keyword">class</span> <span class="code-builtin">JokeDatabase</span>:
|
|
<span class="code-keyword">def</span> <span class="code-function">__init__</span>(<span class="code-keyword">self</span>, db_name=<span class="code-string">'joke_bot.db'</span>):
|
|
<span class="code-keyword">self</span>.db_name = db_name
|
|
<span class="code-keyword">self</span>.init_database()
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">get_connection</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Get a database connection"""</span>
|
|
conn = sqlite3.connect(<span class="code-keyword">self</span>.db_name)
|
|
conn.row_factory = sqlite3.Row <span class="code-comment"># Access columns by name</span>
|
|
<span class="code-keyword">return</span> conn
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">init_database</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Initialize database tables"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
<span class="code-comment"># Create jokes table</span>
|
|
cursor.execute(<span class="code-string">'''CREATE TABLE IF NOT EXISTS jokes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
joke_text TEXT NOT NULL,
|
|
user_id INTEGER,
|
|
username TEXT,
|
|
likes INTEGER DEFAULT 0,
|
|
dislikes INTEGER DEFAULT 0,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)'''</span>)
|
|
|
|
<span class="code-comment"># Create ratings table to track user votes</span>
|
|
cursor.execute(<span class="code-string">'''CREATE TABLE IF NOT EXISTS ratings (
|
|
user_id INTEGER,
|
|
joke_id INTEGER,
|
|
vote INTEGER, <span class="code-comment">-- 1 for like, -1 for dislike</span>
|
|
PRIMARY KEY (user_id, joke_id),
|
|
FOREIGN KEY (joke_id) REFERENCES jokes(id)
|
|
)'''</span>)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
<span class="code-comment"># Add some default jokes if table is empty</span>
|
|
<span class="code-keyword">self</span>.add_default_jokes()
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">add_default_jokes</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Add default jokes if database is empty"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">"SELECT COUNT(*) FROM jokes"</span>)
|
|
count = cursor.fetchone()[<span class="code-number">0</span>]
|
|
|
|
<span class="code-keyword">if</span> count == <span class="code-number">0</span>:
|
|
default_jokes = [
|
|
(<span class="code-string">"Why did the robot go to school? To recharge his brain! 🔋"</span>, <span class="code-number">0</span>, <span class="code-string">"System"</span>),
|
|
(<span class="code-string">"Knock knock!\nWho's there?\nLettuce!\nLettuce who?\nLettuce in!"</span>, <span class="code-number">0</span>, <span class="code-string">"System"</span>),
|
|
(<span class="code-string">"Why don't eggs tell jokes? They'd crack each other up! 🥚"</span>, <span class="code-number">0</span>, <span class="code-string">"System"</span>)
|
|
]
|
|
|
|
cursor.executemany(<span class="code-string">'''INSERT INTO jokes (joke_text, user_id, username)
|
|
VALUES (?, ?, ?)'''</span>, default_jokes)
|
|
|
|
conn.commit()
|
|
print(<span class="code-string">f"Added </span>{len(default_jokes)}<span class="code-string"> default jokes to database"</span>)
|
|
|
|
conn.close()</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Database Schema Explained:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📝</div>
|
|
<div><strong>jokes table:</strong> Stores all jokes with ratings</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">⭐</div>
|
|
<div><strong>ratings table:</strong> Tracks who voted for which joke</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🆔</div>
|
|
<div><strong>Primary keys:</strong> Unique IDs for each record</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🔗</div>
|
|
<div><strong>Foreign key:</strong> Links ratings to jokes</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 5: Step 2 - Database CRUD Operations -->
|
|
<div class="slide" id="slide5">
|
|
<h2>Step 2: Database Operations (15 minutes)</h2>
|
|
<p class="lead">Add CRUD (Create, Read, Update, Delete) functions</p>
|
|
|
|
<div class="code-block"><span class="code-comment"># Add these methods to the JokeDatabase class</span>
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">add_joke</span>(<span class="code-keyword">self</span>, joke_text, user_id, username):
|
|
<span class="code-comment">"""Add a new joke to the database"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">'''INSERT INTO jokes (joke_text, user_id, username)
|
|
VALUES (?, ?, ?)'''</span>, (joke_text, user_id, username))
|
|
|
|
joke_id = cursor.lastrowid
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> joke_id
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">get_random_joke</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Get a random joke, weighted by rating"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
<span class="code-comment"># Weighted random: jokes with higher likes appear more often</span>
|
|
cursor.execute(<span class="code-string">'''SELECT * FROM jokes
|
|
ORDER BY (likes - dislikes + 5) * RANDOM() DESC
|
|
LIMIT 1'''</span>)
|
|
|
|
joke = cursor.fetchone()
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> dict(joke) <span class="code-keyword">if</span> joke <span class="code-keyword">else</span> <span class="code-keyword">None</span>
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">get_joke_by_id</span>(<span class="code-keyword">self</span>, joke_id):
|
|
<span class="code-comment">"""Get a specific joke by ID"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">"SELECT * FROM jokes WHERE id = ?"</span>, (joke_id,))
|
|
joke = cursor.fetchone()
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> dict(joke) <span class="code-keyword">if</span> joke <span class="code-keyword">else</span> <span class="code-keyword">None</span>
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">get_top_jokes</span>(<span class="code-keyword">self</span>, limit=<span class="code-number">5</span>):
|
|
<span class="code-comment">"""Get top-rated jokes"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">'''SELECT * FROM jokes
|
|
ORDER BY (likes - dislikes) DESC, likes DESC
|
|
LIMIT ?'''</span>, (limit,))
|
|
|
|
jokes = [dict(row) <span class="code-keyword">for</span> row <span class="code-keyword">in</span> cursor.fetchall()]
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> jokes
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">get_total_jokes</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Get total number of jokes"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">"SELECT COUNT(*) FROM jokes"</span>)
|
|
count = cursor.fetchone()[<span class="code-number">0</span>]
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> count</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>SQL Functions Explained:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📊</div>
|
|
<div><strong>Weighted Random:</strong> <code>(likes - dislikes + 5) * RANDOM()</code></div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">⬆️</div>
|
|
<div><strong>Top Jokes:</strong> Sorted by net score (likes - dislikes)</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">💾</div>
|
|
<div><strong>Parameterized Queries:</strong> Safe from SQL injection</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🔄</div>
|
|
<div><strong>Connection Management:</strong> Open/close connections properly</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 6: Step 3 - Rating System -->
|
|
<div class="slide" id="slide6">
|
|
<h2>Step 3: Rating System (15 minutes)</h2>
|
|
<p class="lead">Implement like/dislike functionality with vote tracking</p>
|
|
|
|
<div class="code-block"><span class="code-comment"># Add rating methods to JokeDatabase class</span>
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">rate_joke</span>(<span class="code-keyword">self</span>, user_id, joke_id, vote):
|
|
<span class="code-comment">"""
|
|
Rate a joke (like or dislike)
|
|
vote: 1 for like, -1 for dislike, 0 to remove rating
|
|
"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
<span class="code-comment"># Check if user already voted</span>
|
|
cursor.execute(<span class="code-string">'''SELECT vote FROM ratings
|
|
WHERE user_id = ? AND joke_id = ?'''</span>, (user_id, joke_id))
|
|
|
|
existing_vote = cursor.fetchone()
|
|
|
|
<span class="code-keyword">if</span> existing_vote:
|
|
old_vote = existing_vote[<span class="code-number">0</span>]
|
|
|
|
<span class="code-keyword">if</span> vote == <span class="code-number">0</span>:
|
|
<span class="code-comment"># Remove vote</span>
|
|
cursor.execute(<span class="code-string">'''DELETE FROM ratings
|
|
WHERE user_id = ? AND joke_id = ?'''</span>, (user_id, joke_id))
|
|
|
|
<span class="code-comment"># Update joke counts</span>
|
|
<span class="code-keyword">if</span> old_vote == <span class="code-number">1</span>:
|
|
cursor.execute(<span class="code-string">'''UPDATE jokes SET likes = likes - 1
|
|
WHERE id = ?'''</span>, (joke_id,))
|
|
<span class="code-keyword">elif</span> old_vote == -<span class="code-number">1</span>:
|
|
cursor.execute(<span class="code-string">'''UPDATE jokes SET dislikes = dislikes - 1
|
|
WHERE id = ?'''</span>, (joke_id,))
|
|
<span class="code-keyword">elif</span> old_vote != vote:
|
|
<span class="code-comment"># Change vote</span>
|
|
cursor.execute(<span class="code-string">'''UPDATE ratings SET vote = ?
|
|
WHERE user_id = ? AND joke_id = ?'''</span>, (vote, user_id, joke_id))
|
|
|
|
<span class="code-comment"># Update joke counts (remove old, add new)</span>
|
|
<span class="code-keyword">if</span> old_vote == <span class="code-number">1</span> <span class="code-keyword">and</span> vote == -<span class="code-number">1</span>:
|
|
cursor.execute(<span class="code-string">'''UPDATE jokes
|
|
SET likes = likes - 1, dislikes = dislikes + 1
|
|
WHERE id = ?'''</span>, (joke_id,))
|
|
<span class="code-keyword">elif</span> old_vote == -<span class="code-number">1</span> <span class="code-keyword">and</span> vote == <span class="code-number">1</span>:
|
|
cursor.execute(<span class="code-string">'''UPDATE jokes
|
|
SET likes = likes + 1, dislikes = dislikes - 1
|
|
WHERE id = ?'''</span>, (joke_id,))
|
|
<span class="code-keyword">else</span>:
|
|
<span class="code-keyword">if</span> vote != <span class="code-number">0</span>:
|
|
<span class="code-comment"># New vote</span>
|
|
cursor.execute(<span class="code-string">'''INSERT INTO ratings (user_id, joke_id, vote)
|
|
VALUES (?, ?, ?)'''</span>, (user_id, joke_id, vote))
|
|
|
|
<span class="code-comment"># Update joke counts</span>
|
|
<span class="code-keyword">if</span> vote == <span class="code-number">1</span>:
|
|
cursor.execute(<span class="code-string">'''UPDATE jokes SET likes = likes + 1
|
|
WHERE id = ?'''</span>, (joke_id,))
|
|
<span class="code-keyword">elif</span> vote == -<span class="code-number">1</span>:
|
|
cursor.execute(<span class="code-string">'''UPDATE jokes SET dislikes = dislikes + 1
|
|
WHERE id = ?'''</span>, (joke_id,))
|
|
|
|
conn.commit()
|
|
|
|
<span class="code-comment"># Get updated joke info</span>
|
|
cursor.execute(<span class="code-string">"SELECT likes, dislikes FROM jokes WHERE id = ?"</span>, (joke_id,))
|
|
result = cursor.fetchone()
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> {
|
|
<span class="code-string">'likes'</span>: result[<span class="code-number">0</span>] <span class="code-keyword">if</span> result <span class="code-keyword">else</span> <span class="code-number">0</span>,
|
|
<span class="code-string">'dislikes'</span>: result[<span class="code-number">1</span>] <span class="code-keyword">if</span> result <span class="code-keyword">else</span> <span class="code-number">0</span>
|
|
}
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">get_user_vote</span>(<span class="code-keyword">self</span>, user_id, joke_id):
|
|
<span class="code-comment">"""Get user's vote for a specific joke"""</span>
|
|
conn = <span class="code-keyword">self</span>.get_connection()
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">'''SELECT vote FROM ratings
|
|
WHERE user_id = ? AND joke_id = ?'''</span>, (user_id, joke_id))
|
|
|
|
result = cursor.fetchone()
|
|
conn.close()
|
|
|
|
<span class="code-keyword">return</span> result[<span class="code-number">0</span>] <span class="code-keyword">if</span> result <span class="code-keyword">else</span> <span class="code-number">0</span></div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Rating Logic:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">1</div>
|
|
<div><strong>Prevent Double Voting:</strong> Users can only vote once per joke</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">2</div>
|
|
<div><strong>Change Votes:</strong> Users can change their mind</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">3</div>
|
|
<div><strong>Remove Votes:</strong> Users can remove their rating</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">4</div>
|
|
<div><strong>Real-time Updates:</strong> Counts update immediately</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 7: Step 4 - Telegram Bot Integration -->
|
|
<div class="slide" id="slide7">
|
|
<h2>Step 4: Telegram Bot Integration (15 minutes)</h2>
|
|
<p class="lead">Update app.py to use the database</p>
|
|
|
|
<div class="code-block"><span class="code-comment"># Updated app.py with database integration</span>
|
|
|
|
<span class="code-keyword">from</span> telegram <span class="code-keyword">import</span> Update, InlineKeyboardMarkup, InlineKeyboardButton
|
|
<span class="code-keyword">from</span> telegram.ext <span class="code-keyword">import</span> Application, CommandHandler, ContextTypes, MessageHandler, filters, CallbackQueryHandler
|
|
<span class="code-keyword">from</span> database <span class="code-keyword">import</span> JokeDatabase
|
|
<span class="code-keyword">import</span> random
|
|
|
|
<span class="code-comment"># Initialize database</span>
|
|
db = JokeDatabase()
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">start</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Start command handler"""</span>
|
|
welcome_text = <span class="code-string">"""🤖 Welcome to Joke Bot 2.0! 🤖
|
|
|
|
Commands:
|
|
/joke - Get a random joke
|
|
/addjoke - Submit your own joke
|
|
/top - See top-rated jokes
|
|
/like - Like the last joke
|
|
/dislike - Dislike the last joke
|
|
/stats - Bot statistics
|
|
|
|
Now with database, user submissions, and ratings!"""</span>
|
|
|
|
<span class="code-keyword">await</span> update.message.reply_text(welcome_text)
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">send_joke</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Send a random joke from database"""</span>
|
|
joke = db.get_random_joke()
|
|
|
|
<span class="code-keyword">if</span> <span class="code-keyword">not</span> joke:
|
|
<span class="code-keyword">await</span> update.message.reply_text(<span class="code-string">"No jokes in database yet! Use /addjoke to add one."</span>)
|
|
<span class="code-keyword">return</span>
|
|
|
|
<span class="code-comment"># Store joke ID in context for rating</span>
|
|
context.user_data[<span class="code-string">'last_joke_id'</span>] = joke[<span class="code-string">'id'</span>]
|
|
|
|
<span class="code-comment"># Format joke with rating info</span>
|
|
rating_text = <span class="code-string">f"👍 {joke['likes']} 👎 {joke['dislikes']}"</span>
|
|
joke_text = <span class="code-string">f"{joke['joke_text']}\n\n{rating_text}"</span>
|
|
|
|
<span class="code-comment"># Add inline buttons for quick rating</span>
|
|
keyboard = [
|
|
[
|
|
InlineKeyboardButton(<span class="code-string">"👍 Like"</span>, callback_data=<span class="code-string">f"like_{joke['id']}"</span>),
|
|
InlineKeyboardButton(<span class="code-string">"👎 Dislike"</span>, callback_data=<span class="code-string">f"dislike_{joke['id']}"</span>)
|
|
]
|
|
]
|
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
|
|
|
<span class="code-keyword">await</span> update.message.reply_text(joke_text, reply_markup=reply_markup)
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">add_joke_command</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Handle /addjoke command"""</span>
|
|
<span class="code-keyword">if</span> <span class="code-keyword">not</span> context.args:
|
|
<span class="code-keyword">await</span> update.message.reply_text(
|
|
<span class="code-string">"Please provide a joke after the command:\n"</span>
|
|
<span class="code-string">"Example: /addjoke Why did the chicken cross the road?"</span>
|
|
)
|
|
<span class="code-keyword">return</span>
|
|
|
|
joke_text = <span class="code-string">" "</span>.join(context.args)
|
|
user = update.effective_user
|
|
|
|
<span class="code-comment"># Add to database</span>
|
|
joke_id = db.add_joke(joke_text, user.id, user.username)
|
|
|
|
<span class="code-keyword">await</span> update.message.reply_text(
|
|
<span class="code-string">f"✅ Joke added successfully! (ID: {joke_id})\n"</span>
|
|
<span class="code-string">"Others can now rate it with /like and /dislike"</span>
|
|
)</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>New Bot Features:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🤖</div>
|
|
<div><strong>Inline Buttons:</strong> Quick like/dislike without commands</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">💾</div>
|
|
<div><strong>User Context:</strong> Remember last joke for rating</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<span class="feature-number">📊</span></div>
|
|
<div><strong>Real Stats:</strong> Display actual like/dislike counts</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">👤</div>
|
|
<div><strong>User Tracking:</strong> Record who submitted each joke</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 8: Step 5 - Rating Commands -->
|
|
<div class="slide" id="slide8">
|
|
<h2>Step 5: Rating Commands (10 minutes)</h2>
|
|
<p class="lead">Add like/dislike commands and callback handlers</p>
|
|
|
|
<div class="code-block"><span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">like_joke</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Handle /like command"""</span>
|
|
user_id = update.effective_user.id
|
|
|
|
<span class="code-comment"># Get last joke from context or arguments</span>
|
|
<span class="code-keyword">if</span> context.args <span class="code-keyword">and</span> context.args[<span class="code-number">0</span>].isdigit():
|
|
joke_id = int(context.args[<span class="code-number">0</span>])
|
|
<span class="code-keyword">elif</span> <span class="code-string">'last_joke_id'</span> <span class="code-keyword">in</span> context.user_data:
|
|
joke_id = context.user_data[<span class="code-string">'last_joke_id'</span>]
|
|
<span class="code-keyword">else</span>:
|
|
<span class="code-keyword">await</span> update.message.reply_text(
|
|
<span class="code-string">"Please view a joke first with /joke, or specify a joke ID: /like 123"</span>
|
|
)
|
|
<span class="code-keyword">return</span>
|
|
|
|
<span class="code-comment"># Rate the joke</span>
|
|
result = db.rate_joke(user_id, joke_id, <span class="code-number">1</span>)
|
|
joke = db.get_joke_by_id(joke_id)
|
|
|
|
<span class="code-keyword">if</span> joke:
|
|
<span class="code-keyword">await</span> update.message.reply_text(
|
|
<span class="code-string">f"👍 Liked joke #{joke_id}!\n"</span>
|
|
<span class="code-string">f"Current rating: {result['likes']} 👍 {result['dislikes']} 👎"</span>
|
|
)
|
|
<span class="code-keyword">else</span>:
|
|
<span class="code-keyword">await</span> update.message.reply_text(<span class="code-string">"Joke not found!"</span>)
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">dislike_joke</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Handle /dislike command"""</span>
|
|
user_id = update.effective_user.id
|
|
|
|
<span class="code-keyword">if</span> context.args <span class="code-keyword">and</span> context.args[<span class="code-number">0</span>].isdigit():
|
|
joke_id = int(context.args[<span class="code-number">0</span>])
|
|
<span class="code-keyword">elif</span> <span class="code-string">'last_joke_id'</span> <span class="code-keyword">in</span> context.user_data:
|
|
joke_id = context.user_data[<span class="code-string">'last_joke_id'</span>]
|
|
<span class="code-keyword">else</span>:
|
|
<span class="code-keyword">await</span> update.message.reply_text(
|
|
<span class="code-string">"Please view a joke first with /joke, or specify a joke ID: /dislike 123"</span>
|
|
)
|
|
<span class="code-keyword">return</span>
|
|
|
|
result = db.rate_joke(user_id, joke_id, -<span class="code-number">1</span>)
|
|
joke = db.get_joke_by_id(joke_id)
|
|
|
|
<span class="code-keyword">if</span> joke:
|
|
<span class="code-keyword">await</span> update.message.reply_text(
|
|
<span class="code-string">f"👎 Disliked joke #{joke_id}!\n"</span>
|
|
<span class="code-string">f"Current rating: {result['likes']} 👍 {result['dislikes']} 👎"</span>
|
|
)
|
|
<span class="code-keyword">else</span>:
|
|
<span class="code-keyword">await</span> update.message.reply_text(<span class="code-string">"Joke not found!"</span>)
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">button_callback</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Handle inline button clicks"""</span>
|
|
query = update.callback_query
|
|
<span class="code-keyword">await</span> query.answer()
|
|
|
|
data = query.data
|
|
user_id = query.from_user.id
|
|
|
|
<span class="code-keyword">if</span> data.startswith(<span class="code-string">'like_'</span>):
|
|
joke_id = int(data.split(<span class="code-string">'_'</span>)[<span class="code-number">1</span>])
|
|
result = db.rate_joke(user_id, joke_id, <span class="code-number">1</span>)
|
|
<span class="code-keyword">await</span> query.edit_message_text(
|
|
text=query.message.text.split(<span class="code-string">'\n\n'</span>)[<span class="code-number">0</span>] +
|
|
<span class="code-string">f"\n\n👍 {result['likes']} 👎 {result['dislikes']}"</span>,
|
|
reply_markup=query.message.reply_markup
|
|
)
|
|
<span class="code-keyword">elif</span> data.startswith(<span class="code-string">'dislike_'</span>):
|
|
joke_id = int(data.split(<span class="code-string">'_'</span>)[<span class="code-number">1</span>])
|
|
result = db.rate_joke(user_id, joke_id, -<span class="code-number">1</span>)
|
|
<span class="code-keyword">await</span> query.edit_message_text(
|
|
text=query.message.text.split(<span class="code-string">'\n\n'</span>)[<span class="code-number">0</span>] +
|
|
<span class="code-string">f"\n\n👍 {result['likes']} 👎 {result['dislikes']}"</span>,
|
|
reply_markup=query.message.reply_markup
|
|
)</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Rating Features:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">1</div>
|
|
<div><strong>Multiple Methods:</strong> Command or inline button</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">2</div>
|
|
<div><strong>Flexible Targeting:</strong> By ID or last viewed joke</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">3</div>
|
|
<div><strong>Real-time Updates:</strong> Buttons update instantly</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">4</div>
|
|
<div><strong>Feedback:</strong> Users see their vote was counted</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 9: Step 6 - Additional Features -->
|
|
<div class="slide" id="slide9">
|
|
<h2>Step 6: Additional Features (10 minutes)</h2>
|
|
<p class="lead">Add statistics, top jokes, and admin commands</p>
|
|
|
|
<div class="code-block"><span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">top_jokes</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Show top-rated jokes"""</span>
|
|
limit = <span class="code-number">5</span>
|
|
<span class="code-keyword">if</span> context.args <span class="code-keyword">and</span> context.args[<span class="code-number">0</span>].isdigit():
|
|
limit = min(int(context.args[<span class="code-number">0</span>]), <span class="code-number">20</span>) <span class="code-comment"># Max 20 jokes</span>
|
|
|
|
jokes = db.get_top_jokes(limit)
|
|
|
|
<span class="code-keyword">if</span> <span class="code-keyword">not</span> jokes:
|
|
<span class="code-keyword">await</span> update.message.reply_text(<span class="code-string">"No jokes in database yet!"</span>)
|
|
<span class="code-keyword">return</span>
|
|
|
|
response = <span class="code-string">f"🏆 TOP {len(jokes)} JOKES 🏆\n\n"</span>
|
|
|
|
<span class="code-keyword">for</span> i, joke <span class="code-keyword">in</span> enumerate(jokes, <span class="code-number">1</span>):
|
|
score = joke[<span class="code-string">'likes'</span>] - joke[<span class="code-string">'dislikes'</span>]
|
|
response += <span class="code-string">f"{i}. ID {joke['id']}: Score {score} (👍{joke['likes']} 👎{joke['dislikes']})\n"</span>
|
|
response += <span class="code-string">f" \"{joke['joke_text'][:50]}...\"\n\n"</span>
|
|
|
|
<span class="code-keyword">await</span> update.message.reply_text(response)
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">bot_stats</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Show bot statistics"""</span>
|
|
total_jokes = db.get_total_jokes()
|
|
|
|
<span class="code-comment"># Get total likes/dislikes</span>
|
|
conn = db.get_connection()
|
|
cursor = conn.cursor()
|
|
cursor.execute(<span class="code-string">"SELECT SUM(likes), SUM(dislikes) FROM jokes"</span>)
|
|
result = cursor.fetchone()
|
|
total_likes = result[<span class="code-number">0</span>] <span class="code-keyword">or</span> <span class="code-number">0</span>
|
|
total_dislikes = result[<span class="code-number">1</span>] <span class="code-keyword">or</span> <span class="code-number">0</span>
|
|
conn.close()
|
|
|
|
stats_text = <span class="code-string">f"""📊 JOKE BOT STATISTICS 📊
|
|
|
|
Total Jokes: {total_jokes}
|
|
Total Ratings: {total_likes + total_dislikes}
|
|
👍 Total Likes: {total_likes}
|
|
👎 Total Dislikes: {total_dislikes}
|
|
📈 Approval Rate: {(total_likes/(total_likes+total_dislikes)*100 if (total_likes+total_dislikes) > 0 else 0):.1f}%
|
|
|
|
Commands Today:
|
|
/joke - Get random joke
|
|
/addjoke - Submit your joke
|
|
/top - See top jokes
|
|
/stats - View these stats"""</span>
|
|
|
|
<span class="code-keyword">await</span> update.message.reply_text(stats_text)
|
|
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">my_jokes</span>(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Show user's submitted jokes"""</span>
|
|
user_id = update.effective_user.id
|
|
|
|
conn = db.get_connection()
|
|
cursor = conn.cursor()
|
|
cursor.execute(<span class="code-string">'''SELECT id, joke_text, likes, dislikes
|
|
FROM jokes WHERE user_id = ?
|
|
ORDER BY created_at DESC LIMIT 10'''</span>, (user_id,))
|
|
|
|
user_jokes = [dict(row) <span class="code-keyword">for</span> row <span class="code-keyword">in</span> cursor.fetchall()]
|
|
conn.close()
|
|
|
|
<span class="code-keyword">if</span> <span class="code-keyword">not</span> user_jokes:
|
|
<span class="code-keyword">await</span> update.message.reply_text(<span class="code-string">"You haven't submitted any jokes yet! Use /addjoke"</span>)
|
|
<span class="code-keyword">return</span>
|
|
|
|
response = <span class="code-string">f"📝 YOUR JOKES ({len(user_jokes)} total)\n\n"</span>
|
|
|
|
<span class="code-keyword">for</span> joke <span class="code-keyword">in</span> user_jokes:
|
|
score = joke[<span class="code-string">'likes'</span>] - joke[<span class="code-string">'dislikes'</span>]
|
|
response += <span class="code-string">f"ID {joke['id']}: Score {score} (👍{joke['likes']} 👎{joke['dislikes']})\n"</span>
|
|
response += <span class="code-string">f" \"{joke['joke_text'][:60]}...\"\n\n"</span>
|
|
|
|
<span class="code-keyword">await</span> update.message.reply_text(response)</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Enhanced Features:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📊</div>
|
|
<div><strong>Statistics:</strong> Track bot usage and engagement</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🏆</div>
|
|
<div><strong>Leaderboard:</strong> Top jokes ranking system</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">👤</div>
|
|
<div><strong>Personal Stats:</strong> Users see their own contributions</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📈</div>
|
|
<div><strong>Analytics:</strong> Approval rate and engagement metrics</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 10: Step 7 - Complete Main Function -->
|
|
<div class="slide" id="slide10">
|
|
<h2>Step 7: Complete Main Function (5 minutes)</h2>
|
|
<p class="lead">Put it all together and run the upgraded bot</p>
|
|
|
|
<div class="code-block"><span class="code-keyword">def</span> <span class="code-function">main</span>():
|
|
<span class="code-comment"># Using the provided bot token</span>
|
|
BOT_TOKEN = <span class="code-string">"YOUR_BOT_TOKEN_HERE"</span>
|
|
|
|
<span class="code-comment"># Create application</span>
|
|
app = Application.builder().token(BOT_TOKEN).build()
|
|
|
|
<span class="code-comment"># Add command handlers</span>
|
|
app.add_handler(CommandHandler(<span class="code-string">"start"</span>, start))
|
|
app.add_handler(CommandHandler(<span class="code-string">"joke"</span>, send_joke))
|
|
app.add_handler(CommandHandler(<span class="code-string">"addjoke"</span>, add_joke_command))
|
|
app.add_handler(CommandHandler(<span class="code-string">"like"</span>, like_joke))
|
|
app.add_handler(CommandHandler(<span class="code-string">"dislike"</span>, dislike_joke))
|
|
app.add_handler(CommandHandler(<span class="code-string">"top"</span>, top_jokes))
|
|
app.add_handler(CommandHandler(<span class="code-string">"stats"</span>, bot_stats))
|
|
app.add_handler(CommandHandler(<span class="code-string">"myjokes"</span>, my_jokes))
|
|
|
|
<span class="code-comment"># Add callback handler for inline buttons</span>
|
|
app.add_handler(CallbackQueryHandler(button_callback))
|
|
|
|
print(<span class="code-string">"🤖 Joke Bot 2.0 is running..."</span>)
|
|
print(<span class="code-string">"📊 Database initialized: joke_bot.db"</span>)
|
|
print(<span class="code-string">"🎯 Features: User submissions, ratings, leaderboard"</span>)
|
|
print(<span class="code-string">"Press Ctrl+C to stop.\n"</span>)
|
|
|
|
app.run_polling()
|
|
|
|
<span class="code-keyword">if</span> __name__ == <span class="code-string">"__main__"</span>:
|
|
main()</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Project Structure:</h3>
|
|
<div class="code-block" style="margin: 10px 0; padding: 15px; background: #2c3e50; font-size: 0.9rem;">
|
|
joke_bot_2.0/
|
|
├── app.py <span class="code-comment"># Main bot file (updated)</span>
|
|
├── database.py <span class="code-comment"># SQLite database class</span>
|
|
├── joke_bot.db <span class="code-comment"># SQLite database (auto-created)</span>
|
|
├── requirements.txt <span class="code-comment"># python-telegram-bot==20.3</span>
|
|
└── README.md <span class="code-comment"># Setup instructions</span></div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">1</div>
|
|
<div><strong>Install:</strong> <code>pip install python-telegram-bot==20.3</code></div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">2</div>
|
|
<div><strong>Run:</strong> <code>python app.py</code></div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">3</div>
|
|
<div><strong>Test:</strong> Open Telegram, find your bot, type <code>/start</code></div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">4</div>
|
|
<div><strong>Deploy:</strong> Can run on Raspberry Pi, VPS, or cloud</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 11: Database Administration -->
|
|
<div class="slide" id="slide11">
|
|
<h2>Database Administration Tools</h2>
|
|
<p class="lead">Tools to view and manage your SQLite database</p>
|
|
|
|
<div class="code-block"><span class="code-comment"># admin_tools.py - Database management utilities</span>
|
|
|
|
<span class="code-keyword">import</span> sqlite3
|
|
<span class="code-keyword">import</span> pandas <span class="code-keyword">as</span> pd
|
|
<span class="code-keyword">from</span> datetime <span class="code-keyword">import</span> datetime
|
|
|
|
<span class="code-keyword">class</span> <span class="code-builtin">JokeAdmin</span>:
|
|
<span class="code-keyword">def</span> <span class="code-function">__init__</span>(<span class="code-keyword">self</span>, db_name=<span class="code-string">'joke_bot.db'</span>):
|
|
<span class="code-keyword">self</span>.db_name = db_name
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">export_to_csv</span>(<span class="code-keyword">self</span>, filename=<span class="code-string">'jokes_export.csv'</span>):
|
|
<span class="code-comment">"""Export all jokes to CSV"""</span>
|
|
conn = sqlite3.connect(<span class="code-keyword">self</span>.db_name)
|
|
df = pd.read_sql_query(<span class="code-string">"SELECT * FROM jokes"</span>, conn)
|
|
df.to_csv(filename, index=False, encoding=<span class="code-string">'utf-8'</span>)
|
|
conn.close()
|
|
print(<span class="code-string">f"Exported {len(df)} jokes to {filename}"</span>)
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">show_stats</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Show detailed statistics"""</span>
|
|
conn = sqlite3.connect(<span class="code-keyword">self</span>.db_name)
|
|
cursor = conn.cursor()
|
|
|
|
<span class="code-comment"># Basic counts</span>
|
|
cursor.execute(<span class="code-string">"SELECT COUNT(*) FROM jokes"</span>)
|
|
total_jokes = cursor.fetchone()[<span class="code-number">0</span>]
|
|
|
|
cursor.execute(<span class="code-string">"SELECT COUNT(DISTINCT user_id) FROM jokes WHERE user_id != 0"</span>)
|
|
unique_users = cursor.fetchone()[<span class="code-number">0</span>]
|
|
|
|
cursor.execute(<span class="code-string">"SELECT SUM(likes), SUM(dislikes) FROM jokes"</span>)
|
|
likes, dislikes = cursor.fetchone()
|
|
|
|
<span class="code-comment"># Top contributors</span>
|
|
cursor.execute(<span class="code-string">'''SELECT username, COUNT(*) as joke_count
|
|
FROM jokes WHERE user_id != 0
|
|
GROUP BY user_id ORDER BY joke_count DESC LIMIT 5'''</span>)
|
|
top_contributors = cursor.fetchall()
|
|
|
|
conn.close()
|
|
|
|
print(<span class="code-string">f"""📊 DATABASE STATISTICS 📊
|
|
|
|
Total Jokes: {total_jokes}
|
|
Unique Contributors: {unique_users}
|
|
Total Likes: {likes or 0}
|
|
Total Dislikes: {dislikes or 0}
|
|
|
|
🏆 Top Contributors:"""</span>)
|
|
|
|
<span class="code-keyword">for</span> username, count <span class="code-keyword">in</span> top_contributors:
|
|
print(<span class="code-string">f" {username}: {count} jokes"</span>)
|
|
|
|
<span class="code-keyword">def</span> <span class="code-function">backup_database</span>(<span class="code-keyword">self</span>):
|
|
<span class="code-comment">"""Create a backup of the database"""</span>
|
|
timestamp = datetime.now().strftime(<span class="code-string">'%Y%m%d_%H%M%S'</span>)
|
|
backup_name = <span class="code-string">f"backup_joke_bot_{timestamp}.db"</span>
|
|
|
|
<span class="code-keyword">import</span> shutil
|
|
shutil.copy2(<span class="code-keyword">self</span>.db_name, backup_name)
|
|
print(<span class="code-string">f"Backup created: {backup_name}"</span>)
|
|
|
|
<span class="code-comment"># Usage example</span>
|
|
<span class="code-keyword">if</span> __name__ == <span class="code-string">"__main__"</span>:
|
|
admin = JokeAdmin()
|
|
admin.show_stats()
|
|
admin.export_to_csv()
|
|
admin.backup_database()</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Admin Features:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">💾</div>
|
|
<div><strong>Backups:</strong> Automatic database backups</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📊</div>
|
|
<div><strong>Analytics:</strong> Detailed usage statistics</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📤</div>
|
|
<div><strong>Export:</strong> CSV export for analysis</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">👑</div>
|
|
<div><strong>Leaderboards:</strong> Top contributors recognition</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="database-concept">
|
|
<span class="concept-title">SQLite Browser Tool:</span>
|
|
<p>Download <strong>DB Browser for SQLite</strong> (free) to visually explore your database:</p>
|
|
<p>• View tables and data</p>
|
|
<p>• Run SQL queries</p>
|
|
<p>• Export/import data</p>
|
|
<p>• Available for Windows, Mac, Linux</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 12: Advanced Features -->
|
|
<div class="slide" id="slide12">
|
|
<h2>Advanced Features (Optional)</h2>
|
|
<p class="lead">Take your joke bot to the next level</p>
|
|
|
|
<div class="code-block"><span class="code-comment"># advanced_features.py - Optional enhancements</span>
|
|
|
|
<span class="code-comment"># 1. Joke Categories</span>
|
|
<span class="code-keyword">def</span> <span class="code-function">add_categories</span>():
|
|
<span class="code-comment">"""Add category support to jokes"""</span>
|
|
conn = sqlite3.connect(<span class="code-string">'joke_bot.db'</span>)
|
|
cursor = conn.cursor()
|
|
|
|
<span class="code-comment"># Add category column</span>
|
|
<span class="code-keyword">try</span>:
|
|
cursor.execute(<span class="code-string">"ALTER TABLE jokes ADD COLUMN category TEXT DEFAULT 'general'"</span>)
|
|
<span class="code-keyword">except</span> sqlite3.OperationalError:
|
|
pass <span class="code-comment"># Column already exists</span>
|
|
|
|
<span class="code-comment"># Create categories table</span>
|
|
cursor.execute(<span class="code-string">'''CREATE TABLE IF NOT EXISTS categories (
|
|
id INTEGER PRIMARY KEY,
|
|
name TEXT UNIQUE,
|
|
description TEXT
|
|
)'''</span>)
|
|
|
|
<span class="code-comment"># Insert default categories</span>
|
|
categories = [
|
|
(<span class="code-string">'programming'</span>, <span class="code-string">'Programming and tech jokes'</span>),
|
|
(<span class="code-string">'dad'</span>, <span class="code-string">'Classic dad jokes'</span>),
|
|
(<span class="code-string">'animal'</span>, <span class="code-string">'Animal jokes'</span>),
|
|
(<span class="code-string">'school'</span>, <span class="code-string">'School and education jokes'</span>)
|
|
]
|
|
|
|
cursor.executemany(<span class="code-string">'''INSERT OR IGNORE INTO categories (name, description)
|
|
VALUES (?, ?)'''</span>, categories)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
<span class="code-comment"># 2. Daily Joke Notification</span>
|
|
<span class="code-keyword">async</span> <span class="code-keyword">def</span> <span class="code-function">send_daily_joke</span>(context: ContextTypes.DEFAULT_TYPE):
|
|
<span class="code-comment">"""Send daily joke to subscribed users"""</span>
|
|
joke = db.get_random_joke()
|
|
<span class="code-keyword">if</span> joke:
|
|
<span class="code-comment"># Get subscribed users from database</span>
|
|
conn = db.get_connection()
|
|
cursor = conn.cursor()
|
|
cursor.execute(<span class="code-string">"SELECT user_id FROM subscribers WHERE active = 1"</span>)
|
|
users = cursor.fetchall()
|
|
conn.close()
|
|
|
|
<span class="code-keyword">for</span> user_id <span class="code-keyword">in</span> users:
|
|
<span class="code-keyword">try</span>:
|
|
<span class="code-keyword">await</span> context.bot.send_message(
|
|
chat_id=user_id[<span class="code-number">0</span>],
|
|
text=<span class="code-string">f"📅 Daily Joke!\n\n{joke['joke_text']}"</span>
|
|
)
|
|
<span class="code-keyword">except</span> Exception <span class="code-keyword">as</span> e:
|
|
print(<span class="code-string">f"Failed to send to user {user_id}: {e}"</span>)
|
|
|
|
<span class="code-comment"># 3. Joke Search Function</span>
|
|
<span class="code-keyword">def</span> <span class="code-function">search_jokes</span>(search_term, limit=<span class="code-number">10</span>):
|
|
<span class="code-comment">"""Search jokes by keyword"""</span>
|
|
conn = sqlite3.connect(<span class="code-string">'joke_bot.db'</span>)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(<span class="code-string">'''SELECT * FROM jokes
|
|
WHERE joke_text LIKE ?
|
|
ORDER BY (likes - dislikes) DESC
|
|
LIMIT ?'''</span>, (<span class="code-string">f'%{search_term}%'</span>, limit))
|
|
|
|
jokes = [dict(row) <span class="code-keyword">for</span> row <span class="code-keyword">in</span> cursor.fetchall()]
|
|
conn.close()
|
|
<span class="code-keyword">return</span> jokes</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Optional Enhancements:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🏷️</div>
|
|
<div><strong>Categories:</strong> Organize jokes by type</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🔔</div>
|
|
<div><strong>Notifications:</strong> Daily joke subscriptions</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🔍</div>
|
|
<div><strong>Search:</strong> Find jokes by keyword</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🤖</div>
|
|
<div><strong>AI Integration:</strong> Generate jokes with GPT API</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🌐</div>
|
|
<div><strong>Web Dashboard:</strong> View stats in browser</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 13: Testing Your Bot -->
|
|
<div class="slide" id="slide13">
|
|
<h2>Testing Your Upgraded Bot</h2>
|
|
<p class="lead">Comprehensive testing guide</p>
|
|
|
|
<div class="database-concept">
|
|
<span class="concept-title">Test Commands Sequence:</span>
|
|
<p>1. <code>/start</code> - See welcome message with all commands</p>
|
|
<p>2. <code>/joke</code> - Get random joke with rating buttons</p>
|
|
<p>3. Click 👍 and 👎 buttons - Test inline rating</p>
|
|
<p>4. <code>/addjoke Why did the Python cross the road? To get to the other side!</code></p>
|
|
<p>5. <code>/joke</code> again - See if new joke appears</p>
|
|
<p>6. <code>/like</code> and <code>/dislike</code> - Test command rating</p>
|
|
<p>7. <code>/top 3</code> - See top jokes leaderboard</p>
|
|
<p>8. <code>/stats</code> - Check bot statistics</p>
|
|
<p>9. <code>/myjokes</code> - View your submissions</p>
|
|
</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Expected Results:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Database File:</strong> <code>joke_bot.db</code> created automatically</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Default Jokes:</strong> 3 jokes pre-loaded</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Rating Persistence:</strong> Votes saved between restarts</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>User Tracking:</strong> Your username saved with submissions</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Weighted Random:</strong> Better jokes appear more often</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="code-block" style="margin-top: 20px; padding: 15px; background: #e8f5e8; color: #2c3e50;">
|
|
<span class="code-comment"># Quick database check</span>
|
|
<span class="code-keyword">import</span> sqlite3
|
|
conn = sqlite3.connect(<span class="code-string">'joke_bot.db'</span>)
|
|
cursor = conn.cursor()
|
|
cursor.execute(<span class="code-string">"SELECT name FROM sqlite_master WHERE type='table'"</span>)
|
|
tables = cursor.fetchall()
|
|
print(<span class="code-string">"Tables:"</span>, tables)
|
|
cursor.execute(<span class="code-string">"SELECT COUNT(*) FROM jokes"</span>)
|
|
count = cursor.fetchone()[<span class="code-number">0</span>]
|
|
print(<span class="code-string">f"Total jokes: {count}"</span>)
|
|
conn.close()</div>
|
|
</div>
|
|
|
|
<!-- Slide 14: Deployment Options -->
|
|
<div class="slide" id="slide14">
|
|
<h2>Deployment Options</h2>
|
|
<p class="lead">Where to run your upgraded joke bot 24/7</p>
|
|
|
|
<div class="feature-box">
|
|
<h3>Free Options:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🖥️</div>
|
|
<div>
|
|
<strong>Raspberry Pi:</strong><br>
|
|
• Always-on at home<br>
|
|
• Low power consumption<br>
|
|
• Full control
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">☁️</div>
|
|
<div>
|
|
<strong>PythonAnywhere:</strong><br>
|
|
• Free tier available<br>
|
|
• Web-based IDE<br>
|
|
• Scheduled tasks
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">🚀</div>
|
|
<div>
|
|
<strong>Replit:</strong><br>
|
|
• Free cloud IDE<br>
|
|
• Always-on option<br>
|
|
• Easy sharing
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Paid Options:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">💻</div>
|
|
<div>
|
|
<strong>VPS (DigitalOcean, Linode):</strong><br>
|
|
• $5/month<br>
|
|
• Full root access<br>
|
|
• Scalable
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">⚡</div>
|
|
<div>
|
|
<strong>Heroku:</strong><br>
|
|
• Easy deployment<br>
|
|
• Add-ons available<br>
|
|
• Good free tier (with limits)
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-item">
|
|
<div class="feature-number">🐳</div>
|
|
<div>
|
|
<strong>Docker:</strong><br>
|
|
• Containerized deployment<br>
|
|
• Runs anywhere<br>
|
|
• Easy updates
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="database-concept">
|
|
<span class="concept-title">Deployment Checklist:</span>
|
|
<p>1. Test locally first</p>
|
|
<p>2. Backup your database</p>
|
|
<p>3. Set up proper logging</p>
|
|
<p>4. Configure automatic restarts (using systemd or supervisor)</p>
|
|
<p>5. Set up regular database backups</p>
|
|
<p>6. Monitor bot activity</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Slide 15: Complete Project Review -->
|
|
<div class="slide" id="slide15">
|
|
<h2>Complete Project Review</h2>
|
|
<p class="lead">What you've accomplished in 60 minutes</p>
|
|
|
|
<div class="database-concept" style="background: #e8f5e8; border-color: #2ecc71;">
|
|
<span class="concept-title">🎉 CONGRATULATIONS! 🎉</span>
|
|
<p>You've successfully upgraded your simple joke bot to a full-featured application with:</p>
|
|
</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Core Upgrades:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>SQLite Database:</strong> Persistent storage for jokes</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>User Submissions:</strong> Community-driven content</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Rating System:</strong> Like/dislike with tracking</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Inline Buttons:</strong> Better user experience</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Statistics:</strong> Track bot usage and engagement</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">✅</div>
|
|
<div><strong>Leaderboard:</strong> Top jokes ranking</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="feature-box">
|
|
<h3>Technical Skills Learned:</h3>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🐍</div>
|
|
<div><strong>Python:</strong> Advanced Python programming</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🗄️</div>
|
|
<div><strong>SQLite:</strong> Database design and queries</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🤖</div>
|
|
<div><strong>Telegram Bot API:</strong> Advanced bot features</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">📊</div>
|
|
<div><strong>Data Modeling:</strong> Table design and relationships</div>
|
|
</div>
|
|
<div class="feature-item">
|
|
<div class="feature-number">🔧</div>
|
|
<div><strong>Software Architecture:</strong> Modular, maintainable code</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="database-concept">
|
|
<span class="concept-title">Next Steps:</span>
|
|
<p>1. Add joke categories (/programming, /dadjokes)</p>
|
|
<p>2. Implement daily joke notifications</p>
|
|
<p>3. Create a web dashboard for statistics</p>
|
|
<p>4. Add joke search functionality</p>
|
|
<p>5. Implement user profiles and achievements</p>
|
|
<p>6. Add multi-language support</p>
|
|
</div>
|
|
|
|
<div style="text-align: center; margin-top: 30px; padding: 20px; background: #3498db; color: white; border-radius: 10px;">
|
|
<h3 style="color: white;">Your Joke Bot is Now Production-Ready! 🚀</h3>
|
|
<p>Share it with friends, deploy it online, and watch your joke collection grow!</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navigation -->
|
|
<div class="navigation">
|
|
<button class="nav-btn" id="prev-btn" onclick="prevSlide()" disabled="">← Back</button>
|
|
|
|
<div class="slide-counter">
|
|
<span id="current-slide">1</span> of <span id="total-slides">15</span>
|
|
</div>
|
|
|
|
<button class="nav-btn" id="next-btn" onclick="nextSlide()">Next →</button>
|
|
</div>
|
|
|
|
|
|
<script>
|
|
let currentSlide = 1;
|
|
const totalSlides = 15;
|
|
|
|
function showSlide(slideNumber) {
|
|
document.querySelectorAll('.slide').forEach(slide => {
|
|
slide.classList.remove('active');
|
|
});
|
|
|
|
document.getElementById(`slide${slideNumber}`).classList.add('active');
|
|
document.getElementById(`slide${slideNumber}`).scrollTop = 0;
|
|
|
|
document.getElementById('current-slide').textContent = slideNumber;
|
|
|
|
document.getElementById('prev-btn').disabled = slideNumber === 1;
|
|
document.getElementById('next-btn').disabled = slideNumber === totalSlides;
|
|
|
|
if (slideNumber === totalSlides) {
|
|
document.getElementById('next-btn').textContent = 'Finish';
|
|
} else {
|
|
document.getElementById('next-btn').textContent = 'Next →';
|
|
}
|
|
}
|
|
|
|
function nextSlide() {
|
|
if (currentSlide < totalSlides) {
|
|
currentSlide++;
|
|
showSlide(currentSlide);
|
|
}
|
|
}
|
|
|
|
function prevSlide() {
|
|
if (currentSlide > 1) {
|
|
currentSlide--;
|
|
showSlide(currentSlide);
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
showSlide(1);
|
|
|
|
// Keyboard navigation
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') {
|
|
nextSlide();
|
|
} else if (e.key === 'ArrowLeft') {
|
|
prevSlide();
|
|
}
|
|
});
|
|
|
|
// Touch swipe for mobile
|
|
let touchStartY = 0;
|
|
let touchEndY = 0;
|
|
|
|
document.addEventListener('touchstart', (e) => {
|
|
touchStartY = e.changedTouches[0].screenY;
|
|
});
|
|
|
|
document.addEventListener('touchend', (e) => {
|
|
touchEndY = e.changedTouches[0].screenY;
|
|
const swipeThreshold = 100;
|
|
|
|
if (touchEndY < touchStartY - swipeThreshold) {
|
|
nextSlide();
|
|
}
|
|
|
|
if (touchEndY > touchStartY + swipeThreshold) {
|
|
prevSlide();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
</body></html> |