## 效果
能在没有公网,仅拥有 NAT1 的情况下,做到无感 PLEX 外网访问

## 前提
* 确保路由器的 NAT 类型为 NAT1,并且要求 TCP 的 NAT 类型也为 NAT1
  * 主路由场景下基本没什么大问题,如果在旁路由场景下可能需要为旁路由开启 DMZ ,此外,Openclash 等科学插件也有影响
* 安装 Lucky ,这里不多赘述,自行解决


### 相关链接
[Lucky 项目地址]( https://github.com/gdy666/lucky)
[Python-plexapi]( https://python-plexapi.readthedocs.io/en/latest/introduction.html)

## 配置

**脚本配置**

* 以 **Openwrt** 为例,首先确保安装 Python ,随后安装下列库

`pip3 install plexapi`

* 根据其中需要修改的四行代码,修改为自己的实际参数,随后复制并建立一个.py 文件,例如 modify_port.py ,放置于任意位置,例如/root 路径下


**这是需要修改的内容**
```
# Plex 账号信息
PLEX_USERNAME = "example@qq.com"
PLEX_PASSWORD = "password"

PLEX_SERVER_BASEURL = "http://192.168.3.6:32400"  # 替换为你的 Plex 服务器 URL

TOKEN_FILE = "/root/plex_token.json"  # 保存 PLEX Token 的路径
```

**这是脚本代码**

```python
import os
import sys
import json
import requests
from plexapi.server import PlexServer

# Plex 账号信息
PLEX_USERNAME = "example@qq.com"
PLEX_PASSWORD = "password"

# Plex API URL
LOGIN_URL = "https://plex.tv/users/sign_in.json"
PLEX_SERVER_BASEURL = "http://192.168.3.6:32400"  # 替换为你的 Plex 服务器 URL

# 登录请求的头信息
HEADERS = {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "X-Plex-Client-Identifier": "MyPlexApp"
}

# 获取新令牌的登录请求数据
data = {
    "user": {
        "login": PLEX_USERNAME,
        "password": PLEX_PASSWORD
    }
}

def get_plex_token():
    """
    通过向 Plex API 发送登录请求获取 Plex 认证令牌。
    """
    try:
        response = requests.post(LOGIN_URL, headers=HEADERS, data=json.dumps(data))
        if response.status_code == 201:  # 状态码 201 表示登录成功
            return response.json()['user']['authentication_token']
        else:
            print(f"获取令牌失败。状态码: {response.status_code}")
            return None
    except Exception as e:
        print(f"认证过程中发生错误: {str(e)}")
        return None

def read_token_from_file(token_file):
    """
    从指定的 JSON 文件中读取 Plex 令牌。
    """
    if os.path.exists(token_file):
        try:
            with open(token_file, 'r') as file:
                data = json.load(file)
                return data.get('token')
        except Exception as e:
            print(f"读取令牌文件时发生错误: {str(e)}")
            return None
    else:
        return None

def save_token_to_file(token_file, token):
    """
    将 Plex 令牌保存到指定的 JSON 文件中。
    """
    try:
        with open(token_file, 'w') as file:
            json.dump({"token": token}, file)
    except Exception as e:
        print(f"保存令牌到文件时发生错误: {str(e)}")

def check_token_validity(baseurl, token):
    """
    通过向服务器发送验证请求来检查 Plex 令牌的有效性。
    """
    try:
        check_url = f"{baseurl}/library/sections?X-Plex-Token={token}"
        response = requests.get(check_url)
        return response.status_code == 200
    except Exception as e:
        print(f"检查令牌有效性时发生错误: {str(e)}")
        return False

def modify_port(plex, new_port):
    """
    修改 Plex 服务器设置中的手动端口映射。
    """
    manual_port_setting = plex.settings.get('manualPortMappingPort')
   
    if manual_port_setting:
        try:
            manual_port_setting.set(new_port)
            plex.settings.save()
            print(f"手动端口映射已成功更改为: {new_port}")
        except Exception as e:
            print(f"修改端口或保存设置时发生错误: {str(e)}")
    else:
        print("无法找到手动端口映射设置。")

if __name__ == "__main__":
    # 第一步:解析命令行参数,获取新的端口号
    if len(sys.argv) < 2:
        print("用法: python3 modify.py <new_port>")
        sys.exit(1)

    try:
        NEW_PORT = int(sys.argv[1])  # 将提供的端口号转换为整数
    except ValueError:
        print("无效的端口号。请提供有效的整数。")
        sys.exit(1)

    # 第二步:定义令牌存储文件
    TOKEN_FILE = "/root/plex_token.json"  # Token 路径

    # 第三步:从文件中读取令牌或生成新令牌
    PLEX_TOKEN = read_token_from_file(TOKEN_FILE)

    # 第四步:如果文件中没有令牌,或者令牌无效,则生成新令牌
    if PLEX_TOKEN is None or not check_token_validity(PLEX_SERVER_BASEURL, PLEX_TOKEN):
        PLEX_TOKEN = get_plex_token()
        if PLEX_TOKEN:
            save_token_to_file(TOKEN_FILE, PLEX_TOKEN)
        else:
            print("获取有效令牌失败。")
            sys.exit(1)

    # 第五步:使用 plexapi 连接到 Plex 服务器并修改手动端口
    try:
        plex = PlexServer(PLEX_SERVER_BASEURL, PLEX_TOKEN)
        print("成功连接到 Plex 服务器!")
        
        # 修改手动端口映射(使用传入的端口号)
        modify_port(plex, NEW_PORT)
    except Exception as e:
        print(f"连接 Plex 服务器或修改设置时发生错误: {str(e)}")
```

**Lucky 配置**
![CleanShot 2024-10-28 at 15.23.49@2x.png]( https://s2.loli.net/2024/10/28/rh7jfM2tWVHp8PN.png)

创建一个 TCP 穿透规则,这里我指定了一个空闲端口为 13333 ,并且不使用 LUCKY 的内置端口转发。开启自定义脚本触发,代码如下:
```bash
python3 /root/modify_port.py ${port}

if [ $? -ne 0 ]; then
    echo "Python script execution failed with exit code $?."
fi
```
注意此处代码的 /root/modify_port.py 就是刚刚的 python 执行脚本的实际路径。

随后确认即可,规则创建关闭后稍时即可打洞成功。

**端口转发**

由于我们刚刚指定了端口,并且没有使用 Lucky 内置的端口转发,因此我们需要用防火墙配置手动端口转发
![CleanShot 2024-10-28 at 15.35.16@2x.png]( https://s2.loli.net/2024/10/28/K4RWq2GXJVhP1eC.png)

如果你是主路由,源区域应该是防火墙的“WAN”区域,我是旁路由,所以源区域为 LAN

外部端口就是刚刚打洞绑定的端口,例如我刚刚在 LUCKY 配置了 13333 端口

目前区域即可 LAN ,内部 IP 地址即为 PLEX 所在主机 IP ,内部端口通常为 PLEX Server 默认端口 32400

## 常见问题

* 在配置前可单独调试脚本,确保脚本正常运作,脚本调试命令为:
python3 modify_port.py <port>

* 旁路由的可能额外操作
LUCKY 位于旁路由,并且使用了 Openclash ,即使配置了旁路由为 DMZ ,但是 TCP 流量经过了 Openclash ,因此导致了 TCP 的 NAT 类型不为 NAT1 。

解决方案有下列几种:

1. 通过 Openclash 的仅允许常用端口流量功能,可以让打洞的流量不经过 Openclash ,即不包含 3478 端口流量
2. 通过 Openclash 的黑白名单功能中的绕过核心的来源端口功能
![CleanShot 2024-10-28 at 15.49.18@2x.png]( https://s2.loli.net/2024/10/28/ZIX5gd6QGwCz7NT.png)
       
设置几个打洞时绑定的预设端口,这些端口流量就不会经过 Openclash 了,但配置中也指出了,Fake ip 模式下只能过滤纯 IP 类型请求。
       
其它请自行发挥,包括但不限于使用 Openclash 的开发者选项,或者修改防火墙来实现
举报· 13 次点击
登录 注册 站外分享
快来抢沙发
0 条回复  
返回顶部