text:
import rq from "request-promise-native";
import tough from "tough-cookie";
import eve from "~/tools/events.js";
import proxyAgent from "proxy-agent";

const RETRY_LIMIT=5; //重试次数
function  sleep(ms){
    return new Promise(reject=>{
        setTimeout(()=>{reject(ms)},ms);
    })
}
function isJSON(str) {
    if (typeof str == 'string') {
        try {
            var obj=JSON.parse(str);
            if(typeof obj == 'object' && obj ){
                return true;
            }else{
                return false;
            }

        } catch(e) {
            //console.log('error：'+str+'!!!'+e);
            return false;
        }
    }
    return false;
    //console.log('It is not a string!')
}
/* 直接使用api对象,这是一个全局对象 */
export default new class{
    constructor(){
        this.baseURL = `https://api.live.bilibili.com/`;
        this.ts = 0;
        this.thread = 0;
        this.Agent = {
            status :false,
            Global:false,
            Agent:{},
        }
    }
    regist(url,name){ //注册快捷调用
        if(!this.FuckList){
            this.FuckList = [];
        }
        this.FuckList.push({url,name});
    }
    fuck(key,...data){ // 快捷调用
        let Fuckurl  = "";
        for(let {url,name} of this.FuckList){
            if(name.indexOf(key)>=0){
                Fuckurl = url;
            }
        }
        return this.send(Fuckurl,...data);
    }

    use(user){
        if(user.isLogin){
            let sma = {
                eve:eve,
                user:user,
                headers:{
                    'host': 'api.live.bilibili.com',
                },
                ori_rq:this.origin,
                cookies:user.cookies,
                FuckList:this.FuckList,
                baseURL:this.baseURL,
                Agent:this.Agent,
                fuck:function(key,...data){
                    let Fuckurl  = "";
                    for(let {url,name} of this.FuckList){
                        if(name.indexOf(key)>=0){
                            Fuckurl = url;
                        }
                    }
                    return this.send(Fuckurl,...data);
                },
                send:function(url,data,method="get",baseURL=this.baseURL,retry=0){
                    let options = {
                        method,
                        uri: baseURL+url,
                        qs: data,
                        form:data,
                        headers: this.headers,
                        timeout:2000,
                        json: true // Automatically parses the JSON string in the response
                    };
                    return this.origin(options).then((res)=>{
                        if(res.code === 0){
                            return res;
                        }else{
                            /* Un normal */
                            if(res.code===-101){
                                this.eve.emit("user_validate",this.user);
                            }
                            return res;
                        }
                    }).catch((e)=>{

                        if(retry < RETRY_LIMIT){
                            return this.send(url,data,method,baseURL,retry+1);
                        }
                        throw new Error("网络异常");
                    });
                },
                origin:function(options){
                    let jar = new rq.jar();
                    let domain = options.uri.match(/https?:\/\/([^\/]+)/i)[0];
                    domain = domain.substr(domain.indexOf(":")+3,1000);
                    if( !(this.cookies&&this.cookies.cookies)){
                        //console.log(options);
                        throw new Error(`${this.user.name} cookies不存在`);
                    }
                    for(let ck of this.cookies.cookies){
                        jar.setCookie(`${ck.name}=${encodeURIComponent(ck.value)};`,options.uri);
                    }
                    options.jar = jar;

                    if(options.method == "post"){
                        //检查post请求，全局插入CSRF
                        for(let t of this.cookies.cookies){
                            if(t.name == "bili_jct"){
                                options.form["csrf"] = t.value;
                                options.form["csrf_token"] = t.value;
                            }
                        }
                    }

                    if(this.Agent.status){
                        //检查是否使用代理,仅对用户的连接使用代理
                        options.agent = this.Agent.agent;
                        options.timeout = 10000; //增加代理的延迟
                    }
                    return this.ori_rq(options);;
                }
            }
            return sma;
        }else{
            throw new Error("未登陆");
        }
    }

    async TestAndSetProxy(proxyString){
        //测试代理
        let p = new proxyAgent(proxyString);
        try{
            let res = await this.origin({
                uri:"https://api.live.bilibili.com/ip_service/v1/ip_service/get_ip_addr",
                method:"get",
                agent:p,
            });
            if(res.code == 0){
                this.Agent = {
                    agent : p,
                    status:true,
                }
                return {
                    useful:true,
                    rq:res
                }
            }else{
                throw new Error("查询IP时返回值不为0");
            }
        }
        catch(e){
            eve.emit("error",e.message);
            return {
                useful:false,
            }
        }

    }
    /* 使用原始request */
    // async 的函数实际上是promise和genarate的语法糖，在于其可以继续被.then链处理,可以经过普通函数传递
    // 在写这一部分的时候对promise更深有感触，只要在端到端使用await/then,不管中间是经由普通函数还是async函数
    // 都可以被正确的处理。

    async origin(options){
        /* 全局请求定向到这个函数，方便以后重构到多CDN高并发 */
        let s = (new Date()).valueOf(); //获取当前毫秒时间戳
        if(this.thread >= 7){
            /* 并发限制，不得超过7 */
            await sleep(200);
            return this.origin(options);
        }
        if(s - this.ts < 70){
            /* 频率限制，不得超过50 */
            await sleep(50);
        }
        this.ts = s;
        this.thread++;
        let res;

        if(this.Agent.status && this.Agent.Global){
            //检查全局代理
            options.agent = this.Agent.agent;
            options.timeout = 10000; //增加代理的延迟
        }

        try{

            /*正式执行request*/

            res = await rq(options);//必须返回request-promise模块的promise
            //必须放置在try块中，否则会抛出错误导致thread死锁
            if(isJSON(res)){
                res = JSON.parse(res);
            }
        }catch(e){
            console.log(e);
            throw e;
        }finally{
            this.thread--;
        }

        return res;
    }

    /* 发送请求 */
    send(url,data={},method="get",baseURL=this.baseURL,retry=0){
        let options = {
            method,
            uri: baseURL+url,
            qs: data,
            form:data,
            headers: {
                'host': 'api.live.bilibili.com'
            },
            timeout:2000,
            json: true // Automatically parses the JSON string in the response
        };
        return this.origin(options).then((res)=>{
            if(res.code === 0){
                return res;
            }else{
                /* Un normal */
                return res;
            }
        }).catch(()=>{
            if(retry < RETRY_LIMIT){
                return this.send(url,data,method,baseURL,retry+1);
            }
            throw new Error("网络异常");
        });
    }
}();


text:
import rq from "request-promise-native"
import crypto from "crypto"
import api from "./api.js"
import eve from "./events"
import {userLogin} from "./BLSv2_utils/tools"

class user {
    constructor(id, password,store={},name="new User") {
        /* Global */
        this.uid = 0;
        this.config = {};//为configCenter准备的插槽，这样其他的组件可以直接使用user.config.xxx而不会报错了
        this.name = name;
        this.store = store;
        this.id = id;
        this.isLogin = false
        this.password = password;

    }
    async CheckUid(){
        //获取用户UID，顺便确认登录成功
        try{
            this.isLogin = true; // 允许 api 直接调用一次
            let rq= await api.use(this).send("User/getUserInfo");
            if(rq.code == "REPONSE_OK" ||rq.msg =="success" ){
                this.uid = rq.data.uid;
                //暂时只需要uid
                this.isLogin = true;
                return true
            }else{
                this.isLogin = false;
                return false
            }
        }catch(e){
            eve.emit("error",e.message);
            this.isLogin = false;
            return false
        }
    }

    async Login(force=false) {//static用于指定是否强制更新，强制更新用于修改了某些值之后需要更新
        const storeKey = "Cookie--"+this.id // 新id
        if(!force){
            // 非强制刷新，检查用户当前存档的登录态
            let cookies = JSON.parse(this.store[storeKey] || '[]')
            if(cookies.length){
                // 有缓存
                this.cookies = {
                    cookies,
                }
                await this.CheckUid()
                if(this.isLogin){
                    // Cookies 有效
                    return true
                }
            }
            // cookies 缓存失效
        }
        // 进入强制获取 cookies 流程
        this.cookies = {
            cookies: []
        };
        try{
            this.cookies.cookies = await userLogin(this.id,this.password)
            this.store[storeKey] = JSON.stringify(this.cookies.cookies)
            this.CheckUid()
        }catch(e){
            console.error(e)
        }finally{
            return this.isLogin
        }

    }
    async RefreshCookie() {
        this.Login();
    }
    async RefreshToken() {
        this.Login();
    }
}

/* user CLASS

初始化参数：
登录ID
密码
cookies存储库，一个对象，要求可以使用store[this.id]的方式进行设置
名称，用于辨认

*/
export default user;

repo_name: mscststs/BLS

repo_language: Vue

mime_type: text/plain


text:
/*
		以事件监听方式进行 部署，所有功能均需运行在eve接口之上。
		eve接口暴露了三个方法，使用on方法可以绑定一个事件和回调函数，index参数用于确认回调函数的优先级，index越大，越优先，相同的以先绑定的优先
		使用off方法可以解绑一个事件，如果绑定时使用了index，则解绑时也需要使用index，否则无法匹配
		使用emit可以触发一个事件，在回调函数中返回false将会阻止 index 较小的参数的继续回调

		例如：回调函数a和b同时绑定了“foo”，事件，a index为10，b index 为11，若b返回false，则 a 不会被回调

		本脚本内部所有的index值保持在 [-100,100]

	*/

export default new class{
    constructor(){
        this.handles={};
    }
    on(event,callback,index=0){
        if(!this.handles[event]){
            this.handles[event]=[];
        }
        this.handles[event].push({callback:callback,index:index});
        this.handles[event].sort((a,b)=>{return b.index-a.index});
    }
    emit(event,...data){
        if(this.handles[event]){
            for(let i of this.handles[event]){
                if(i.callback(...data)===false) break;
            }
        }
    }
    off(event,callback,index=0){
        if(this.handles[event]){
            let s = [];
            for(let i of this.handles[event]){
                if(i.callback!=callback && i.index != index){s.push(i)};
            }
            this.handles[event] = s;
        }
    }
}();


text:
/* 使用一个window.localStorage["bls-config"]进行初始化和数据保存 */
import events from "~/tools/events"

class store{
    constructor(storeObj,storeID){
        this.users = [];
        if(storeObj[storeID]){
            this.data = JSON.parse(storeObj[storeID]);
        }else{
            storeObj[storeID] = "";
            this.data = {};
        }
        this.store = {storeObj:storeObj,storeID:storeID};
    }
    update(cb){
        /* 使用callback进行修改 */
        if(typeof(cb)==="function"){
            cb(this.data);
        }else if(typeof(cb)==="object"){// 传递新的对象,用深拷贝解除引用
            this.data = JSON.parse(JSON.stringify(cb));
        }else if(typeof(cb)==="string"){
            this.data = JSON.parse(cb);
        }else{
            return false;
        }
        this.save();
        events.emit("storeUpdated");
        return true;
    }
    toString(){
        return JSON.stringify(this.data);
    }
    save(){
        this.store.storeObj[this.store.storeID] = JSON.stringify(this.data);
    }
}

export default store;


text:
import fs from "fs"

function exists(path){
    return new Promise(resolve=>{
        fs.exists(path,status=>{
            resolve(status);
        });
    });
    // async 判断文件是否存在
}
function WriteString(path,string){
    return new Promise((resolve,reject)=>{
        fs.open(path, 'w', (err, fd) => {
            if (err) {
              reject(err);
            }

            fs.writeFile(fd,string,(err)=>{
                if(err){
                    reject(err);
                }else{
                    resolve();
                }
            });
          });
    });
    // 同步写入文件
}
export default {exists,WriteString}


