Where I used to work we had external software that looked for a block on end of lines, as you asked, but it was not necessary to have multiple blocks at the same point. In the image the 4 lines meet at a common point so this is a variation and will only put 1 block at any line ends that meet.

It may be useful or not.
(defun c:block_every_point (/ curves_sset ename lst lst2 x remove_doubles)
; defun Code by Gile
(defun remove_doubles (lst)
(if lst
(cons (car lst) (remove_doubles (vl-remove (car lst) lst)))
)
)
(if (or (null block_name)(= block_name "")) (setq block_name "BLOCK DEFINED IN DRAWING"))
(if (= block_scale nil) (setq block_scale 0.5))
(setq bname (getstring t (strcat "\nEnter block name (Unhighlight to accept) <" block_name ">: ")))
(if (= bname "")(princ)(setq block_name bname))
(setq bscale (getreal (strcat "\nEnter scale factor for block (Unhighlight to accept) <" (rtos block_scale 2 1) ">: ")))
(if (= bscale nil)(princ)(setq block_scale bscale))
(setq curves_sset (ssget '((0 . "LWPOLYLINE,LINE,ARC"))))
(setq lst '())
(repeat (setq x (sslength curves_sset))
(setq ename (ssname curves_sset (setq x (1- x))))
(if (= (cdr (assoc 0 (entget ename))) "LWPOLYLINE")
(progn
(setq co-ord (mapcar 'cdr (vl-remove-if-not '(lambda (group) (= 10 (car group))) (entget ename))))
(setq lst (cons (car co-ord) lst))
(setq lst (cons (last co-ord) lst))
)
(progn
(setq lst (cons (vlax-get (vlax-ename->vla-object ename) 'startpoint) lst))
(setq lst (cons (vlax-get (vlax-ename->vla-object ename) 'endpoint) lst))
)
)
(setq lst (vl-sort lst
'(lambda (a b)
(cond
((< (car a) (car b)))
((= (car a) (car b)) (< (cadr a) (cadr b)))
)
)
))
)
(setq lst2 (remove_doubles lst))
(setq curspace (vla-get-block (vla-get-activelayout (vla-get-activedocument (vlax-get-acad-object)))))
(foreach pt lst2
(vla-insertblock curspace
(vlax-3d-point pt)
block_name
block_scale_factor block_scale_factor block_scale_factor 0
)
)
(princ)
)
(c:block_every_point)