Skip to content

Commit d7864fc

Browse files
client: add initial proxy support (#1062)
1 parent 0f0b21b commit d7864fc

7 files changed

Lines changed: 113 additions & 4 deletions

File tree

cmd/mautrix-telegram/legacymigrate.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ func migrateLegacyConfig(helper up.Helper) {
8888
bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "max_initial_member_sync"}, []string{"network", "member_list", "max_initial_sync"})
8989
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "sync_channel_members"}, []string{"network", "member_list", "sync_broadcast_channels"})
9090
bridgeconfig.CopyToOtherLocation(helper, up.Bool, []string{"bridge", "skip_deleted_members"}, []string{"network", "member_list", "skip_deleted"})
91+
bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"telegram", "proxy", "type"}, []string{"network", "proxy", "type"})
92+
proxyAddress, _ := helper.Get(up.Str, "telegram", "proxy", "address")
93+
proxyPort, _ := helper.Get(up.Int, "telegram", "proxy", "port")
94+
helper.Set(up.Str, fmt.Sprintf("%s:%s", proxyAddress, proxyPort), "network", "proxy", "address")
95+
bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"telegram", "proxy", "username"}, []string{"network", "proxy", "username"})
96+
bridgeconfig.CopyToOtherLocation(helper, up.Str, []string{"telegram", "proxy", "password"}, []string{"network", "proxy", "password"})
9197
bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "max_member_count"}, []string{"network", "max_member_count"})
9298
bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "sync_update_limit"}, []string{"network", "sync", "update_limit"})
9399
bridgeconfig.CopyToOtherLocation(helper, up.Int, []string{"bridge", "sync_create_limit"}, []string{"network", "sync", "create_limit"})