text:
import { remote } from 'electron'
import autoSlideScript from './script/autoSlide' // 自动滑动登录

/**
 * @description 以用户名和密码登录
 * @param {string} username
 * @param {string} password
 */
export async function userLogin (username = '', password = '') {
  // Step 1 打开浏览器页面
  let currentWindow = remote.getCurrentWindow()
  let child = new remote.BrowserWindow({
    title: 'bilibili 快速登录',
    parent: currentWindow,
    webPreferences: {
      devTools: false
    },
    darkTheme: true
  })
  child.show()
  // JS 在处理双层事件循环和跨作用域操作时，简直反人类，只能通过这种写法来解决
  return Promise.race([
    // 函数 1 ，正常流程，完成登录和返回Cookies
    (async () => {
      child.loadURL('https://account.bilibili.com/ajax/miniLogin/minilogin') // B 站登录地址
      // Step 2 填写用户名密码(如果有的话)
      let childWebContents = child.webContents
      childWebContents.on('new-window', function (event) {
        event.preventDefault() // 阻止打开新窗口
      })
      await new Promise((resolve, reject) => {
        childWebContents.once('did-finish-load', () => {
          resolve()
        })
      })
      childWebContents.executeJavaScript(`
        document.querySelector("#login-username").value = "${username}";
        document.querySelector("#login-passwd").value = "${password}";
        ${autoSlideScript}
      `)
      // 等待重定向 -- 可能密码
      let cookies = await new Promise((resolve, reject) => {
        childWebContents.once('will-navigate', (event, url) => {
          if (~url.indexOf('https://passport.biligame.com/crossDomain')) {
            // Step 3 从 url 解析出 cookies
            let { searchParams } = new URL(url)
            let expires = searchParams.get('Expires')
            let cookies = ['DedeUserID', 'DedeUserID__ckMd5', 'SESSDATA', 'bili_jct'].map(key => {
              return {
                name: key,
                value: searchParams.get(key),
                expires: expires
              }
            })
            resolve(cookies)
          }
        })
      })
      child.close()
      return cookies
    })(),
    // 函数 2 , 意外流程，检查到窗口关闭时，直接返回
    new Promise((resolve, reject) => {
      child.on('close', (event) => {
        reject(new Error('login refused'))
      })
    })
  ])
}


text:
module.exports = `
// ==UserScript==
// @name         GeeTest Hacker
// @namespace    mscststs
// @version      0.1
// @description  极验破解工具
// @author       mscststs
// @match        https://passport.bilibili.com/ajax/miniLogin/minilogin
// @grant        none
// ==/UserScript==

(function() {
    'use strict';
    // 工具库
    var mscststs= new class{
    sleep(miliseconds){
      return new Promise(resolve=>{
        setTimeout(()=>{resolve();},miliseconds);
      });
    }
    async _Step(selector,callback,need_content,timeout){
      while(timeout--){
                if(document.querySelector(selector)===null){
            await this.sleep(100);
          continue;
                }else{
                    if(need_content){
                            if(document.querySelector(selector).innerText.length==0){
                              await this.sleep(100);
                continue;
                        }
                    }
                }
        break;
      }

      callback(selector);
    }
    wait(selector,need_content = false,timeout=Infinity){
      return new Promise(resolve=>{
        this._Step(selector,function(selector){resolve(document.querySelector(selector));},need_content,timeout);
      });
    }
  }();
    async function eventHijacker(cb){
        let hijackEventList  = ["mousedown","mousemove","mouseup","click","dblclick","pointerup","pointerdown","pointermove"];
        function handler(e){
            if(!e.fake){
                // 拦截真事件，防止触发
                e.stopPropagation();
                e.preventDefault();
                return false
            }
        }
        hijackEventList.forEach(eventKey=>{
            window.addEventListener(eventKey,handler,true)
        })
        await cb()
        hijackEventList.forEach(eventKey=>{
            window.removeEventListener(eventKey,handler,true)
        })
    }
    (async ()=>{
        // 初始化 ，监听canvas
        await mscststs.sleep(1000)
        document.querySelector("#login-submit").dispatchEvent(new Event("click"))
        await mscststs.wait(".geetest_slider_tip")
        await mscststs.sleep(500)
        // 重放开始
        eventHijacker(async ()=>{
            await replay(getTargetRecorder(fetchCanvas()-9,1000),".geetest_slider_button")
        })

    })()

    // 亲自录制的老奶奶轨迹，成功率 100%
    const recoder = [{"type":"pointerdown","clientX":256,"clientY":709,"ts":2176},{"type":"pointermove","clientX":256,"clientY":709,"ts":2190},{"type":"pointermove","clientX":256,"clientY":707,"ts":2239},{"type":"pointermove","clientX":257,"clientY":707,"ts":2247},{"type":"pointermove","clientX":258,"clientY":707,"ts":2254},{"type":"pointermove","clientX":259,"clientY":707,"ts":2263},{"type":"pointermove","clientX":259,"clientY":708,"ts":2279},{"type":"pointermove","clientX":260,"clientY":708,"ts":2287},{"type":"pointermove","clientX":261,"clientY":708,"ts":2296},{"type":"pointermove","clientX":263,"clientY":708,"ts":2303},{"type":"pointermove","clientX":265,"clientY":708,"ts":2312},{"type":"pointermove","clientX":268,"clientY":708,"ts":2319},{"type":"pointermove","clientX":271,"clientY":709,"ts":2328},{"type":"pointermove","clientX":277,"clientY":709,"ts":2335},{"type":"pointermove","clientX":280,"clientY":710,"ts":2346},{"type":"pointermove","clientX":284,"clientY":710,"ts":2351},{"type":"pointermove","clientX":289,"clientY":711,"ts":2362},{"type":"pointermove","clientX":294,"clientY":711,"ts":2367},{"type":"pointermove","clientX":299,"clientY":713,"ts":2379},{"type":"pointermove","clientX":303,"clientY":713,"ts":2386},{"type":"pointermove","clientX":306,"clientY":713,"ts":2396},{"type":"pointermove","clientX":309,"clientY":713,"ts":2399},{"type":"pointermove","clientX":311,"clientY":713,"ts":2407},{"type":"pointermove","clientX":315,"clientY":713,"ts":2415},{"type":"pointermove","clientX":318,"clientY":713,"ts":2422},{"type":"pointermove","clientX":322,"clientY":713,"ts":2431},{"type":"pointermove","clientX":326,"clientY":713,"ts":2439},{"type":"pointermove","clientX":330,"clientY":713,"ts":2447},{"type":"pointermove","clientX":333,"clientY":713,"ts":2455},{"type":"pointermove","clientX":336,"clientY":713,"ts":2463},{"type":"pointermove","clientX":338,"clientY":714,"ts":2471},{"type":"pointermove","clientX":339,"clientY":714,"ts":2481},{"type":"pointermove","clientX":338,"clientY":714,"ts":2719},{"type":"pointermove","clientX":337,"clientY":716,"ts":2732},{"type":"pointermove","clientX":336,"clientY":716,"ts":2735},{"type":"pointermove","clientX":334,"clientY":716,"ts":2746},{"type":"pointermove","clientX":333,"clientY":716,"ts":2751},{"type":"pointermove","clientX":332,"clientY":716,"ts":2762},{"type":"pointermove","clientX":330,"clientY":716,"ts":2767},{"type":"pointermove","clientX":329,"clientY":716,"ts":2779},{"type":"pointermove","clientX":328,"clientY":716,"ts":2783},{"type":"pointermove","clientX":327,"clientY":716,"ts":2799},{"type":"pointermove","clientX":328,"clientY":716,"ts":3063},{"type":"pointermove","clientX":329,"clientY":716,"ts":3087},{"type":"pointermove","clientX":330,"clientY":716,"ts":3103},{"type":"pointermove","clientX":331,"clientY":716,"ts":3127},{"type":"pointermove","clientX":332,"clientY":716,"ts":3146},{"type":"pointermove","clientX":331,"clientY":716,"ts":3583},{"type":"pointermove","clientX":330,"clientY":716,"ts":3639},{"type":"pointermove","clientX":329,"clientY":716,"ts":3655},{"type":"pointerup","clientX":329,"clientY":716,"ts":4407},{"type":"click","clientX":329,"clientY":716,"ts":4420},{"type":"pointermove","clientX":329,"clientY":716,"ts":4422},{"type":"mousemove","clientX":329,"clientY":716,"ts":4422}]
    /**
    * ImageData 转色值矩阵
    */
    function imageDataToBrightnessArray(imageData,width,height){
        let brightnessArray = []
        let pixel_color = imageData
        let pointer = 0
        for(let i=0;i< height;i++)
        {
            brightnessArray[i] = []; //将每一个子元素又定义为数组
            for( let n=0;n< width;n++)
            {
                brightnessArray[i][n]= parseInt((pixel_color[pointer] + pixel_color[pointer+1] + pixel_color[pointer+2]) / 3) ; //此时pix[i][n]可以看作是一个二级数组
                pointer = pointer+4;
            }
        }
        return brightnessArray

    }
    function zipArray_row(b_array){
        let result = []
        let height = b_array.length;
        let width = b_array[0].length;
        for(let i = 0;i<width;i++){
            let val = 0;
            for(let j= 0;j<height ; j++){
                val += b_array[j][i]
            }
            result[i] = parseInt(val/height)
        }
        return result
    }
    function drawVline(ctx,left){
        ctx.fillStyle = "#FF0000";
        ctx.fillRect(left, 0, 1, ctx.height)
    }
    function getctx(selector){
        let bgCanvas = document.querySelector(selector);
        let {width, height} = bgCanvas; // 宽度，高度
        let ctx = bgCanvas.getContext("2d");
        ctx.height = height;
        ctx.width = width;
        return ctx
    }
    function getImageData(selector){
        let ctx = getctx(selector)
        return ctx.getImageData(0,0,ctx.width,ctx.height).data
    }
    window.fetchCanvas = function(brightStep=10){
        let fullBgData = getImageData(".geetest_canvas_fullbg") // 完整背景图
        let bgData = getImageData(".geetest_canvas_bg") // 残缺背景图
        let bgctx = getctx(".geetest_canvas_bg")
        window.brightnessArray = imageDataToBrightnessArray(bgData.map((item,index)=>{
            return item^fullBgData[index]
        }),bgctx.width,bgctx.height)

        window.brightnessArray_row = zipArray_row(window.brightnessArray)
        let leftOffset = 0;
        window.brightnessArray_row.find((item,index)=>{
            function testNextPix(array,index,step){
                let flag = 1;
                for(let i = index; i < Math.min(array.length,index+10); i++ ){
                    if(array[i] <step ){
                        flag = 0;
                    }
                }
                return flag
            }
            if(testNextPix(window.brightnessArray_row ,index,brightStep)){
                leftOffset = index
                drawVline(bgctx ,index)
                return true
            }
        })
        // 拿到 leftOffset ，就是对应的缺口的偏移量
        console.log(leftOffset)
        return leftOffset
    }
    // 录制鼠标事件
    window.record = async function(){
        return await new Promise((resolve,reject)=>{
            let ms = new Date().valueOf();
            const getTime = ()=>{ // 时间打点
                return new Date().valueOf() - ms;
            }
            let eventList = [];
            function eventRecorder(e){
                let { type, clientX, clientY, target } = e;
                let eventMsg = { type, clientX, clientY, ts:getTime()}
                eventList.push(eventMsg)
            }
            // 开始录制
            window.addEventListener("keyup",(e)=>{
                ["mousedown","mousemove","mouseup","click","dblclick","pointerup","pointerdown","pointermove"].forEach(eventKey=>{
                    window.addEventListener(eventKey,eventRecorder,true)
                })
                // 停止录制
                window.addEventListener("keyup",(e)=>{
                    ["mousedown","mousemove","mouseup","click","dblclick","pointerup","pointerdown","pointermove"].forEach(eventKey=>{
                        window.removeEventListener(eventKey,eventRecorder,true)
                    })
                    resolve(eventList)
                },{once:true})
            },{once:true})
        })
    }
    // 轨迹压缩和重整
    window.getTargetRecorder = function getTargetRecorder(targetlength=100, targetduration=1000, recorder=recoder, ){
        // step 1. 首先以第一个事件的位置为起始，压缩整个轨迹
        let base = recorder[0];
        let ziped_recorder = recorder.map(event=>{
            return {
                type:event.type,
                offsetX:event.clientX - base.clientX,
                offsetY:event.clientY - base.clientY,
                ts:event.ts - base.ts
            }
        })
        // 压缩后的事件记录
        // console.log("ziped", ziped_recorder)
        let max = ziped_recorder[ziped_recorder.length -1] // 拿到最后一个
        ziped_recorder.reduce((p,e,i)=>{
            e.offsetX = parseInt(e.offsetX * targetlength / max.offsetX) // 轨迹缩放
            e.ts = parseInt(e.ts * targetduration / max.ts ) // 时间戳缩放
            return e
        })
        return ziped_recorder
    }
    // 轨迹事件重放
    window.replay = function(recorder, dom){
        if(typeof dom === "string"){
            dom = document.querySelector(dom)
        }
        let {left, top} = document.querySelector(".geetest_slider_button").getBoundingClientRect();
        left = left + 20* Math.random()
        top = top + 20* Math.random()
        return Promise.all(
            recorder.map(e=>{
                return new Promise(resolve=>{

                    setTimeout(()=>{
                        let event = new Event(e.type,{"bubbles":true, "cancelable":false});
                        event.offsetX = e.offsetX;
                        event.offsetY = e.offsetY;
                        event.screenX = event.pageX = event.clientX = e.offsetX + left;
                        event.screenY = event.pageY = event.clientY = e.offsetY + top;
                        event.fake = true;
                        dom.dispatchEvent(event);
                        resolve()
                        //console.log("....",e.type,event)
                    },e.ts)
                })
            })
        )
    }
})();
`


