yeziruo 发布的文章

如题,玩了这么久Minecraft,开了那么多服,总该要会写服务器插件吧。

环境部署

JDK要求1.8,IDE随意,另外还要有Spigot的服务端,将服务端以库的形式导入到IDE即可。
比方说IDEA:文件 > 项目结构 > 项目设置 > 模块 来导入,导入后勾选导出选项。
13160207.png

入口类

现在我们随意取个名字,新建个类:

package com.yeziruo.mc.test;

import org.bukkit.plugin.java.JavaPlugin;

public class MainClass extends JavaPlugin {
    //服务器开启时执行
    public void onEnable() {
        super.onEnable();
    }

    //服务器关闭时执行
    public void onDisable() {
        super.onDisable();
    }
}

接着新建一个叫plugin.yml的文件,填写如下内容:

//名称,入口点,版本(打包时请删除本行说明)
name: Test
main: com.yeziruo.mc.test.MainClass
version: 0.0.1

接着打包,不同IDE方式不同,以IDEA为例:文件 > 项目结构 > 项目设置 > 工件 里添加。
13161101.png
并点击绿色箭头的加号,添加模块输出与plugin.yml文件,之后点击构建菜单下的构建工件,等打包完成后扔进服务端测试即可。

监听器

上面的例子只会在控制台有有一行输出罢了,下面我们做一个简单的欢迎插件,当玩家进入服务器后向其发送欢迎信息。
新建一个类,名称随意。

package com.yeziruo.mc.test;

//颜色
import org.bukkit.ChatColor;
//玩家对象
import org.bukkit.entity.Player;
//监听器头
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
//玩家加入事件
import org.bukkit.event.player.PlayerJoinEvent;

public class Welcome implements Listener {
    @EventHandler
    //on加监听器名去Event
    //监听器均在org.bukkit.event下,具体用法请翻阅文档
    public void onPlayerJoin(PlayerJoinEvent event) {
        Player player = event.getPlayer();
        player.sendMessage(ChatColor.GREEN + "欢迎加入," + player.getName() + "!");
    }
}

回到入口类,在onEnable中添加一行:

this.getServer().getPluginManager().registerEvents(new Welcome(), this);

第一个个命令

同样新建一个类,名称随意。

package com.yeziruo.mc.test;

import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class PlayerCommand implements CommandExecutor {
    public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
        //commandSender 执行实体(比方说玩家) strings 参数
        //if(strings.length == 0) return false;
        commandSender.sendMessage("PONG!");
        //成功执行返回true,否则返回false
        return true;
    }
}

当然,同样要注册命令,首先在plugin.yml下面添加:

//记得删除注释
commands:
  //命令名
  ping:
    //介绍
    description: Return PONG!
    //用法
    usage: /ping

然后又回到入口类,在onEnable中添加一行:

//命令节点名与执行类
this.getCommand("ping").setExecutor(new PlayerCommand());

同样打包,在服务器中输入/ping即可看到回复。

最后

SpigotMC JavaDoc:
https://hub.spigotmc.org/javadocs/spigot/index.html

如果要实时计算或产生效果,可以建个线程来跑。

摸了!
博客现在在缓慢更新中,没有嗝屁!

出于学习的需要,配置了一台测试环境用机,铭瑄N3160,配置了一张8111千兆为网卡,这主板板载百兆,不过我的出口只有50Mbps,所以板载口作为WAN口,8111作为LAN口。
我这里已经做好了,所以会和大家操作的有所不同。

创建虚拟机

PVE的安装在这里不再赘述。
由于我的机器不支持VT-D,所以没有写直通,不过也够用了。
首先创建一个虚拟机,在左边选中我们的数据节点,右键新建虚拟机:
212717.jpg

213110.jpg

