Compare commits

...
14 Commits
15 changed files with 122 additions and 36 deletions
+30
View File
@@ -0,0 +1,30 @@
FROM docker.io/alpine:3.7
ENV UID=1337 \
GID=1337
COPY . /opt/mautrixtelegram
RUN apk add --no-cache \
python3-dev \
py3-virtualenv \
py3-pillow \
py3-aiohttp \
py3-lxml \
py3-magic \
py3-numpy \
py3-asn1crypto \
py3-sqlalchemy \
build-base \
ffmpeg \
bash \
ca-certificates \
su-exec \
s6 \
&& cd /opt/mautrixtelegram \
&& cp -r docker/root/* / \
&& rm docker -rf \
&& pip3 install -r requirements.txt -r optional-requirements.txt
VOLUME /data
CMD ["/bin/s6-svscan", "/etc/s6.d"]
+2 -2
View File
@@ -3,7 +3,7 @@ A Matrix-Telegram hybrid puppeting/relaybot bridge.
### [Wiki](https://github.com/tulir/mautrix-telegram/wiki) ### [Wiki](https://github.com/tulir/mautrix-telegram/wiki)
### [Features & Roadmap](ROADMAP.md) ### [Features & Roadmap](https://github.com/tulir/mautrix-telegram/blob/master/ROADMAP.md)
## Discussion ## Discussion
Matrix room: [`#telegram:maunium.net`](https://matrix.to/#/#telegram:maunium.net) Matrix room: [`#telegram:maunium.net`](https://matrix.to/#/#telegram:maunium.net)
@@ -11,4 +11,4 @@ Matrix room: [`#telegram:maunium.net`](https://matrix.to/#/#telegram:maunium.net
Telegram chat: [`mautrix_telegram`](https://t.me/mautrix_telegram) (bridged to Matrix room) Telegram chat: [`mautrix_telegram`](https://t.me/mautrix_telegram) (bridged to Matrix room)
## Preview ## Preview
![Preview](preview.png) ![Preview](https://raw.githubusercontent.com/tulir/mautrix-telegram/master/preview.png)
-3
View File
@@ -35,9 +35,6 @@ script_location = alembic
# are written from script.py.mako # are written from script.py.mako
# output_encoding = utf-8 # output_encoding = utf-8
sqlalchemy.url = sqlite:///mautrix-telegram.db
# Logging configuration # Logging configuration
[loggers] [loggers]
keys = root,sqlalchemy,alembic keys = root,sqlalchemy,alembic
+7 -1
View File
@@ -1,4 +1,3 @@
from __future__ import with_statement
from alembic import context from alembic import context
from sqlalchemy import engine_from_config, pool from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig from logging.config import fileConfig
@@ -8,12 +7,19 @@ from os.path import abspath, dirname
sys.path.insert(0, dirname(dirname(abspath(__file__)))) sys.path.insert(0, dirname(dirname(abspath(__file__))))
from mautrix_telegram.base import Base from mautrix_telegram.base import Base
from mautrix_telegram.config import Config
import mautrix_telegram.db import mautrix_telegram.db
# this is the Alembic Config object, which provides # this is the Alembic Config object, which provides
# access to the values within the .ini file in use. # access to the values within the .ini file in use.
config = context.config config = context.config
mxtg_config_path = context.get_x_argument(as_dictionary=True).get("config", "config.yaml")
mxtg_config = Config(mxtg_config_path, None, None)
mxtg_config.load()
config.set_main_option("sqlalchemy.url",
mxtg_config.get("appservice.database", "sqlite:///mautrix-telegram.db"))
# Interpret the config file for Python logging. # Interpret the config file for Python logging.
# This line sets up loggers basically. # This line sets up loggers basically.
fileConfig(config.config_file_name) fileConfig(config.config_file_name)
@@ -19,31 +19,31 @@ depends_on = None
def upgrade(): def upgrade():
Session = op.create_table('telethon_sessions', Session = op.create_table('telethon_sessions',
sa.Column('session_id', sa.VARCHAR(), nullable=False), sa.Column('session_id', sa.String, nullable=False),
sa.Column('dc_id', sa.INTEGER(), nullable=False), sa.Column('dc_id', sa.Integer, nullable=False),
sa.Column('server_address', sa.VARCHAR(), nullable=True), sa.Column('server_address', sa.String, nullable=True),
sa.Column('port', sa.INTEGER(), nullable=True), sa.Column('port', sa.Integer, nullable=True),
sa.Column('auth_key', sa.BLOB(), nullable=True), sa.Column('auth_key', sa.LargeBinary, nullable=True),
sa.PrimaryKeyConstraint('session_id', 'dc_id')) sa.PrimaryKeyConstraint('session_id', 'dc_id'))
SentFile = op.create_table('telethon_sent_files', SentFile = op.create_table('telethon_sent_files',
sa.Column('session_id', sa.VARCHAR(), nullable=False), sa.Column('session_id', sa.String, nullable=False),
sa.Column('md5_digest', sa.BLOB(), nullable=False), sa.Column('md5_digest', sa.LargeBinary, nullable=False),
sa.Column('file_size', sa.INTEGER(), nullable=False), sa.Column('file_size', sa.Integer, nullable=False),
sa.Column('type', sa.INTEGER(), nullable=False), sa.Column('type', sa.Integer, nullable=False),
sa.Column('id', sa.INTEGER(), nullable=True), sa.Column('id', sa.BigInteger, nullable=True),
sa.Column('hash', sa.INTEGER(), nullable=True), sa.Column('hash', sa.BigInteger, nullable=True),
sa.PrimaryKeyConstraint('session_id', 'md5_digest', 'file_size', sa.PrimaryKeyConstraint('session_id', 'md5_digest', 'file_size',
'type')) 'type'))
Entity = op.create_table('telethon_entities', Entity = op.create_table('telethon_entities',
sa.Column('session_id', sa.VARCHAR(), nullable=False), sa.Column('session_id', sa.String, nullable=False),
sa.Column('id', sa.INTEGER(), nullable=False), sa.Column('id', sa.Integer, nullable=False),
sa.Column('hash', sa.INTEGER(), nullable=False), sa.Column('hash', sa.Integer, nullable=False),
sa.Column('username', sa.VARCHAR(), nullable=True), sa.Column('username', sa.String, nullable=True),
sa.Column('phone', sa.INTEGER(), nullable=True), sa.Column('phone', sa.Integer, nullable=True),
sa.Column('name', sa.VARCHAR(), nullable=True), sa.Column('name', sa.String, nullable=True),
sa.PrimaryKeyConstraint('session_id', 'id')) sa.PrimaryKeyConstraint('session_id', 'id'))
Version = op.create_table('telethon_version', Version = op.create_table('telethon_version',
sa.Column('version', sa.INTEGER(), nullable=False), sa.Column('version', sa.Integer, nullable=False),
sa.PrimaryKeyConstraint('version')) sa.PrimaryKeyConstraint('version'))
conn = op.get_bind() conn = op.get_bind()
sessions = [os.path.basename(f) for f in os.listdir(".") if f.endswith(".session")] sessions = [os.path.basename(f) for f in os.listdir(".") if f.endswith(".session")]
@@ -18,7 +18,7 @@ depends_on = None
def upgrade(): def upgrade():
op.add_column('telegram_file', op.add_column('telegram_file',
sa.Column('timestamp', sa.BigInteger(), nullable=False, default=0, sa.Column('timestamp', sa.BigInteger(), nullable=False, default=0,
server_default="true")) server_default="0"))
def downgrade(): def downgrade():
@@ -29,7 +29,7 @@ def upgrade():
sa.UniqueConstraint('mxid')) sa.UniqueConstraint('mxid'))
op.create_table('user', op.create_table('user',
sa.Column('mxid', sa.String), sa.Column('mxid', sa.String),
sa.Column('tgid', sa.Integer, nullable=True), sa.Column('tgid', sa.Integer, nullable=True, unique=True),
sa.Column('tg_username', sa.String, nullable=True), sa.Column('tg_username', sa.String, nullable=True),
sa.Column('saved_contacts', sa.Integer, nullable=False, default=0), sa.Column('saved_contacts', sa.Integer, nullable=False, default=0),
sa.PrimaryKeyConstraint('mxid')) sa.PrimaryKeyConstraint('mxid'))
+1
View File
@@ -0,0 +1 @@
#!/bin/sh
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
s6-svscanctl -t /etc/s6.d
+39
View File
@@ -0,0 +1,39 @@
#!/bin/bash
# Define functions
function fixperms {
chown -R ${UID}:${GID} /data /opt/mautrixtelegram
}
# Go into env
cd /opt/mautrixtelegram
export FFMPEG_BINARY=/usr/bin/ffmpeg
# Replace database path in config.
sed -i "s#sqlite:///mautrix-telegram.db#sqlite:////data/mautrix-telegram.db#" /data/config.yaml
# Check that database is in the right state
alembic -x config=/data/config.yaml upgrade head
if [[ ! -f /data/config.yaml ]]; then
cp example-config.yaml /data/config.yaml
echo "Didn't find a config file."
echo "Copied default config file to /data/config.yaml"
echo "Modify that config file to your liking."
echo "Start the container again after that to generate the registration file."
fixperms
exit
fi
if [[ ! -f /data/registration.yaml ]]; then
python3 -m mautrix_telegram -g -c /data/config.yaml -r /data/registration.yaml
echo "Didn't find a registration file."
echo "Generated ode for you."
echo "Copy that over to synapses app service directory."
fixperms
exit
fi
fixperms
exec su-exec ${UID}:${GID} python3 -m mautrix_telegram -c /data/config.yaml
+1 -1
View File
@@ -1,2 +1,2 @@
__version__ = "0.2.0rc1" __version__ = "0.2.0rc4"
__author__ = "Tulir Asokan <tulir@maunium.net>" __author__ = "Tulir Asokan <tulir@maunium.net>"
+1 -1
View File
@@ -26,7 +26,7 @@ from alchemysession import AlchemySessionContainer
from mautrix_appservice import AppService from mautrix_appservice import AppService
from .base import Base from .base import Base
from .config import Config, DictWithRecursion from .config import Config
from .matrix import MatrixHandler from .matrix import MatrixHandler
from .db import init as init_db from .db import init as init_db
+1 -1
View File
@@ -166,7 +166,7 @@ class Bot(AbstractUser):
if not self.mxid_regex.match(mxid): if not self.mxid_regex.match(mxid):
return await reply("That doesn't look like a Matrix ID.") return await reply("That doesn't look like a Matrix ID.")
user = await u.User.get_by_mxid(mxid).ensure_started() user = await u.User.get_by_mxid(mxid).ensure_started()
if not user.whitelisted: if not user.relaybot_whitelisted:
return await reply("That user is not whitelisted to use the bridge.") return await reply("That user is not whitelisted to use the bridge.")
elif user.logged_in: elif user.logged_in:
displayname = f"@{user.username}" if user.username else user.displayname displayname = f"@{user.username}" if user.username else user.displayname
+17 -6
View File
@@ -102,7 +102,7 @@ def _get_portal_murder_function(action, room_id, function, command, completed_me
} }
@command_handler() @command_handler(needs_auth=False)
async def delete_portal(evt: CommandEvent): async def delete_portal(evt: CommandEvent):
portal, ok = await _get_portal_and_check_permission(evt, "delete_portal") portal, ok = await _get_portal_and_check_permission(evt, "delete_portal")
if not ok: if not ok:
@@ -117,7 +117,7 @@ async def delete_portal(evt: CommandEvent):
"by typing `$cmdprefix+sp confirm-delete`") "by typing `$cmdprefix+sp confirm-delete`")
@command_handler() @command_handler(needs_auth=False)
async def unbridge(evt: CommandEvent): async def unbridge(evt: CommandEvent):
portal, ok = await _get_portal_and_check_permission(evt, "unbridge_room") portal, ok = await _get_portal_and_check_permission(evt, "unbridge_room")
if not ok: if not ok:
@@ -131,7 +131,7 @@ async def unbridge(evt: CommandEvent):
"by typing `$cmdprefix+sp confirm-unbridge`") "by typing `$cmdprefix+sp confirm-unbridge`")
@command_handler() @command_handler(needs_auth=False)
async def bridge(evt: CommandEvent): async def bridge(evt: CommandEvent):
if len(evt.args) == 0: if len(evt.args) == 0:
return await evt.reply("**Usage:** " return await evt.reply("**Usage:** "
@@ -247,14 +247,25 @@ async def confirm_bridge(evt: CommandEvent):
return await evt.reply("Please use `$cmdprefix+sp continue` to confirm the bridging or " return await evt.reply("Please use `$cmdprefix+sp continue` to confirm the bridging or "
"`$cmdprefix+sp cancel` to cancel.") "`$cmdprefix+sp cancel` to cancel.")
user = evt.sender if evt.sender.logged_in else evt.tgbot
try:
entity = await user.client.get_entity(portal.peer)
except Exception:
evt.log.exception("Failed to get_entity(%s) for manual bridging.", portal.peer)
if evt.sender.logged_in:
return await evt.reply("Failed to get info of telegram chat. "
"You are logged in, are you in that chat?")
else:
return await evt.reply("Failed to get info of telegram chat. "
"You're not logged in, is the relay bot in the chat?")
direct = False
portal.mxid = bridge_to_mxid portal.mxid = bridge_to_mxid
portal.title, portal.about, levels = await _get_initial_state(evt) portal.title, portal.about, levels = await _get_initial_state(evt)
portal.photo_id = "" portal.photo_id = ""
portal.save() portal.save()
entity = await evt.sender.client.get_entity(portal.peer) asyncio.ensure_future(portal.update_matrix_room(user, entity, direct, levels=levels),
direct = False
asyncio.ensure_future(portal.update_matrix_room(evt.sender, entity, direct, levels=levels),
loop=evt.loop) loop=evt.loop)
return await evt.reply("Bridging complete. Portal synchronization should begin momentarily.") return await evt.reply("Bridging complete. Portal synchronization should begin momentarily.")
+1 -1
View File
@@ -70,7 +70,7 @@ class User(Base):
__tablename__ = "user" __tablename__ = "user"
mxid = Column(String, primary_key=True) mxid = Column(String, primary_key=True)
tgid = Column(Integer, nullable=True) tgid = Column(Integer, nullable=True, unique=True)
tg_username = Column(String, nullable=True) tg_username = Column(String, nullable=True)
saved_contacts = Column(Integer, default=0) saved_contacts = Column(Integer, default=0)
contacts = relationship("Contact", uselist=True, contacts = relationship("Contact", uselist=True,