text:
import Consts from './consts.js'

function getPacketLength (payload) {
  return Buffer.byteLength(payload) + Consts.headerLength
}

function writePacketLength (buff, packetLength) {
  buff.writeInt32BE(packetLength, 0)
}

function writeConsts (buff) {
  buff.writeInt16BE(Consts.magic, 4)
  buff.writeInt16BE(Consts.version, 6)
  buff.writeInt32BE(Consts.magicParam, 12)
}

function writeAction (buff, action) {
  buff.writeInt32BE(action, 8)
}

function writePayload (buff, payload) {
  buff.write(payload, Consts.headerLength)
}

function generatePacket (action, payload) {
  payload = payload || ''
  let packetLength = getPacketLength(payload)
  let buff = new Buffer(packetLength)

  writePacketLength(buff, packetLength)
  writeConsts(buff)
  writeAction(buff, action)
  writePayload(buff, payload)

  return buff
}

function encodeHeartbeat () {
  return generatePacket(Consts.actions.heartbeat)
}

function encodeJoinRoom (rid, uid) {
  let userId = Number(uid)
  let roomId = Number(rid)
  let packet = JSON.stringify({uid: userId, roomid: roomId})
  return generatePacket(Consts.actions.joinChannel, packet)
}

export default {
  encodeJoinRoom,
  encodeHeartbeat,
}


text:
该文件夹下代码来自 pandaGao@github
链接:https://github.com/pandaGao/bilibili-live/blob/master/src/service/room/danmaku/
授权协议 MIT


text:
import WebSocket from 'ws'
import EventEmitter from 'events'
import net from 'net'
import _ from 'lodash'

import DMDecoder from './decoder'
import DMEncoder from './encoder'

const DMPORT = 2243
const DMSERVER = 'livecmt-2.bilibili.com'

const WSDMPROTOCOL = 'ws'
const WSSDMPROTOCOL = 'wss'
const WSDMSERVER = 'broadcastlv.chat.bilibili.com'
const WSDMPORT = 2244
const WSSDMPORT = 2245
const WSDMPATH = 'sub'

const HEARTBEAT_DELAY = 1e4
const CHECK_ERROR_DELAY = 15e3

export default class DanmakuService extends EventEmitter {

  constructor(config = {}) {
    super()

    this.roomId = config.roomId || '23058'  // 此处需要使用原始房间号
    this.userId = config.userId || this.randUid()
    this.useWebsocket = config.useWebsocket === false ? false : true
    this.useWSS = config.useWSS || false
    this.useGiftBundle = config.useGiftBundle || false
    this.giftBundleDelay = config.giftBundleDelay || 3e3

    this._socket = null
    this._socketEvents = {
      connect: 'connect',
      data: 'data',
      close: 'close',
      error: 'error'
    }
    this._websocketEvents = {
      connect: 'open',
      data: 'message',
      close: 'close',
      error: 'error'
    }
    this._heartbeatService = null
    this._checkErrorService =  _.debounce(() => {
      this.emit('error', new Error('check failed'))
      this.reconnect()
    }, CHECK_ERROR_DELAY)
    this._giftBundleMap = new Map()
  }

  randUid() {
    return 1E15 + Math.floor(2E15 * Math.random())
  }

  setUseGiftBundle(use) {
    this.useGiftBundle = use
  }

  connect() {
    if (this.useWebsocket) {
      if (this.useWSS) {
        this._socket = new WebSocket(`${WSSDMPROTOCOL}://${WSDMSERVER}:${WSSDMPORT}/${WSDMPATH}`)
      } else {
        this._socket = new WebSocket(`${WSDMPROTOCOL}://${WSDMSERVER}:${WSDMPORT}/${WSDMPATH}`)
      }
    } else {
      this._socket = net.connect(DMPORT, DMSERVER)
    }
    this.handleEvents()
  }

  disconnect() {
    clearTimeout(this._heartbeatService)
    this._checkErrorService.cancel()

    if (this.useWebsocket) {
      this._socket.close();
    } else {
      this._socket.end()
    }
    this._socket = null
  }

  reconnect() {
    this.disconnect()
    this.connect()
  }

  handleEvents() {
    let socket = this._socket
    let events = this._socketEvents
    if (this.useWebsocket) {
      events = this._websocketEvents
    }

    socket.on(events.connect, () => {
      if (socket !== this._socket) return
      this.sendJoinRoom()
      this.emit('connect')
    })

    socket.on(events.data, (msg) => {
      if (socket !== this._socket) return
      this._checkErrorService()
      DMDecoder.decodeData(msg).map(m => {
        if (m.type === 'connected') {
          this.sendHeartbeat()
          this.emit(m.type, m)
        } else {
          if (m.type === 'gift' && this.useGiftBundle) {
            this.bundleGift(m)
          } else {
            this.emit('data', m)
            this.emit(m.type, m)
          }
        }
      })
    })

    socket.on(events.close, () => {
      if (socket !== this._socket) return
      this.emit('close')
    })

    socket.on(events.error, (err) => {
      if (socket !== this._socket) return
      this.emit('error', err)
      this.reconnect()
    })
  }

  sendJoinRoom() {
    if (this.useWebsocket) {
      this._socket.send(DMEncoder.encodeJoinRoom(this.roomId, this.userId))
    } else {
      this._socket.write(DMEncoder.encodeJoinRoom(this.roomId, this.userId))
    }
  }

  sendHeartbeat() {
    if (this.useWebsocket) {
      this._socket.send(DMEncoder.encodeHeartbeat())
    } else {
      this._socket.write(DMEncoder.encodeHeartbeat())
    }
    this._heartbeatService = setTimeout(() => {
      this.sendHeartbeat()
    }, HEARTBEAT_DELAY)
  }

  bundleGift(msg) {
    let key = `${msg.user.id}.${msg.gift.id}`

    if (this._giftBundleMap.has(key)) {
      let giftEvent = this._giftBundleMap.get(key)
      giftEvent.msg.gift.count = giftEvent.msg.gift.count*1 + msg.gift.count*1
      giftEvent.event()
    } else {
      let giftEvent = {
        msg: _.merge({}, msg),
        event: _.debounce(() => {
          this.emit('data', giftEvent.msg)
          this.emit('gift', giftEvent.msg)
          this._giftBundleMap.delete(key)
        }, this.giftBundleDelay)
      }
      giftEvent.event()
      this._giftBundleMap.set(key, giftEvent)
    }
  }

}


text:
const WS_OP_HEARTBEAT = 2
const WS_OP_HEARTBEAT_REPLY = 3
const WS_OP_MESSAGE = 5
const WS_OP_USER_AUTHENTICATION = 7
const WS_OP_CONNECT_SUCCESS = 8
const GIFT_SYS_GIFT = 0
const GIFT_SYS_LUCKY_MONEY = 1
const GIFT_SYS_TV = 2
const GIFT_SYS_ANNOUNCEMENT = 3
const GIFT_SYS_GUARD = 4
const GIFT_SYS_ACTIVITY_RED_PACKET = 6
const WS_PACKAGE_OFFSET = 0
const WS_HEADER_OFFSET = 4
const WS_VERSION_OFFSET = 6
const WS_OPERATION_OFFSET = 8
const WS_SEQUENCE_OFFSET = 12
const WS_PACKAGE_HEADER_TOTAL_LENGTH = 16
const WS_HEADER_DEFAULT_VERSION = 1
const WS_HEADER_DEFAULT_OPERATION = 1
const WS_HEADER_DEFAULT_SEQUENCE = 1

export default {
  version: 1,
  magic: 16,
  magicParam: 1,
  headerLength: 16,
  actions: {
    heartbeat: 2,
    joinChannel: 7,
  },
  WS_OP_HEARTBEAT,
  WS_OP_HEARTBEAT_REPLY,
  WS_OP_MESSAGE,
  WS_OP_USER_AUTHENTICATION,
  WS_OP_CONNECT_SUCCESS,
  WS_PACKAGE_OFFSET,
  WS_HEADER_OFFSET,
  WS_VERSION_OFFSET,
  WS_OPERATION_OFFSET,
  WS_SEQUENCE_OFFSET,
  WS_PACKAGE_HEADER_TOTAL_LENGTH,
  WS_HEADER_DEFAULT_VERSION,
  WS_HEADER_DEFAULT_OPERATION,
  WS_HEADER_DEFAULT_SEQUENCE,
  dataStruct: [{
      name: "Header Length",
      key: "headerLen",
      bytes: 2,
      offset: WS_HEADER_OFFSET,
      value: WS_PACKAGE_HEADER_TOTAL_LENGTH
  }, {
      name: "Protocol Version",
      key: "ver",
      bytes: 2,
      offset: WS_VERSION_OFFSET,
      value: WS_HEADER_DEFAULT_VERSION
  }, {
      name: "Operation",
      key: "op",
      bytes: 4,
      offset: WS_OPERATION_OFFSET,
      value: WS_HEADER_DEFAULT_OPERATION
  }, {
      name: "Sequence Id",
      key: "seq",
      bytes: 4,
      offset: WS_SEQUENCE_OFFSET,
      value: WS_HEADER_DEFAULT_SEQUENCE
  }]
}


