aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorclarkzjw <[email protected]>2023-02-20 13:49:51 -0800
committerclarkzjw <[email protected]>2023-02-20 13:49:51 -0800
commitf927324709d639adfcd5487c7740e3d60f2246b8 (patch)
tree125b6ec833af57b2ec1813309506dbe6b791b35f
parenta099e6cf153ae7155957bb16bc56233d2420288c (diff)
parentf7f99f41e768e6740adbf2ee708488b29fe6265a (diff)
downloadswarm2fediverse-f927324709d639adfcd5487c7740e3d60f2246b8.tar.gz
basic features
-rw-r--r--.gitignore4
-rw-r--r--Dockerfile1
-rw-r--r--README10
-rw-r--r--bot.py49
-rw-r--r--config.ini.example9
-rw-r--r--config.py13
-rw-r--r--dbstore/dbm_store.py34
-rw-r--r--foursquare/query_poi.py33
-rw-r--r--mastodon/__init__.py0
-rw-r--r--mastodon/toot.py21
-rw-r--r--toot.py11
11 files changed, 147 insertions, 38 deletions
diff --git a/.gitignore b/.gitignore
index 68bc17f..eb701d6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
1foursquare/location_example.json
2config.ini
3.idea/
4fsq_poi.db
1# Byte-compiled / optimized / DLL files 5# Byte-compiled / optimized / DLL files
2__pycache__/ 6__pycache__/
3*.py[cod] 7*.py[cod]
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..2d13273
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1 @@
FROM python:3
diff --git a/README b/README
index 6c8ffd4..9b00713 100644
--- a/README
+++ b/README
@@ -1,3 +1,13 @@
1# Swarm2Fediverse 1# Swarm2Fediverse
2 2
3Foursquare Swarm like Telegram bot to checkin at places and post to Fediverse (Mastodon/Pleroma) 3Foursquare Swarm like Telegram bot to checkin at places and post to Fediverse (Mastodon/Pleroma)
4
5TODO:
6
7- Attach up to four images to each checkin
8- Set default checkin visibility to followers-only and add option to set individual checkin visibility
9- Amazon .bot domain
10- i18n
11- Anonymized analysis
12- OAuth login to Mastodon/Pleroma
13- Delayed checkins
diff --git a/bot.py b/bot.py
index 517f810..398e433 100644
--- a/bot.py
+++ b/bot.py
@@ -8,6 +8,7 @@ Basic example for a bot that uses inline keyboards. For an in-depth explanation,
8""" 8"""
9import logging 9import logging
10 10
11import telegram.constants
11from telegram import __version__ as TG_VER 12from telegram import __version__ as TG_VER
12 13
13try: 14try:
@@ -22,7 +23,12 @@ if __version_info__ < (20, 0, 0, "alpha", 1):
22 f"visit https://docs.python-telegram-bot.org/en/v{TG_VER}/examples.html" 23 f"visit https://docs.python-telegram-bot.org/en/v{TG_VER}/examples.html"
23 ) 24 )
24from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update 25from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
25from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes 26from telegram.ext import Application, CallbackQueryHandler, CommandHandler, ContextTypes, MessageHandler, filters
27
28from config import BOT_TOKEN
29from foursquare.query_poi import query_poi
30from dbstore.dbm_store import get_loc
31from toot import mastodon_client
26 32
27# Enable logging 33# Enable logging
28logging.basicConfig( 34logging.basicConfig(
@@ -32,29 +38,39 @@ logger = logging.getLogger(__name__)
32 38
33 39
34async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 40async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
35 """Sends a message with three inline buttons attached.""" 41 hello = "Hello, this is `checkin.bot`. \n\n" \
36 keyboard = [ 42 "This is a Telegram bot with functionality similar to Foursquare Swarm, " \
37 [ 43 "but check in and post your location to the Fediverse (Mastodon/Pleroma) instead of Twitter.\n\n" \
38 InlineKeyboardButton("Option 1", callback_data="1"), 44 "Aware of privacy concerns, this bot will not store your location data."\
39 InlineKeyboardButton("Option 2", callback_data="2"), 45 "*Be safe and cautious when sharing your real time location on the web.* \n\n"\
40 ], 46 "Start using this bot by sharing your location using Telegram context menu to it."
41 [InlineKeyboardButton("Option 3", callback_data="3")],
42 ]
43 47
44 reply_markup = InlineKeyboardMarkup(keyboard) 48 await update.message.reply_text(hello, parse_mode=telegram.constants.ParseMode.MARKDOWN)
45 49
46 await update.message.reply_text("Please choose:", reply_markup=reply_markup) 50
51async def checkin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
52 keyboard = []
53
54 for poi in query_poi(update.message.location.latitude, update.message.location.longitude):
55 keyboard.append([
56 InlineKeyboardButton(poi["name"], callback_data=poi["fsq_id"]),
57 ])
58
59 reply_markup = InlineKeyboardMarkup(keyboard)
60 await update.message.reply_text("Select a place", reply_markup=reply_markup)
47 61
48 62
49async def button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 63async def button(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
50 """Parses the CallbackQuery and updates the message text.""" 64 """Parses the CallbackQuery and updates the message text."""
51 query = update.callback_query 65 query = update.callback_query
52 66
53 # CallbackQueries need to be answered, even if no notification to the user is needed
54 # Some clients may have trouble otherwise. See https://core.telegram.org/bots/api#callbackquery
55 await query.answer() 67 await query.answer()
68 poi = get_loc(query.data)
69
70 mastodon_client.status_post(f"I'm at {poi['name']} in {poi['locality']}, {poi['region']}, \n[OSM]({poi['osm_url']})",
71 visibility="private")
56 72
57 await query.edit_message_text(text=f"Selected option: {query.data}") 73 await query.edit_message_text(text=f"Selected option: {poi}")
58 74
59 75
60async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: 76async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
@@ -63,12 +79,11 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
63 79
64 80
65def main() -> None: 81def main() -> None:
66 """Run the bot.""" 82 application = Application.builder().token(BOT_TOKEN).build()
67 # Create the Application and pass it your bot's token.
68 application = Application.builder().token("TOKEN").build()
69 83
70 application.add_handler(CommandHandler("start", start)) 84 application.add_handler(CommandHandler("start", start))
71 application.add_handler(CallbackQueryHandler(button)) 85 application.add_handler(CallbackQueryHandler(button))
86 application.add_handler(MessageHandler(filters.LOCATION & ~filters.COMMAND, checkin))
72 application.add_handler(CommandHandler("help", help_command)) 87 application.add_handler(CommandHandler("help", help_command))
73 88
74 # Run the bot until the user presses Ctrl-C 89 # Run the bot until the user presses Ctrl-C
diff --git a/config.ini.example b/config.ini.example
new file mode 100644
index 0000000..4c29644
--- /dev/null
+++ b/config.ini.example
@@ -0,0 +1,9 @@
1[DEFAULT]
2BOT_TOKEN =
3FOURSQUARE_API_KEY =
4
5[TOOT]
6CLIENT_ID =
7CLIENT_SECRET =
8API_BASE_URL = https://mastodon.social
9ACCESS_TOKEN =
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..a403db0
--- /dev/null
+++ b/config.py
@@ -0,0 +1,13 @@
1# https://docs.python.org/3/library/configparser.html
2
3import configparser
4config = configparser.ConfigParser()
5config.read("config.ini")
6
7BOT_TOKEN = config["DEFAULT"]["BOT_TOKEN"]
8FSQ_API_KEY = config["DEFAULT"]["FOURSQUARE_API_KEY"]
9
10TOOT_API_BASE_URL = config["TOOT"]["API_BASE_URL"]
11TOOT_CLIENT_ID = config["TOOT"]["CLIENT_ID"]
12TOOT_CLIENT_SECRET = config["TOOT"]["CLIENT_SECRET"]
13TOOT_ACCESS_TOKEN = config["TOOT"]["ACCESS_TOKEN"]
diff --git a/dbstore/dbm_store.py b/dbstore/dbm_store.py
new file mode 100644
index 0000000..fedb505
--- /dev/null
+++ b/dbstore/dbm_store.py
@@ -0,0 +1,34 @@
1import dbm
2
3db = None
4store_file = "fsq_poi.db"
5
6
7def get_loc(fsq_id):
8 global db
9 if db is None:
10 db = dbm.open(store_file, 'c')
11 if fsq_id in db:
12 res = db[fsq_id].decode("utf-8").split("|")
13 return {
14 "name": res[0],
15 "locality": res[1],
16 "region": res[2],
17 "latitude": res[3],
18 "longitude": res[4],
19 "osm_url": res[5],
20 }
21 else:
22 return None
23
24
25def store_loc(loc):
26 global db
27 if db is None:
28 db = dbm.open(store_file, 'c')
29 db[loc["fsq_id"]] = "{}|{}|{}|{}|{}|{}".format(loc["name"],
30 loc["locality"],
31 loc["region"],
32 loc["latitude"],
33 loc["longitude"],
34 loc["osm_url"])
diff --git a/foursquare/query_poi.py b/foursquare/query_poi.py
new file mode 100644
index 0000000..efdd1b1
--- /dev/null
+++ b/foursquare/query_poi.py
@@ -0,0 +1,33 @@
1import requests
2import json
3from config import FSQ_API_KEY
4from dbstore.dbm_store import get_loc, store_loc
5
6POI_API_ENDPOINT = "https://api.foursquare.com/v3/places/nearby?ll={}%2C{}"
7OSM_ENDPOINT = "https://www.openstreetmap.org/?mlat={}&mlon={}&zoom=15&layers=M"
8
9def query_poi(latitude, longitude):
10 locations = list()
11
12 url = POI_API_ENDPOINT.format(latitude, longitude)
13 headers = {
14 "accept": "application/json",
15 "Authorization": FSQ_API_KEY
16 }
17
18 response = requests.get(url, headers=headers)
19
20 for poi in json.loads(response.text)["results"]:
21 loc = {
22 "fsq_id": poi["fsq_id"],
23 "name": poi["name"],
24 "locality": poi["location"]["locality"],
25 "region": poi["location"]["region"],
26 "latitude": poi["geocodes"]["main"]["latitude"],
27 "longitude": poi["geocodes"]["main"]["longitude"],
28 "osm_url": OSM_ENDPOINT.format(poi["geocodes"]["main"]["latitude"], poi["geocodes"]["main"]["longitude"])
29 }
30 locations.append(loc)
31 store_loc(loc)
32
33 return locations
diff --git a/mastodon/__init__.py b/mastodon/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/mastodon/__init__.py
+++ /dev/null
diff --git a/mastodon/toot.py b/mastodon/toot.py
deleted file mode 100644
index 2d07f0b..0000000
--- a/mastodon/toot.py
+++ /dev/null
@@ -1,21 +0,0 @@
1from mastodon import Mastodon
2
3'''
4https://mastodonpy.readthedocs.io/en/stable/index.html
5
6Mastodon.create_app(
7 'pytooterapp',
8 api_base_url = 'https://mastodon.social',
9 to_file = 'pytooter_clientcred.secret'
10)
11'''
12
13# mastodon = Mastodon(client_id = 'pytooter_clientcred.secret',)
14# mastodon.log_in(
15# '[email protected]',
16# 'incrediblygoodpassword',
17# to_file = 'pytooter_usercred.secret'
18# )
19
20mastodon = Mastodon(access_token = 'pytooter_usercred.secret')
21mastodon.toot('Tooting from Python using #mastodonpy !') \ No newline at end of file
diff --git a/toot.py b/toot.py
new file mode 100644
index 0000000..f1db858
--- /dev/null
+++ b/toot.py
@@ -0,0 +1,11 @@
1from mastodon import Mastodon
2from config import TOOT_API_BASE_URL, TOOT_CLIENT_SECRET, TOOT_ACCESS_TOKEN, TOOT_CLIENT_ID
3
4'''
5https://mastodonpy.readthedocs.io/en/stable/index.html
6'''
7
8mastodon_client = Mastodon(client_id=TOOT_CLIENT_ID,
9 client_secret=TOOT_CLIENT_SECRET,
10 api_base_url=TOOT_API_BASE_URL,
11 access_token=TOOT_ACCESS_TOKEN)
Powered by cgit v1.2.3 (git 2.41.0)