├── .github └── workflows │ └── builder.yaml ├── .gitignore ├── Dockerfile ├── IPLocation ├── IpLocation.php ├── IpParserInterface.php ├── IpV6wry.php ├── QQwry.php ├── StringParser.php ├── ipv6wry.db └── qqwry.dat ├── LICENSE ├── Makefile ├── README.md ├── basic-auth.php ├── bot.go ├── bot.service ├── config.json ├── go.mod ├── go.sum ├── pics ├── 1.png ├── 2.jpg └── plugin.png ├── test.php └── tgbot.php /.github/workflows/builder.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - 'master' 5 | 6 | jobs: 7 | docker: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | with: 13 | submodules: true 14 | 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v1 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v1 20 | 21 | - name: Cache Docker layers 22 | uses: actions/cache@v2 23 | with: 24 | path: /tmp/.buildx-cache 25 | key: ${{ runner.os }}-buildx-${{ github.sha }} 26 | restore-keys: | 27 | ${{ runner.os }}-buildx- 28 | 29 | - name: Login to DockerHub 30 | uses: docker/login-action@v1 31 | with: 32 | username: ${{ secrets.DOCKERHUB_USERNAME }} 33 | password: ${{ secrets.DOCKERHUB_TOKEN }} 34 | 35 | - name: Lower case 36 | id: string 37 | uses: ASzc/change-string-case-action@v1 38 | with: 39 | string: ${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }} 40 | 41 | - name: Build and push 42 | uses: docker/build-push-action@v2 43 | with: 44 | context: . 45 | platforms: linux/arm,linux/amd64,linux/arm64 46 | push: true 47 | tags: ${{ steps.string.outputs.lowercase }} 48 | cache-from: type=local,src=/tmp/.buildx-cache 49 | cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max 50 | 51 | - name: Move cache 52 | run: | 53 | rm -rf /tmp/.buildx-cache 54 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore everything in the root except the "wp-content" directory. 2 | !wp-content/ 3 | 4 | # ignore everything in the "wp-content" directory, except: 5 | # "mu-plugins", "plugins", "themes" directory 6 | wp-content/* 7 | !wp-content/mu-plugins/ 8 | !wp-content/plugins/ 9 | !wp-content/themes/ 10 | 11 | # ignore these plugins 12 | wp-content/plugins/hello.php 13 | 14 | # ignore specific themes 15 | wp-content/themes/twenty*/ 16 | 17 | # ignore node dependency directories 18 | node_modules/ 19 | 20 | # ignore log files and databases 21 | *.log 22 | *.sql 23 | *.sqlite 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.19.0-alpine as builder 2 | 3 | RUN apk update && apk add --no-cache make ca-certificates && mkdir /build 4 | COPY go.mod /build 5 | RUN cd /build && go mod download 6 | COPY . /build 7 | RUN cd /build && make static 8 | 9 | 10 | FROM scratch 11 | 12 | COPY --from=builder /build/comments /comments 13 | COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 14 | WORKDIR / 15 | 16 | ENTRYPOINT ["/comments","-c","/config.json"] 17 | 18 | # docker run -d --restart=always-v ./config.json:/config.json bennythink/archiver 19 | -------------------------------------------------------------------------------- /IPLocation/IpLocation.php: -------------------------------------------------------------------------------- 1 | setDBPath(self::getIpV4Path()); 48 | $location = $ins->getIp($ip); 49 | } else if (self::isIpV6($ip)) { 50 | $ins = new IpV6wry(); 51 | $ins->setDBPath(self::getIpV6Path()); 52 | $location = $ins->getIp($ip); 53 | 54 | } else { 55 | $location = [ 56 | 'error' => 'IP Invalid' 57 | ]; 58 | } 59 | 60 | return $location; 61 | } 62 | 63 | /** 64 | * @param $ip 65 | * @param string $ipV4Path 66 | * @param string $ipV6Path 67 | * @return array|mixed 68 | */ 69 | public static function getLocation($ip, $ipV4Path='', $ipV6Path='') { 70 | $location = self::getLocationWithoutParse($ip, $ipV4Path, $ipV6Path); 71 | if (isset($location['error'])) { 72 | return $location; 73 | } 74 | return StringParser::parse($location); 75 | } 76 | 77 | /** 78 | * @param $path 79 | */ 80 | public static function setIpV4Path($path) 81 | { 82 | self::$ipV4Path = $path; 83 | } 84 | 85 | /** 86 | * @param $path 87 | */ 88 | public static function setIpV6Path($path) 89 | { 90 | self::$ipV6Path = $path; 91 | } 92 | 93 | /** 94 | * @return string 95 | */ 96 | private static function getIpV4Path() { 97 | return self::$ipV4Path ? : self::src('/qqwry.dat'); 98 | } 99 | 100 | /** 101 | * @return string 102 | */ 103 | private static function getIpV6Path() { 104 | return self::$ipV6Path ? : self::src('/ipv6wry.db'); 105 | } 106 | 107 | /** 108 | * @param $ip 109 | * @return bool 110 | */ 111 | private static function isIpV4($ip) { 112 | return false !== filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4); 113 | } 114 | 115 | /** 116 | * @param $ip 117 | * @return bool 118 | */ 119 | private static function isIpV6($ip) { 120 | return false !== filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6); 121 | } 122 | 123 | /** 124 | * @param $filename 125 | * @return string 126 | */ 127 | public static function src($filename) { 128 | return self::root('/IPLocation'.$filename); 129 | } 130 | 131 | /** 132 | * @param $filename 133 | * @return string 134 | */ 135 | public static function root($filename) { 136 | return IP_DATABASE_ROOT_DIR . $filename; 137 | } 138 | } -------------------------------------------------------------------------------- /IPLocation/IpParserInterface.php: -------------------------------------------------------------------------------- 1 | $exception->getMessage(), 29 | ]; 30 | } 31 | 32 | $return = [ 33 | 'ip' => $ip, 34 | 'country' => $tmp['addr'][0], 35 | 'area' => $tmp['addr'][1], 36 | ]; 37 | return $return; 38 | } 39 | 40 | 41 | private static $filePath; 42 | 43 | const FORMAT = 'J2'; 44 | private static $total = null; 45 | // 索引区 46 | private static $index_start_offset; 47 | private static $index_end_offset; 48 | private static $offlen; 49 | private static $iplen; 50 | private static $has_initialized = false; 51 | 52 | /** 53 | * return database record count 54 | * @return int|string 55 | */ 56 | public static function total() 57 | { 58 | if (null === static::$total) { 59 | $fd = fopen(static::$filePath, 'rb'); 60 | static::initialize($fd); 61 | fclose($fd); 62 | } 63 | return static::$total; 64 | } 65 | 66 | public static function initialize($fd) 67 | { 68 | if (!static::$has_initialized) { 69 | if (PHP_INT_SIZE < 8) { 70 | throw new \RuntimeException('64bit OS supported only'); 71 | } 72 | if (version_compare(PHP_VERSION, "7.0", "<")) { 73 | throw new \RuntimeException('php version 7.0 or greater'); 74 | } 75 | static::$index_start_offset = static::read8($fd, 16); 76 | static::$offlen = static::read1($fd, 6); 77 | static::$iplen = static::read1($fd, 7); 78 | static::$total = static::read8($fd, 8); 79 | static::$index_end_offset = static::$index_start_offset 80 | + (static::$iplen + static::$offlen) * static::$total; 81 | static::$has_initialized = true; 82 | } 83 | } 84 | 85 | /** 86 | * query ipv6 87 | * @param $ip 88 | * @return array 89 | */ 90 | public static function query($ip) 91 | { 92 | $ip_bin = inet_pton($ip); 93 | if (false === $ip_bin) { 94 | throw new \RuntimeException("error IPv6 address: $ip"); 95 | } 96 | if (16 !== strlen($ip_bin)) { 97 | throw new \RuntimeException("error IPv6 address: $ip"); 98 | } 99 | $fd = fopen(static::$filePath, 'rb'); 100 | static::initialize($fd); 101 | $ip_num_arr = unpack(static::FORMAT, $ip_bin); 102 | // IP地址前半部分转换成有int 103 | $ip_num1 = $ip_num_arr[1]; 104 | // IP地址后半部分转换成有int 105 | $ip_num2 = $ip_num_arr[2]; 106 | $ip_find = static::find($fd, $ip_num1, $ip_num2, 0, static::$total); 107 | $ip_offset = static::$index_start_offset + $ip_find * (static::$iplen + static::$offlen); 108 | $ip_offset2 = $ip_offset + static::$iplen + static::$offlen; 109 | $ip_start = inet_ntop(pack(static::FORMAT, static::read8($fd, $ip_offset), 0)); 110 | try { 111 | $ip_end = inet_ntop(pack(static::FORMAT, static::read8($fd, $ip_offset2) - 1, 0)); 112 | } catch (\RuntimeException $e) { 113 | $ip_end = "FFFF:FFFF:FFFF:FFFF::"; 114 | } 115 | $ip_record_offset = static::read8($fd, $ip_offset + static::$iplen, static::$offlen); 116 | $ip_addr = static::read_record($fd, $ip_record_offset); 117 | $ip_addr_disp = $ip_addr[0] . " " . $ip_addr[1]; 118 | if (is_resource($fd)) { 119 | fclose($fd); 120 | } 121 | return ["start" => $ip_start, "end" => $ip_end, "addr" => $ip_addr, "disp" => $ip_addr_disp]; 122 | } 123 | 124 | /** 125 | * 读取记录 126 | * @param $fd 127 | * @param $offset 128 | * @return string[] 129 | */ 130 | public static function read_record($fd, $offset) 131 | { 132 | $record = [0 => "", 1 => ""]; 133 | $flag = static::read1($fd, $offset); 134 | if ($flag == 1) { 135 | $location_offset = static::read8($fd, $offset + 1, static::$offlen); 136 | return static::read_record($fd, $location_offset); 137 | } 138 | $record[0] = static::read_location($fd, $offset); 139 | if ($flag == 2) { 140 | $record[1] = static::read_location($fd, $offset + static::$offlen + 1); 141 | } else { 142 | $record[1] = static::read_location($fd, $offset + strlen($record[0]) + 1); 143 | } 144 | return $record; 145 | } 146 | 147 | /** 148 | * 读取地区 149 | * @param $fd 150 | * @param $offset 151 | * @return string 152 | */ 153 | public static function read_location($fd, $offset) 154 | { 155 | if ($offset == 0) { 156 | return ""; 157 | } 158 | $flag = static::read1($fd, $offset); 159 | // 出错 160 | if ($flag == 0) { 161 | return ""; 162 | } 163 | // 仍然为重定向 164 | if ($flag == 2) { 165 | $offset = static::read8($fd, $offset + 1, static::$offlen); 166 | return static::read_location($fd, $offset); 167 | } 168 | return static::readstr($fd, $offset); 169 | } 170 | 171 | /** 172 | * 查找 ip 所在的索引 173 | * @param $fd 174 | * @param $ip_num1 175 | * @param $ip_num2 176 | * @param $l 177 | * @param $r 178 | * @return mixed 179 | */ 180 | public static function find($fd, $ip_num1, $ip_num2, $l, $r) 181 | { 182 | if ($l + 1 >= $r) { 183 | return $l; 184 | } 185 | $m = intval(($l + $r) / 2); 186 | $m_ip1 = static::read8($fd, static::$index_start_offset + $m * (static::$iplen + static::$offlen), 187 | static::$iplen); 188 | $m_ip2 = 0; 189 | if (static::$iplen <= 8) { 190 | $m_ip1 <<= 8 * (8 - static::$iplen); 191 | } else { 192 | $m_ip2 = static::read8($fd, static::$index_start_offset + $m * (static::$iplen + static::$offlen) + 8, 193 | static::$iplen - 8); 194 | $m_ip2 <<= 8 * (16 - static::$iplen); 195 | } 196 | if (static::uint64cmp($ip_num1, $m_ip1) < 0) { 197 | return static::find($fd, $ip_num1, $ip_num2, $l, $m); 198 | } 199 | if (static::uint64cmp($ip_num1, $m_ip1) > 0) { 200 | return static::find($fd, $ip_num1, $ip_num2, $m, $r); 201 | } 202 | if (static::uint64cmp($ip_num2, $m_ip2) < 0) { 203 | return static::find($fd, $ip_num1, $ip_num2, $l, $m); 204 | } 205 | return static::find($fd, $ip_num1, $ip_num2, $m, $r); 206 | } 207 | 208 | public static function readraw($fd, $offset = null, $size = 0) 209 | { 210 | if (!is_null($offset)) { 211 | fseek($fd, $offset); 212 | } 213 | return fread($fd, $size); 214 | } 215 | 216 | public static function read1($fd, $offset = null) 217 | { 218 | if (!is_null($offset)) { 219 | fseek($fd, $offset); 220 | } 221 | $a = fread($fd, 1); 222 | return @unpack("C", $a)[1]; 223 | } 224 | 225 | public static function read8($fd, $offset = null, $size = 8) 226 | { 227 | if (!is_null($offset)) { 228 | fseek($fd, $offset); 229 | } 230 | $a = fread($fd, $size) . "\0\0\0\0\0\0\0\0"; 231 | return @unpack("P", $a)[1]; 232 | } 233 | 234 | public static function readstr($fd, $offset = null) 235 | { 236 | if (!is_null($offset)) { 237 | fseek($fd, $offset); 238 | } 239 | $str = ""; 240 | $chr = static::read1($fd, $offset); 241 | while ($chr != 0) { 242 | $str .= chr($chr); 243 | $offset++; 244 | $chr = static::read1($fd, $offset); 245 | } 246 | return $str; 247 | } 248 | 249 | public static function ip2num($ip) 250 | { 251 | return unpack("N", inet_pton($ip))[1]; 252 | } 253 | 254 | public static function inet_ntoa($nip) 255 | { 256 | $ip = []; 257 | for ($i = 3; $i > 0; $i--) { 258 | $ip_seg = intval($nip / pow(256, $i)); 259 | $ip[] = $ip_seg; 260 | $nip -= $ip_seg * pow(256, $i); 261 | } 262 | $ip[] = $nip; 263 | return join(".", $ip); 264 | } 265 | 266 | public static function uint64cmp($a, $b) 267 | { 268 | if ($a >= 0 && $b >= 0 || $a < 0 && $b < 0) { 269 | return $a <=> $b; 270 | } 271 | if ($a >= 0 && $b < 0) { 272 | return -1; 273 | } 274 | return 1; 275 | } 276 | 277 | } 278 | -------------------------------------------------------------------------------- /IPLocation/QQwry.php: -------------------------------------------------------------------------------- 1 | filePath = $filePath; 17 | } 18 | 19 | /** 20 | * @param $ip 21 | * @return array 22 | */ 23 | public function getIp($ip) 24 | { 25 | try { 26 | $tmp = $this->getAddr($ip); 27 | } catch (\Exception $exception) { 28 | return [ 29 | 'error' => $exception->getMessage(), 30 | ]; 31 | } 32 | 33 | $return = [ 34 | 'ip' => $ip, 35 | 'country' => $tmp['country'], 36 | 'area' => $tmp['area'], 37 | ]; 38 | return $return; 39 | } 40 | 41 | /** 42 | * 文件路径 43 | * @var string 44 | */ 45 | private $filePath; 46 | /** 47 | * qqwry.dat文件指针 48 | * 49 | * @var resource 50 | */ 51 | private $fp; 52 | /** 53 | * 第一条IP记录的偏移地址 54 | * 55 | * @var int 56 | */ 57 | private $firstIp; 58 | /** 59 | * 最后一条IP记录的偏移地址 60 | * 61 | * @var int 62 | */ 63 | private $lastIp; 64 | /** 65 | * IP记录的总条数(不包含版本信息记录) 66 | * 67 | * @var int 68 | */ 69 | private $totalIp; 70 | 71 | 72 | /** 73 | * 如果ip错误 74 | * 75 | * $result 是返回的数组 76 | * $result['ip'] 输入的ip 77 | * $result['country'] 国家 如 中国 78 | * $result['area'] 最完整的信息 如 中国河北省邢台市威县新科网吧(北外街) 79 | * 80 | * 81 | * @param $ip 82 | * @return array 83 | */ 84 | public function getAddr($ip) 85 | { 86 | $filename = $this->filePath; 87 | if (!file_exists($filename)) { 88 | trigger_error("Failed open ip database file!"); 89 | throw new \Exception('Failed open ip database'); 90 | } 91 | if (is_null($this->fp)) { 92 | $this->fp = 0; 93 | if (($this->fp = fopen($filename, 'rb')) !== false) { 94 | $this->firstIp = $this->getLong(); 95 | $this->lastIp = $this->getLong(); 96 | $this->totalIp = ($this->lastIp - $this->firstIp) / 7; 97 | } 98 | } 99 | $location = $this->getLocation($ip); 100 | return $location; 101 | } 102 | 103 | /** 104 | * 返回读取的长整型数 105 | * 106 | * @access private 107 | * @return int 108 | */ 109 | private function getLong() 110 | { 111 | //将读取的little-endian编码的4个字节转化为长整型数 112 | $result = unpack('Vlong', fread($this->fp, 4)); 113 | return $result['long']; 114 | } 115 | 116 | /** 117 | * 根据所给 IP 地址或域名返回所在地区信息 118 | * 119 | * @access public 120 | * @param string $ip 121 | * @return array ip country area beginip endip 122 | */ 123 | private function getLocation($ip) 124 | { 125 | if (!$this->fp) { 126 | return null; 127 | } 128 | // 如果数据文件没有被正确打开,则直接返回空 129 | $location['ip'] = $ip; 130 | $ip = $this->packIp($location['ip']); 131 | // 将输入的IP地址转化为可比较的IP地址 132 | // 不合法的IP地址会被转化为255.255.255.255 133 | // 对分搜索 134 | $l = 0; 135 | // 搜索的下边界 136 | $u = $this->totalIp; 137 | // 搜索的上边界 138 | $findip = $this->lastIp; 139 | // 如果没有找到就返回最后一条IP记录(qqwry.dat的版本信息) 140 | while ($l <= $u) { 141 | // 当上边界小于下边界时,查找失败 142 | $i = floor(($l + $u) / 2); 143 | // 计算近似中间记录 144 | fseek($this->fp, $this->firstIp + $i * 7); 145 | $beginip = strrev(fread($this->fp, 4)); 146 | // 获取中间记录的开始IP地址 147 | // strrev函数在这里的作用是将little-endian的压缩IP地址转化为big-endian的格式 148 | // 以便用于比较,后面相同。 149 | if ($ip < $beginip) { 150 | // 用户的IP小于中间记录的开始IP地址时 151 | $u = $i - 1; 152 | // 将搜索的上边界修改为中间记录减一 153 | } else { 154 | fseek($this->fp, $this->getLong3()); 155 | $endip = strrev(fread($this->fp, 4)); 156 | // 获取中间记录的结束IP地址 157 | if ($ip > $endip) { 158 | // 用户的IP大于中间记录的结束IP地址时 159 | $l = $i + 1; 160 | // 将搜索的下边界修改为中间记录加一 161 | } else { 162 | // 用户的IP在中间记录的IP范围内时 163 | $findip = $this->firstIp + $i * 7; 164 | break; 165 | // 则表示找到结果,退出循环 166 | } 167 | } 168 | } 169 | //获取查找到的IP地理位置信息 170 | fseek($this->fp, $findip); 171 | $location['beginip'] = long2ip($this->getLong()); 172 | // 用户IP所在范围的开始地址 173 | $offset = $this->getLong3(); 174 | fseek($this->fp, $offset); 175 | $location['endip'] = long2ip($this->getLong()); 176 | // 用户IP所在范围的结束地址 177 | $byte = fread($this->fp, 1); 178 | // 标志字节 179 | switch (ord($byte)) { 180 | case 1: // 标志字节为1,表示国家和区域信息都被同时重定向 181 | $countryOffset = $this->getLong3(); 182 | // 重定向地址 183 | fseek($this->fp, $countryOffset); 184 | $byte = fread($this->fp, 1); 185 | // 标志字节 186 | switch (ord($byte)) { 187 | case 2: // 标志字节为2,表示国家信息被重定向 188 | fseek($this->fp, $this->getLong3()); 189 | $location['country'] = $this->getString(); 190 | fseek($this->fp, $countryOffset + 4); 191 | $location['area'] = $this->getArea(); 192 | break; 193 | default: // 否则,表示国家信息没有被重定向 194 | $location['country'] = $this->getString($byte); 195 | $location['area'] = $this->getArea(); 196 | break; 197 | } 198 | break; 199 | case 2: // 标志字节为2,表示国家信息被重定向 200 | fseek($this->fp, $this->getLong3()); 201 | $location['country'] = $this->getString(); 202 | fseek($this->fp, $offset + 8); 203 | $location['area'] = $this->getArea(); 204 | break; 205 | default: // 否则,表示国家信息没有被重定向 206 | $location['country'] = $this->getString($byte); 207 | $location['area'] = $this->getArea(); 208 | break; 209 | } 210 | $location['country'] = iconv("GBK", "UTF-8", $location['country']); 211 | $location['area'] = iconv("GBK", "UTF-8", $location['area']); 212 | if ($location['country'] == " CZ88.NET" || $location['country'] == "纯真网络") { 213 | // CZ88.NET表示没有有效信息 214 | $location['country'] = "无数据"; 215 | } 216 | if ($location['area'] == " CZ88.NET") { 217 | $location['area'] = ""; 218 | } 219 | return $location; 220 | } 221 | 222 | /** 223 | * 返回压缩后可进行比较的IP地址 224 | * 225 | * @access private 226 | * @param string $ip 227 | * @return string 228 | */ 229 | private function packIp($ip) 230 | { 231 | // 将IP地址转化为长整型数,如果在PHP5中,IP地址错误,则返回False, 232 | // 这时intval将Flase转化为整数-1,之后压缩成big-endian编码的字符串 233 | return pack('N', intval($this->ip2long($ip))); 234 | } 235 | 236 | /** 237 | * Ip 地址转为数字地址 238 | * php 的 ip2long 这个函数有问题 239 | * 133.205.0.0 ==>> 2244804608 240 | * 241 | * @param string $ip 要转换的 ip 地址 242 | * @return int 转换完成的数字 243 | */ 244 | private function ip2long($ip) 245 | { 246 | $ip_arr = explode('.', $ip); 247 | $iplong = (16777216 * intval($ip_arr[0])) + (65536 * intval($ip_arr[1])) + (256 * intval($ip_arr[2])) + intval($ip_arr[3]); 248 | return $iplong; 249 | } 250 | 251 | /** 252 | * 返回读取的3个字节的长整型数 253 | * 254 | * @access private 255 | * @return int 256 | */ 257 | private function getLong3() 258 | { 259 | //将读取的little-endian编码的3个字节转化为长整型数 260 | $result = unpack('Vlong', fread($this->fp, 3) . chr(0)); 261 | return $result['long']; 262 | } 263 | 264 | /** 265 | * 返回读取的字符串 266 | * 267 | * @access private 268 | * @param string $data 269 | * @return string 270 | */ 271 | private function getString($data = "") 272 | { 273 | $char = fread($this->fp, 1); 274 | while (ord($char) > 0) { 275 | // 字符串按照C格式保存,以\0结束 276 | $data .= $char; 277 | // 将读取的字符连接到给定字符串之后 278 | $char = fread($this->fp, 1); 279 | } 280 | return $data; 281 | } 282 | 283 | /** 284 | * 返回地区信息 285 | * 286 | * @access private 287 | * @return string 288 | */ 289 | private function getArea() 290 | { 291 | $byte = fread($this->fp, 1); 292 | // 标志字节 293 | switch (ord($byte)) { 294 | case 0: // 没有区域信息 295 | $area = ""; 296 | break; 297 | case 1: 298 | case 2: // 标志字节为1或2,表示区域信息被重定向 299 | fseek($this->fp, $this->getLong3()); 300 | $area = $this->getString(); 301 | break; 302 | default: // 否则,表示区域信息没有被重定向 303 | $area = $this->getString($byte); 304 | break; 305 | } 306 | return $area; 307 | } 308 | 309 | /** 310 | * 析构函数,用于在页面执行结束后自动关闭打开的文件。 311 | */ 312 | public function __destruct() 313 | { 314 | if ($this->fp) { 315 | fclose($this->fp); 316 | } 317 | $this->fp = 0; 318 | } 319 | } -------------------------------------------------------------------------------- /IPLocation/StringParser.php: -------------------------------------------------------------------------------- 1 | $value) { 141 | 142 | if (false !== strpos($location['country'], $value)) { 143 | $isChina = true; 144 | $location['province'] = $value; 145 | 146 | //直辖市 147 | if (in_array($value, self::$dictCityDirectly)) { 148 | 149 | //直辖市 150 | $_tmp_province = explode($value, $location['country']); 151 | 152 | //市辖区 153 | if (isset($_tmp_province[1])) { 154 | 155 | $_tmp_province[1] = self::lTrim($_tmp_province[1], $separatorCity); 156 | 157 | 158 | if (strpos($_tmp_province[1], $separatorDistrict) !== false) { 159 | $_tmp_qu = explode($separatorDistrict, $_tmp_province[1]); 160 | 161 | //解决 休息休息校区 变成城市区域 162 | $isHitBlackTail = false; 163 | foreach (self::$dictDistrictBlackTails as $blackTail) { 164 | //尾 165 | if (mb_substr($_tmp_qu[0], -mb_strlen($blackTail)) == $blackTail) { 166 | $isHitBlackTail = true; 167 | break; 168 | } 169 | } 170 | 171 | //校区,学区 172 | if ((!$isHitBlackTail) && mb_strlen($_tmp_qu[0]) < 5) { 173 | //有点尴尬 174 | $location['city'] = $_tmp_qu[0] . $separatorDistrict; 175 | } 176 | } 177 | } 178 | } else { 179 | 180 | //没有省份标志 只能替换 181 | $_tmp_city = str_replace($location['province'], '', $location['country']); 182 | 183 | //防止直辖市捣乱 上海市xxx区 =》 市xx区 184 | $_tmp_city = self::lTrim($_tmp_city, $separatorCity); 185 | 186 | //内蒙古 类型的 获取市县信息 187 | if (strpos($_tmp_city, $separatorCity) !== false) { 188 | //市 189 | $_tmp_city = explode($separatorCity, $_tmp_city); 190 | 191 | $location['city'] = $_tmp_city[0] . $separatorCity; 192 | 193 | //县 194 | if (isset($_tmp_city[1])) { 195 | if (strpos($_tmp_city[1], $separatorCounty) !== false) { 196 | $_tmp_county = explode($separatorCounty, $_tmp_city[1]); 197 | $location['county'] = $_tmp_county[0] . $separatorCounty; 198 | } 199 | 200 | //区 201 | if (!$location['county'] && strpos($_tmp_city[1], $separatorDistrict) !== false) { 202 | $_tmp_qu = explode($separatorDistrict, $_tmp_city[1]); 203 | $location['county'] = $_tmp_qu[0] . $separatorDistrict; 204 | } 205 | } 206 | } 207 | } 208 | 209 | break; 210 | } 211 | } 212 | } 213 | 214 | if ($isChina) { 215 | $location['country'] = '中国'; 216 | } 217 | 218 | 219 | $result['ip'] = $location['ip']; 220 | 221 | $result['country'] = $location['country']; 222 | $result['province'] = $location['province']; 223 | $result['city'] = $location['city']; 224 | $result['county'] = $location['county']; 225 | 226 | $result['area'] = $location['country'] . $location['province'] . $location['city'] . $location['county'] . ' ' . $location['org_area']; 227 | 228 | $result['isp'] = self::getIsp($result['area']); 229 | 230 | if ($withOriginal) { 231 | $result['org'] = $org; 232 | } 233 | 234 | return $result; 235 | } 236 | 237 | 238 | /** 239 | * @param $str 240 | * @return string 241 | */ 242 | private static function getIsp($str) 243 | { 244 | $ret = ''; 245 | 246 | foreach (self::$dictIsp as $k => $v) { 247 | if (false !== strpos($str, $v)) { 248 | $ret = $v; 249 | break; 250 | } 251 | } 252 | 253 | return $ret; 254 | } 255 | 256 | private static function lTrim($word, $w) { 257 | $pos = mb_stripos($word, $w); 258 | if ($pos === 0) { 259 | $word = mb_substr($word, 1); 260 | } 261 | return $word; 262 | } 263 | 264 | 265 | } -------------------------------------------------------------------------------- /IPLocation/ipv6wry.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/wp-comments/133421305ebfc26546a43611ce498273902641c8/IPLocation/ipv6wry.db -------------------------------------------------------------------------------- /IPLocation/qqwry.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/wp-comments/133421305ebfc26546a43611ce498273902641c8/IPLocation/qqwry.dat -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OS = darwin linux windows 2 | ARCH = amd64 3 | default: 4 | git pull 5 | @echo "Build current platform executable..." 6 | go build . 7 | 8 | static: 9 | @echo "Build static files..." 10 | CGO_ENABLED=0 go build -a -ldflags '-s -w -extldflags "-static"' -o comments . 11 | 12 | all: 13 | git pull 14 | @echo "Build all platform executables..." 15 | @for o in $(OS) ; do \ 16 | for a in $(ARCH) ; do \ 17 | CGO_ENABLED=0 GOOS=$$o GOARCH=$$a go build -ldflags="-s -w" -o builds/DailyGakki-$$o-$$a .; \ 18 | done \ 19 | done -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wp-comments-tgbot 2 | A Telegram Bot for WordPress comments! Simple and fast reply to your comments! 3 | ![](pics/1.png) 4 | 5 | ![](pics/2.jpg) 6 | 7 | # Warning 8 | By using this plugin, you agree to open Basic Auth on your WordPress. 9 | 10 | Please setup a complex passphrase for your WordPress 11 | 12 | # Requirements 13 | 1. WordPress native comment system. 14 | 15 | 2. Network connection to Telegram. 16 | 17 | # General guide 18 | ## 1. Install plugin 19 | ssh to your blog 20 | ```shell script 21 | cd /path/to/root/wp-content/plugins 22 | git clone https://github.com/BennyThink/wp-comments-tgbot 23 | ``` 24 | 25 | Then navigate to your Plugin and enable WordPress Comments Telegram Bot. 26 | ## 2. Create bot and find your user id 27 | Talk to [@BotFather](https://t.me/BotFather), create your own bot and copy bot token. 28 | 29 | Talk to [@get_id_bot](https://t.me/get_id_bot), get your own user id. 30 | 31 | ## 3. Edit configuration on WordPress 32 | Fill in your token, user id and proxy if needed. 33 | ![Alt text](pics/plugin.png) 34 | 35 | ## 3. Download binary on GitHub Release 36 | [GitHub Release](https://github.com/BennyThink/wp-comments-tgbot/releases) 37 | 38 | ## 4. Create config 39 | Create your own `config.json` 40 | ```json 41 | { 42 | "username": "admin", 43 | "password": "admin", 44 | "url": "http://localhost/", 45 | "token": "907J6Tw", 46 | "uid": "23231321", 47 | "admin": 1, 48 | "tail": "本评论由Telegram Bot回复~❤️" 49 | } 50 | ``` 51 | Explanations: 52 | * username & password: username and password for WordPress 53 | * url: site url, must include `/` as suffix. 54 | * token: Telegram bot token 55 | * uid: your Telegram user id 56 | * admin: your user id in WordPress, typically it should be 1 57 | * tail: suffix message to your reply. 58 | 59 | ## 5. Run 60 | Supply `config.json` as `-c /path/tp/config.json`. By default it will search on working directory. 61 | ```shell script 62 | chmod u+x /path/to/bot 63 | /path/to/bot -c /path/to/config.json 64 | ``` 65 | ### cli arguments 66 | ```text 67 | -c file set configuration file (default "config.json") 68 | -f force to run even on http sites. 69 | -h this help 70 | -v show version and exit 71 | ``` 72 | # Q&A 73 | ## 1. No email notifications while replying by telegram bot? 74 | Potentially bug about your theme/plugin. Try to find the code of sending email. 75 | 76 | You may find some hook like this: 77 | ``php 78 | add_action('comment_post', 'comment_mail_notify'); 79 | `` 80 | Change `comment_post` to `wp_insert_comment` will solve this issue. 81 | 82 | [Reference commit](https://github.com/BennyThink/WordPressGit/commit/c64a3a5e70e10239ba9217debf16a075e2a13874) 83 | 84 | # Credits 85 | * [Basic Auth](https://github.com/WP-API/Basic-Auth) 86 | 87 | # License 88 | GPLv2 -------------------------------------------------------------------------------- /basic-auth.php: -------------------------------------------------------------------------------- 1 | ID; 42 | } 43 | 44 | add_filter( 'determine_current_user', 'json_basic_auth_handler', 20 ); 45 | 46 | function json_basic_auth_error( $error ) { 47 | // Passthrough other errors 48 | if ( ! empty( $error ) ) { 49 | return $error; 50 | } 51 | 52 | global $wp_json_basic_auth_error; 53 | 54 | return $wp_json_basic_auth_error; 55 | } 56 | 57 | add_filter( 'rest_authentication_errors', 'json_basic_auth_error' ); 58 | -------------------------------------------------------------------------------- /bot.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | tb "gopkg.in/tucnak/telebot.v2" 10 | "io/ioutil" 11 | "log" 12 | "math/rand" 13 | "net/http" 14 | "os" 15 | "strings" 16 | "time" 17 | ) 18 | import "github.com/tgbot-collection/tgbot_ping" 19 | 20 | const version = "0.0.1" 21 | 22 | var ( 23 | h, v, f bool 24 | c string 25 | ) 26 | 27 | type config struct { 28 | Username string `json:"username"` 29 | Password string `json:"password"` 30 | Url string `json:"url"` 31 | Token string `json:"token"` 32 | Uid int64 `json:"uid,string"` 33 | Admin int `json:"admin"` 34 | Tail string `json:"tail,omitempty"` 35 | } 36 | 37 | func init() { 38 | flag.BoolVar(&h, "h", false, "this help") 39 | flag.BoolVar(&v, "v", false, "show version and exit") 40 | flag.BoolVar(&f, "f", false, "force to run even on http sites.") 41 | flag.StringVar(&c, "c", "config.json", "set configuration `file`") 42 | 43 | } 44 | func getArgs() config { 45 | flag.Parse() 46 | 47 | if v { 48 | fmt.Println(version) 49 | os.Exit(0) 50 | } 51 | if h { 52 | flag.Usage() 53 | os.Exit(0) 54 | } 55 | var configFile = c 56 | configData, err := readConfig(configFile) 57 | 58 | if err != nil { 59 | fmt.Printf("config file is corrupted or not found. \n%v\n", err) 60 | flag.Usage() 61 | os.Exit(2) 62 | 63 | } else if !f && !strings.HasPrefix(configData.Url, "https://") { 64 | fmt.Println("Your website is not https. Exit now.") 65 | fmt.Println("Please use -f to force start.") 66 | os.Exit(1) 67 | } else { 68 | fmt.Println("Okay let's roll😄") 69 | } 70 | return configData 71 | 72 | } 73 | 74 | func readConfig(cp string) (config, error) { 75 | 76 | jsonFile, err := os.Open(cp) 77 | if err != nil { 78 | return config{}, err 79 | } 80 | defer jsonFile.Close() 81 | 82 | var conf config 83 | byteValue, _ := ioutil.ReadAll(jsonFile) 84 | err = json.Unmarshal(byteValue, &conf) 85 | return conf, err 86 | } 87 | 88 | func randomEmoji() string { 89 | 90 | reasons := []string{"🙃", "🤨", "🤪", "😒", "🙂", "🥶", "🤔", "😶", "😐"} 91 | rand.Seed(time.Now().Unix()) 92 | return reasons[rand.Intn(len(reasons))] 93 | } 94 | 95 | func replyComment(msg, reply string, conf config) (result string) { 96 | type postFormat struct { 97 | Author int `json:"author"` 98 | AuthorUserAgent string `json:"author_user_agent"` 99 | Content string `json:"content"` 100 | Parent string `json:"parent"` 101 | Post string `json:"post"` 102 | } 103 | reply += conf.Tail 104 | g := strings.Split(strings.Split(msg, "id: ")[1], ",") 105 | pid, cid := g[0], g[1] 106 | post := postFormat{ 107 | Author: 1, 108 | AuthorUserAgent: "Telegram Bot by BennyThink", 109 | Content: reply, 110 | Parent: pid, 111 | Post: cid, 112 | } 113 | bytesData, err := json.Marshal(post) 114 | if err != nil { 115 | fmt.Println(err.Error()) 116 | } 117 | 118 | data := bytes.NewReader(bytesData) 119 | wpApi := conf.Url + "wp-json/wp/v2/comments" 120 | request, err := http.NewRequest("POST", wpApi, data) 121 | auth := []byte(fmt.Sprintf("%s:%s", conf.Username, conf.Password)) 122 | request.Header.Set("Content-Type", "application/json;charset=UTF-8") 123 | request.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString(auth)) 124 | client := http.Client{} 125 | resp, err := client.Do(request) 126 | if err != nil { 127 | fmt.Println(err.Error()) 128 | } 129 | var body struct { 130 | Message string `json:"message"` 131 | } 132 | if resp.StatusCode == 201 { 133 | result = "ok" 134 | } else { 135 | json.NewDecoder(resp.Body).Decode(&body) 136 | result = body.Message 137 | } 138 | return 139 | 140 | } 141 | 142 | func bot(conf config) { 143 | var owner = &tb.Chat{ID: conf.Uid} 144 | b, err := tb.NewBot(tb.Settings{ 145 | Token: conf.Token, 146 | Poller: &tb.LongPoller{Timeout: 10 * time.Second}, 147 | }) 148 | 149 | if err != nil { 150 | log.Fatal(err) 151 | return 152 | } 153 | b.Handle(tb.OnText, func(m *tb.Message) { 154 | if m.Chat.ID != conf.Uid { 155 | b.Notify(m.Sender, "Typing") 156 | b.Send(m.Sender, randomEmoji()) 157 | b.Send(m.Sender, "拒绝调戏。有问题请联系@BennyThink") 158 | 159 | } else if m.ReplyTo == nil { 160 | b.Notify(m.Sender, "Typing") 161 | b.Send(m.Sender, randomEmoji()) 162 | } else { 163 | comment := m.ReplyTo.Text 164 | reply := m.Text 165 | b.Notify(m.Sender, "upload_document") 166 | resp := replyComment(comment, reply, conf) 167 | b.Notify(m.Sender, "typing") 168 | b.Send(owner, resp) 169 | 170 | } 171 | }) 172 | b.Handle("/ping", func(m *tb.Message) { 173 | _ = b.Notify(m.Chat, tb.Typing) 174 | info := tgbot_ping.GetRuntime("botsrunner_wp-comments_1", "WordPress Comments Bot", "html") 175 | _, _ = b.Send(m.Chat, info, &tb.SendOptions{ParseMode: tb.ModeHTML}) 176 | }) 177 | 178 | b.Start() 179 | } 180 | func main() { 181 | c := getArgs() 182 | bot(c) 183 | } 184 | -------------------------------------------------------------------------------- /bot.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=A Telegram Bot for WordPress comments. 3 | After=network.target network-online.target nss-lookup.target 4 | 5 | [Service] 6 | User=nobody 7 | 8 | Restart=on-failure 9 | Type=simple 10 | WorkingDirectory=/opt/bin 11 | ExecStart=/opt/bin/bot 12 | 13 | [Install] 14 | WantedBy=multi-user.target -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "admin", 3 | "password": "admin", 4 | "url": "http://localhost/", 5 | "token": "907J6Tw", 6 | "uid": "23231321", 7 | "admin": 1, 8 | "tail": "
--本评论由Telegram Bot回复~❤️" 9 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module tgbot 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/tgbot-collection/tgbot_ping v1.0.2 7 | gopkg.in/tucnak/telebot.v2 v2.0.0-20200120165535-b6c3367fed99 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 h1:/EMHruHCFXR9xClkGV/t0rmHrdhX4+trQUcBqjwc9xE= 2 | code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= 3 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 6 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 7 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 8 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 9 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 10 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 11 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 12 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 13 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 14 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 15 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 16 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 17 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 18 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 19 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 20 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 21 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 22 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 23 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 24 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 25 | github.com/onsi/ginkgo v1.14.2 h1:8mVmC9kjFFmA8H4pKMUhcblgifdkOIXPvbhN1T36q1M= 26 | github.com/onsi/ginkgo v1.14.2/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 27 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 28 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 29 | github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA= 30 | github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= 31 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 32 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 33 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 37 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 38 | github.com/tgbot-collection/tgbot_ping v1.0.2 h1:CGKk6HCe3y87BQYHMKUpds3oOtSq0YcHUuezp3i0WZ0= 39 | github.com/tgbot-collection/tgbot_ping v1.0.2/go.mod h1:ee78nvZdBml2vl0yIKDGiNt/g8KFUHjB3s8gCevgrFc= 40 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 41 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 42 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 43 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 44 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 45 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M= 46 | golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 47 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 48 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 49 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 50 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 51 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 52 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 53 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 54 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 55 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= 57 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 58 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 59 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 60 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 61 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 62 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 63 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 64 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 65 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 66 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 67 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 68 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 69 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 70 | google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= 71 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 72 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 73 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 75 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 76 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 77 | gopkg.in/tucnak/telebot.v2 v2.0.0-20200120165535-b6c3367fed99 h1:EMQ2hWPVLRFE/1jEruLFkhy1ACshSEg3X3PeCtGM8mA= 78 | gopkg.in/tucnak/telebot.v2 v2.0.0-20200120165535-b6c3367fed99/go.mod h1:+//wyPtHTeW2kfyEBwB05Hqnxev7AGrsLIyylSH++KU= 79 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 80 | gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= 81 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 82 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 83 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 84 | -------------------------------------------------------------------------------- /pics/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/wp-comments/133421305ebfc26546a43611ce498273902641c8/pics/1.png -------------------------------------------------------------------------------- /pics/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/wp-comments/133421305ebfc26546a43611ce498273902641c8/pics/2.jpg -------------------------------------------------------------------------------- /pics/plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tgbot-collection/wp-comments/133421305ebfc26546a43611ce498273902641c8/pics/plugin.png -------------------------------------------------------------------------------- /test.php: -------------------------------------------------------------------------------- 1 | %s', $url, $text); 71 | } 72 | 73 | $token = $options["tg_token"]; 74 | $uid = $options["tg_uid"]; 75 | $proxy = $options["tg_proxy"]; 76 | $api = "https://api.telegram.org/bot$token/sendMessage"; 77 | 78 | $text = sprintf("A new comment on %s 79 | 80 | Title: %s 81 | User: %s (IP: %s %s) 82 | Email: %s 83 | Website: %s 84 | Comment: 85 | %s 86 | 87 | See all comments here: 88 | %s 89 | 90 | Permanent url: %s 91 | 92 | id: %s,%s 93 | ", 94 | a_link($comment_data["comment_website"], get_bloginfo()), 95 | $comment_data["comment_post_title"], 96 | $comment_data["comment_author"], 97 | $comment_data["comment_author_IP"], convertIP2Location($comment_data["comment_author_IP"]), 98 | $comment_data["comment_author_email"], 99 | a_link($comment_data["comment_author_url"]), 100 | $comment_data["comment_content"], 101 | a_link($comment_data['post_link'] . '#comments'), 102 | a_link($comment_data['comment_permlink']), 103 | $comment_data["comment_ID"], $comment_data["comment_post_ID"] 104 | ); 105 | $postdata = http_build_query(array('chat_id' => $uid, 'parse_mode' => "html", "text" => $text)); 106 | $opts = array( 107 | 'http' => array( 108 | 'proxy' => 'tcp://127.0.0.1:1087', 109 | 'method' => 'POST', 110 | 'header' => 'Content-type: application/x-www-form-urlencoded', 111 | 'content' => $postdata 112 | ) 113 | ); 114 | if ($proxy) { 115 | $opts["http"]["proxy"] = "tcp://$proxy"; 116 | } else { 117 | unset($opts["http"]["proxy"]); 118 | } 119 | 120 | $context = stream_context_create($opts); 121 | 122 | file_get_contents($api, false, $context); 123 | 124 | } 125 | 126 | add_filter('plugin_action_links', 'bt_tgbot_settings', 10, 2); 127 | function bt_tgbot_settings($links, $file) 128 | { 129 | static $this_plugin; 130 | if (!$this_plugin) { 131 | $this_plugin = plugin_basename(__FILE__); 132 | } 133 | 134 | if ($file == $this_plugin) { 135 | $settings_link = 'Settings'; 136 | array_unshift($links, $settings_link); 137 | } 138 | 139 | return $links; 140 | } 141 | 142 | 143 | add_action('admin_menu', 'bt_tgbot_admin_add_page'); 144 | function bt_tgbot_admin_add_page() 145 | { 146 | add_options_page( 147 | 'wp tgbot 设置页面', 148 | 'wptgbot 设置', 149 | 'manage_options', 150 | 'wptgbot', 151 | 'bt_tgbot_options_page'); 152 | } 153 | 154 | function bt_tgbot_options_page() 155 | { 156 | ?> 157 |
158 |

WordPress Comments Telegram Bot

159 | Setup bot token and uid 160 |
161 | 162 | 163 | 164 | 165 |
166 |
167 | 168 | "; 205 | 206 | } 207 | 208 | function bt_tgbot_uid() 209 | { 210 | $options = get_option('wptgbot'); 211 | echo ""; 212 | 213 | } 214 | 215 | function bt_tgbot_proxy() 216 | { 217 | $options = get_option('wptgbot'); 218 | echo "Leave blank if your don't need it.
"; 219 | 220 | echo ""; 223 | echo "

"; 224 | 225 | } 226 | --------------------------------------------------------------------------------