text:
import Consts from './consts.js'
import { StringDecoder } from 'string_decoder'
import {unzipSync} from "zlib"

function decodeBuffer (buff) {
  const textDecoder = new StringDecoder('utf8')
  let data = {}
  data.packetLen = buff.readInt32BE(Consts.WS_PACKAGE_OFFSET)
  Consts.dataStruct.forEach((struct) => {
    if (struct.bytes === 4) {
      data[struct.key] = buff.readInt32BE(struct.offset)
    } else if (struct.bytes === 2) {
      data[struct.key] = buff.readInt16BE(struct.offset)
    }
  })
  if (data.op && data.op === Consts.WS_OP_MESSAGE) {
    data.body = []
    let packetLen = data.packetLen
    let headerLen = 0
    for (let offset = Consts.WS_PACKAGE_OFFSET; offset < buff.byteLength; offset += packetLen) {
      packetLen = buff.readInt32BE(offset)
      headerLen = buff.readInt16BE(offset + Consts.WS_HEADER_OFFSET)
      try {
        let body = JSON.parse(textDecoder.write(buff.slice(offset + headerLen, offset + packetLen)))
        data.body.push(body)
      } catch (e) {
        // B 站添加了代码压缩，必须解压一下
        try{
          let unzipedData = unzipSync(buff.slice(offset + headerLen,offset + packetLen))
          return decodeBuffer(unzipedData)
        }catch(e){
          console.log("decode body error:", textDecoder.write(buff.slice(offset + headerLen, offset + packetLen)), data,e)
        }
      }
    }
  } else if (data.op && data.op === Consts.WS_OP_HEARTBEAT_REPLY) {
    data.body = {
      number: buff.readInt32BE(Consts.WS_PACKAGE_HEADER_TOTAL_LENGTH)
    }
  }
  return data
}

function parseMessage (msg) {
  switch (msg.op) {
    case Consts.WS_OP_HEARTBEAT_REPLY:
      msg.body.type = 'online'
      msg.body.ts = new Date().getTime()
      return msg.body
    case Consts.WS_OP_MESSAGE:
      return msg.body.map((m) => {
        return transformMessage(m)
      })
    case Consts.WS_OP_CONNECT_SUCCESS:
      return {
        type: 'connected',
        ts: new Date().getTime()
      }
  }
}

function transformMessage (msg) {
  let message = {}
  switch (msg.cmd) {
    case 'LIVE':
      message.type = 'live'
      message.roomId = msg.roomid
      break
    case 'PREPARING':
      message.type = 'preparing'
      message.roomId = msg.roomid
      break
    case 'DANMU_MSG':
      message.type = 'comment'
      message.comment = msg.info[1]
      message.user = {
        id: msg.info[2][0],
        name: msg.info[2][1],
        isAdmin: !!msg.info[2][2],
        isVIP: !!msg.info[2][3],
        isSVIP: !!msg.info[2][4],
        guard: msg.info[7]
      }
      if (msg.info[3].length) {
        message.user.medal = {
          level: msg.info[3][0],
          title: msg.info[3][1],
          anchor: msg.info[3][2],
          roomURL: msg.info[3][3]
        }
      }
      if (msg.info[4].length) {
        message.user.level = msg.info[4][0]
      }
      if (msg.info[5].length) {
        message.user.title = {
          name: msg.info[5][0],
          source: msg.info[5][1]
        }
      }
      break
    case 'WELCOME':
      message.type = 'welcome'
      message.user = {
        id: msg.data.uid,
        name: msg.data.uname,
        isAdmin: !!msg.data.isadmin,
        isVIP: !!msg.data.vip,
        isSVIP: !!msg.data.svip
      }
      break
    case 'WELCOME_GUARD':
      message.type = 'welcomeGuard'
      message.user = {
        id: msg.data.uid,
        name: msg.data.username,
        guard: msg.data.guard_level
      }
      break
    case 'GUARD_BUY':
      message.type = 'guardBuy'
      message.user = {
        id: msg.data.uid,
        name: msg.data.username
      }
      message.level = msg.data.guard_level
      message.count = msg.data.num
      break
    case 'SEND_GIFT':
      message.type = 'gift'
      message.gift = {
        id: msg.data.giftId,
        type: msg.data.giftType,
        name: msg.data.giftName,
        count: msg.data.num,
        price: msg.data.price
      }
      message.user = {
        id: msg.data.uid,
        name: msg.data.uname
      }
      break
    case 'ROOM_BLOCK_MSG':
      message.type = 'block'
      message.user = {
        id: msg.uid,
        name: msg.uname
      }
      break
    default:
      message = msg
      message.type = msg.cmd
  }
  message.ts = new Date().getTime()
  return message
}

function decodeData (buff) {
  let messages = []
  try {
    let data = parseMessage(decodeBuffer(buff))
    if (data instanceof Array) {
      data.forEach((m) => {
        messages.push(m)
      })
    } else if (data instanceof Object) {
      messages.push(data)
    }
  } catch (e) {
    console.log("Socket message error", buff, e)
  }
  return messages
}

export default {
  decodeData
}


text:
import { encode, decode}  from './base65536'
import { brotliDecompress,brotliDecompressSync } from "zlib"
import rq from "request-promise-native"

async function getGuardList(){
    let data = await rq({
        uri:"https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=465671",
        method:"get",
        json:true
    })
    let text = data.data.room_info.description;
    text = text.replace(/^<p>/,"").replace(/<\/p>$/,"").replace(/^b6/,"")
    const uint8Array2 = decode(text)
    const ori = brotliDecompressSync(uint8Array2.buffer).toString()
    return JSON.parse(ori)
}

async function getGuardListByUrl(urlConfig,uid=100000){
    let [url,parse] = urlConfig.split("|")
    try{
        let res = await rq({
            uri:url,
            method:"get",
            timeout:10000,
            headers:{
                "User-Agent":`bilibili-live-tools/${uid}`
            },
            json:true,
            gzip:true,
        })
        let target = res
        if(parse){
            parse.split(".").forEach(key=>{
                target = target[key]
            })
        }
        return target
    }catch(e){
        return []
    }
}

export default async function(urlConfig,uid){
    let [i,j] = await Promise.all([
        getGuardList(),
        urlConfig?getGuardListByUrl(urlConfig,uid):[]
    ])
    return [...i,...j]
}


text:
/**
 * Routines for converting binary data into text data which can be sent safely
 * through 'Unicode-clean' text systems without information being lost. Analogous
 * to Base64 with a significantly larger character repertoire enabling the
 * encoding of 2.00 bytes per character (for comparison, Base64 manages 0.75 bytes
 * per character).
 */

// Z is a number, usually a uint16 but sometimes a uint8

const BITS_PER_CHAR = 16; // Base65536 is an 16-bit encoding
const BITS_PER_BYTE = 8;

// Compressed representation of inclusive-exclusive ranges of characters used in this encoding.
const pairStrings = [
  '㐀䴀一鼀ꄀꐀꔀꘀ𐘀𐜀𒀀𒌀𓀀𓐀𔐀𔘀𖠀𖨀𠀀𨘀',
  'ᔀᘀ'
];

// Decompression
const lookupE = {};
const lookupD = {};
pairStrings.forEach((pairString, r) => {
  const numZBits = BITS_PER_CHAR - BITS_PER_BYTE * r; // 0 -> 16, 1 -> 8
  lookupE[numZBits] = {};
  let z2 = 0;
  pairString.match(/../gu).forEach(pair => {
    const [first, last] = [...pair].map(x => x.codePointAt(0));
    for (let codePoint = first; codePoint < last; codePoint++) {
      const chr = String.fromCodePoint(codePoint);

      // SPECIAL CASE: flip the bytes around, because Base65536 was constructed to take the bytes
      // in the wrong order originally
      const z = numZBits === BITS_PER_CHAR ? 256 * (z2 % 256) + (z2 >> 8) : z2;

      lookupE[numZBits][z] = chr;
      lookupD[chr] = [numZBits, z];
      z2++;
    }
  });
});

const encode = uint8Array => {
  const length = uint8Array.length;

  let str = '';
  let z = 0;
  let numZBits = 0;

  for (let i = 0; i < length; i++) {
    const uint8 = uint8Array[i];

    // Take most significant bit first
    for (let j = BITS_PER_BYTE - 1; j >= 0; j--) {
      const bit = (uint8 >> j) & 1;

      z = (z << 1) + bit;
      numZBits++;

      if (numZBits === BITS_PER_CHAR) {
        str += lookupE[numZBits][z];
        z = 0;
        numZBits = 0;
      }
    }
  }

  if (numZBits !== 0) {
    // Final bits require special treatment.
    while (!(numZBits in lookupE)) {
      z = (z << 1) + 1;
      numZBits++;
    }

    str += lookupE[numZBits][z];
  }

  return str
};

const decode = str => {
  const length = str.length;

  // This length is a guess. There's a chance we allocate one more byte here
  // than we actually need. But we can count and slice it off later
  const uint8Array = new Uint8Array(Math.floor(length * BITS_PER_CHAR / BITS_PER_BYTE));
  let numUint8s = 0;
  let uint8 = 0;
  let numUint8Bits = 0;
  let shouldBeNoMoreChars = false;

  for (const chr of str) {
    if (shouldBeNoMoreChars) {
      throw new Error('Secondary character found before end of input')
    }

    if (!(chr in lookupD)) {
      throw new Error(`Unrecognised Base65536 character: ${chr}`)
    }

    const [numZBits, z] = lookupD[chr];

    // Take most significant bit first
    for (let j = numZBits - 1; j >= 0; j--) {
      const bit = (z >> j) & 1;

      uint8 = (uint8 << 1) + bit;
      numUint8Bits++;

      if (numUint8Bits === BITS_PER_BYTE) {
        uint8Array[numUint8s] = uint8;
        numUint8s++;
        uint8 = 0;
        numUint8Bits = 0;
      }
    }

    if (numZBits !== BITS_PER_CHAR) {
      shouldBeNoMoreChars = true;
    }
  }

  // Final padding bits! Requires special consideration!
  // Remember how we always pad with 1s?
  // Note: there could be 0 such bits, check still works though
  if (uint8 !== ((1 << numUint8Bits) - 1)) {
    throw new Error('Padding mismatch')
  }

  return new Uint8Array(uint8Array.buffer, 0, numUint8s)
};

export { decode, encode };


text:
<!DOCTYPE html><meta charset=utf-8><title>苹朵</title><link href=styles.css rel=stylesheet>

载入中...


text:
'use strict'

process.env.BABEL_ENV = 'main'

const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')

const BabiliWebpackPlugin = require('babili-webpack-plugin')

let mainConfig = {
  entry: {
    main: path.join(__dirname, '../src/main/index.js')
  },
  externals: [
    ...Object.keys(dependencies || {})
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.node$/,
        use: 'node-loader'
      }
    ]
  },
  node: {
    __dirname: process.env.NODE_ENV !== 'production',
    __filename: process.env.NODE_ENV !== 'production'
  },
  output: {
    filename: '[name].js',
    libraryTarget: 'commonjs2',
    path: path.join(__dirname, '../dist/electron')
  },
  plugins: [
    new webpack.NoEmitOnErrorsPlugin()
  ],
  resolve: {
    extensions: ['.js', '.json', '.node']
  },
  target: 'electron-main'
}