这里一步一步来:

  • 一般:填写名称,勾选开机自启动,VM ID可指定也可默认
  • 操作系统:不使用任何介质
  • 系统:显卡选择VMWare兼容
  • 硬盘:默认,反正后面会删除
  • CPU:按需分配,这里有CPU权重,就是优先级
  • 内存:按需分配,1G足够
  • 网络:默认vmbr0,模型选择virtio半虚拟化

最后确认创建即可。

创建好后点击我们的虚拟机,点击硬件:

213926.jpg

点击我们新建的硬盘,点击分离它会变成未使用磁盘,再点击它删除,将这个硬盘删除掉。
将我们准备的Openwrt的镜像通过WinSCP上传到我们的PVE上,并执行:

qm importdisk <VM ID> <镜像名称> local-lvm

等完成后回到硬件界面,会发现多了一个未使用磁盘,双击它,直接点击添加,再然后来到选项,双击引导顺序,只选择我们的硬盘。

214823.jpg

这样,虚拟机的配置告一段落。

网络配置

回到服务器节点,点击网络:

215034.jpg

点击创建选择LinuxBridge,名称默认,桥接端口填写物理网卡名称,比方说enp3s0:

215313.jpg

这样,PVE上就有了两个虚拟交换机(网桥),其中默认就有的vmbr0我们作为LAN交换机,新建的vmbr1作为WAN交换机。

我们双击默认交换机vmbr0,更改里面的管理IP与网关,改为我们之后Openwrt的网段,比方说192.168.1.x。

215918.jpg

配置完成后返回Openwrt虚拟机,来到硬件,点击添加,添加网络设备:

220141.jpg

这里分别添加vmbr0与vmbr1,模型选择virtio半虚拟化(更新:不要选E1000,不然性能不行),之后选择节点,点击右上角的重启来生效。

后续配置

连接网线到vmbr0所绑定的网口上。
打开 控制面板\网络和 Internet\网络连接,右键属性,互联网协议4,属性,填写与PVE和将要配置的Openwrt同一网段。
在上面重启后,我们输入PVE的管理IP,回到我们的虚拟机,点击控制台,在里面配置我们的WAN口与LAN口,你可以输入ifconfig来查看端口的MAC地址,再根据硬件选项卡中网络设备所对应的MAC地址来判断,之后在控制台中按照常规配置WAN口与LAN口即可,之后可以通过网页来配置把玩Openwrt。

221032.jpg

后续工作

之后需要配置BIOS来电自启,我这里工作日晚上会断电,所以还要配置提前关机。

(/etc/crontab)
30 22 * * * root /sbin/shutdown -h now

配置单

  • 主板:铭瑄N3160
  • 内存:金士顿DDR3 1333 4GB (这板子挑内存条,手头上的内存条没办法组双通道)
  • 存储:旧的东芝500GB硬盘 (反正对速度没有需求)
  • 网卡:PCIE RTL8111
  • 预计功耗:25w

由MaxMind提供,有ASN,国家与市三种类型,支持IPv4与IPv6,使用mmdb或CSV格式分发。
注意:自2019年12月30起,需要注册账号下载。
https://dev.maxmind.com/geoip/geoip2/geolite2/

Python的使用

安装官方提供的模块:

pip3 install geoip2

示例:

import geoip2.database
gi = geoip2.database.Reader('GeoLite2-Country.mmdb')
gn = gi.country('1.1.1.1')
#city,country,asnisp
#print(gn)
print(gn.country.names['zh-CN'])
print(gn.country.iso_code)
#输出:
澳大利亚
AU

Nginx的使用

Nginx需要编译Geoip2模块(有Geoip模块,但只适用旧版的dat格式)。
https://github.com/leev/ngx_http_geoip2_module

(http)
geoip2 /etc/nginx/geoip2/GeoLite2-Country.mmdb {
    #自动重载
    auto_reload 5m;
    $geoip2_metadata_country_build metadata build_epoch;
    #国家代码
    $geoip2_country_code default=US country iso_code;
    #国家名称
    #$geoip2_country_name country names zh-CN;
}
if($geoip2_country_code != CN) {
    deny all;
}

