hunk.code_window: type WebSocketResponse struct {
   	Status   string                 `json:"status"`
   	SeqReply int64                  `json:"seq_reply,omitempty"`
   	Data     map[string]interface{} `json:"data,omitempty"`
   	Error    *AppError              `json:"error,omitempty"`
   }
   func (m *WebSocketResponse) Add(key string, value interface{}) {
   	m.Data[key] = value
  
hunk.labels: keep replace replace replace replace keep keep keep keep
hunk.after_edit: 	Status   string                 `json:"status"`              // The status of the response. For example: OK, FAIL.
   	SeqReply int64                  `json:"seq_reply,omitempty"` // A counter which is incremented for every response sent.
   	Data     map[string]interface{} `json:"data,omitempty"`      // The data contained in the response.
   	Error    *AppError              `json:"error,omitempty"`     // A field that is set if any error has occurred.
  
hunk.file_path: model/websocket_message.go
hunk.type: replace
file: package html2text
  
  import (
  	"bytes"
  	"io"
  	"regexp"
  	"strings"
  	"unicode"
  
  	"github.com/olekukonko/tablewriter"
  	"github.com/ssor/bom"
  	"golang.org/x/net/html"
  	"golang.org/x/net/html/atom"
  )
  
  // Options provide toggles and overrides to control specific rendering behaviors.
  type Options struct {
  	PrettyTables        bool                 // Turns on pretty ASCII rendering for table elements.
  	PrettyTablesOptions *PrettyTablesOptions // Configures pretty ASCII rendering for table elements.
  	OmitLinks           bool                 // Turns on omitting links
  }
  
  // PrettyTablesOptions overrides tablewriter behaviors
  type PrettyTablesOptions struct {
  	AutoFormatHeader     bool
  	AutoWrapText         bool
  	ReflowDuringAutoWrap bool
  	ColWidth             int
  	ColumnSeparator      string
  	RowSeparator         string
  	CenterSeparator      string
  	HeaderAlignment      int
  	FooterAlignment      int
  	Alignment            int
  	ColumnAlignment      []int
  	NewLine              string
  	HeaderLine           bool
  	RowLine              bool
  	AutoMergeCells       bool
  	Borders              tablewriter.Border
  }
  
  // NewPrettyTablesOptions creates PrettyTablesOptions with default settings
  func NewPrettyTablesOptions() *PrettyTablesOptions {
  	return &PrettyTablesOptions{
  		AutoFormatHeader:     true,
  		AutoWrapText:         true,
  		ReflowDuringAutoWrap: true,
  		ColWidth:             tablewriter.MAX_ROW_WIDTH,
  		ColumnSeparator:      tablewriter.COLUMN,
  		RowSeparator:         tablewriter.ROW,
  		CenterSeparator:      tablewriter.CENTER,
  		HeaderAlignment:      tablewriter.ALIGN_DEFAULT,
  		FooterAlignment:      tablewriter.ALIGN_DEFAULT,
  		Alignment:            tablewriter.ALIGN_DEFAULT,
  		ColumnAlignment:      []int{},
  		NewLine:              tablewriter.NEWLINE,
  		HeaderLine:           true,
  		RowLine:              false,
  		AutoMergeCells:       false,
  		Borders:              tablewriter.Border{Left: true, Right: true, Bottom: true, Top: true},
  	}
  }
  
  // FromHTMLNode renders text output from a pre-parsed HTML document.
  func FromHTMLNode(doc *html.Node, o ...Options) (string, error) {
  	var options Options
  	if len(o) > 0 {
  		options = o[0]
  	}
  
  	ctx := textifyTraverseContext{
  		buf:     bytes.Buffer{},
  		options: options,
  	}
  	if err := ctx.traverse(doc); err != nil {
  		return "", err
  	}
  
  	text := strings.TrimSpace(newlineRe.ReplaceAllString(
  		strings.Replace(ctx.buf.String(), "\n ", "\n", -1), "\n\n"),
  	)
  	return text, nil
  }
  
  // FromReader renders text output after parsing HTML for the specified
  // io.Reader.
  func FromReader(reader io.Reader, options ...Options) (string, error) {
  	newReader, err := bom.NewReaderWithoutBom(reader)
  	if err != nil {
  		return "", err
  	}
  	doc, err := html.Parse(newReader)
  	if err != nil {
  		return "", err
  	}
  	return FromHTMLNode(doc, options...)
  }
  
  // FromString parses HTML from the input string, then renders the text form.
  func FromString(input string, options ...Options) (string, error) {
  	bs := bom.CleanBom([]byte(input))
  	text, err := FromReader(bytes.NewReader(bs), options...)
  	if err != nil {
  		return "", err
  	}
  	return text, nil
  }
  
  var (
  	spacingRe = regexp.MustCompile(`[ \r\n\t]+`)
  	newlineRe = regexp.MustCompile(`\n\n+`)
  )
  
  // traverseTableCtx holds text-related context.
  type textifyTraverseContext struct {
  	buf bytes.Buffer
  
  	prefix          string
  	tableCtx        tableTraverseContext
  	options         Options
  	endsWithSpace   bool
  	justClosedDiv   bool
  	blockquoteLevel int
  	lineLength      int
  	isPre           bool
  }
  
  // tableTraverseContext holds table ASCII-form related context.
  type tableTraverseContext struct {
  	header     []string
  	body       [][]string
  	footer     []string
  	tmpRow     int
  	isInFooter bool
  }
  
  func (tableCtx *tableTraverseContext) init() {
  	tableCtx.body = [][]string{}
  	tableCtx.header = []string{}
  	tableCtx.footer = []string{}
  	tableCtx.isInFooter = false
  	tableCtx.tmpRow = 0
  }
  
  func (ctx *textifyTraverseContext) handleElement(node *html.Node) error {
  	ctx.justClosedDiv = false
  
  	switch node.DataAtom {
  	case atom.Br:
  		return ctx.emit("\n")
  
  	case atom.H1, atom.H2, atom.H3:
  		subCtx := textifyTraverseContext{}
  		if err := subCtx.traverseChildren(node); err != nil {
  			return err
  		}
  
  		str := subCtx.buf.String()
  		dividerLen := 0
  		for _, line := range strings.Split(str, "\n") {
  			if lineLen := len([]rune(line)); lineLen-1 > dividerLen {
  				dividerLen = lineLen - 1
  			}
  		}
  		var divider string
  		if node.DataAtom == atom.H1 {
  			divider = strings.Repeat("*", dividerLen)
  		} else {
  			divider = strings.Repeat("-", dividerLen)
  		}
  
  		if node.DataAtom == atom.H3 {
  			return ctx.emit("\n\n" + str + "\n" + divider + "\n\n")
  		}
  		return ctx.emit("\n\n" + divider + "\n" + str + "\n" + divider + "\n\n")
  
  	case atom.Blockquote:
  		ctx.blockquoteLevel++
  		ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel) + " "
  		if err := ctx.emit("\n"); err != nil {
  			return err
  		}
  		if ctx.blockquoteLevel == 1 {
  			if err := ctx.emit("\n"); err != nil {
  				return err
  			}
  		}
  		if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  		ctx.blockquoteLevel--
  		ctx.prefix = strings.Repeat(">", ctx.blockquoteLevel)
  		if ctx.blockquoteLevel > 0 {
  			ctx.prefix += " "
  		}
  		return ctx.emit("\n\n")
  
  	case atom.Div:
  		if ctx.lineLength > 0 {
  			if err := ctx.emit("\n"); err != nil {
  				return err
  			}
  		}
  		if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  		var err error
  		if !ctx.justClosedDiv {
  			err = ctx.emit("\n")
  		}
  		ctx.justClosedDiv = true
  		return err
  
  	case atom.Li:
  		if err := ctx.emit("* "); err != nil {
  			return err
  		}
  
  		if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  
  		return ctx.emit("\n")
  
  	case atom.B, atom.Strong:
  		subCtx := textifyTraverseContext{}
  		subCtx.endsWithSpace = true
  		if err := subCtx.traverseChildren(node); err != nil {
  			return err
  		}
  		str := subCtx.buf.String()
  		return ctx.emit("*" + str + "*")
  
  	case atom.A:
  		linkText := ""
  		// For simple link element content with single text node only, peek at the link text.
  		if node.FirstChild != nil && node.FirstChild.NextSibling == nil && node.FirstChild.Type == html.TextNode {
  			linkText = node.FirstChild.Data
  		}
  
  		// If image is the only child, take its alt text as the link text.
  		if img := node.FirstChild; img != nil && node.LastChild == img && img.DataAtom == atom.Img {
  			if altText := getAttrVal(img, "alt"); altText != "" {
  				if err := ctx.emit(altText); err != nil {
  					return err
  				}
  			}
  		} else if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  
  		hrefLink := ""
  		if attrVal := getAttrVal(node, "href"); attrVal != "" {
  			attrVal = ctx.normalizeHrefLink(attrVal)
  			// Don't print link href if it matches link element content or if the link is empty.
  			if !ctx.options.OmitLinks && attrVal != "" && linkText != attrVal {
  				hrefLink = "( " + attrVal + " )"
  			}
  		}
  
  		return ctx.emit(hrefLink)
  
  	case atom.P, atom.Ul:
  		return ctx.paragraphHandler(node)
  
  	case atom.Table, atom.Tfoot, atom.Th, atom.Tr, atom.Td:
  		if ctx.options.PrettyTables {
  			return ctx.handleTableElement(node)
  		} else if node.DataAtom == atom.Table {
  			return ctx.paragraphHandler(node)
  		}
  		return ctx.traverseChildren(node)
  
  	case atom.Pre:
  		ctx.isPre = true
  		err := ctx.traverseChildren(node)
  		ctx.isPre = false
  		return err
  
  	case atom.Style, atom.Script, atom.Head:
  		// Ignore the subtree.
  		return nil
  
  	default:
  		return ctx.traverseChildren(node)
  	}
  }
  
  // paragraphHandler renders node children surrounded by double newlines.
  func (ctx *textifyTraverseContext) paragraphHandler(node *html.Node) error {
  	if err := ctx.emit("\n\n"); err != nil {
  		return err
  	}
  	if err := ctx.traverseChildren(node); err != nil {
  		return err
  	}
  	return ctx.emit("\n\n")
  }
  
  // handleTableElement is only to be invoked when options.PrettyTables is active.
  func (ctx *textifyTraverseContext) handleTableElement(node *html.Node) error {
  	if !ctx.options.PrettyTables {
  		panic("handleTableElement invoked when PrettyTables not active")
  	}
  
  	switch node.DataAtom {
  	case atom.Table:
  		if err := ctx.emit("\n\n"); err != nil {
  			return err
  		}
  
  		// Re-intialize all table context.
  		ctx.tableCtx.init()
  
  		// Browse children, enriching context with table data.
  		if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  
  		buf := &bytes.Buffer{}
  		table := tablewriter.NewWriter(buf)
  		if ctx.options.PrettyTablesOptions != nil {
  			options := ctx.options.PrettyTablesOptions
  			table.SetAutoFormatHeaders(options.AutoFormatHeader)
  			table.SetAutoWrapText(options.AutoWrapText)
  			table.SetReflowDuringAutoWrap(options.ReflowDuringAutoWrap)
  			table.SetColWidth(options.ColWidth)
  			table.SetColumnSeparator(options.ColumnSeparator)
  			table.SetRowSeparator(options.RowSeparator)
  			table.SetCenterSeparator(options.CenterSeparator)
  			table.SetHeaderAlignment(options.HeaderAlignment)
  			table.SetFooterAlignment(options.FooterAlignment)
  			table.SetAlignment(options.Alignment)
  			table.SetColumnAlignment(options.ColumnAlignment)
  			table.SetNewLine(options.NewLine)
  			table.SetHeaderLine(options.HeaderLine)
  			table.SetRowLine(options.RowLine)
  			table.SetAutoMergeCells(options.AutoMergeCells)
  			table.SetBorders(options.Borders)
  		}
  		table.SetHeader(ctx.tableCtx.header)
  		table.SetFooter(ctx.tableCtx.footer)
  		table.AppendBulk(ctx.tableCtx.body)
  
  		// Render the table using ASCII.
  		table.Render()
  		if err := ctx.emit(buf.String()); err != nil {
  			return err
  		}
  
  		return ctx.emit("\n\n")
  
  	case atom.Tfoot:
  		ctx.tableCtx.isInFooter = true
  		if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  		ctx.tableCtx.isInFooter = false
  
  	case atom.Tr:
  		ctx.tableCtx.body = append(ctx.tableCtx.body, []string{})
  		if err := ctx.traverseChildren(node); err != nil {
  			return err
  		}
  		ctx.tableCtx.tmpRow++
  
  	case atom.Th:
  		res, err := ctx.renderEachChild(node)
  		if err != nil {
  			return err
  		}
  
  		ctx.tableCtx.header = append(ctx.tableCtx.header, res)
  
  	case atom.Td:
  		res, err := ctx.renderEachChild(node)
  		if err != nil {
  			return err
  		}
  
  		if ctx.tableCtx.isInFooter {
  			ctx.tableCtx.footer = append(ctx.tableCtx.footer, res)
  		} else {
  			ctx.tableCtx.body[ctx.tableCtx.tmpRow] = append(ctx.tableCtx.body[ctx.tableCtx.tmpRow], res)
  		}
  
  	}
  	return nil
  }
  
  func (ctx *textifyTraverseContext) traverse(node *html.Node) error {
  	switch node.Type {
  	default:
  		return ctx.traverseChildren(node)
  
  	case html.TextNode:
  		var data string
  		if ctx.isPre {
  			data = node.Data
  		} else {
  			data = strings.TrimSpace(spacingRe.ReplaceAllString(node.Data, " "))
  		}
  		return ctx.emit(data)
  
  	case html.ElementNode:
  		return ctx.handleElement(node)
  	}
  }
  
  func (ctx *textifyTraverseContext) traverseChildren(node *html.Node) error {
  	for c := node.FirstChild; c != nil; c = c.NextSibling {
  		if err := ctx.traverse(c); err != nil {
  			return err
  		}
  	}
  
  	return nil
  }
  
  func (ctx *textifyTraverseContext) emit(data string) error {
  	if data == "" {
  		return nil
  	}
  	var (
  		lines = ctx.breakLongLines(data)
  		err   error
  	)
  	for _, line := range lines {
  		runes := []rune(line)
  		startsWithSpace := unicode.IsSpace(runes[0])
  		if !startsWithSpace && !ctx.endsWithSpace && !strings.HasPrefix(data, ".") {
  			if err = ctx.buf.WriteByte(' '); err != nil {
  				return err
  			}
  			ctx.lineLength++
  		}
  		ctx.endsWithSpace = unicode.IsSpace(runes[len(runes)-1])
  		for _, c := range line {
  			if _, err = ctx.buf.WriteString(string(c)); err != nil {
  				return err
  			}
  			ctx.lineLength++
  			if c == '\n' {
  				ctx.lineLength = 0
  				if ctx.prefix != "" {
  					if _, err = ctx.buf.WriteString(ctx.prefix); err != nil {
  						return err
  					}
  				}
  			}
  		}
  	}
  	return nil
  }
  
  const maxLineLen = 74
  
  func (ctx *textifyTraverseContext) breakLongLines(data string) []string {
  	// Only break lines when in blockquotes.
  	if ctx.blockquoteLevel == 0 {
  		return []string{data}
  	}
  	var (
  		ret      = []string{}
  		runes    = []rune(data)
  		l        = len(runes)
  		existing = ctx.lineLength
  	)
  	if existing >= maxLineLen {
  		ret = append(ret, "\n")
  		existing = 0
  	}
  	for l+existing > maxLineLen {
  		i := maxLineLen - existing
  		for i >= 0 && !unicode.IsSpace(runes[i]) {
  			i--
  		}
  		if i == -1 {
  			// No spaces, so go the other way.
  			i = maxLineLen - existing
  			for i < l && !unicode.IsSpace(runes[i]) {
  				i++
  			}
  		}
  		ret = append(ret, string(runes[:i])+"\n")
  		for i < l && unicode.IsSpace(runes[i]) {
  			i++
  		}
  		runes = runes[i:]
  		l = len(runes)
  		existing = 0
  	}
  	if len(runes) > 0 {
  		ret = append(ret, string(runes))
  	}
  	return ret
  }
  
  func (ctx *textifyTraverseContext) normalizeHrefLink(link string) string {
  	link = strings.TrimSpace(link)
  	link = strings.TrimPrefix(link, "mailto:")
  	return link
  }
  
  // renderEachChild visits each direct child of a node and collects the sequence of
  // textuual representaitons separated by a single newline.
  func (ctx *textifyTraverseContext) renderEachChild(node *html.Node) (string, error) {
  	buf := &bytes.Buffer{}
  	for c := node.FirstChild; c != nil; c = c.NextSibling {
  		s, err := FromHTMLNode(c, ctx.options)
  		if err != nil {
  			return "", err
  		}
  		if _, err = buf.WriteString(s); err != nil {
  			return "", err
  		}
  		if c.NextSibling != nil {
  			if err = buf.WriteByte('\n'); err != nil {
  				return "", err
  			}
  		}
  	}
  	return buf.String(), nil
  }
  
  func getAttrVal(node *html.Node, attrName string) string {
  	for _, attr := range node.Attr {
  		if attr.Key == attrName {
  			return attr.Val
  		}
  	}
  
  	return ""
  }
  