/**
 * Adjust mainConfig for development settings
 */
if (process.env.NODE_ENV !== 'production') {
  mainConfig.plugins.push(
    new webpack.DefinePlugin({
      '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
    })
  )
}

/**
 * Adjust mainConfig for production settings
 */
if (process.env.NODE_ENV === 'production') {
  mainConfig.plugins.push(
    new BabiliWebpackPlugin(),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    })
  )
}

module.exports = mainConfig


text:
'use strict'

process.env.NODE_ENV = 'production'

const { say } = require('cfonts')
const chalk = require('chalk')
const del = require('del')
const { spawn } = require('child_process')
const webpack = require('webpack')
const Multispinner = require('multispinner')

const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')
const webConfig = require('./webpack.web.config')

const doneLog = chalk.bgGreen.white(' DONE ') + ' '
const errorLog = chalk.bgRed.white(' ERROR ') + ' '
const okayLog = chalk.bgBlue.white(' OKAY ') + ' '
const isCI = process.env.CI || false

if (process.env.BUILD_TARGET === 'clean') clean()
else if (process.env.BUILD_TARGET === 'web') web()
else build()

function clean () {
  del.sync(['build/*', '!build/icons', '!build/icons/icon.*'])
  console.log(`\n${doneLog}\n`)
  process.exit()
}

function build () {
  greeting()

  del.sync(['dist/electron/*', '!.gitkeep'])

  const tasks = ['main', 'renderer']
  const m = new Multispinner(tasks, {
    preText: 'building',
    postText: 'process'
  })

  let results = ''

  m.on('success', () => {
    process.stdout.write('\x1B[2J\x1B[0f')
    console.log(`\n\n${results}`)
    console.log(`${okayLog}take it away ${chalk.yellow('`electron-builder`')}\n`)
    process.exit()
  })

  pack(mainConfig).then(result => {
    results += result + '\n\n'
    m.success('main')
  }).catch(err => {
    m.error('main')
    console.log(`\n  ${errorLog}failed to build main process`)
    console.error(`\n${err}\n`)
    process.exit(1)
  })

  pack(rendererConfig).then(result => {
    results += result + '\n\n'
    m.success('renderer')
  }).catch(err => {
    m.error('renderer')
    console.log(`\n  ${errorLog}failed to build renderer process`)
    console.error(`\n${err}\n`)
    process.exit(1)
  })
}

function pack (config) {
  return new Promise((resolve, reject) => {
    webpack(config, (err, stats) => {
      if (err) reject(err.stack || err)
      else if (stats.hasErrors()) {
        let err = ''

        stats.toString({
          chunks: false,
          colors: true
        })
        .split(/\r?\n/)
        .forEach(line => {
          err += `    ${line}\n`
        })

        reject(err)
      } else {
        resolve(stats.toString({
          chunks: false,
          colors: true
        }))
      }
    })
  })
}

function web () {
  del.sync(['dist/web/*', '!.gitkeep'])
  webpack(webConfig, (err, stats) => {
    if (err || stats.hasErrors()) console.log(err)

    console.log(stats.toString({
      chunks: false,
      colors: true
    }))

    process.exit()
  })
}

function greeting () {
  const cols = process.stdout.columns
  let text = ''

  if (cols > 85) text = 'lets-build'
  else if (cols > 60) text = 'lets-|build'
  else text = false

  if (text && !isCI) {
    say(text, {
      colors: ['yellow'],
      font: 'simple3d',
      space: false
    })
  } else console.log(chalk.yellow.bold('\n  lets-build'))
  console.log()
}


text:
const hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')

hotClient.subscribe(event => {
  /**
   * Reload browser when HTMLWebpackPlugin emits a new index.html
   *
   * Currently disabled until jantimon/html-webpack-plugin#680 is resolved.
   * https://github.com/SimulatedGREG/electron-vue/issues/437
   * https://github.com/jantimon/html-webpack-plugin/issues/680
   */
  // if (event.action === 'reload') {
  //   window.location.reload()
  // }

  /**
   * Notify `mainWindow` when `main` process is compiling,
   * giving notice for an expected reload of the `electron` process
   */
  if (event.action === 'compiling') {
    document.body.innerHTML += `
      <style>
        #dev-client {
          background: #4fc08d;
          border-radius: 4px;
          bottom: 20px;
          box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12), 0 2px 4px -1px rgba(0, 0, 0, 0.3);
          color: #fff;
          font-family: 'Source Sans Pro', sans-serif;
          left: 20px;
          padding: 8px 12px;
          position: absolute;
        }
      </style>

      <div id="dev-client">
        Compiling Main Process...
      </div>
    `
  }
})


text:
'use strict'

process.env.BABEL_ENV = 'renderer'

const path = require('path')
const { dependencies } = require('../package.json')
const webpack = require('webpack')

const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

/**
 * List of node_modules to include in webpack bundle
 *
 * Required for specific packages like Vue UI libraries
 * that provide pure *.vue files that need compiling
 * https://simulatedgreg.gitbooks.io/electron-vue/content/en/webpack-configurations.html#white-listing-externals
 */
let whiteListedModules = ['vue']

let rendererConfig = {
  devtool: '#cheap-module-eval-source-map',
  entry: {
    renderer: path.join(__dirname, '../src/renderer/main.js')
  },
  externals: [
    ...Object.keys(dependencies || {}).filter(d => !whiteListedModules.includes(d))
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      },
      {
        test: /\.html$/,
        use: 'vue-html-loader'
      },
      {
        test: /\.js$/,
        use: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.node$/,
        use: 'node-loader'
      },
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
          options: {
            extractCSS: process.env.NODE_ENV === 'production',
            loaders: {
              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
              scss: 'vue-style-loader!css-loader!sass-loader'
            }
          }
        }
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          query: {
            limit: 10000,
            name: 'imgs/[name]--[folder].[ext]'
          }
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'media/[name]--[folder].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          query: {
            limit: 10000,
            name: 'fonts/[name]--[folder].[ext]'
          }
        }
      }
    ]
  },
  node: {
    __dirname: process.env.NODE_ENV !== 'production',
    __filename: process.env.NODE_ENV !== 'production'
  },
  plugins: [
    new ExtractTextPlugin('styles.css'),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve(__dirname, '../src/index.ejs'),
      minify: {
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        removeComments: true
      },
      nodeModules: process.env.NODE_ENV !== 'production'
        ? path.resolve(__dirname, '../node_modules')
        : false
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin()
  ],
  output: {
    filename: '[name].js',
    libraryTarget: 'commonjs2',
    path: path.join(__dirname, '../dist/electron')
  },
  resolve: {
    alias: {
      '~':path.join(__dirname, '../src'),
      '@': path.join(__dirname, '../src/renderer'),
      'vue$': 'vue/dist/vue.esm.js'
    },
    extensions: ['.js', '.vue', '.json', '.css', '.node']
  },
  target: 'electron-renderer'
}

/**
 * Adjust rendererConfig for development settings
 */
if (process.env.NODE_ENV !== 'production') {
  rendererConfig.plugins.push(
    new webpack.DefinePlugin({
      '__static': `"${path.join(__dirname, '../static').replace(/\\/g, '\\\\')}"`
    })
  )
}

/**
 * Adjust rendererConfig for production settings
 */
if (process.env.NODE_ENV === 'production') {
  rendererConfig.devtool = ''

  rendererConfig.plugins.push(
    new BabiliWebpackPlugin(),
    new CopyWebpackPlugin([
      {
        from: path.join(__dirname, '../static'),
        to: path.join(__dirname, '../dist/electron/static'),
        ignore: ['.*']
      }
    ]),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  )
}

module.exports = rendererConfig


text:
'use strict'

process.env.BABEL_ENV = 'web'

const path = require('path')
const webpack = require('webpack')

const BabiliWebpackPlugin = require('babili-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

let webConfig = {
  devtool: '#cheap-module-eval-source-map',
  entry: {
    web: path.join(__dirname, '../src/renderer/main.js')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: 'css-loader'
        })
      },
      {
        test: /\.html$/,
        use: 'vue-html-loader'
      },
      {
        test: /\.js$/,
        use: 'babel-loader',
        include: [ path.resolve(__dirname, '../src/renderer') ],
        exclude: /node_modules/
      },
      {
        test: /\.vue$/,
        use: {
          loader: 'vue-loader',
          options: {
            extractCSS: true,
            loaders: {
              sass: 'vue-style-loader!css-loader!sass-loader?indentedSyntax=1',
              scss: 'vue-style-loader!css-loader!sass-loader'
            }
          }
        }
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          query: {
            limit: 10000,
            name: 'imgs/[name].[ext]'
          }
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        use: {
          loader: 'url-loader',
          query: {
            limit: 10000,
            name: 'fonts/[name].[ext]'
          }
        }
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('styles.css'),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: path.resolve(__dirname, '../src/index.ejs'),
      minify: {
        collapseWhitespace: true,
        removeAttributeQuotes: true,
        removeComments: true
      },
      nodeModules: false
    }),
    new webpack.DefinePlugin({
      'process.env.IS_WEB': 'true'
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin()
  ],
  output: {
    filename: '[name].js',
    path: path.join(__dirname, '../dist/web')
  },
  resolve: {
    alias: {
      '~':path.join(__dirname, '../src'),
      '@': path.join(__dirname, '../src/renderer'),
      'vue$': 'vue/dist/vue.esm.js'
    },
    extensions: ['.js', '.vue', '.json', '.css']
  },
  target: 'web'
}

/**
 * Adjust webConfig for production settings
 */
if (process.env.NODE_ENV === 'production') {
  webConfig.devtool = ''

  webConfig.plugins.push(
    new BabiliWebpackPlugin(),
    new CopyWebpackPlugin([
      {
        from: path.join(__dirname, '../static'),
        to: path.join(__dirname, '../dist/web/static'),
        ignore: ['.*']
      }
    ]),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': '"production"'
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  )
}

module.exports = webConfig


text:
'use strict'

const chalk = require('chalk')
const electron = require('electron')
const path = require('path')
const { say } = require('cfonts')
const { spawn } = require('child_process')
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const webpackHotMiddleware = require('webpack-hot-middleware')

const mainConfig = require('./webpack.main.config')
const rendererConfig = require('./webpack.renderer.config')

let electronProcess = null
let manualRestart = false
let hotMiddleware

function logStats (proc, data) {
  let log = ''

  log += chalk.yellow.bold(`┏ ${proc} Process ${new Array((19 - proc.length) + 1).join('-')}`)
  log += '\n\n'

  if (typeof data === 'object') {
    data.toString({
      colors: true,
      chunks: false
    }).split(/\r?\n/).forEach(line => {
      log += '  ' + line + '\n'
    })
  } else {
    log += `  ${data}\n`
  }

  log += '\n' + chalk.yellow.bold(`┗ ${new Array(28 + 1).join('-')}`) + '\n'

  console.log(log)
}

function startRenderer () {
  return new Promise((resolve, reject) => {
    rendererConfig.entry.renderer = [path.join(__dirname, 'dev-client')].concat(rendererConfig.entry.renderer)

    const compiler = webpack(rendererConfig)
    hotMiddleware = webpackHotMiddleware(compiler, {
      log: false,
      heartbeat: 2500
    })

    compiler.plugin('compilation', compilation => {
      compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
        hotMiddleware.publish({ action: 'reload' })
        cb()
      })
    })

    compiler.plugin('done', stats => {
      logStats('Renderer', stats)
    })

    const server = new WebpackDevServer(
      compiler,
      {
        contentBase: path.join(__dirname, '../'),
        quiet: true,
        before (app, ctx) {
          app.use(hotMiddleware)
          ctx.middleware.waitUntilValid(() => {
            resolve()
          })
        }
      }
    )

    server.listen(9080)
  })
}