注意替换example.com与证书路径!
配置如下:

server {
    listen [::]:443;
    listen 443;
    server_name duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    sub_filter_types text/html text/css application/x-javascript text/xml;
    access_log off;

    location / {
        proxy_pass https://duckduckgo.com;
        proxy_set_header Host duckduckgo.com;
        proxy_set_header Accept-Encoding "";
        proxy_set_header Referer "";
        sub_filter "duckduckgo.com" "duckgo.example.com";
        sub_filter_once off;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name images.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://images.duckduckgo.com;
        proxy_set_header Host images.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name icons.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://icons.duckduckgo.com;
        proxy_set_header Host icons.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name ac.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://ac.duckduckgo.com;
        proxy_set_header Host ac.duckduckgo.com;
    }
}

server {
    listen [::]:443;
    listen 443;
    server_name external-content.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://external-content.duckduckgo.com;
        proxy_set_header Host external-content.duckduckgo.com;
    }
}
server {
    listen [::]:443;
    listen 443;
    server_name improving.duckgo.example.com;
    ssl on;
    ssl_certificate /root/ssl_cert/duck.crt;
    ssl_certificate_key /root/ssl_cert/duck.key;
    access_log off;

    location / {
        proxy_pass https://improving.duckduckgo.com;
        proxy_set_header Host improving.duckduckgo.com;
    }
}

引入

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/lrsjng.jquery-qrcode/0.18.0/jquery-qrcode.min.js"></script>

快速使用

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" name="viewport" content="width=device-width,initial-scale=1" />
    <title>QRCode</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/lrsjng.jquery-qrcode/0.18.0/jquery-qrcode.min.js"></script>
</head>
<body>
    <div id="qrcode_demo"></div>
    <script>
        $(function(){
            $('#qrcode_demo').qrcode({text:"https://blog.yeziruo.cn/"});
        });
    </script>
</body>
</html>

参数

render: "table" 或 "canvas"(默认)
text: 文本内容
width: 256 宽
height: 256 高
background: "#ffffff" 背景颜色
foreground: "#66ccff" 前景颜色
typeNumber: -1 计算模式(暂不清楚作用)
correctLevel: QRErrorCorrectLevel.H 纠错等级(不建议指定,否则将不会渲染)
    QRErrorCorrectLevel.L,  (7%)
    QRErrorCorrectLevel.M, (15%)
    QRErrorCorrectLevel.Q, (25%)
    QRErrorCorrectLevel.H, (30%)

中文支持

使用该插件生成二维码,如果内容包含中文,则需要将Unicode(UTF-16)转为UTF-8。

function utf16to8(str) {
    var out, i, len, c;
    out = "";
    len = str.length;
    for(i = 0; i < len; i++) {
        c = str.charCodeAt(i);
        if ((c >= 0x0001) && (c <= 0x007F)) {
            out += str.charAt(i);
        } else if (c > 0x07FF) {
            out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
            out += String.fromCharCode(0x80 | ((c >>  6) & 0x3F));
            out += String.fromCharCode(0x80 | ((c >>  0) & 0x3F));
        } else {
            out += String.fromCharCode(0xC0 | ((c >>  6) & 0x1F));
            out += String.fromCharCode(0x80 | ((c >>  0) & 0x3F));
        }
    }
    return out;
}

缘由

在上一篇文章中,我使用了Python来控制我的键盘,但我的OBS好像有时有反应,有时没反应,所以从抽屉中找到了5年前买的Digispark ,用它来做一个硬键盘。
不得不说幸好当初买了两块,有一块被我改错熔丝锁住了。

Arduino环境部署

文件>首选项>附加开发板管理器网址添加如下:

http://digistump.com/package_digistump_index.json

工具>开发板>开发板管理器中找到Digispark后安装即可。

