├── 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 | License: Apache-2.0 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 | ![image](https://github.com/labi-xiaoxin/img/blob/main/wechat-private-talk.jpg?raw=true) 74 | ![image](https://github.com/labi-xiaoxin/img/blob/main/wechat-public-room.jpg?raw=true) 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 | --------------------------------------------------------------------------------