function startMain () {
  return new Promise((resolve, reject) => {
    mainConfig.entry.main = [path.join(__dirname, '../src/main/index.dev.js')].concat(mainConfig.entry.main)

    const compiler = webpack(mainConfig)

    compiler.plugin('watch-run', (compilation, done) => {
      logStats('Main', chalk.white.bold('compiling...'))
      hotMiddleware.publish({ action: 'compiling' })
      done()
    })

    compiler.watch({}, (err, stats) => {
      if (err) {
        console.log(err)
        return
      }

      logStats('Main', stats)

      if (electronProcess && electronProcess.kill) {
        manualRestart = true
        process.kill(electronProcess.pid)
        electronProcess = null
        startElectron()

        setTimeout(() => {
          manualRestart = false
        }, 5000)
      }

      resolve()
    })
  })
}

function startElectron () {
  electronProcess = spawn(electron, ['--inspect=5858', path.join(__dirname, '../dist/electron/main.js')])

  electronProcess.stdout.on('data', data => {
    electronLog(data, 'blue')
  })
  electronProcess.stderr.on('data', data => {
    electronLog(data, 'red')
  })

  electronProcess.on('close', () => {
    if (!manualRestart) process.exit()
  })
}

function electronLog (data, color) {
  let log = ''
  data = data.toString().split(/\r?\n/)
  data.forEach(line => {
    log += `  ${line}\n`
  })
  if (/[0-9A-z]+/.test(log)) {
    console.log(
      chalk[color].bold('┏ Electron -------------------') +
      '\n\n' +
      log +
      chalk[color].bold('┗ ----------------------------') +
      '\n'
    )
  }
}

function greeting () {
  const cols = process.stdout.columns
  let text = ''

  if (cols > 104) text = 'electron-vue'
  else if (cols > 76) text = 'electron-|vue'
  else text = false

  if (text) {
    say(text, {
      colors: ['yellow'],
      font: 'simple3d',
      space: false
    })
  } else console.log(chalk.yellow.bold('\n  electron-vue'))
  console.log(chalk.blue('  getting ready...') + '\n')
}

function init () {
  greeting()

  Promise.all([startRenderer(), startMain()])
    .then(() => {
      startElectron()
    })
    .catch(err => {
      console.error(err)
    })
}

init()


text:
# Функциональное программирование

Функциональное программирование — одна из основных парадигм программирования и сегодня ее знание нужно все чаще. Основанные на ФП инструменты встречаются в .NET и сторонних библиотеках, многие идеи в современной разработке так или иначе связаны с функциональным подходом, а в проектах все чаще встречается что-то относящееся к ФП. На этой странице вы найдете разнообразную информацию, посвященную изучению функционального программирования.