pkg/connector/client.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,11 +210,15 @@ func NewTelegramClient(ctx context.Context, tc *TelegramConnector, login *bridge
210210
Storage: client.ScopedStore,
211211
AccessHasher: client.ScopedStore,
212212
})
213-
213+
resolver, err := GetProxyResolver(tc.Config.ProxyConfig)
214+
if err != nil {
215+
return nil, err
216+
}
214217
client.client = telegram.NewClient(tc.Config.APIID, tc.Config.APIHash, telegram.Options{
215218
CustomSessionStorage: &login.Metadata.(*UserLoginMetadata).Session,
216219
Logger: zaplog,
217220
UpdateHandler: client.updatesManager,
221+
Resolver: resolver,
218222
OnDead: client.onDead,
219223
OnSession: client.onSession,
220224
OnConnected: client.onConnected,

pkg/connector/config.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ type DeviceInfo struct {
5555
LangCode string `yaml:"lang_code"`
5656
}
5757

58+
type ProxyConfig struct {
59+
Type string `yaml:"type"`
60+
Address string `yaml:"address"`
61+
Username string `yaml:"username"`
62+
Password string `yaml:"password"`
63+
}
64+
5865
type TelegramConfig struct {
5966
APIID int `yaml:"api_id"`
6067
APIHash string `yaml:"api_hash"`
@@ -68,6 +75,8 @@ type TelegramConfig struct {
6875
TimeoutSeconds int `yaml:"timeout_seconds"`
6976
} `yaml:"ping"`
7077

78+
ProxyConfig ProxyConfig `yaml:"proxy"`
79+
7180
Sync struct {
7281
UpdateLimit int `yaml:"update_limit"`
7382
CreateLimit int `yaml:"create_limit"`
@@ -161,6 +170,10 @@ func upgradeConfig(helper up.Helper) {
161170
helper.Copy(up.Bool, "member_list", "skip_deleted")
162171
helper.Copy(up.Int, "ping", "interval_seconds")
163172
helper.Copy(up.Int, "ping", "timeout_seconds")
173+
helper.Copy(up.Str, "proxy", "type")
174+
helper.Copy(up.Str|up.Null, "proxy", "address")
175+
helper.Copy(up.Str|up.Null, "proxy", "username")
176+
helper.Copy(up.Str|up.Null, "proxy", "password")
164177
helper.Copy(up.Int, "sync", "update_limit")
165178
helper.Copy(up.Int, "sync", "create_limit")
166179
helper.Copy(up.Int, "sync", "login_sync_limit")
@@ -187,6 +200,7 @@ func (tc *TelegramConnector) GetConfig() (example string, data any, upgrader up.
187200
{"animated_sticker"},
188201
{"member_list"},
189202
{"ping"},
203+
{"proxy"},
190204
{"sync"},
191205
{"takeout"},
192206
{"max_member_count"},

pkg/connector/example-config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,16 @@ ping:
5454
# The timeout (in seconds) for a single ping.
5555
timeout_seconds: 10
5656

57+
# Proxy settings
58+
proxy:
59+
# Allowed types: disabled, socks5, mtproxy
60+
type: disabled
61+
# Proxy IP address/domain name and port.
62+
address: "127.0.0.1:1080"
63+
# Proxy authentication (optional). Put MTProxy secret in password field.
64+
username:
65+
password:
66+
5767
sync:
5868
# Number of most recently active dialogs to check when syncing chats.
5969
# Set to -1 to remove limit.

pkg/connector/login.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@ func (bl *baseLogin) makeClient(ctx context.Context, dispatcher *tg.UpdateDispat
125125
if dispatcher == nil {
126126
dispatcher = ptr.Ptr(tg.NewUpdateDispatcher())
127127
}
128+
resolver, err := GetProxyResolver(bl.main.Config.ProxyConfig)
129+
if err != nil {
130+
return err
131+
}
128132
bl.client = telegram.NewClient(bl.main.Config.APIID, bl.main.Config.APIHash, telegram.Options{
133+
Resolver: resolver,
129134
CustomSessionStorage: &bl.session,
130135
Logger: zaplog,
131136
Device: bl.main.deviceConfig(),

pkg/connector/loginbot.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,16 @@ func (bl *BotLogin) SubmitUserInput(ctx context.Context, input map[string]string
6363
ctx = log.WithContext(ctx)
6464

6565
botToken := input[LoginStepIDBotToken]
66-
err := logoutBotAPI(ctx, botToken)
66+
dialFunc, err := GetProxyDialFunc(bl.main.Config.ProxyConfig)
67+
if err != nil {
68+
return nil, err
69+
}
70+
httpClient := &http.Client{
71+
Transport: &http.Transport{
72+
DialContext: dialFunc,
73+
},
74+
}
75+
err = logoutBotAPI(ctx, botToken, httpClient)
6776
if err != nil {
6877
return nil, fmt.Errorf("failed to logout from bot API: %w", err)
6978
}
@@ -86,12 +95,12 @@ type botAPIResponse struct {
8695
Description string `json:"description"`
8796
}
8897

89-
func logoutBotAPI(ctx context.Context, token string) error {
98+
func logoutBotAPI(ctx context.Context, token string, client *http.Client) error {
9099
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.telegram.org/bot"+token+"/logOut", nil)
91100
if err != nil {
92101
return fmt.Errorf("failed to prepare request: %w", err)
93102
}
94-
resp, err := http.DefaultClient.Do(req)
103+
resp, err := client.Do(req)
95104
if err != nil {
96105
return fmt.Errorf("failed to send request: %w", err)
97106
}

pkg/connector/proxy.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// mautrix-telegram - A Matrix-Telegram puppeting bridge.
2+
// Copyright (C) 2026 Vladislav Agarkov
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
package connector
18+
19+
import (
20+
"fmt"
21+
22+
"golang.org/x/net/proxy"
23+
24+
"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/dcs"
25+
)
26+
27+
func GetProxyDialFunc(cfg ProxyConfig) (dcs.DialFunc, error) {
28+
switch cfg.Type {
29+
// we can't proxy HTTP through mtproxy
30+
case "disabled", "mtproxy":
31+
return nil, nil
32+
case "socks5":
33+
var auth *proxy.Auth
34+
if cfg.Username != "" && cfg.Password != "" {
35+
auth = &proxy.Auth{User: cfg.Username, Password: cfg.Password}
36+
}
37+
sock5, err := proxy.SOCKS5("tcp", cfg.Address, auth, proxy.Direct)
38+
if err != nil {
39+
return nil, err
40+
}
41+
return sock5.(proxy.ContextDialer).DialContext, nil
42+
default:
43+
return nil, fmt.Errorf("unsupported proxy type %s", cfg.Type)
44+
}
45+
}
46+
47+
func GetProxyResolver(cfg ProxyConfig) (dcs.Resolver, error) {
48+
switch cfg.Type {
49+
case "disabled", "socks5":
50+
dialer, err := GetProxyDialFunc(cfg)
51+
if err != nil {
52+
return nil, err
53+
}
54+
resolver := dcs.Plain(dcs.PlainOptions{Dial: dialer})
55+
return resolver, nil
56+
case "mtproxy":
57+
return dcs.MTProxy(cfg.Address, []byte(cfg.Password), dcs.MTProxyOptions{})
58+
default:
59+
return nil, fmt.Errorf("unsupported proxy type %s", cfg.Type)
60+
}
61+
}

0 commit comments

Comments
 (0)