├── config
├── config-chatgpt.js
├── config-proxy.js
└── config.js
├── src
├── entity
│ ├── Message-Type.js
│ ├── wx-push-data.js
│ └── ChatGPTModel.js
└── core
│ └── sendMessage.js
├── package.json
├── test.js
├── index.js
├── README.md
└── LICENSE
/config/config-chatgpt.js:
--------------------------------------------------------------------------------
1 | // ChatGPT url
2 | export const CHATGPT_URL = "https://api.openai.com/v1/chat/completions";
3 | // ChatGPT api-key
4 | export const CHATGPT_API_KEY = "sk-xxxxxxxxxxxxxxxxxx";
5 | // 使用模型
6 | export const CHATGPT_MODEL = "gpt-3.5-turbo-16k";
--------------------------------------------------------------------------------
/config/config-proxy.js:
--------------------------------------------------------------------------------
1 | // 代理配置:国内环境需要配置
2 | const PROXY_HOST = "127.0.0.1";
3 | const PROXY_PROTOCOL = "http";
4 | const PROXY_PORT = "1087";
5 |
6 | export const PROXY_CONFIG = {
7 | protocol: PROXY_PROTOCOL,
8 | host: PROXY_HOST,
9 | port: PROXY_PORT,
10 | }
--------------------------------------------------------------------------------
/config/config.js:
--------------------------------------------------------------------------------
1 | // 机器人名
2 | export const robotName = '迷茫的21世纪的新青年'
3 |
4 | // 群聊白名单,白名单内的群聊才会自动回复
5 | export const roomWhiteList = ['小陈要勤奋','522','毁灭吧']
6 |
7 | // 联系人白名单,白名单内的联系人才会自动回复
8 | export const aliasWhiteList = ['讲道理', '静守时光', '小陈要勤奋','干净']
9 |
10 | // 后端消息处理接口
11 | export const msgPushUrl = "http://127.0.0.1:8078/wx/order/wxMsg"
--------------------------------------------------------------------------------
/src/entity/Message-Type.js:
--------------------------------------------------------------------------------
1 | //未知消息类型
2 | export const MESSAGE_TYPE_UNKNOWN = 0;
3 | //附件消息类型
4 | export const MESSAGE_TYPE_ATTACHMENT = 1;
5 | //音频消息类型
6 | export const MESSAGE_TYPE_AUDIO = 2;
7 | //名片消息类型
8 | export const MESSAGE_TYPE_CONTACT = 3;
9 | //表情消息类型
10 | export const MESSAGE_TYPE_EMOTICON = 5;
11 | //图片消息类型
12 | export const MESSAGE_TYPE_IMAGE = 6;
13 | //文本消息类型
14 | export const MESSAGE_TYPE_TEXT = 7;
15 | //链接消息类型
16 | export const MESSAGE_TYPE_URL = 14;
17 | //视频消息类型
18 | export const MESSAGE_TYPE_VIDEO = 15;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wechat-bot-wechat4u",
3 | "version": "1.0.0",
4 | "description": "基于wechat4u构建的微信机器人",
5 | "main": "wechat-bot-wechat4u.js",
6 | "type": "module",
7 | "scripts": {
8 | "dev": "node ./index.js",
9 | "test": "node ./test.js"
10 | },
11 | "author": "labi-xiaoxin",
12 | "license": "Apache-2.0",
13 | "dependencies": {
14 | "axios": "^1.6.7",
15 | "https-proxy-agent": "^7.0.4",
16 | "qrcode-terminal": "^0.12.0",
17 | "wechaty": "^1.20.2",
18 | "wechaty-puppet-wechat4u": "^1.14.14"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/entity/wx-push-data.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion: 微信消息推送对象
3 | * @version: 0.0.1
4 | * @Author: xiaoxin
5 | * @Date: 2024-02-02 17:37:47
6 | * @LastEditors: xiaoxin
7 | * @LastEditTime: 2024-02-04 16:24:59
8 | */
9 | export class WxPushData {
10 | /**
11 | * 消息内容
12 | */
13 | content;
14 | /**
15 | * 消息发送人
16 | */
17 | contact;
18 | /**
19 | * 消息发送人ID
20 | */
21 | talkerId;
22 | /**
23 | * 监听ID 私聊才有
24 | */
25 | listenerId;
26 | /**
27 | * 房间ID 群聊才有
28 | */
29 | roomId;
30 |
31 |
32 | /**
33 | * 构造器
34 | * @param content 消息内容
35 | * @param contact 消息发送人
36 | */
37 | constructor(content, contact,talkerId,listenerId,roomId) {
38 | this.content = content;
39 | this.contact = contact;
40 | this.talkerId = talkerId;
41 | this.listenerId = listenerId;
42 | this.roomId = roomId;
43 | }
44 | }
45 |
46 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | import { HttpsProxyAgent } from "https-proxy-agent"
2 | import axios from "axios"
3 | import { PROXY_CONFIG } from "./config/config-proxy.js";
4 | import * as CHATGPT_CONFIG from "./config/config-chatgpt.js"
5 |
6 | //代理配置
7 | const agent = new HttpsProxyAgent(PROXY_CONFIG);
8 | //对话参数配置
9 | let data = JSON.stringify({
10 | "model": CHATGPT_CONFIG.CHATGPT_MODEL,
11 | "messages": [
12 | {
13 | "role": "user",
14 | "content": "你好?”"
15 | }
16 | ],
17 | "max_tokens": 1024,
18 | "temperature": 1,
19 | "stream": false
20 | });
21 | //请求参数配置
22 | let config = {
23 | timeout: 120000,
24 | method: 'post',
25 | maxBodyLength: Infinity,
26 | url: CHATGPT_CONFIG.CHATGPT_URL,
27 | headers: {
28 | 'Authorization': `Bearer ${CHATGPT_CONFIG.CHATGPT_API_KEY}`,
29 | 'Content-Type': 'application/json'
30 | },
31 | httpsAgent: agent,
32 | data: data
33 | };
34 |
35 | axios.request(config)
36 | .then((response) => {
37 | console.log("返回消息:" + JSON.stringify(response.data));
38 | console.log("\n 测试成功");
39 | })
40 | .catch((error) => {
41 | console.log("异常消息:" + error);
42 | console.log("\n 测试失败")
43 | });
44 |
--------------------------------------------------------------------------------
/src/entity/ChatGPTModel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version: 0.0.1
4 | * @Author: xiaoxin
5 | * @Date: 2024-03-06 13:22:07
6 | * @LastEditors: xiaoxin
7 | * @LastEditTime: 2024-03-06 15:58:03
8 | */
9 | /**
10 | * 这里只封装一些常用对象,如需微调自行查看https://platform.openai.com/docs/api-reference/chat/create文档进行微调
11 | */
12 | export class ChatGPTModel {
13 | /**
14 | * 模型
15 | * 获取:https://platform.openai.com/docs/models/model-endpoint-compatibility
16 | * string
17 | */
18 | model;
19 | /**
20 | * 对话消息
21 | * array
22 | */
23 | messages;
24 | /**
25 | * 聊天完成过程中可以生成的最大令牌数。
26 | * integer or null
27 | * 默认值为不同模型不同的值
28 | */
29 | max_tokens;
30 | /**
31 | * 使用的采样温度,介于0和2之间。较高的值(如0.8)将使输出更具随机性,而较低的值(如0.2)将使输出更具针对性和确定性。
32 | * number or null
33 | * 默认1
34 | */
35 | temperature;
36 | /**
37 | * 流输出
38 | */
39 | stream;
40 |
41 |
42 | /**
43 | * 构造器
44 | */
45 | constructor(model, messages,max_tokens,temperature) {
46 | this.model = model;
47 | this.messages = messages;
48 | this.max_tokens = max_tokens;
49 | this.temperature = temperature;
50 | this.stream = false
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * @Descripttion:
3 | * @version: 0.0.1
4 | * @Author: xiaoxin
5 | * @Date: 2024-02-21 16:23:39
6 | * @LastEditors: xiaoxin
7 | * @LastEditTime: 2024-02-27 18:56:09
8 | */
9 | // 导包
10 | import { WechatyBuilder, ScanStatus, log } from "wechaty"
11 | import qrcodeTerminal from "qrcode-terminal"
12 | import { sendMessage } from './src/core/sendMessage.js'
13 | import { robotName } from "./config/config.js"
14 |
15 | // 扫描二维码
16 | function onScan(qrCode,status){
17 | if (status === ScanStatus.Waiting || status === ScanStatus.Timeout) {
18 | // 在控制台显示二维码
19 | qrcodeTerminal.generate(qrCode, { small: true })
20 | const qrcodeImageUrl = ['https://api.qrserver.com/v1/create-qr-code/?data=', encodeURIComponent(qrCode)].join('')
21 | console.log(`二维码地址: ${qrcodeImageUrl}`)
22 | } else {
23 | log.info(`二维码扫描结果: ${ScanStatus[status]} ${status}`)
24 | }
25 | }
26 |
27 | //登录
28 | function onLogin(user){
29 | log.warn(`当前时间: ${new Date()} --- 机器人:${user} --- 登录成功`)
30 | }
31 |
32 | //注销
33 | function onLoout(user){
34 | log.warn("当前时间: %s --- 机器人:%s --- 登出",new Date(),user)
35 | }
36 |
37 | //接收消息
38 | async function onMessage(message){
39 | await sendMessage(message)
40 | }
41 |
42 |
43 | //初始化机器人
44 | const bot = WechatyBuilder.build({
45 | name: robotName,
46 | puppet: 'wechaty-puppet-wechat4u'
47 | })
48 |
49 | //机器人监听事件
50 | bot
51 | .on("scan", onScan)
52 | .on("login",onLogin)
53 | .on("logout", onLoout)
54 | .on("message", onMessage)
55 |
56 | //启动机器人
57 | bot
58 | .start()
59 | .then(() => {
60 | log.warn(`机器人启动成功`);
61 | })
62 | .catch((e) => log.error(e))
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 欢迎使用👏🏻wechat-bot-wechat4u
3 |
4 |
5 |
6 |
7 |
8 | ----------
9 |
10 | ## 🌆 简介
11 |
12 | wechat-bot-wechat4u,基于`wechat4u`进行开发,接收微信账号消息并提供自动回复、记录存储、消息推送、消息转发等功能,可通过自定义实现各种功能,诸如:社群接单机器人、人工智能机器人、聊天伴侣等。
13 |
14 | ----------
15 |
16 | ## 🚀 功能点
17 |
18 | - **消息自动回复**:支持前端处理,支持对接后端接口实现更多功能
19 | - **群聊白名单**:支持指定名单内群聊回复
20 | - **私聊白名单**:支持指定名单内私聊回复
21 | - **支持手机同时在线**:基于网页版API开发,能够在手机登录微信时,同时使用机器人
22 | - **接入ChatGPT**:理论上4也能支持,图片暂不支持。
23 |
24 | ----------
25 |
26 | ## 🔥 使用
27 |
28 | ### NodeJS运行
29 |
30 | > 请确认NodeJS版本为18.0.0
31 | > 请确保您的账号可以登录[网页版微信](https://wx.qq.com/)
32 |
33 | - 克隆本项目:`git clone https://github.com/labi-xiaoxin/wechat-bot-wechat4u.git`
34 | - 进入本项目:`cd wechat-bot-wechat4u`
35 | - 安装依赖项:`npm install`
36 | - 自定义相关配置:
37 | - `config.js`:该文件配置机器人名称、群聊白名单、私聊白名单、(如有)后端接口
38 | - `config-chatgpt.js`:该文件配置ChatGPT:接口请求地址、apikey、model
39 | - `config-proxy.js`:国内环境下,需要使用代理才能请求ChatGPT。该文件配置相关代理:代理主机、代理端口、代理协议。如代理需要账号密码,自行调整。
40 | - `src/core/sendMessage.js`:该文件内`reMsg()`方法目前有2种回复方式:
41 | - 1、简单返回消息:直接返回定义好的消息
42 | - 2、接口返回消息:将消息封装好,通过Axios发送请求至后端,后端处理返回消息(如需要实现AI机器人等功能,可自行编写后端)
43 | - 3、请求ChatGPT:将消息通过Axios发送请求至OpenAI,处理完成后响应消息。
44 | - 测试项目:`npm run test`
45 | - 启动项目:`npm run dev`
46 |
47 | 项目正常运行日志如下:
48 | ```log
49 | > wechat-bot-wechat4u@1.0.0 dev
50 | > node ./index.js
51 |
52 | 10:21:58 WARN 机器人启动成功
53 | ▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
54 | █ ▄▄▄▄▄ █▀▄█▀ ▀▀ █▀█▄▀█ ▄▄▄▄▄ █
55 | █ █ █ █▄ ▄█ ▀▄█▀▀█ █ █ █
56 | █ █▄▄▄█ █ ▀█▀██▄▀▄ ▀▀█ █▄▄▄█ █
57 | █▄▄▄▄▄▄▄█ ▀▄█ █ ▀ ▀▄▀ █▄▄▄▄▄▄▄█
58 | █ ▄▀▄▀▀▄ ▀█ ▀█ ▄██▀█▄█▀ ▄▄▀▄▄▀█
59 | ██▄▀█▄█▄▀█▄▄██▀██▄▄ ▄▄ ▄ ▄ ██
60 | █▀ ▀▀ █▄ ▀▀▀▀ ██▀▄▄█ ▀▄██▄█▄▄█
61 | █ █ ▄▀▄ ▀█ ▀▀▀▀▀▀▀▄ ▀██ ▄ ▄█
62 | ██ ▄▄▀▄█▄▄██▄█▄ ██▄▄▀▀█▀█▄▀█
63 | █▄█▀▀ ▀▄█▄ █▀██▀▀▀▀▀█▄▄▄▄▀██ █
64 | █▄████▄▄▄▀▀▄ ▄▀ ▀█▄▄ ▄▄▄ █ ▀▀█
65 | █ ▄▄▄▄▄ █▄▀▀ █▀▄▄▀█ █▄█ ▀▀ ▀█
66 | █ █ █ █▀▀ █▀▄▀▄▄█▀▄▄ ▀▀▀██
67 | █ █▄▄▄█ █▀▄▄ █▄▄█▀ █▀ ██▀▄▄▄▀▄█
68 | █▄▄▄▄▄▄▄█▄▄▄█▄▄█▄█▄▄█▄▄█▄█▄█▄██
69 |
70 | 二维码地址: https://api.qrserver.com/v1/create-qr-code/?data=https%3A%2F%2Flogin.weixin.qq.com%2Fl%2FAY5C0WwogA%3D%3D
71 | ```
72 |
73 | 
74 | 
75 | ## 🤝 为项目添砖加瓦
76 |
77 | 欢迎提出 Contributions, issues 与 feature requests!
78 | 随时查看 [issues page](https://github.com/labi-xiaoxin/wechat-bot-wechat4u/issues).
79 |
80 | ## 感谢支持 🙏
81 |
82 | 如果这个项目对你产生了一点的帮助,请为这个项目点上一颗 ⭐️
--------------------------------------------------------------------------------
/src/core/sendMessage.js:
--------------------------------------------------------------------------------
1 | import { log } from "wechaty"
2 | import * as MessageType from "../entity/Message-Type.js"
3 | import * as MYCONFIG from "../../config/config.js"
4 | import axios from "axios"
5 | import { WxPushData } from "../entity/wx-push-data.js"
6 | import * as CHATGPT_CONFIG from "../../config/config-chatgpt.js"
7 | import { ChatGPTModel } from "../entity/ChatGPTModel.js"
8 | import { HttpsProxyAgent } from "https-proxy-agent"
9 | import { PROXY_CONFIG } from "../../config/config-proxy.js";
10 |
11 |
12 | /**
13 | * 处理消息是否需要回复
14 | * @param message 消息对象
15 | */
16 | export async function sendMessage(message) {
17 | const MyMessage = {
18 | type: await message.type(),
19 | self: await message.self(),
20 | text: await message.text(),
21 | room: await message.room(),
22 | mentionSelf: await message.mentionSelf(),
23 | roomName: (await message.room()?.topic()) || null,
24 | alias: await message.talker().alias(),
25 | date: await message.date(),
26 | talkerId: await message.payload.talkerId,
27 | listenerId: await message.payload.listenerId == undefined ? null : message.payload.listenerId,
28 | roomId: await message.payload.roomId == undefined ? null : message.payload.roomId
29 | }
30 | //1、判断消息是否符合逻辑
31 | var checkResult = checkMessage(MyMessage);
32 | if (!checkResult) {
33 | return
34 | }
35 | //2、发送后端处理消息,并返回发送微信
36 | forwardMsg(MyMessage,message);
37 | }
38 |
39 | /**
40 | * 判断消息是否符合逻辑
41 | *
42 | * @param {MyMessage} message 消息
43 | * @returns 符合逻辑返回true;否则返回false
44 | */
45 | function checkMessage(message) {
46 | //消息类型不是文本
47 | if (message.type != MessageType.MESSAGE_TYPE_TEXT) {
48 | return false
49 | }
50 | //自己发送的消息不处理
51 | if (message.self) {
52 | return false
53 | }
54 | //引用的文本不处理
55 | const regexp = /「[^」]+」\n- - - - - - - - - - - - - -/;
56 | if (regexp.test(message.text)) {
57 | return false
58 | }
59 | //非白名单内的不处理
60 | if (isRoomOrPrivate(message) == 0) {
61 | return false;
62 | }
63 | return true;
64 | }
65 |
66 | /**
67 | * 判断消息是否
68 | *
69 | * 是房间消息且@机器人,则返回1
70 | * 是私聊且在白名单内,则返回2
71 | * 否则返回0
72 | *
73 | * @param {MyMessage} message 消息内容
74 | */
75 | function isRoomOrPrivate(message) {
76 | //房间内的消息需要@ 且群聊在名单内
77 | if (message.room != null && message.mentionSelf == true && MYCONFIG.roomWhiteList.includes(message.roomName)) {
78 | return 1;
79 | }//非房间内消息,且发送人备注在名单内
80 | else if (message.room == null && MYCONFIG.aliasWhiteList.includes(message.alias)) {
81 | return 2;
82 | } else {
83 | return 0;
84 | }
85 | }
86 |
87 | /**
88 | * 发送后端处理消息,并返回发送微信
89 | * @param {Message} message 消息对象
90 | */
91 | async function forwardMsg(MyMessage,message) {
92 | log.info(`\n 消息发送时间:${MyMessage.date}
93 | 消息发送人:${MyMessage.alias}
94 | 消息类型:${MyMessage.type}
95 | 消息是否@我:${MyMessage.mentionSelf}
96 | 消息内容:${MyMessage.text} `)
97 |
98 | //1、简单返回消息
99 | // sendSay(message,"你好");
100 |
101 | //2、发送后端
102 | // axios({
103 | // url: MYCONFIG.msgPushUrl,
104 | // method: 'post',
105 | // headers: {
106 | // 'Content-Type': 'application/json'
107 | // },
108 | // data: JSON.stringify(
109 | // new WxPushData(
110 | // //消息
111 | // MyMessage.text,
112 | // //消息发送人备注
113 | // MyMessage.alias,
114 | // //消息发送者ID 微信ID不是永久性
115 | // MyMessage.talkerId,
116 | // //私聊才有listenerID
117 | // MyMessage.listenerId,
118 | // //群聊才有房间ID
119 | // MyMessage.roomId,
120 | // //apikey
121 | // MYCONFIG.apiKey
122 | // ))
123 | // }).then(result => {
124 | // var reMsg = result.data.msg;
125 | // sendSay(message, reMsg,MyMessage);
126 | // }).catch(response => {
127 | // log.error(`异常响应:${response}`);
128 | // sendSay(message, `异常响应:${response}`,MyMessage);
129 | // return `异常响应:${responese}`;
130 | // })
131 |
132 | //3、发送ChatGPT
133 | const agent = new HttpsProxyAgent(PROXY_CONFIG);
134 | //对话参数配置
135 | let data = JSON.stringify({
136 | "model": CHATGPT_CONFIG.CHATGPT_MODEL,
137 | "messages": [
138 | {
139 | "role": "user",
140 | "content": `${MyMessage.text}`
141 | }
142 | ],
143 | "max_tokens": 1024,
144 | "temperature": 1,
145 | "stream": false
146 | });
147 | //请求参数配置
148 | let config = {
149 | timeout: 120000,
150 | method: 'post',
151 | maxBodyLength: Infinity,
152 | url: CHATGPT_CONFIG.CHATGPT_URL,
153 | headers: {
154 | 'Authorization': `Bearer ${CHATGPT_CONFIG.CHATGPT_API_KEY}`,
155 | 'Content-Type': 'application/json'
156 | },
157 | httpsAgent: agent,
158 | data: data
159 | };
160 |
161 | axios.request(config)
162 | .then((response) => {
163 | var reMsg = response.data.choices[0].message.content;
164 | sendSay(message, reMsg,MyMessage);
165 | })
166 | .catch((error) => {
167 | log.error(`异常响应:${JSON.stringify(error)}`);
168 | sendSay(message, `异常响应:${JSON.stringify(error)}`,MyMessage);
169 | return `异常响应:${JSON.stringify(error)}`;
170 | });
171 | }
172 |
173 | /**
174 | * 发送回复逻辑
175 | *
176 | * 区分群聊私聊
177 | * @param {Message} message 消息内容
178 | * @param {String} reStr 回复内容
179 | */
180 | function sendSay(message, reStr, MyMessage) {
181 | const isROP = isRoomOrPrivate(MyMessage);
182 | //房间内消息
183 | if (isROP == 1) {
184 | message.room().say(`${(reStr)}\n @by ${MYCONFIG.robotName}`, message.talker())
185 | } else if (isROP == 2) {
186 | //私聊消息
187 | message.talker().say(`${(reStr)}\n @by ${MYCONFIG.robotName}`)
188 | }
189 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------