А еще на YouTube доступна [запись нашего митапа](https://youtu.be/OPgijKNKmsU), на котором Вагиф Абилов рассказывал, в чем польза изучения F# и функционального программирования.

- [Базовая информация о функциональном программировании](#%D0%B1%D0%B0%D0%B7%D0%BE%D0%B2%D0%B0%D1%8F-%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%86%D0%B8%D1%8F-%D0%BE-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B8)
- [Функциональное программирование в C#](#%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B5-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5-%D0%B2-c)
- [Дизайн кода в функциональном стиле](#%D0%B4%D0%B8%D0%B7%D0%B0%D0%B9%D0%BD-%D0%BA%D0%BE%D0%B4%D0%B0-%D0%B2-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%BC-%D1%81%D1%82%D0%B8%D0%BB%D0%B5)
- [Теоретические основы функционального программирования](#%D1%82%D0%B5%D0%BE%D1%80%D0%B5%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5-%D0%BE%D1%81%D0%BD%D0%BE%D0%B2%D1%8B-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D0%BE%D0%B3%D0%BE-%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F)

## Базовая информация о функциональном программировании

Здесь материалы об основных особенностях и концепциях функционального програмирования. Что оно дает? А в чем недостатки и сложности? На чем все это основывается, за счет чего работает?

В этой части будет много материалов, рассказывающих об одном и том же — выбирайте наиболее подходящий вам по примерам и подаче.

**[Статья на Хабре "Функциональное программирование — это не то, что нам рассказывают"](https://habr.com/ru/post/479238/)**

Хорошее базовое введение в ФП со стороны практики. Минимум общей теории и абстрактных понятий, много практических демонстраций. Хорошо объясняет, зачем ФП нужно в разработке и за счет чего эта польза обеспечивается.

В то же время, более сложных концептов в этой статье разобрано не будет, только самые базовые.

**[Статья в документации Microsoft "Введение в концепции ФП на F#"](https://docs.microsoft.com/ru-ru/dotnet/fsharp/tutorials/functional-programming-concepts)**

Очень простая статья, показывающая основные концепции ФП на примерах из F#. В отличие от прошлой, здесь нет какого-то разбора практической применимости этих концепций, но зато все показано на примерах из F# и хорошо объяснено. Помогает понять принципы неизменяемости, чистоты функций и ссылочной прозрачности.

**["Введение в функцональное программирование" от Ильи Фофанова](https://youtu.be/3q4ov8168H4)**

Видео из небольшого курса на YouTube о функциональном программировании. Во многом рассказывает о мотивации изучать ФП, показывает его актуальность, применение в C#, основные приниципы и прочее. Все максимально понятно, емко и хорошо подано.

**[Доклад "Не морочьте мне голову со своим функциональным программированием" Виталия Брагилевского](https://youtu.be/mmvHC3UgYmg)**

Отличная демонстрация не только основных приницпов ФП, но и того, как они уже применяются во вроде бы не-функциональных языках программирования. Поясняет, откуда растут ноги у функционального подхода, какие проблемы он решает, как влияет на производительность и так далее. Выступление никак не относится к .NET, но будет понятно вне зависимости от языка, на котором вы пишете.

**[Серия статей "Thinking functionally" от Скотта Влащина](https://fsharpforfunandprofit.com/series/thinking-functionally/)**

Серия статей от автора сайта [F# for Fun and Profit](https://fsharpforfunandprofit.com/) и одного из главных популяризаторов F# и ФП Скотта Влащина. Очень конкретно и подробно раскрывает тему, дает много хороших примеров на F#, в деталях объясняет самые-самые базовые вещи. Для кого-то материал может оказаться даже слишком подробным, но в любом случае — это отличный вариант вникнуть в тему для новичка.

## Функциональное программирование в C#

Материалы о том, как идеи из функционального мира могут применятся в C# разработке. Часть из них включает в себя и объяснение основ ФП — его можно пропустить, если с ними вы уже разобрались.

**[Серия статей Владимира Хорикова "Functional C#"](https://habr.com/ru/post/266873/)**

Рассказывает о том, как (и зачем) реализовывать многие фундаментальные вещи в функциональном стиле. Иммутабельность, работа с данными, работа с ошибками и прочее. Статьи были написаны еще в 2015 году, но с каждым годом становятся все актуальнее (а в C# появляется все больше удобных инструментов для работы в таком стиле).

**[Роман Неволин — Почему ваша архитектура функциональная и как с этим жить](https://youtu.be/9s_4wpzENhg)**

Доклад с DotNext о том, какие концепции из функционального программирования встречаются в любом энтерпрайзном приложении и как их применять с наибольшей пользой для себя. Нацелен на применение функциональных принципов к повседневным задачам.

**[Серия статей "Functional programming in C#"](https://dev.to/shimmer/functional-programming-in-c-3h6e)**

Еще одна демонстрация тех же приниципов, двигающаяся от простого к сложному. Из плюсов — много интересных идей и хороший разбор. Из минусов — в отличие от прошлых, этот материал на английском.

## Дизайн кода в функциональном стиле

Материалы о применении функционального программирования в каждодневной разработке. Какие его свойства могут быть особенно полезны в больших проектах с высокими требованиями к надежности? Или в проектах со сложной бизнес-логикой? Или при высоких требованиях к стабильности кода? И вообще, как организовывать код с использованием функционального подхода?

### Доклады, статьи и книга Скотта Влащина

Скотт Влащин — один из главных популяризаторов функционального программирования, так что его материалы достойны отдельной категории. Он много рассказывает о функциональном дизайне приложений — принципах организации кода, основанных на концепциях ФП. Его доклады в основном нацелены на новичков, которые прежде не погружались в тему функционального дизайна.

**[Доклад "Functional Design Patterns"](https://youtu.be/srQt1NAHYC0)**

Один из самых известных докладов по этой теме, бесконечно вспоминаемый и цитируемый. Рассказывает о простых принципах организации кода в функциональном программировании, которые подменяют аналогичные в ООП. Здесь рассказывается о композиции функций, организации из в пайплайн, обработке ошибок, всяческой удобной организации логики приложений и, конечно, монадах. Максимально практичный и доступный доклад для тех, кто не нырял глубоко в тему.

**[Доклад "The power of composition"](https://youtu.be/oquuPOkz8xo)**

В этом докладе Скотт фокусируется на композиции — от самого простого соединения двух функций до композиции высокоуровневых блоков приложения. Простой, понятный, и в то же время полный доклад, заканчивающийся примером веб-приложения на этих же принципах.

**[Доклад "Domain Modeling Made Functional"](https://youtu.be/2JB1_e5wZmU)**

Доклад о том, почему именно функциональная система типов и способ организации логики отлично подходят для DDD (и разнообразных бизнес-приложений в целом). Этот доклад во многом именно про систему типов и то, как она позволяет сделать код максимально надежным и очевидным.

**[Доклад "Designing with capabilities"](https://youtu.be/RqlnWv6NZos)**

Здесь Скотт рассказывает о дизайне кода, в котором неправильное состояние становится невозможным, а вероятность ошибки радикально снижается. По сути, описываемые концепции преследуют те же цели, что и SOLID — создание модульного, надежного и понятного кода. [На сайте F# for Fun and Profit](https://fsharpforfunandprofit.com/cap/) можно также найти относящиеся к этой теме статьи и примеры.

**[Книга "Domain Modeling Made Functional"](https://www.amazon.com/Domain-Modeling-Made-Functional-Domain-Driven/dp/1680502549)**

Книга, суммирующая все описанное выше и добавляющая к этому еще много важных важных кирпичиков. В ней разъясняются многие нюансы работы с типами и доменом, рассказывается о том, как в функциональном стиле взаимодействовать с базой данных и так далее.

Иначе говоря, эта книга дает ответ на вопрос "как в функциональном стиле создавать полноценные приложения?", объясняя все детали и нюансы (на которые в докладах не хватает времени).

### Что еще изучать?

**["Functional architecture  The pits of success" — Mark Seemann](https://youtu.be/US8QG9I1XW0)**

Доклад о best practices в мире функциональной разработки. Всегда актуален и касается многих вечных проблем — работы с "грязными" функциями (вроде чтения-записи из БД), валидации, тестируемости, работы с моделями и так далее.

**["Functional Programming Patterns for Mere Mortals" — Daniel Chambers](https://youtu.be/v9QGWbGppis)**

Здесь объясняется, зачем функторы, аппликаторы, монады и прочие страшно звучащие конструкции нужны в реальном коде — какие преимущества они несут, как используются и как выглядит код без их использования. Никакого предварительного понимания этих понятий доклад не требует и очень хорошо поясняет их сразу в практическом контексте.

## Теоретические основы функционального программирования

Здесь будут как большие курсы, рассказывающие теоретические основы и подоплеку функционального подхода, так и материалы, посвященные конкретным темам. Этот раздел намеренно помещен в конце — если начинать изучать ФП сразу с теории, то она может показаться неоправданно сложной. Однако если вы уже заинтересовались темой и хотите использовать ФП в каждодневной разработке, теория вам явно пригодится.

Многие материалы в этом блоке будут так или иначе связаны с Haskell.

**[Курс на Stepic "Функциональное программирование на языке Haskell"](https://stepik.org/course/75/syllabus)**

Вероятно, самый известный курс об основах функционального программирования на русском языке. Рассказывает о системе типов Haskell (традиционной для функциональных языков), работе со списками (которая традиционно имеет очень большое значение в ФП) и, разумеется, монадах.

Этот курс полезен для получения основ. Из преимуществ — опытный лектор, отличная подача и много практических задач. Из недостатков — иногда объяснений в лекциях не хватает для решения задач, а ряд тем недостаточно раскрыт.

**[Курс на Ulearn "Введение в функциональное программирование"](https://ulearn.me/Course/fpintroduction)**

Аналогичный курс — также на Haskell, также посвящен основам ФП. Он несколько больше по объему материала, покрывает большее количество тем (например, здесь детально разобрано лямбда-исчисление, трансформеры и некоторые практические темы вроде тестирования в Haskell).

Из преимуществ курса, помимо объема материала, можно назвать большее количество практических задач. Они большие, близкие к реальному коду и достаточно интересные для выполнения. Например, в финальной задаче курса нужно будет реализовать на Haskell API для бронирования билетов в кино. Из недостатков, курс иногда слишком академичен — он создавался для студентов матмеха и местами это может усложнять понимание.

**Книга "Изучай Haskell во имя добра" ([русское издание](https://www.ozon.ru/product/izuchay-haskell-vo-imya-dobra-8696277), [полностью бесплатная англоязычная версия](http://learnyouahaskell.com/chapters))**

Самая известная книга о Haskell и функциональном программировании. Доступная, понятная, с классными иллюстрациями, бонусом — доступна онлайн совсем бесплатно, правда на английском. Покрывает чуть больше тем, чем курс на Stepic и чуть меньше, чем курс на Ulearn.

Из плюсов, книга совсем не академичная, доступная и максимально понятная. Англоязычная версия достаточно актуальна и соответствует тому, как сегодня пишут код на Haskell. Из минусов — многие отмечают, что примерам в книге не хватает практичности, да и задачи в онлайн-курсах включают в себя автотесты.

### Монады

**[Доклад Виталия Брагилевского "Монады — не приговор"](https://www.youtube.com/watch?v=IkXg_mjNgG4)**

Максимально подробный и понятный доклад Виталия Брагилевского о том, что же такое монады, как они устроены и почему это важная конструкция. Хороший гайд для новичков, не знакомых с темой.

**[Статья "Функторы, аппликативные функторы и монады в картинках"](https://habr.com/ru/post/183150/)**

Еще одна статья на ту же тему. Из плюсов — все очень просто, понятно и в картинках. Из недостатков — статья несколько дальше от практики, чем упомянутый выше доклад. Так или иначе, иллюстрации отлично помогают понять концепцию.

**[Серия статей "Map and Bind and Apply, Oh my!" на сайте F# for Fun and Profit](https://fsharpforfunandprofit.com/series/map-and-bind-and-apply-oh-my/)**

Та же тема, но в гораздо более близком для F# разработчиков изложении. Полно, объемно, очень практично и легко перекладывается на практическую F# разработку.

repo_name: skbkontur/dotEducation


text:
# DotEducation — база знаний для .NET разработчиков

В этом репозитории мы собираем базу знаний для .NET разработчиков. Узнать больше о проекте можно из [статьи на Хабре](https://habr.com/ru/company/skbkontur/blog/580374/) или [записи круглого стола](https://youtu.be/G_XEYLNT6s4) с обсуждением проекта.

Раз в две недели здесь будут появляться новые направления для изучения. Для каждого из таких направлений мы дадим темы, которые в нем стоит изучить, ссылки на материалы, которые помогут в этом и запись лекции, на которой вам расскажут, почему эти темы важно знать.

## Актуальные направления
- [Как начинать разрабатывать на .NET?](beginners/README.md)
- [Что нужно знать про язык в .NET?](csharp/README.md)
- [Как изучать функциональное программирование?](/fprog.md)


text:
# Expression Trees, Query Providers, LINQ

LINQ это удобный и, на первый взгляд, простой инструмент для работы с коллекциями данных. Однако то, что на первый взгляд кажется простым, на самом деле скрывает много тонкостей и нюансов. Без их понимания невозможно добиться от LINQ достойной производительности или заточить этот инструмент под какие-то специфичные задачи.

Материалы на этой странице помогут вам разобраться в концепции, устройстве и практическом применении деревьев выражений, провайдеров запросов и LINQ.

## С чего начать?

**[Документация Microsoft по LINQ](https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/concepts/linq/)**

Традиционно входной точкой является документация. Если вам совсем непонятно, что это за LINQ и как к нему подступиться — вводная страница это поясняет. Если у вас возникли вопросы по синтаксису в процессе использования — как правило, там можно найти ответы.

**[Глава о LINQ в курсе на Ulearn](https://ulearn.me/course/basicprogramming2/3446fab2-15df-4045-ab40-abc1f3dc87c8)**

Другой хороший способ разобраться с основами LINQ. Там отлично описываются основные методы и даются вопросы для самопроверки и практические задания.

**[Статья «IEnumerable и IQueryable в Entity Framework»](https://metanit.com/sharp/entityframework/1.4.php)**

Небольшая статья на metanit, показывающая применение интерфейсов IEnumarable и IQueryable в Entity Framework. Там на примере генерируемых SQL запросов отлично показана разница между этими интерфейсами.

**[Статья «IQueryable порождает сильную связанность» от Mark Seemann](https://habr.com/ru/post/543774/)**

Статья, показывающая важный недостаток IQueryable. Перед использованием IQueryable полезно понимать, почему использование этого интерфейса для построения API может оказаться плохой идеей.

## Как это работает?

**[Доклад «Деревья выражений в enterprise-разработке» от Максима Аршинова](https://www.youtube.com/watch?v=J2XzsCoJM4o&feature=emb_logo&ab_channel=DotNext) ([Расшифровка](https://habr.com/ru/company/jugru/blog/423891/) и [актуальная англоязычная версия](https://www.youtube.com/watch?v=ncdRDv6sV1A))**

Один из самых объемных докладов о деревьях выражений — концепции, лежащей в основе LINQ. Он поможет вам лучше понять, как работает LINQ, и научиться использовать деревья выражений независимо. А это мощнейший механизм, понимание которого очень сильно упростит ваши рабочие задачи.

**[Статья «Принципы работы IQueryable и LINQ-провайдеров данных»](https://habr.com/ru/post/256821/)**

В статье разбираются основные принципы устройства LINQ и реализация собственного простенького аналога IQueryable. На этом примере можно понять, как могут выглядеть LINQ запросы изнутри.

**[Доклад «Реализация своих поставщиков запросов» от Антона Третьякова](https://www.youtube.com/watch?v=QVdfx51mlao)**

Классный обзор того, что же за зверь IQueryProvider, в каких целях его можно использовать и как реализовывать. Очень практичный доклад с множеством примеров кода и практическими кейсами применения.

**[Статья «Implementing a custom LINQ provider»](https://jacopretorius.net/2010/01/implementing-a-custom-linq-provider.html)**

Пример реализации собственного LINQ-провайдера на примере провайдера для файловой системы. Понятная задача, понятный код. Простой способ на базовом уровне понять устройство LINQ-провайдера.

 **[Что такое Expression.Quote и чем это лучше Expression.Constant?](https://stackoverflow.com/questions/3716492/what-does-expression-quote-do-that-expression-constant-can-t-already-do/3753382#3753382)**

Отличный ответ на вопрос на stackoverflow, показывающий, как Expression.Quote и Expression.Constant отражаются в деревьях выражений.


text:
# CIL и JIT

Далеко не все программисты понимают принципы работы JIT и могут читать CIL. В то же время, эти знания служат отправной точкой для понимания многих нюансов .NET. Высокоуровневые языки скрывают от вас информацию о том, что происходит под капотом приложения, а без этого трудно оптимизировать код, искать баги, да и просто в полной мере понимать происходящее в вашем приложениии.

## С чего начать?

**[Статья «What is managed code» в документации Microsoft](https://docs.microsoft.com/en-us/dotnet/standard/managed-code)**

Небольшая и совсем вводная статья, кратко объясняющая термины Managed Code, CLR и CIL. С нее стоит начать, если все эти слова выглядят совсем непонятно.

**[Статья «CIL or MSIL» от GeeksForGeeks](https://www.geeksforgeeks.org/cil-or-msil-microsoft-intermediate-language-or-common-intermediate-language/)**

Также короткая вводная статья, но уже конкретно про CIL.

**[Статья «What is JIT» от GeeksForGeeks](https://www.geeksforgeeks.org/what-is-just-in-time-jit-compiler-in-dot-net/)**

Аналогичная вводная статья про JIT компиляцию в .NET. Не погружается глубоко, но кратко рассказывает, что такое JIT и какие типы JIT-компиляторов существуют.

## Как начать понимать IL?

**Гайд по написанию кода на IL — [часть 1](https://dolinkamark.wordpress.com/2015/10/21/cil-programming-tutorial-the-basics/), [часть 2](https://dolinkamark.wordpress.com/2016/04/24/cil-programming-tutorial-the-basics-part-ii/)**

Этот гайд позволяет понять, что IL это достаточно несложный язык программирования, который можно читать также как и любой другой. В нем объясняются основные принципы и инструкции этого языка.

**[Что может IL и не могут C#/VB?](https://stackoverflow.com/questions/541936/what-can-you-do-in-msil-that-you-cannot-do-in-c-sharp-or-vb-net)**

Дискуссия на stackoverflow, которая поможет лучше понять возможности IL.

## Как работает JIT?

**[Доклад «Как устроен JIT-компилятор в CoreCLR» от Егора Богатова](https://youtu.be/H1ksFnLjLoY)**

Отличный рассказ о полном цикле компиляции C# кода, от запуска до выполнения на процессоре. С этого доклада можно начать погружение в нюансы работы JIT.

**[Анонс полного перехода на RyuJIT в блоге Microsoft](https://devblogs.microsoft.com/dotnet/the-ryujit-transition-is-complete/)**

Рассказ о современной версии JIT компилятора, использующейся в .NET. Помимо общего рассказа, там приведено много ссылок на другие материалы и относящиеся к теме PR в репозитории CoreCLR.

**[Что такое Tiered Compilation — глава из анонса .NET Core 3](https://docs.microsoft.com/en-us/dotnet/core/whats-new/dotnet-core-3-0#tiered-compilation)**

Tiered Compilation это одна из возможностей .NET Core 3, оптимизирующая работу JIT компилятора. В этой же главе дается ссылка на [документ в репозитории Microsoft](https://github.com/dotnet/runtime/blob/main/docs/design/features/tiered-compilation.md), рассказывающий о Tiered Compilation несколько подробнее.

**[Обсуждение Tiered Compilation в репозитории .NET Core Runtime](https://github.com/dotnet/runtime/issues/12515)**

Отличная дискуссия, позволяющая лучше понять особенности и ограничения Tiered Compilation.


text:
# .NET Memory Model

Memory Model это сложная, страшная, а в .NET еще и плохо покрытая спецификацией тема. Однако понимание модели памяти служит отправной точкой для понимания работы с concurrency, без этих знаний будет очень сложно правильно применять механизмы работы с параллелизмом в .NET и отлаживать связанные с этим ошибки.

## Общая информация по Memory Model

**[Доклад «The C++ and CLR Memory Models» от Саши Гольдштейна](https://www.youtube.com/watch?v=6wZVpg2SyJQ&ab_channel=DotNext) ([Расшифровка](https://habr.com/ru/company/jugru/blog/541362/))**

Подробный, точный и всегда актуальный доклад о Memory Model в CLR. Саша рассказывает о базовых принципах работы с многопоточным кодом, связанных с этим трудностях, модели памяти и том, как она помогает с многопоточными трудностями бороться.

**[Расшифровка доклада «Multithreading Deep Dive» от Гаэла Фретёра](https://habr.com/ru/company/jugru/blog/543380/)**

Близкий доклад, но подходящий к вопросу немного с другой стороны сделал Гаэл Фретёр. Здесь фокус идет скорее на детали реализации, замерах производительности и конкретных алгоритмах. На него полезно обратить внимание после доклада Саши, когда общая концепция уже понятна.

## Шипилев про Java Memory Model

Казалось бы, что в нашей документации делает Алексей Шипилев и его рассказы про модель памяти в Java? Но именно его доклады про модель памяти самые подробные и обстоятельные из тех, что нам получилось найти в интернете, а различия между моделями памяти в .NET и Java не так велики.

Так, концепция гонок и правила расстановки volatile почти одинаковые для двух платформ (за исключением неявных барьеров, о которых рассказывает Гольдштейн). Концепции acquire/release и happens before специфичны для платформ, но с этим можно будет разобраться в более специализированных материалах, на этом уровне дсотаточно знать об их существовании.

**[Java Memory Model Pragmatics](https://shipilev.net/#jmm)**

**[Java Memory Model, Close Encounters](https://shipilev.net/#jmm-close-encounters)**

**[Java Memory Model, Unlearning Experience](https://shipilev.net/#jmm-unlearning-experience)**

Доклады выстроены в хронологической последовательности и погружаются все глубже и глубже в работу модели памяти. Несмотря на пугающее слово Java еще раз рекомендуем вам обратить на них внимание.

## А как изучить специфичные моменты?

А для специфичных моментов существует спецификация! В частности, две основные спецификации ECMA 334 и ECMA 335. Первая покрывает CLI, вторая — C#.

**[ECMA CLI Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-335_6th_edition_june_2012.pdf)**

**[ECMA C# Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-334_5th_edition_december_2017.pdf)**

Из первой спецификации нам интересны (...), из второй только (...). TBA, в общем.

**[Статья 2007 года «CLR 2.0 memory model»](http://joeduffyblog.com/2007/11/10/clr-20-memory-model/)**

Почему нам интересна коротенькая статья 2007 года? В основном из-за последнего абзаца, который описывает документированность модели памяти в .NET.

> It is unfortunate that we’ve never gone to the level of detail and thoroughness the Java memory model folks have gone to. We have constructed our model over years of informal work and design-by-example, but something about the JMM approach is far more attractive. Lastly, what I’ve described applies to the implemented memory model, and not to what was specified in ECMA. So this is apt to change from one implementation to the next. I have no idea what Mono implements, for example.

Если вкратце — модель памяти в .NET памяти слабо специфицированна, и в этом плане ситуация с 2007 года не слишком изменилась. А значит, не покрытые спецификацией нюансы нам остается изучать по отдельным записям в блогах и докладам. Кстати, блог в котором размещена эта статья, является одним из самых полезных по теме (пусть и сложным для понимания).


text:
# Метапрограммирование

Слово «метапрограммирование» звучит пугающе, а одна из первых статей, которая находится в поиске по этому запросу это «почему вам стоит избегать метапрограммирования». Но при этом метапрограммирование занимает важную роль в .NET — оно используется во многих фреймворках, а платформа дает нам отличные инструменты, чтобы самим использовать мощь метапрограммирования.

## Что такое метапрограммирование?

**[Первая глава книги «Metaprogramming in .NET»](https://livebook.manning.com/book/metaprogramming-in-dot-net/chapter-1/)**

Эта глава поможет ответить на вопрос, что же такое метапрограммирование и зачем оно нужно. Там приводятся как обще-концептуальные пояснения, так и способы реализации метапрограммирования в .NET и примеры его использования. И раз уж она бесплатно доступна на сайте издательства, почему бы этим не воспользоваться.

Читать ли остальные главы — вопрос открытый. С одной стороны, в книге очень детально разобраны методы метапрограммирования и более полного и детального источника мы найти не смогли.

С другой — книга вышла в 2012 году и достаточно сильно устарела. Пусть общие концепции метапрограммирования особо не изменились, отдельные примеры и инструменты могут быть не особо актуальны. Так что советую воспользоваться превью на сайте и решить самостоятельно, подходит ли вам эта книга.

**[How Stuff Works in C# - Meta Programming 101](https://www.youtube.com/watch?v=cnpFNUFRqjQ)**

Англоязычный доклад, дающий верхнеуровневое представление о метапрограммировании и способах его применения на примере тестового фреймворка или реализации ASP.NET MVC. Доклад несколько современнее, но выполняет примерно ту же задачу, что и глава из книги выше.

## Рефлексия

**[Описание концепции рефлексии](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection) и [API для работы с рефлексией](https://docs.microsoft.com/en-us/dotnet/framework/reflection-and-codedom/reflection) в документации Microsoft**

В .NET рефлексия является одним из основных инструментов метапрограммирования, а документация на сайте Microsoft дает отличную точку для старта. К информации отсюда можно обращаться в любой момент, когда у вас появятся вопросы вида «как сделать что-то с помощью рефлексии».

**[Курс на Ulearn, глава про рефлексию](https://ulearn.me/course/basicprogramming2/Refleksiya_Klass_Type_8386b127-ea69-465d-87ba-24e08df9f6d2)**

Открытый видеокурс, который позволит быстро погрузиться в тему. Короткие и понятные видеоуроки, примеры кода, вопросы для самопроверки и отличное практическое задание в конце курса.

## Dynamic

**[Доклад «Эффективное использование DLR» от Карлена Симоняна](https://www.youtube.com/watch?v=lltDIUQrjgY&ab_channel=DotNext)**

 Отличный вводный доклад от Карлена Симоняна. Он поможет вам понять, что такое DLR, откуда такой инструмент взялся в C#, зачем он нам нужен и как его использовать максимально эффективно.

**[Статья «Multiple dispatch в C#» от Карлена Симоняна](https://habr.com/ru/post/283522/)**

Отличный практический пример применения dynamic в .NET. В этой статье для реализации паттерна multiple dispatch используется dynamic, что позволяет очень сильно упростить задачу. К слову, в блоге Карлена есть и другие материалы, раскрывающие связанные с dynamic нюансы.

## Анализ и генерация кода

Когда-нибудь тут появятся материалы о Roslyn, Code Generators и прочем, но пока что тут грустно и одиноко.


text:
# Асинхронность в .NET

Асинхронность сегодня встречается в коде практически любых приложений. Несмотря на внешнюю простоту использования асинхронности через async/await, в этой теме есть много нюансов и тонкостей. Как в них разобраться?

_Для полного понимания этого блока будет полезно понимать работу [memory model](memory_model.md) в .NET._

## С чего начать?

**[Статья про асинхронное программирование в официальной документации Microsoft](https://docs.microsoft.com/ru-ru/dotnet/csharp/programming-guide/concepts/async/)**

Как обычно, официальная документация Microsoft это хорошая точка для старта. В статье неплохо объясняется как сама концепция асинхронности, так и инструменты для работы с асинхронностью в .NET. В этом же разделе документации есть и несколько других, более узких статей.

**[Доклад «Async programming in .NET: Best practices» от Дмитрия Иванова](https://www.youtube.com/watch?v=wM-h6P1BJRk&ab_channel=DotNext) ([Расшифровка](https://habr.com/ru/company/jugru/blog/491236/))**

Этот доклад позволит погрузиться в тему несколько глубже и понять, как правильно использовать Async. В частности — какие проблемы с ним могут возникнуть и как их избежать.

**[Доклад «Building responsive and scalable applications» от Jeffrey Richter](https://www.youtube.com/watch?v=xGSabgBo-S8)**

Доклад, во многом повторяющий аналогичную информацию из CLR via C#, тем не менее весьма полезен в нашем контексте. Во-первых, он позволяет за час понять важность применения async/await и связанные с этим нюансы. Во-вторых, он доступен онлайн и совершенно бесплатен.

**[Доклад «Asynchronous streams» от Stephen Cleary](https://www.youtube.com/watch?v=-Tq4wLyen7Q) ([Расшифровка](https://habr.com/ru/company/jugru/blog/573904/))**

Помимо еще одного объяснения, почему асинхронность — это важно, из этого видео можно получить полезную информацию о работе с асинхронными потоками данных. Показаны разные подходы, разобраны их преимущества и ограничения.

## Как это работает?

Важный дисклеймер: многие материалы в этом блоке были выпущены еще во времена C# 5. Это может выглядеть пугающе, но устройство механизмов асинхронности с тех пор радикально не менялось и эти материалы актуальны до сих пор.

**[Доклад «Async programming deep dive» от Bard De Smet](https://channel9.msdn.com/events/TechDays/Techdays-2014-the-Netherlands/Async-programming-deep-dive)**

Отличной отправной точкой в понимании внутренностей async может стать этот доклад от Барта. Здесь классно показано, как асинхронность рабоатет на самых разных уровнях и во что компилируется асинхронный код.

**[Статья «Async/await и механизм реализации в C# 5.0»](https://habr.com/ru/post/260217/)**

Здесь автор заглядывает под капот async, смотрит на IL код, в который компилируются асинхронные вызовы. А в комментах к этой статье рекомендуют много других отличных источников информации по теме.

**[Документ с описанием «Task-based Asynchronous Pattern» от Stephen Toub](https://www.microsoft.com/en-us/download/details.aspx?id=19957)**

Как раз один из источников, рекомендованных в комментариях к прошлой статье. Здесь можно найти максимально детальное пояснение концепции, реализации и особенностей использования асинхронности в .NET.


text:
# Что нужно знать про язык в .NET?

Изучение языка зачастую воспринимается людьми как изучение его синтаксиса и возможностей, однако это только вершина айсберга. Понимание возможностей и ограничений языка крепко завязано на понимании его низкоуровневых особенностей, отдельных механик платформы и каких-то общих концепций, не привязанных к .NET.

Материалы в этом блоке помогут вам лучше ориентироваться в принципах и понятиях, лежащих в основе C#.

**[Лекция от Максима Аршинова на эту тему](https://www.youtube.com/watch?v=68Dh-rd7uAs)**

В лекции рассказывается, почему перечисленные ниже темы важны для изучения C#, как они относятся к повседневной разработке и в чем важность изучения каждой из тем.

Направления изучения:
- [Введение: где получить базовые знания о C#?](/beginners/README.md)
- [CIL и JIT](cil_jit.md)
- [Асинхронность](async.md)
- [Метапрограммирование](metaprogramming.md)
- [Expression Trees, Query Providers, LINQ](linq.md)