原理图

  • 由于Digispark只有6个IO,且有2个IO用作USB通信,所以最后只剩下4个可用IO(如果你没有改过熔丝位,那就是3个,P5不可用)。
  • 为了塞下5个按钮,使用了ADC(P5/A0)来检测按键按下。
  • 该板P1接板载红色LED。

IMG114153.png

从KEY1到KEY5的ADC值为(VCC=5v):0|509|520|530|540。

代码

#include "DigiKeyboard.h"

void setup() {
  pinMode(1,OUTPUT);
}

//void button(int key_a,int key_b){}
void loop() {
  int AnalogValue = analogRead(A0);
  if(AnalogValue == 0){
    digitalWrite(1,HIGH);
    DigiKeyboard.sendKeyStroke(KEY_F1,MOD_CONTROL_RIGHT);
    delay(200);
  }
  if(508 <= AnalogValue && AnalogValue <= 512){
    digitalWrite(1,HIGH);
    DigiKeyboard.sendKeyStroke(KEY_F2,MOD_CONTROL_RIGHT);
    delay(200);
  }
  if(518 <= AnalogValue && AnalogValue <= 522){
    digitalWrite(1,HIGH);
    DigiKeyboard.sendKeyStroke(KEY_F3,MOD_CONTROL_RIGHT);
    delay(200);
  }
  if(528 <= AnalogValue && AnalogValue <= 532){
    digitalWrite(1,HIGH);
    DigiKeyboard.sendKeyStroke(KEY_F4,MOD_CONTROL_RIGHT);
    delay(200);
  }
  if(538 <= AnalogValue && AnalogValue <= 542){
    digitalWrite(1,HIGH);
    DigiKeyboard.sendKeyStroke(KEY_F5,MOD_CONTROL_RIGHT);
    delay(200);
  }
  digitalWrite(1,LOW);
  DigiKeyboard.delay(200);
}

成品

IMG103157.jpg

网上有使用PyMouse,PyKeyboard,PyUserInput(前两者的整合,不活跃)的,但发现我并不适用,Pip都装不上,所以寻着PyUserInput的Readme文件找到了Pynput这个库。
这是一篇水文章。

pip install pynput

键盘

from pynput.keyboard import Key,Controller
keyboard = Controller()

#dir(Key) 功能键
#['__class__', '__doc__', '__members__', '__module__', 'alt', 'alt_l', 'alt_r', 'backspace', 'caps_lock', 'cmd', 'cmd_r', 'ctrl', 'ctrl_l', 'ctrl_r', 'delete', 'down', 'end', 'enter', 'esc', 'f1', 'f10', 'f11', 'f12', 'f13', 'f14', 'f15', 'f16', 'f17', 'f18', 'f19', 'f2', 'f20', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'home', 'insert', 'left', 'media_next', 'media_play_pause', 'media_previous', 'media_volume_down', 'media_volume_mute', 'media_volume_up', 'menu', 'num_lock', 'page_down', 'page_up', 'pause', 'print_screen', 'right', 'scroll_lock', 'shift', 'shift_r', 'space', 'tab', 'up']

#按下按键
keyboard.press(Key.space)
#释放按键
keyboard.release(Key.space)
#等待按下按键
with keyboard.pressed(Key.shift):
    keyboard.press('a')
    keyboard.release('a')

鼠标

from pynput.mouse import Button,Controller
mouse = Controller()
#读取位置
print(mouse.position)
#设置一个位置
mouse.position = (1926,2020)
#相对移动
mouse.move(88,-88)
#鼠标按键
mouse.press(Button.left)
mouse.release(Button.left)
#双击
mouse.click(Button.left,2)
#滚轮(向下滚动2格)
mouse.scroll(0,2)

另有监控键盘和鼠标的官方例程,不过我用不上,所以就不复制到文章里了:

https://pynput.readthedocs.io/en/latest/mouse.html#monitoring-the-mouse
https://pynput.readthedocs.io/en/latest/keyboard.html#monitoring-the-keyboard

应用

可以写一个Web宏键盘:
Screenshot_2020-07-25.jpg